hevm 0.52.0 → 0.53.0
raw patch · 28 files changed
+8689/−7170 lines, 28 filesdep +MissingHdep +exceptionsdep +unliftio-corePVP ok
version bump matches the API change (PVP)
Dependencies added: MissingH, exceptions, unliftio-core
API changes (from Hackage documentation)
- EVM: branch :: forall s. Expr EWord -> (Bool -> EVM s ()) -> EVM s ()
- EVM: costOfCall :: FeeSchedule Word64 -> Bool -> Expr EWord -> Word64 -> Word64 -> Expr EAddr -> EVM s (Word64, Word64)
- EVM: costOfCreate :: FeeSchedule Word64 -> Word64 -> W256 -> Bool -> (Word64, Word64)
- EVM: forceConcrete3 :: (Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256) -> EVM s ()) -> EVM s ()
- EVM: forceConcrete4 :: (Expr EWord, Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256, W256) -> EVM s ()) -> EVM s ()
- EVM: forceConcrete5 :: (Expr EWord, Expr EWord, Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256, W256, W256) -> EVM s ()) -> EVM s ()
- EVM: forceConcrete6 :: (Expr EWord, Expr EWord, Expr EWord, Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256, W256, W256, W256) -> EVM s ()) -> EVM s ()
- EVM: partial :: PartialExec -> EVM s ()
- EVM: use' :: (VM s -> a) -> EVM s a
- EVM.Fetch: type Fetcher s = Query s -> IO (EVM s ())
- EVM.Format: unindexed :: [(Text, AbiType, Indexed)] -> [AbiType]
- EVM.Keccak: instance GHC.Show.Show EVM.Keccak.BuilderState
- EVM.SMT: newtype RefinementEqs
- EVM.Solvers: [$sel:stderr:SolverInstance] :: SolverInstance -> Handle
- EVM.SymExec: [$sel:abstRefineConfig:VeriOpts] :: VeriOpts -> AbstRefineConfig
- EVM.SymExec: [$sel:debug:VeriOpts] :: VeriOpts -> Bool
- EVM.SymExec: debugAbstVeriOpts :: VeriOpts
- EVM.SymExec: debugVeriOpts :: VeriOpts
- EVM.Types: AbstRefineConfig :: Bool -> Bool -> AbstRefineConfig
- EVM.Types: Choose :: Choose s -> Effect s
- EVM.Types: HandleEffect :: Effect s -> VMResult s
- EVM.Types: Query :: Query s -> Effect s
- EVM.Types: Unfinished :: PartialExec -> VMResult s
- EVM.Types: VMFailure :: EvmError -> VMResult s
- EVM.Types: VMSuccess :: Expr Buf -> VMResult s
- EVM.Types: [$sel:arith:AbstRefineConfig] :: AbstRefineConfig -> Bool
- EVM.Types: [$sel:keccakEqs:VM] :: VM s -> [Prop]
- EVM.Types: [$sel:mem:AbstRefineConfig] :: AbstRefineConfig -> Bool
- EVM.Types: abstRefineDefault :: AbstRefineConfig
- EVM.Types: data AbstRefineConfig
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ (EVM.Types.Expr 'EVM.Types.Buf, [EVM.Types.Prop]), b GHC.Types.~ (EVM.Types.Expr 'EVM.Types.Buf, [EVM.Types.Prop])) => Optics.Label.LabelOptic "calldata" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) [EVM.Types.W256], b GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) [EVM.Types.W256]) => Optics.Label.LabelOptic "txAccessList" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.CodeLocation (GHC.Types.Int, [EVM.Types.Expr 'EVM.Types.EWord]), b GHC.Types.~ Data.Map.Internal.Map EVM.Types.CodeLocation (GHC.Types.Int, [EVM.Types.Expr 'EVM.Types.EWord])) => Optics.Label.LabelOptic "iterations" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Tree.Zipper.TreePos Data.Tree.Zipper.Empty EVM.Types.Trace, b GHC.Types.~ Data.Tree.Zipper.TreePos Data.Tree.Zipper.Empty EVM.Types.Trace) => Optics.Label.LabelOptic "traces" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.FeeSchedule.FeeSchedule GHC.Word.Word64, b GHC.Types.~ EVM.FeeSchedule.FeeSchedule GHC.Word.Word64) => Optics.Label.LabelOptic "schedule" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.BaseState, b GHC.Types.~ EVM.Types.BaseState) => Optics.Label.LabelOptic "baseState" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Block, b GHC.Types.~ EVM.Types.Block) => Optics.Label.LabelOptic "block" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Cache, b GHC.Types.~ EVM.Types.Cache) => Optics.Label.LabelOptic "cache" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Contract, b GHC.Types.~ EVM.Types.Contract) => Optics.Label.LabelOptic "contract" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.ContractCode, b GHC.Types.~ EVM.Types.ContractCode) => Optics.Label.LabelOptic "code" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Env, b GHC.Types.~ EVM.Types.Env) => Optics.Label.LabelOptic "env" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.Buf, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.Buf) => Optics.Label.LabelOptic "calldata" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.Buf, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.Buf) => Optics.Label.LabelOptic "returndata" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "address" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "caller" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "caller" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "codeContract" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "coinbase" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "contract" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "origin" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "callvalue" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "timestamp" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "value" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.FrameContext, b GHC.Types.~ EVM.Types.FrameContext) => Optics.Label.LabelOptic "context" k (EVM.Types.Frame s) (EVM.Types.Frame s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.FrameState s, b GHC.Types.~ EVM.Types.FrameState s) => Optics.Label.LabelOptic "state" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.FrameState s1, b GHC.Types.~ EVM.Types.FrameState s2) => Optics.Label.LabelOptic "state" k (EVM.Types.Frame s1) (EVM.Types.Frame s2) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Memory s1, b GHC.Types.~ EVM.Types.Memory s2) => Optics.Label.LabelOptic "memory" k (EVM.Types.FrameState s1) (EVM.Types.FrameState s2) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.RuntimeConfig, b GHC.Types.~ EVM.Types.RuntimeConfig) => Optics.Label.LabelOptic "config" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.TxState, b GHC.Types.~ EVM.Types.TxState) => Optics.Label.LabelOptic "tx" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "baseFee" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "chainId" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "gasprice" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "maxCodeSize" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "number" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "prevRandao" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "priorityFee" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe (EVM.Types.VMResult s), b GHC.Types.~ GHC.Maybe.Maybe (EVM.Types.VMResult s)) => Optics.Label.LabelOptic "result" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Bool, b GHC.Types.~ GHC.Types.Bool) => Optics.Label.LabelOptic "allowFFI" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Bool, b GHC.Types.~ GHC.Types.Bool) => Optics.Label.LabelOptic "create" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Bool, b GHC.Types.~ GHC.Types.Bool) => Optics.Label.LabelOptic "static" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Label.LabelOptic "pc" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "blockGaslimit" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "burned" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "gas" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "gas" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "gaslimit" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "memorySize" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [(EVM.Types.Expr 'EVM.Types.EAddr, EVM.Types.Contract)], b GHC.Types.~ [(EVM.Types.Expr 'EVM.Types.EAddr, EVM.Types.Contract)]) => Optics.Label.LabelOptic "otherContracts" k EVM.Types.VMOpts EVM.Types.VMOpts a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Expr 'EVM.Types.EWord], b GHC.Types.~ [EVM.Types.Expr 'EVM.Types.EWord]) => Optics.Label.LabelOptic "stack" k (EVM.Types.FrameState s) (EVM.Types.FrameState s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Expr 'EVM.Types.Log], b GHC.Types.~ [EVM.Types.Expr 'EVM.Types.Log]) => Optics.Label.LabelOptic "logs" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Frame s], b GHC.Types.~ [EVM.Types.Frame s]) => Optics.Label.LabelOptic "frames" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Prop], b GHC.Types.~ [EVM.Types.Prop]) => Optics.Label.LabelOptic "constraints" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Prop], b GHC.Types.~ [EVM.Types.Prop]) => Optics.Label.LabelOptic "keccakEqs" k (EVM.Types.VM s) (EVM.Types.VM s) a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.An_AffineTraversal, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "offset" k EVM.Types.FrameContext EVM.Types.FrameContext a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.An_AffineTraversal, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "size" k EVM.Types.FrameContext EVM.Types.FrameContext a b
- EVM.Types: instance GHC.Classes.Eq EVM.Types.AbstRefineConfig
- EVM.Types: instance GHC.Generics.Generic (EVM.Types.FrameState s)
- EVM.Types: instance GHC.Generics.Generic (EVM.Types.VM s)
- EVM.Types: instance GHC.Show.Show (EVM.Types.Effect s)
- EVM.Types: instance GHC.Show.Show (EVM.Types.Frame s)
- EVM.Types: instance GHC.Show.Show (EVM.Types.FrameState s)
- EVM.Types: instance GHC.Show.Show (EVM.Types.Query s)
- EVM.Types: instance GHC.Show.Show (EVM.Types.VM s)
- EVM.Types: instance GHC.Show.Show (EVM.Types.VMResult s)
- EVM.Types: instance GHC.Show.Show EVM.Types.AbstRefineConfig
- EVM.Types: instance GHC.Show.Show EVM.Types.VMOpts
- EVM.UnitTest: [$sel:smtDebug:UnitTestOptions] :: UnitTestOptions s -> Bool
+ EVM: forceLit :: Expr EWord -> W256
+ EVM: instance EVM.Types.VMOps 'EVM.Types.Concrete
+ EVM: instance EVM.Types.VMOps 'EVM.Types.Symbolic
+ EVM: symbolify :: VM Concrete s -> VM Symbolic s
+ EVM: symbolifyFrame :: Frame Concrete s -> Frame Symbolic s
+ EVM: symbolifyFrameState :: FrameState Concrete s -> FrameState Symbolic s
+ EVM: symbolifyResult :: VMResult Concrete s -> VMResult Symbolic s
+ EVM.Effects: Config :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Integer -> Bool -> Config
+ EVM.Effects: Env :: Config -> Env
+ EVM.Effects: [$sel:abstRefineArith:Config] :: Config -> Bool
+ EVM.Effects: [$sel:abstRefineMem:Config] :: Config -> Bool
+ EVM.Effects: [$sel:config:Env] :: Env -> Config
+ EVM.Effects: [$sel:debug:Config] :: Config -> Bool
+ EVM.Effects: [$sel:dumpEndStates:Config] :: Config -> Bool
+ EVM.Effects: [$sel:dumpExprs:Config] :: Config -> Bool
+ EVM.Effects: [$sel:dumpQueries:Config] :: Config -> Bool
+ EVM.Effects: [$sel:dumpTrace:Config] :: Config -> Bool
+ EVM.Effects: [$sel:numCexFuzz:Config] :: Config -> Integer
+ EVM.Effects: [$sel:onlyCexFuzz:Config] :: Config -> Bool
+ EVM.Effects: class Monad m => ReadConfig m
+ EVM.Effects: class Monad m => TTY m
+ EVM.Effects: data Config
+ EVM.Effects: defaultConfig :: Config
+ EVM.Effects: defaultEnv :: Env
+ EVM.Effects: instance (GHC.Base.Monad m, Control.Monad.IO.Class.MonadIO m) => EVM.Effects.TTY (Control.Monad.Trans.Reader.ReaderT EVM.Effects.Env m)
+ EVM.Effects: instance GHC.Base.Monad m => EVM.Effects.ReadConfig (Control.Monad.Trans.Reader.ReaderT EVM.Effects.Env m)
+ EVM.Effects: instance GHC.Classes.Eq EVM.Effects.Config
+ EVM.Effects: instance GHC.Show.Show EVM.Effects.Config
+ EVM.Effects: newtype Env
+ EVM.Effects: readConfig :: ReadConfig m => m Config
+ EVM.Effects: runApp :: ReaderT Env m a -> m a
+ EVM.Effects: runEnv :: Env -> ReaderT Env m a -> m a
+ EVM.Effects: type App m = (MonadUnliftIO m, ReadConfig m, TTY m)
+ EVM.Effects: writeErr :: TTY m => Text -> m ()
+ EVM.Effects: writeOutput :: TTY m => Text -> m ()
+ EVM.Effects: writeTrace :: App m => VM t RealWorld -> m ()
+ EVM.Effects: writeTraceDapp :: App m => DappInfo -> VM t RealWorld -> m ()
+ EVM.Expr: Array :: StorageType
+ EVM.Expr: Map :: StorageType
+ EVM.Expr: Mixed :: StorageType
+ EVM.Expr: SmallSlot :: StorageType
+ EVM.Expr: UNK :: StorageType
+ EVM.Expr: concKeccakOnePass :: Expr a -> Expr a
+ EVM.Expr: concKeccakProps :: [Prop] -> [Prop]
+ EVM.Expr: concKeccakSimpExpr :: Expr a -> Expr a
+ EVM.Expr: data StorageType
+ EVM.Expr: decomposeStorage :: Expr a -> Maybe (Expr a)
+ EVM.Expr: getLogicalIdx :: Expr Storage -> Maybe W256
+ EVM.Expr: instance GHC.Classes.Eq EVM.Expr.StorageType
+ EVM.Expr: instance GHC.Show.Show EVM.Expr.StorageType
+ EVM.Expr: safeToDecompose :: Expr a -> Bool
+ EVM.Fetch: type Fetcher t m s = App m => Query t s -> m (EVM t s ())
+ EVM.Format: formatProp :: Prop -> Text
+ EVM.Fuzz: CollectBufs :: Set (Expr Buf) -> CollectBufs
+ EVM.Fuzz: CollectStorage :: Set (Expr EAddr, Maybe W256) -> Set W256 -> Set W256 -> CollectStorage
+ EVM.Fuzz: CollectVars :: Set (Expr EWord) -> Set W256 -> CollectVars
+ EVM.Fuzz: [$sel:addrs:CollectStorage] :: CollectStorage -> Set (Expr EAddr, Maybe W256)
+ EVM.Fuzz: [$sel:bufs:CollectBufs] :: CollectBufs -> Set (Expr Buf)
+ EVM.Fuzz: [$sel:keys:CollectStorage] :: CollectStorage -> Set W256
+ EVM.Fuzz: [$sel:vals:CollectStorage] :: CollectStorage -> Set W256
+ EVM.Fuzz: [$sel:vals:CollectVars] :: CollectVars -> Set W256
+ EVM.Fuzz: [$sel:vars:CollectVars] :: CollectVars -> Set (Expr EWord)
+ EVM.Fuzz: data CollectStorage
+ EVM.Fuzz: data CollectVars
+ EVM.Fuzz: extractBufs :: [Prop] -> [Expr Buf]
+ EVM.Fuzz: extractStorage :: [Prop] -> CollectStorage
+ EVM.Fuzz: extractVars :: [Prop] -> CollectVars
+ EVM.Fuzz: filterCorrectKeccak :: [Prop] -> [Prop]
+ EVM.Fuzz: findBufProp :: Prop -> State CollectBufs Prop
+ EVM.Fuzz: findStoragePropComp :: Prop -> State CollectStorage Prop
+ EVM.Fuzz: findStoragePropInner :: Prop -> State CollectStorage Prop
+ EVM.Fuzz: findVarProp :: Prop -> State CollectVars Prop
+ EVM.Fuzz: getBufs :: [Expr Buf] -> Gen (Map (Expr Buf) BufModel)
+ EVM.Fuzz: getRndW256 :: Gen W256
+ EVM.Fuzz: getStores :: CollectStorage -> Gen (Map (Expr EAddr) (Map W256 W256))
+ EVM.Fuzz: getvals :: CollectVars -> Gen (Map (Expr EWord) W256)
+ EVM.Fuzz: initBufsState :: CollectBufs
+ EVM.Fuzz: initStorageState :: CollectStorage
+ EVM.Fuzz: initVarsState :: CollectVars
+ EVM.Fuzz: instance GHC.Base.Semigroup EVM.Fuzz.CollectStorage
+ EVM.Fuzz: instance GHC.Show.Show EVM.Fuzz.CollectBufs
+ EVM.Fuzz: instance GHC.Show.Show EVM.Fuzz.CollectStorage
+ EVM.Fuzz: instance GHC.Show.Show EVM.Fuzz.CollectVars
+ EVM.Fuzz: newtype CollectBufs
+ EVM.Fuzz: substituteBuf :: Map (Expr Buf) BufModel -> Prop -> Prop
+ EVM.Fuzz: substituteEWord :: Map (Expr EWord) W256 -> Prop -> Prop
+ EVM.Fuzz: substituteStores :: Map (Expr 'EAddr) (Map W256 W256) -> Prop -> Prop
+ EVM.Fuzz: tryCexFuzz :: [Prop] -> Integer -> Maybe SMTCex
+ EVM.Keccak: instance GHC.Show.Show EVM.Keccak.KeccakStore
+ EVM.SMT: data RefinementEqs
+ EVM.Solidity: FoundryStdLib :: ProjectType
+ EVM.Solvers: writeSMT2File :: SMT2 -> Int -> String -> IO ()
+ EVM.SymExec: isSt :: CalldataFragment -> Bool
+ EVM.Types: Concrete :: VMType
+ EVM.Types: ForkState :: Env -> Block -> Cache -> String -> ForkState
+ EVM.Types: NonexistentFork :: Int -> EvmError
+ EVM.Types: Symbolic :: VMType
+ EVM.Types: [$sel:block:ForkState] :: ForkState -> Block
+ EVM.Types: [$sel:cache:ForkState] :: ForkState -> Cache
+ EVM.Types: [$sel:currentFork:VM] :: VM (t :: VMType) s -> Int
+ EVM.Types: [$sel:env:ForkState] :: ForkState -> Env
+ EVM.Types: [$sel:forks:VM] :: VM (t :: VMType) s -> Seq ForkState
+ EVM.Types: [$sel:freshGasVals:Env] :: Env -> Int
+ EVM.Types: [$sel:urlOrAlias:ForkState] :: ForkState -> String
+ EVM.Types: [Choose] :: Choose s -> Effect Symbolic s
+ EVM.Types: [HandleEffect] :: Effect t s -> VMResult t s
+ EVM.Types: [Query] :: Query t s -> Effect t s
+ EVM.Types: [Unfinished] :: PartialExec -> VMResult Symbolic s
+ EVM.Types: [VMFailure] :: EvmError -> VMResult t s
+ EVM.Types: [VMSuccess] :: Expr Buf -> VMResult t s
+ EVM.Types: branch :: VMOps t => Expr EWord -> (Bool -> EVM t s ()) -> EVM t s ()
+ EVM.Types: burn' :: VMOps t => Gas t -> EVM t s () -> EVM t s ()
+ EVM.Types: burnCalldatacopy :: VMOps t => Expr EWord -> EVM t s () -> EVM t s ()
+ EVM.Types: burnCodecopy :: VMOps t => Expr EWord -> EVM t s () -> EVM t s ()
+ EVM.Types: burnExp :: VMOps t => Expr EWord -> EVM t s () -> EVM t s ()
+ EVM.Types: burnExtcodecopy :: VMOps t => Expr EAddr -> Expr EWord -> EVM t s () -> EVM t s ()
+ EVM.Types: burnLog :: VMOps t => Expr EWord -> Word8 -> EVM t s () -> EVM t s ()
+ EVM.Types: burnReturndatacopy :: VMOps t => Expr EWord -> EVM t s () -> EVM t s ()
+ EVM.Types: burnSha3 :: VMOps t => Expr EWord -> EVM t s () -> EVM t s ()
+ EVM.Types: class VMOps (t :: VMType)
+ EVM.Types: costOfCall :: VMOps t => FeeSchedule Word64 -> Bool -> Expr EWord -> Gas t -> Gas t -> Expr EAddr -> (Word64 -> Word64 -> EVM t s ()) -> EVM t s ()
+ EVM.Types: costOfCreate :: VMOps t => FeeSchedule Word64 -> Gas t -> Expr EWord -> Bool -> (Gas t, Gas t)
+ EVM.Types: data ForkState
+ EVM.Types: data VMType
+ EVM.Types: enoughGas :: VMOps t => Word64 -> Gas t -> Bool
+ EVM.Types: ensureGas :: VMOps t => Word64 -> EVM t s () -> EVM t s ()
+ EVM.Types: gasTryFrom :: VMOps t => Expr EWord -> Either () (Gas t)
+ EVM.Types: initialGas :: VMOps t => Gas t
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ (EVM.Types.Expr 'EVM.Types.Buf, [EVM.Types.Prop]), b GHC.Types.~ (EVM.Types.Expr 'EVM.Types.Buf, [EVM.Types.Prop])) => Optics.Label.LabelOptic "calldata" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) [EVM.Types.W256], b GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) [EVM.Types.W256]) => Optics.Label.LabelOptic "txAccessList" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.CodeLocation (GHC.Types.Int, [EVM.Types.Expr 'EVM.Types.EWord]), b GHC.Types.~ Data.Map.Internal.Map EVM.Types.CodeLocation (GHC.Types.Int, [EVM.Types.Expr 'EVM.Types.EWord])) => Optics.Label.LabelOptic "iterations" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Sequence.Internal.Seq EVM.Types.ForkState, b GHC.Types.~ Data.Sequence.Internal.Seq EVM.Types.ForkState) => Optics.Label.LabelOptic "forks" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Tree.Zipper.TreePos Data.Tree.Zipper.Empty EVM.Types.Trace, b GHC.Types.~ Data.Tree.Zipper.TreePos Data.Tree.Zipper.Empty EVM.Types.Trace) => Optics.Label.LabelOptic "traces" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.FeeSchedule.FeeSchedule GHC.Word.Word64, b GHC.Types.~ EVM.FeeSchedule.FeeSchedule GHC.Word.Word64) => Optics.Label.LabelOptic "schedule" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.BaseState, b GHC.Types.~ EVM.Types.BaseState) => Optics.Label.LabelOptic "baseState" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Block, b GHC.Types.~ EVM.Types.Block) => Optics.Label.LabelOptic "block" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Cache, b GHC.Types.~ EVM.Types.Cache) => Optics.Label.LabelOptic "cache" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Contract, b GHC.Types.~ EVM.Types.Contract) => Optics.Label.LabelOptic "contract" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.ContractCode, b GHC.Types.~ EVM.Types.ContractCode) => Optics.Label.LabelOptic "code" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Env, b GHC.Types.~ EVM.Types.Env) => Optics.Label.LabelOptic "env" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.Buf, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.Buf) => Optics.Label.LabelOptic "calldata" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.Buf, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.Buf) => Optics.Label.LabelOptic "returndata" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "address" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "caller" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "caller" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "codeContract" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "coinbase" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "contract" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "origin" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "callvalue" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "timestamp" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "value" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.FrameContext, b GHC.Types.~ EVM.Types.FrameContext) => Optics.Label.LabelOptic "context" k (EVM.Types.Frame t s) (EVM.Types.Frame t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.FrameState t s, b GHC.Types.~ EVM.Types.FrameState t s) => Optics.Label.LabelOptic "state" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.FrameState t1 s1, b GHC.Types.~ EVM.Types.FrameState t2 s2) => Optics.Label.LabelOptic "state" k (EVM.Types.Frame t1 s1) (EVM.Types.Frame t2 s2) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Gas t, b GHC.Types.~ EVM.Types.Gas t) => Optics.Label.LabelOptic "burned" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Gas t1, b GHC.Types.~ EVM.Types.Gas t2) => Optics.Label.LabelOptic "gas" k (EVM.Types.FrameState t1 s) (EVM.Types.FrameState t2 s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Gas t1, b GHC.Types.~ EVM.Types.Gas t2) => Optics.Label.LabelOptic "gas" k (EVM.Types.VMOpts t1) (EVM.Types.VMOpts t2) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Memory s1, b GHC.Types.~ EVM.Types.Memory s2) => Optics.Label.LabelOptic "memory" k (EVM.Types.FrameState t s1) (EVM.Types.FrameState t s2) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.RuntimeConfig, b GHC.Types.~ EVM.Types.RuntimeConfig) => Optics.Label.LabelOptic "config" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.TxState, b GHC.Types.~ EVM.Types.TxState) => Optics.Label.LabelOptic "tx" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "baseFee" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "chainId" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "gasprice" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "maxCodeSize" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "number" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "prevRandao" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.W256, b GHC.Types.~ EVM.Types.W256) => Optics.Label.LabelOptic "priorityFee" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe (EVM.Types.VMResult t s), b GHC.Types.~ GHC.Maybe.Maybe (EVM.Types.VMResult t s)) => Optics.Label.LabelOptic "result" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Bool, b GHC.Types.~ GHC.Types.Bool) => Optics.Label.LabelOptic "allowFFI" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Bool, b GHC.Types.~ GHC.Types.Bool) => Optics.Label.LabelOptic "create" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Bool, b GHC.Types.~ GHC.Types.Bool) => Optics.Label.LabelOptic "static" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Label.LabelOptic "currentFork" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Label.LabelOptic "freshGasVals" k EVM.Types.Env EVM.Types.Env a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Label.LabelOptic "pc" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "blockGaslimit" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "gaslimit" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Word.Word64, b GHC.Types.~ GHC.Word.Word64) => Optics.Label.LabelOptic "memorySize" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [(EVM.Types.Expr 'EVM.Types.EAddr, EVM.Types.Contract)], b GHC.Types.~ [(EVM.Types.Expr 'EVM.Types.EAddr, EVM.Types.Contract)]) => Optics.Label.LabelOptic "otherContracts" k (EVM.Types.VMOpts t) (EVM.Types.VMOpts t) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Expr 'EVM.Types.EWord], b GHC.Types.~ [EVM.Types.Expr 'EVM.Types.EWord]) => Optics.Label.LabelOptic "stack" k (EVM.Types.FrameState t s) (EVM.Types.FrameState t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Expr 'EVM.Types.Log], b GHC.Types.~ [EVM.Types.Expr 'EVM.Types.Log]) => Optics.Label.LabelOptic "logs" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Frame t s], b GHC.Types.~ [EVM.Types.Frame t s]) => Optics.Label.LabelOptic "frames" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Prop], b GHC.Types.~ [EVM.Types.Prop]) => Optics.Label.LabelOptic "constraints" k (EVM.Types.VM t s) (EVM.Types.VM t s) a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.An_AffineTraversal, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "offset" k EVM.Types.FrameContext EVM.Types.FrameContext a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.An_AffineTraversal, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "size" k EVM.Types.FrameContext EVM.Types.FrameContext a b
+ EVM.Types: instance GHC.Generics.Generic (EVM.Types.FrameState t s)
+ EVM.Types: instance GHC.Generics.Generic (EVM.Types.VM t s)
+ EVM.Types: instance GHC.Generics.Generic EVM.Types.ForkState
+ EVM.Types: instance GHC.Show.Show (EVM.Types.Effect t s)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.Frame 'EVM.Types.Concrete s)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.Frame 'EVM.Types.Symbolic s)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.FrameState 'EVM.Types.Concrete s)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.FrameState 'EVM.Types.Symbolic s)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.Query t s)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.VM 'EVM.Types.Concrete s)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.VM 'EVM.Types.Symbolic s)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.VMOpts 'EVM.Types.Concrete)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.VMOpts 'EVM.Types.Symbolic)
+ EVM.Types: instance GHC.Show.Show (EVM.Types.VMResult t s)
+ EVM.Types: instance GHC.Show.Show EVM.Types.ForkState
+ EVM.Types: partial :: VMOps t => PartialExec -> EVM t s ()
+ EVM.Types: payRefunds :: VMOps t => EVM t s ()
+ EVM.Types: pushGas :: VMOps t => EVM t s ()
+ EVM.Types: reclaimRemainingGasAllowance :: VMOps t => VM t s -> EVM t s ()
+ EVM.Types: subGas :: VMOps t => Gas t -> Word64 -> Gas t
+ EVM.Types: toGas :: VMOps t => Word64 -> Gas t
+ EVM.Types: type family Gas (t :: VMType) = r | r -> t
+ EVM.Types: untilFixpoint :: Eq a => (a -> a) -> a -> a
+ EVM.Types: whenSymbolicElse :: VMOps t => EVM t s a -> EVM t s a -> EVM t s a
- EVM: accessAccountForGas :: Expr EAddr -> EVM s Bool
+ EVM: accessAccountForGas :: Expr EAddr -> EVM t s Bool
- EVM: accessAndBurn :: Expr EAddr -> EVM s () -> EVM s ()
+ EVM: accessAndBurn :: VMOps t => Expr EAddr -> EVM t s () -> EVM t s ()
- EVM: accessMemoryRange :: W256 -> W256 -> EVM s () -> EVM s ()
+ EVM: accessMemoryRange :: VMOps t => Expr EWord -> Expr EWord -> EVM t s () -> EVM t s ()
- EVM: accessMemoryWord :: W256 -> EVM s () -> EVM s ()
+ EVM: accessMemoryWord :: VMOps t => Expr EWord -> EVM t s () -> EVM t s ()
- EVM: accessStorage :: Expr EAddr -> Expr EWord -> (Expr EWord -> EVM s ()) -> EVM s ()
+ EVM: accessStorage :: VMOps t => Expr EAddr -> Expr EWord -> (Expr EWord -> EVM t s ()) -> EVM t s ()
- EVM: accessStorageForGas :: Expr EAddr -> Expr EWord -> EVM s Bool
+ EVM: accessStorageForGas :: Expr EAddr -> Expr EWord -> EVM t s Bool
- EVM: accessUnboundedMemoryRange :: Word64 -> Word64 -> EVM s () -> EVM s ()
+ EVM: accessUnboundedMemoryRange :: VMOps t => Word64 -> Word64 -> EVM t s () -> EVM t s ()
- EVM: accountExists :: Expr EAddr -> VM s -> Bool
+ EVM: accountExists :: Expr EAddr -> VM t s -> Bool
- EVM: blankState :: ST s (FrameState s)
+ EVM: blankState :: VMOps t => ST s (FrameState t s)
- EVM: burn :: Word64 -> EVM s () -> EVM s ()
+ EVM: burn :: VMOps t => Word64 -> EVM t s () -> EVM t s ()
- EVM: callChecks :: (?op :: Word8) => Contract -> Word64 -> Expr EAddr -> Expr EAddr -> Expr EWord -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> (Word64 -> EVM s ()) -> EVM s ()
+ EVM: callChecks :: forall (t :: VMType) s. (?op :: Word8, VMOps t) => Contract -> Gas t -> Expr EAddr -> Expr EAddr -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> [Expr EWord] -> (Gas t -> EVM t s ()) -> EVM t s ()
- EVM: cheat :: (?op :: Word8) => (W256, W256) -> (W256, W256) -> EVM s ()
+ EVM: cheat :: (?op :: Word8, VMOps t) => (Expr EWord, Expr EWord) -> (Expr EWord, Expr EWord) -> EVM t s ()
- EVM: cheatActions :: Map FunctionSelector (CheatAction s)
+ EVM: cheatActions :: VMOps t => Map FunctionSelector (CheatAction t s)
- EVM: checkJump :: Int -> [Expr EWord] -> EVM s ()
+ EVM: checkJump :: VMOps t => Int -> [Expr EWord] -> EVM t s ()
- EVM: choose :: Choose s -> EVM s ()
+ EVM: choose :: Choose s -> EVM Symbolic s ()
- EVM: codeloc :: EVM s CodeLocation
+ EVM: codeloc :: EVM t s CodeLocation
- EVM: copyBytesToMemory :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM s ()
+ EVM: copyBytesToMemory :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM t s ()
- EVM: copyCallBytesToMemory :: Expr Buf -> Expr EWord -> Expr EWord -> EVM s ()
+ EVM: copyCallBytesToMemory :: Expr Buf -> Expr EWord -> Expr EWord -> EVM t s ()
- EVM: create :: (?op :: Word8) => Expr EAddr -> Contract -> W256 -> Word64 -> Expr EWord -> [Expr EWord] -> Expr EAddr -> Expr Buf -> EVM s ()
+ EVM: create :: forall t s. (?op :: Word8, VMOps t) => Expr EAddr -> Contract -> Expr EWord -> Gas t -> Expr EWord -> [Expr EWord] -> Expr EAddr -> Expr Buf -> EVM t s ()
- EVM: create2Address :: Expr EAddr -> W256 -> ByteString -> EVM s (Expr EAddr)
+ EVM: create2Address :: Expr EAddr -> W256 -> ByteString -> EVM t s (Expr EAddr)
- EVM: createAddress :: Expr EAddr -> Maybe W64 -> EVM s (Expr EAddr)
+ EVM: createAddress :: Expr EAddr -> Maybe W64 -> EVM t s (Expr EAddr)
- EVM: currentContract :: VM s -> Maybe Contract
+ EVM: currentContract :: VM t s -> Maybe Contract
- EVM: delegateCall :: (?op :: Word8) => Contract -> Word64 -> Expr EAddr -> Expr EAddr -> Expr EWord -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> (Expr EAddr -> EVM s ()) -> EVM s ()
+ EVM: delegateCall :: (VMOps t, ?op :: Word8) => Contract -> Gas t -> Expr EAddr -> Expr EAddr -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> [Expr EWord] -> (Expr EAddr -> EVM t s ()) -> EVM t s ()
- EVM: exec1 :: EVM s ()
+ EVM: exec1 :: forall (t :: VMType) s. VMOps t => EVM t s ()
- EVM: executePrecompile :: (?op :: Word8) => Addr -> Word64 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> EVM s ()
+ EVM: executePrecompile :: (?op :: Word8, VMOps t) => Addr -> Gas t -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> [Expr EWord] -> EVM t s ()
- EVM: fetchAccount :: Expr EAddr -> (Contract -> EVM s ()) -> EVM s ()
+ EVM: fetchAccount :: VMOps t => Expr EAddr -> (Contract -> EVM t s ()) -> EVM t s ()
- EVM: finalize :: EVM s ()
+ EVM: finalize :: VMOps t => EVM t s ()
- EVM: finishFrame :: FrameResult -> EVM s ()
+ EVM: finishFrame :: VMOps t => FrameResult -> EVM t s ()
- EVM: forceAddr :: Expr EWord -> String -> (Expr EAddr -> EVM s ()) -> EVM s ()
+ EVM: forceAddr :: VMOps t => Expr EWord -> String -> (Expr EAddr -> EVM t s ()) -> EVM t s ()
- EVM: forceConcrete :: Expr EWord -> String -> (W256 -> EVM s ()) -> EVM s ()
+ EVM: forceConcrete :: VMOps t => Expr EWord -> String -> (W256 -> EVM t s ()) -> EVM t s ()
- EVM: forceConcrete2 :: (Expr EWord, Expr EWord) -> String -> ((W256, W256) -> EVM s ()) -> EVM s ()
+ EVM: forceConcrete2 :: VMOps t => (Expr EWord, Expr EWord) -> String -> ((W256, W256) -> EVM t s ()) -> EVM t s ()
- EVM: forceConcreteAddr :: Expr EAddr -> String -> (Addr -> EVM s ()) -> EVM s ()
+ EVM: forceConcreteAddr :: VMOps t => Expr EAddr -> String -> (Addr -> EVM t s ()) -> EVM t s ()
- EVM: forceConcreteAddr2 :: (Expr EAddr, Expr EAddr) -> String -> ((Addr, Addr) -> EVM s ()) -> EVM s ()
+ EVM: forceConcreteAddr2 :: VMOps t => (Expr EAddr, Expr EAddr) -> String -> ((Addr, Addr) -> EVM t s ()) -> EVM t s ()
- EVM: forceConcreteBuf :: Expr Buf -> String -> (ByteString -> EVM s ()) -> EVM s ()
+ EVM: forceConcreteBuf :: VMOps t => Expr Buf -> String -> (ByteString -> EVM t s ()) -> EVM t s ()
- EVM: freezeMemory :: MutableMemory s -> EVM s (Expr Buf)
+ EVM: freezeMemory :: MutableMemory s -> EVM t s (Expr Buf)
- EVM: freshSymAddr :: EVM s (Expr EAddr)
+ EVM: freshSymAddr :: EVM t s (Expr EAddr)
- EVM: getCodeLocation :: VM s -> CodeLocation
+ EVM: getCodeLocation :: VM t s -> CodeLocation
- EVM: insertTrace :: TraceData -> EVM s ()
+ EVM: insertTrace :: TraceData -> EVM t s ()
- EVM: isValidJumpDest :: VM s -> Int -> Bool
+ EVM: isValidJumpDest :: VM t s -> Int -> Bool
- EVM: limitStack :: Int -> EVM s () -> EVM s ()
+ EVM: limitStack :: VMOps t => Int -> EVM (t :: VMType) s () -> EVM t s ()
- EVM: loadContract :: Expr EAddr -> State (VM s) ()
+ EVM: loadContract :: Expr EAddr -> State (VM t s) ()
- EVM: makeVm :: VMOpts -> ST s (VM s)
+ EVM: makeVm :: VMOps t => VMOpts t -> ST s (VM t s)
- EVM: next :: (?op :: Word8) => EVM s ()
+ EVM: next :: (?op :: Word8) => EVM t s ()
- EVM: noJumpIntoInitData :: Int -> EVM s () -> EVM s ()
+ EVM: noJumpIntoInitData :: VMOps t => Int -> EVM t s () -> EVM t s ()
- EVM: notStatic :: EVM s () -> EVM s ()
+ EVM: notStatic :: VMOps t => EVM t s () -> EVM t s ()
- EVM: popTrace :: EVM s ()
+ EVM: popTrace :: EVM t s ()
- EVM: precompiledContract :: (?op :: Word8) => Contract -> Word64 -> Addr -> Addr -> Expr EWord -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> EVM s ()
+ EVM: precompiledContract :: (?op :: Word8, VMOps t) => Contract -> Gas t -> Addr -> Addr -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> [Expr EWord] -> EVM t s ()
- EVM: push :: W256 -> EVM s ()
+ EVM: push :: W256 -> EVM t s ()
- EVM: pushAddr :: Expr EAddr -> EVM s ()
+ EVM: pushAddr :: Expr EAddr -> EVM t s ()
- EVM: pushSym :: Expr EWord -> EVM s ()
+ EVM: pushSym :: Expr EWord -> EVM t s ()
- EVM: pushTrace :: TraceData -> EVM s ()
+ EVM: pushTrace :: TraceData -> EVM t s ()
- EVM: query :: Query s -> EVM s ()
+ EVM: query :: Query t s -> EVM t s ()
- EVM: readMemory :: Expr EWord -> Expr EWord -> EVM s (Expr Buf)
+ EVM: readMemory :: Expr EWord -> Expr EWord -> EVM t s (Expr Buf)
- EVM: refund :: Word64 -> EVM s ()
+ EVM: refund :: Word64 -> EVM t s ()
- EVM: replaceCode :: Expr EAddr -> ContractCode -> EVM s ()
+ EVM: replaceCode :: Expr EAddr -> ContractCode -> EVM t s ()
- EVM: replaceCodeOfSelf :: ContractCode -> EVM s ()
+ EVM: replaceCodeOfSelf :: ContractCode -> EVM t s ()
- EVM: resetState :: EVM s ()
+ EVM: resetState :: VMOps t => EVM t s ()
- EVM: selfdestruct :: Expr EAddr -> EVM s ()
+ EVM: selfdestruct :: Expr EAddr -> EVM t s ()
- EVM: stackOp1 :: (?op :: Word8) => Word64 -> (Expr EWord -> Expr EWord) -> EVM s ()
+ EVM: stackOp1 :: (?op :: Word8, VMOps t) => Word64 -> (Expr EWord -> Expr EWord) -> EVM t s ()
- EVM: stackOp2 :: (?op :: Word8) => Word64 -> (Expr EWord -> Expr EWord -> Expr EWord) -> EVM s ()
+ EVM: stackOp2 :: (?op :: Word8, VMOps t) => Word64 -> (Expr EWord -> Expr EWord -> Expr EWord) -> EVM t s ()
- EVM: stackOp3 :: (?op :: Word8) => Word64 -> (Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord) -> EVM s ()
+ EVM: stackOp3 :: (?op :: Word8, VMOps t) => Word64 -> (Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord) -> EVM t s ()
- EVM: touchAccount :: Expr EAddr -> EVM s ()
+ EVM: touchAccount :: Expr EAddr -> EVM t s ()
- EVM: traceForest :: VM s -> Forest Trace
+ EVM: traceForest :: VM t s -> Forest Trace
- EVM: traceTopLog :: [Expr Log] -> EVM s ()
+ EVM: traceTopLog :: [Expr Log] -> EVM t s ()
- EVM: transfer :: Expr EAddr -> Expr EAddr -> Expr EWord -> EVM s ()
+ EVM: transfer :: VMOps t => Expr EAddr -> Expr EAddr -> Expr EWord -> EVM t s ()
- EVM: type CheatAction s = Expr EWord -> Expr EWord -> Expr Buf -> EVM s ()
+ EVM: type CheatAction t s = Expr EWord -> Expr EWord -> Expr Buf -> EVM t s ()
- EVM: unRefund :: Word64 -> EVM s ()
+ EVM: unRefund :: Word64 -> EVM t s ()
- EVM: underrun :: EVM s ()
+ EVM: underrun :: VMOps t => EVM t s ()
- EVM: vmError :: EvmError -> EVM s ()
+ EVM: vmError :: VMOps t => EvmError -> EVM t s ()
- EVM: vmOp :: VM s -> Maybe Op
+ EVM: vmOp :: VM t s -> Maybe Op
- EVM: vmOpIx :: VM s -> Maybe Int
+ EVM: vmOpIx :: VM t s -> Maybe Int
- EVM: withTraceLocation :: TraceData -> EVM s Trace
+ EVM: withTraceLocation :: TraceData -> EVM t s Trace
- EVM: writeMemory :: MutableMemory s -> Int -> ByteString -> EVM s ()
+ EVM: writeMemory :: MutableMemory s -> Int -> ByteString -> EVM t s ()
- EVM.Exec: exec :: EVM s (VMResult s)
+ EVM.Exec: exec :: VMOps t => EVM t s (VMResult t s)
- EVM.Exec: execWhile :: (VM s -> Bool) -> State (VM s) Int
+ EVM.Exec: execWhile :: (VM t s -> Bool) -> State (VM t s) Int
- EVM.Exec: run :: EVM s (VM s)
+ EVM.Exec: run :: VMOps t => EVM t s (VM t s)
- EVM.Exec: vmForEthrunCreation :: ByteString -> ST s (VM s)
+ EVM.Exec: vmForEthrunCreation :: VMOps t => ByteString -> ST s (VM t s)
- EVM.Fetch: checkBranch :: SolverGroup -> Prop -> Prop -> IO BranchCondition
+ EVM.Fetch: checkBranch :: App m => SolverGroup -> Prop -> Prop -> m BranchCondition
- EVM.Fetch: http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher s
+ EVM.Fetch: http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher t m s
- EVM.Fetch: oracle :: SolverGroup -> RpcInfo -> Fetcher s
+ EVM.Fetch: oracle :: SolverGroup -> RpcInfo -> Fetcher t m s
- EVM.Fetch: zero :: Natural -> Maybe Natural -> Fetcher s
+ EVM.Fetch: zero :: Natural -> Maybe Natural -> Fetcher t m s
- EVM.Format: showTraceTree :: DappInfo -> VM s -> Text
+ EVM.Format: showTraceTree :: DappInfo -> VM t s -> Text
- EVM.SMT: CexVars :: [Text] -> [Text] -> Map Text (Expr EWord) -> Map (Expr EAddr) (Set (Expr EWord)) -> [Text] -> [Text] -> CexVars
+ EVM.SMT: CexVars :: [Text] -> [Text] -> Map Text (Expr EWord) -> Map (Expr EAddr, Maybe W256) (Set (Expr EWord)) -> [Text] -> [Text] -> CexVars
- EVM.SMT: RefinementEqs :: [Builder] -> RefinementEqs
+ EVM.SMT: RefinementEqs :: [Builder] -> [Prop] -> RefinementEqs
- EVM.SMT: SMT2 :: [Builder] -> RefinementEqs -> CexVars -> SMT2
+ EVM.SMT: SMT2 :: [Builder] -> RefinementEqs -> CexVars -> [Prop] -> SMT2
- EVM.SMT: [$sel:storeReads:CexVars] :: CexVars -> Map (Expr EAddr) (Set (Expr EWord))
+ EVM.SMT: [$sel:storeReads:CexVars] :: CexVars -> Map (Expr EAddr, Maybe W256) (Set (Expr EWord))
- EVM.SMT: abstractAwayProps :: AbstRefineConfig -> [Prop] -> ([Prop], AbstState)
+ EVM.SMT: abstractAwayProps :: Config -> [Prop] -> ([Prop], AbstState)
- EVM.SMT: assertProps :: AbstRefineConfig -> [Prop] -> SMT2
+ EVM.SMT: assertProps :: Config -> [Prop] -> SMT2
- EVM.SMT: assertPropsNoSimp :: AbstRefineConfig -> [Prop] -> SMT2
+ EVM.SMT: assertPropsNoSimp :: Config -> [Prop] -> SMT2
- EVM.SMT: findStorageReads :: Prop -> Map (Expr EAddr) (Set (Expr EWord))
+ EVM.SMT: findStorageReads :: Prop -> Map (Expr EAddr, Maybe W256) (Set (Expr EWord))
- EVM.SMT: getStore :: (Text -> IO Text) -> Map (Expr EAddr) (Set (Expr EWord)) -> IO (Map (Expr EAddr) (Map W256 W256))
+ EVM.SMT: getStore :: (Text -> IO Text) -> Map (Expr EAddr, Maybe W256) (Set (Expr EWord)) -> IO (Map (Expr EAddr) (Map W256 W256))
- EVM.SMT: storeName :: Expr EAddr -> Builder
+ EVM.SMT: storeName :: Expr EAddr -> Maybe W256 -> Builder
- EVM.Solidity: solcRuntime :: Text -> Text -> IO (Maybe ByteString)
+ EVM.Solidity: solcRuntime :: MonadUnliftIO m => Text -> Text -> m (Maybe ByteString)
- EVM.Solidity: solidity :: Text -> Text -> IO (Maybe ByteString)
+ EVM.Solidity: solidity :: MonadUnliftIO m => Text -> Text -> m (Maybe ByteString)
- EVM.Solvers: SolverInstance :: Solver -> Handle -> Handle -> Handle -> ProcessHandle -> SolverInstance
+ EVM.Solvers: SolverInstance :: Solver -> Handle -> Handle -> ProcessHandle -> SolverInstance
- EVM.Solvers: withSolvers :: Solver -> Natural -> Maybe Natural -> (SolverGroup -> IO a) -> IO a
+ EVM.Solvers: withSolvers :: App m => Solver -> Natural -> Maybe Natural -> (SolverGroup -> m a) -> m a
- EVM.Stepper: [Ask] :: Choose s -> Action s ()
+ EVM.Stepper: [Ask] :: Choose s -> Action Symbolic s ()
- EVM.Stepper: [EVM] :: EVM s a -> Action s a
+ EVM.Stepper: [EVM] :: EVM t s a -> Action t s a
- EVM.Stepper: [Exec] :: Action s (VMResult s)
+ EVM.Stepper: [Exec] :: Action t s (VMResult t s)
- EVM.Stepper: [IOAct] :: IO a -> Action s a
+ EVM.Stepper: [IOAct] :: IO a -> Action t s a
- EVM.Stepper: [Wait] :: Query s -> Action s ()
+ EVM.Stepper: [Wait] :: Query t s -> Action t s ()
- EVM.Stepper: ask :: Choose s -> Stepper s ()
+ EVM.Stepper: ask :: Choose s -> Stepper Symbolic s ()
- EVM.Stepper: data Action s a
+ EVM.Stepper: data Action t s a
- EVM.Stepper: enter :: Text -> Stepper s ()
+ EVM.Stepper: enter :: Text -> Stepper t s ()
- EVM.Stepper: evm :: EVM s a -> Stepper s a
+ EVM.Stepper: evm :: EVM t s a -> Stepper t s a
- EVM.Stepper: evmIO :: IO a -> Stepper s a
+ EVM.Stepper: evmIO :: IO a -> Stepper t s a
- EVM.Stepper: exec :: Stepper s (VMResult s)
+ EVM.Stepper: exec :: Stepper t s (VMResult t s)
- EVM.Stepper: execFully :: Stepper s (Either EvmError (Expr Buf))
+ EVM.Stepper: execFully :: Stepper Concrete s (Either EvmError (Expr Buf))
- EVM.Stepper: interpret :: Fetcher RealWorld -> VM RealWorld -> Stepper RealWorld a -> IO a
+ EVM.Stepper: interpret :: forall m a. App m => Fetcher Concrete m RealWorld -> VM Concrete RealWorld -> Stepper Concrete RealWorld a -> m a
- EVM.Stepper: run :: Stepper s (VM s)
+ EVM.Stepper: run :: Stepper t s (VM t s)
- EVM.Stepper: runFully :: Stepper s (VM s)
+ EVM.Stepper: runFully :: Stepper t s (VM t s)
- EVM.Stepper: type Stepper s a = Program (Action s) a
+ EVM.Stepper: type Stepper t s a = Program (Action t s) a
- EVM.Stepper: wait :: Query s -> Stepper s ()
+ EVM.Stepper: wait :: Query t s -> Stepper t s ()
- EVM.SymExec: VeriOpts :: Bool -> Bool -> Maybe Integer -> Integer -> LoopHeuristic -> AbstRefineConfig -> RpcInfo -> VeriOpts
+ EVM.SymExec: VeriOpts :: Bool -> Maybe Integer -> Integer -> LoopHeuristic -> RpcInfo -> VeriOpts
- EVM.SymExec: abstractVM :: (Expr Buf, [Prop]) -> ByteString -> Maybe (Precondition s) -> Bool -> ST s (VM s)
+ EVM.SymExec: abstractVM :: (Expr Buf, [Prop]) -> ByteString -> Maybe (Precondition s) -> Bool -> ST s (VM Symbolic s)
- EVM.SymExec: askSmtItersReached :: VM s -> Integer -> Bool
+ EVM.SymExec: askSmtItersReached :: VM Symbolic s -> Integer -> Bool
- EVM.SymExec: checkAssert :: SolverGroup -> [Word256] -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> IO (Expr End, [VerifyResult])
+ EVM.SymExec: checkAssert :: App m => SolverGroup -> [Word256] -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> m (Expr End, [VerifyResult])
- EVM.SymExec: equivalenceCheck :: SolverGroup -> ByteString -> ByteString -> VeriOpts -> (Expr Buf, [Prop]) -> IO [EquivResult]
+ EVM.SymExec: equivalenceCheck :: forall m. App m => SolverGroup -> ByteString -> ByteString -> VeriOpts -> (Expr Buf, [Prop]) -> m [EquivResult]
- EVM.SymExec: equivalenceCheck' :: SolverGroup -> [Expr End] -> [Expr End] -> VeriOpts -> IO [EquivResult]
+ EVM.SymExec: equivalenceCheck' :: forall m. App m => SolverGroup -> [Expr End] -> [Expr End] -> m [EquivResult]
- EVM.SymExec: expandCex :: VM s -> SMTCex -> SMTCex
+ EVM.SymExec: expandCex :: VM Symbolic s -> SMTCex -> SMTCex
- EVM.SymExec: getExpr :: SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> IO (Expr End)
+ EVM.SymExec: getExpr :: App m => SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> m (Expr End)
- EVM.SymExec: interpret :: Fetcher RealWorld -> Maybe Integer -> Integer -> LoopHeuristic -> VM RealWorld -> Stepper RealWorld (Expr End) -> IO (Expr End)
+ EVM.SymExec: interpret :: forall m. App m => Fetcher Symbolic m RealWorld -> Maybe Integer -> Integer -> LoopHeuristic -> VM Symbolic RealWorld -> Stepper Symbolic RealWorld (Expr End) -> m (Expr End)
- EVM.SymExec: isLoopHead :: LoopHeuristic -> VM s -> Maybe Bool
+ EVM.SymExec: isLoopHead :: LoopHeuristic -> VM Symbolic s -> Maybe Bool
- EVM.SymExec: loadSymVM :: ContractCode -> Expr EWord -> (Expr Buf, [Prop]) -> Bool -> ST s (VM s)
+ EVM.SymExec: loadSymVM :: ContractCode -> Expr EWord -> (Expr Buf, [Prop]) -> Bool -> ST s (VM Symbolic s)
- EVM.SymExec: maxIterationsReached :: VM s -> Maybe Integer -> Maybe Bool
+ EVM.SymExec: maxIterationsReached :: VM Symbolic s -> Maybe Integer -> Maybe Bool
- EVM.SymExec: produceModels :: SolverGroup -> Expr End -> IO [(Expr End, CheckSatResult)]
+ EVM.SymExec: produceModels :: App m => SolverGroup -> Expr End -> m [(Expr End, CheckSatResult)]
- EVM.SymExec: reachable :: SolverGroup -> Expr End -> IO ([SMT2], Expr End)
+ EVM.SymExec: reachable :: App m => SolverGroup -> Expr End -> m ([SMT2], Expr End)
- EVM.SymExec: runExpr :: Stepper RealWorld (Expr End)
+ EVM.SymExec: runExpr :: Stepper Symbolic RealWorld (Expr End)
- EVM.SymExec: type Postcondition s = VM s -> Expr End -> Prop
+ EVM.SymExec: type Postcondition s = VM Symbolic s -> Expr End -> Prop
- EVM.SymExec: type Precondition s = VM s -> Prop
+ EVM.SymExec: type Precondition s = VM Symbolic s -> Prop
- EVM.SymExec: verify :: SolverGroup -> VeriOpts -> VM RealWorld -> Maybe (Postcondition RealWorld) -> IO (Expr End, [VerifyResult])
+ EVM.SymExec: verify :: App m => SolverGroup -> VeriOpts -> VM Symbolic RealWorld -> Maybe (Postcondition RealWorld) -> m (Expr End, [VerifyResult])
- EVM.SymExec: verifyContract :: SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> Maybe (Precondition RealWorld) -> Maybe (Postcondition RealWorld) -> IO (Expr End, [VerifyResult])
+ EVM.SymExec: verifyContract :: App m => SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> Maybe (Precondition RealWorld) -> Maybe (Postcondition RealWorld) -> m (Expr End, [VerifyResult])
- EVM.Transaction: initTx :: VM s -> VM s
+ EVM.Transaction: initTx :: VM t s -> VM t s
- EVM.Types: CallContext :: Expr EAddr -> Expr EAddr -> W256 -> W256 -> Expr EWord -> Maybe W256 -> Expr Buf -> Map (Expr EAddr) Contract -> SubState -> FrameContext
+ EVM.Types: CallContext :: Expr EAddr -> Expr EAddr -> Expr EWord -> Expr EWord -> Expr EWord -> Maybe W256 -> Expr Buf -> Map (Expr EAddr) Contract -> SubState -> FrameContext
- EVM.Types: Env :: Map (Expr EAddr) Contract -> W256 -> Int -> Env
+ EVM.Types: Env :: Map (Expr EAddr) Contract -> W256 -> Int -> Int -> Env
- EVM.Types: Frame :: FrameContext -> FrameState s -> Frame s
+ EVM.Types: Frame :: FrameContext -> FrameState t s -> Frame (t :: VMType) s
- EVM.Types: FrameState :: Expr EAddr -> Expr EAddr -> ContractCode -> {-# UNPACK #-} !Int -> [Expr EWord] -> Memory s -> Word64 -> Expr Buf -> Expr EWord -> Expr EAddr -> {-# UNPACK #-} !Word64 -> Expr Buf -> Bool -> FrameState s
+ EVM.Types: FrameState :: Expr EAddr -> Expr EAddr -> ContractCode -> {-# UNPACK #-} !Int -> [Expr EWord] -> Memory s -> Word64 -> Expr Buf -> Expr EWord -> Expr EAddr -> !Gas t -> Expr Buf -> Bool -> FrameState (t :: VMType) s
- EVM.Types: MaxInitCodeSizeExceeded :: W256 -> W256 -> EvmError
+ EVM.Types: MaxInitCodeSizeExceeded :: W256 -> Expr EWord -> EvmError
- EVM.Types: VM :: Maybe (VMResult s) -> FrameState s -> [Frame s] -> Env -> Block -> TxState -> [Expr Log] -> TreePos Empty Trace -> Cache -> {-# UNPACK #-} !Word64 -> Map CodeLocation (Int, [Expr EWord]) -> [Prop] -> [Prop] -> RuntimeConfig -> VM s
+ EVM.Types: VM :: Maybe (VMResult t s) -> FrameState t s -> [Frame t s] -> Env -> Block -> TxState -> [Expr Log] -> TreePos Empty Trace -> Cache -> !Gas t -> Map CodeLocation (Int, [Expr EWord]) -> [Prop] -> RuntimeConfig -> Seq ForkState -> Int -> VM (t :: VMType) s
- EVM.Types: VMOpts :: Contract -> [(Expr EAddr, Contract)] -> (Expr Buf, [Prop]) -> BaseState -> Expr EWord -> W256 -> Expr EAddr -> Expr EAddr -> Expr EAddr -> Word64 -> Word64 -> W256 -> Expr EWord -> Expr EAddr -> W256 -> W256 -> Word64 -> W256 -> W256 -> FeeSchedule Word64 -> W256 -> Bool -> Map (Expr EAddr) [W256] -> Bool -> VMOpts
+ EVM.Types: VMOpts :: Contract -> [(Expr EAddr, Contract)] -> (Expr Buf, [Prop]) -> BaseState -> Expr EWord -> W256 -> Expr EAddr -> Expr EAddr -> Expr EAddr -> Gas t -> Word64 -> W256 -> Expr EWord -> Expr EAddr -> W256 -> W256 -> Word64 -> W256 -> W256 -> FeeSchedule Word64 -> W256 -> Bool -> Map (Expr EAddr) [W256] -> Bool -> VMOpts (t :: VMType)
- EVM.Types: [$sel:address:VMOpts] :: VMOpts -> Expr EAddr
+ EVM.Types: [$sel:address:VMOpts] :: VMOpts (t :: VMType) -> Expr EAddr
- EVM.Types: [$sel:allowFFI:VMOpts] :: VMOpts -> Bool
+ EVM.Types: [$sel:allowFFI:VMOpts] :: VMOpts (t :: VMType) -> Bool
- EVM.Types: [$sel:baseFee:VMOpts] :: VMOpts -> W256
+ EVM.Types: [$sel:baseFee:VMOpts] :: VMOpts (t :: VMType) -> W256
- EVM.Types: [$sel:baseState:VMOpts] :: VMOpts -> BaseState
+ EVM.Types: [$sel:baseState:VMOpts] :: VMOpts (t :: VMType) -> BaseState
- EVM.Types: [$sel:block:VM] :: VM s -> Block
+ EVM.Types: [$sel:block:VM] :: VM (t :: VMType) s -> Block
- EVM.Types: [$sel:blockGaslimit:VMOpts] :: VMOpts -> Word64
+ EVM.Types: [$sel:blockGaslimit:VMOpts] :: VMOpts (t :: VMType) -> Word64
- EVM.Types: [$sel:burned:VM] :: VM s -> {-# UNPACK #-} !Word64
+ EVM.Types: [$sel:burned:VM] :: VM (t :: VMType) s -> !Gas t
- EVM.Types: [$sel:cache:VM] :: VM s -> Cache
+ EVM.Types: [$sel:cache:VM] :: VM (t :: VMType) s -> Cache
- EVM.Types: [$sel:calldata:FrameState] :: FrameState s -> Expr Buf
+ EVM.Types: [$sel:calldata:FrameState] :: FrameState (t :: VMType) s -> Expr Buf
- EVM.Types: [$sel:calldata:VMOpts] :: VMOpts -> (Expr Buf, [Prop])
+ EVM.Types: [$sel:calldata:VMOpts] :: VMOpts (t :: VMType) -> (Expr Buf, [Prop])
- EVM.Types: [$sel:caller:FrameState] :: FrameState s -> Expr EAddr
+ EVM.Types: [$sel:caller:FrameState] :: FrameState (t :: VMType) s -> Expr EAddr
- EVM.Types: [$sel:caller:VMOpts] :: VMOpts -> Expr EAddr
+ EVM.Types: [$sel:caller:VMOpts] :: VMOpts (t :: VMType) -> Expr EAddr
- EVM.Types: [$sel:callvalue:FrameState] :: FrameState s -> Expr EWord
+ EVM.Types: [$sel:callvalue:FrameState] :: FrameState (t :: VMType) s -> Expr EWord
- EVM.Types: [$sel:chainId:VMOpts] :: VMOpts -> W256
+ EVM.Types: [$sel:chainId:VMOpts] :: VMOpts (t :: VMType) -> W256
- EVM.Types: [$sel:code:FrameState] :: FrameState s -> ContractCode
+ EVM.Types: [$sel:code:FrameState] :: FrameState (t :: VMType) s -> ContractCode
- EVM.Types: [$sel:codeContract:FrameState] :: FrameState s -> Expr EAddr
+ EVM.Types: [$sel:codeContract:FrameState] :: FrameState (t :: VMType) s -> Expr EAddr
- EVM.Types: [$sel:coinbase:VMOpts] :: VMOpts -> Expr EAddr
+ EVM.Types: [$sel:coinbase:VMOpts] :: VMOpts (t :: VMType) -> Expr EAddr
- EVM.Types: [$sel:config:VM] :: VM s -> RuntimeConfig
+ EVM.Types: [$sel:config:VM] :: VM (t :: VMType) s -> RuntimeConfig
- EVM.Types: [$sel:constraints:VM] :: VM s -> [Prop]
+ EVM.Types: [$sel:constraints:VM] :: VM (t :: VMType) s -> [Prop]
- EVM.Types: [$sel:context:Frame] :: Frame s -> FrameContext
+ EVM.Types: [$sel:context:Frame] :: Frame (t :: VMType) s -> FrameContext
- EVM.Types: [$sel:contract:FrameState] :: FrameState s -> Expr EAddr
+ EVM.Types: [$sel:contract:FrameState] :: FrameState (t :: VMType) s -> Expr EAddr
- EVM.Types: [$sel:contract:VMOpts] :: VMOpts -> Contract
+ EVM.Types: [$sel:contract:VMOpts] :: VMOpts (t :: VMType) -> Contract
- EVM.Types: [$sel:create:VMOpts] :: VMOpts -> Bool
+ EVM.Types: [$sel:create:VMOpts] :: VMOpts (t :: VMType) -> Bool
- EVM.Types: [$sel:env:VM] :: VM s -> Env
+ EVM.Types: [$sel:env:VM] :: VM (t :: VMType) s -> Env
- EVM.Types: [$sel:frames:VM] :: VM s -> [Frame s]
+ EVM.Types: [$sel:frames:VM] :: VM (t :: VMType) s -> [Frame t s]
- EVM.Types: [$sel:gas:FrameState] :: FrameState s -> {-# UNPACK #-} !Word64
+ EVM.Types: [$sel:gas:FrameState] :: FrameState (t :: VMType) s -> !Gas t
- EVM.Types: [$sel:gas:VMOpts] :: VMOpts -> Word64
+ EVM.Types: [$sel:gas:VMOpts] :: VMOpts (t :: VMType) -> Gas t
- EVM.Types: [$sel:gaslimit:VMOpts] :: VMOpts -> Word64
+ EVM.Types: [$sel:gaslimit:VMOpts] :: VMOpts (t :: VMType) -> Word64
- EVM.Types: [$sel:gasprice:VMOpts] :: VMOpts -> W256
+ EVM.Types: [$sel:gasprice:VMOpts] :: VMOpts (t :: VMType) -> W256
- EVM.Types: [$sel:iterations:VM] :: VM s -> Map CodeLocation (Int, [Expr EWord])
+ EVM.Types: [$sel:iterations:VM] :: VM (t :: VMType) s -> Map CodeLocation (Int, [Expr EWord])
- EVM.Types: [$sel:logs:VM] :: VM s -> [Expr Log]
+ EVM.Types: [$sel:logs:VM] :: VM (t :: VMType) s -> [Expr Log]
- EVM.Types: [$sel:maxCodeSize:VMOpts] :: VMOpts -> W256
+ EVM.Types: [$sel:maxCodeSize:VMOpts] :: VMOpts (t :: VMType) -> W256
- EVM.Types: [$sel:memory:FrameState] :: FrameState s -> Memory s
+ EVM.Types: [$sel:memory:FrameState] :: FrameState (t :: VMType) s -> Memory s
- EVM.Types: [$sel:memorySize:FrameState] :: FrameState s -> Word64
+ EVM.Types: [$sel:memorySize:FrameState] :: FrameState (t :: VMType) s -> Word64
- EVM.Types: [$sel:number:VMOpts] :: VMOpts -> W256
+ EVM.Types: [$sel:number:VMOpts] :: VMOpts (t :: VMType) -> W256
- EVM.Types: [$sel:offset:CreationContext] :: FrameContext -> W256
+ EVM.Types: [$sel:offset:CreationContext] :: FrameContext -> Expr EWord
- EVM.Types: [$sel:origin:VMOpts] :: VMOpts -> Expr EAddr
+ EVM.Types: [$sel:origin:VMOpts] :: VMOpts (t :: VMType) -> Expr EAddr
- EVM.Types: [$sel:otherContracts:VMOpts] :: VMOpts -> [(Expr EAddr, Contract)]
+ EVM.Types: [$sel:otherContracts:VMOpts] :: VMOpts (t :: VMType) -> [(Expr EAddr, Contract)]
- EVM.Types: [$sel:pc:FrameState] :: FrameState s -> {-# UNPACK #-} !Int
+ EVM.Types: [$sel:pc:FrameState] :: FrameState (t :: VMType) s -> {-# UNPACK #-} !Int
- EVM.Types: [$sel:prevRandao:VMOpts] :: VMOpts -> W256
+ EVM.Types: [$sel:prevRandao:VMOpts] :: VMOpts (t :: VMType) -> W256
- EVM.Types: [$sel:priorityFee:VMOpts] :: VMOpts -> W256
+ EVM.Types: [$sel:priorityFee:VMOpts] :: VMOpts (t :: VMType) -> W256
- EVM.Types: [$sel:result:VM] :: VM s -> Maybe (VMResult s)
+ EVM.Types: [$sel:result:VM] :: VM (t :: VMType) s -> Maybe (VMResult t s)
- EVM.Types: [$sel:returndata:FrameState] :: FrameState s -> Expr Buf
+ EVM.Types: [$sel:returndata:FrameState] :: FrameState (t :: VMType) s -> Expr Buf
- EVM.Types: [$sel:schedule:VMOpts] :: VMOpts -> FeeSchedule Word64
+ EVM.Types: [$sel:schedule:VMOpts] :: VMOpts (t :: VMType) -> FeeSchedule Word64
- EVM.Types: [$sel:size:CreationContext] :: FrameContext -> W256
+ EVM.Types: [$sel:size:CreationContext] :: FrameContext -> Expr EWord
- EVM.Types: [$sel:stack:FrameState] :: FrameState s -> [Expr EWord]
+ EVM.Types: [$sel:stack:FrameState] :: FrameState (t :: VMType) s -> [Expr EWord]
- EVM.Types: [$sel:state:Frame] :: Frame s -> FrameState s
+ EVM.Types: [$sel:state:Frame] :: Frame (t :: VMType) s -> FrameState t s
- EVM.Types: [$sel:state:VM] :: VM s -> FrameState s
+ EVM.Types: [$sel:state:VM] :: VM (t :: VMType) s -> FrameState t s
- EVM.Types: [$sel:static:FrameState] :: FrameState s -> Bool
+ EVM.Types: [$sel:static:FrameState] :: FrameState (t :: VMType) s -> Bool
- EVM.Types: [$sel:timestamp:VMOpts] :: VMOpts -> Expr EWord
+ EVM.Types: [$sel:timestamp:VMOpts] :: VMOpts (t :: VMType) -> Expr EWord
- EVM.Types: [$sel:traces:VM] :: VM s -> TreePos Empty Trace
+ EVM.Types: [$sel:traces:VM] :: VM (t :: VMType) s -> TreePos Empty Trace
- EVM.Types: [$sel:tx:VM] :: VM s -> TxState
+ EVM.Types: [$sel:tx:VM] :: VM (t :: VMType) s -> TxState
- EVM.Types: [$sel:txAccessList:VMOpts] :: VMOpts -> Map (Expr EAddr) [W256]
+ EVM.Types: [$sel:txAccessList:VMOpts] :: VMOpts (t :: VMType) -> Map (Expr EAddr) [W256]
- EVM.Types: [$sel:value:VMOpts] :: VMOpts -> Expr EWord
+ EVM.Types: [$sel:value:VMOpts] :: VMOpts (t :: VMType) -> Expr EWord
- EVM.Types: [AbstractStore] :: Expr EAddr -> Expr Storage
+ EVM.Types: [AbstractStore] :: Expr EAddr -> Maybe W256 -> Expr Storage
- EVM.Types: [Gas] :: Int -> Int -> Expr EWord
+ EVM.Types: [Gas] :: Int -> Expr EWord
- EVM.Types: [PleaseAskSMT] :: Expr EWord -> [Prop] -> (BranchCondition -> EVM s ()) -> Query s
+ EVM.Types: [PleaseAskSMT] :: Expr EWord -> [Prop] -> (BranchCondition -> EVM Symbolic s ()) -> Query Symbolic s
- EVM.Types: [PleaseChoosePath] :: Expr EWord -> (Bool -> EVM s ()) -> Choose s
+ EVM.Types: [PleaseChoosePath] :: Expr EWord -> (Bool -> EVM Symbolic s ()) -> Choose s
- EVM.Types: [PleaseDoFFI] :: [String] -> (ByteString -> EVM s ()) -> Query s
+ EVM.Types: [PleaseDoFFI] :: [String] -> (ByteString -> EVM t s ()) -> Query t s
- EVM.Types: [PleaseFetchContract] :: Addr -> BaseState -> (Contract -> EVM s ()) -> Query s
+ EVM.Types: [PleaseFetchContract] :: Addr -> BaseState -> (Contract -> EVM t s ()) -> Query t s
- EVM.Types: [PleaseFetchSlot] :: Addr -> W256 -> (W256 -> EVM s ()) -> Query s
+ EVM.Types: [PleaseFetchSlot] :: Addr -> W256 -> (W256 -> EVM t s ()) -> Query t s
- EVM.Types: data Effect s
+ EVM.Types: data Effect t s
- EVM.Types: data Frame s
+ EVM.Types: data Frame (t :: VMType) s
- EVM.Types: data FrameState s
+ EVM.Types: data FrameState (t :: VMType) s
- EVM.Types: data Query s
+ EVM.Types: data Query t s
- EVM.Types: data VM s
+ EVM.Types: data VM (t :: VMType) s
- EVM.Types: data VMOpts
+ EVM.Types: data VMOpts (t :: VMType)
- EVM.Types: data VMResult s
+ EVM.Types: data VMResult (t :: VMType) s
- EVM.Types: type EVM s a = StateT (VM s) (ST s) a
+ EVM.Types: type EVM (t :: VMType) s a = StateT (VM t s) (ST s) a
- EVM.UnitTest: UnitTestOptions :: RpcInfo -> SolverGroup -> Maybe Int -> Maybe Integer -> Integer -> Bool -> Maybe Natural -> Maybe Text -> Text -> DappInfo -> TestVMParams -> Bool -> UnitTestOptions s
+ EVM.UnitTest: UnitTestOptions :: RpcInfo -> SolverGroup -> Maybe Int -> Maybe Integer -> Integer -> Maybe Natural -> Maybe Text -> Text -> DappInfo -> TestVMParams -> Bool -> UnitTestOptions s
- EVM.UnitTest: abiCall :: TestVMParams -> Either (Text, AbiValue) ByteString -> EVM s ()
+ EVM.UnitTest: abiCall :: VMOps t => TestVMParams -> Either (Text, AbiValue) ByteString -> EVM t s ()
- EVM.UnitTest: checkSymFailures :: UnitTestOptions RealWorld -> Stepper RealWorld (VM RealWorld)
+ EVM.UnitTest: checkSymFailures :: VMOps t => UnitTestOptions RealWorld -> Stepper t RealWorld (VM t RealWorld)
- EVM.UnitTest: execSymTest :: UnitTestOptions RealWorld -> ABIMethod -> (Expr Buf, [Prop]) -> Stepper RealWorld (Expr End)
+ EVM.UnitTest: execSymTest :: UnitTestOptions RealWorld -> ABIMethod -> (Expr Buf, [Prop]) -> Stepper Symbolic RealWorld (Expr End)
- EVM.UnitTest: failOutput :: VM s -> UnitTestOptions s -> Text -> Text
+ EVM.UnitTest: failOutput :: VM t s -> UnitTestOptions s -> Text -> Text
- EVM.UnitTest: initialUnitTestVm :: UnitTestOptions s -> SolcContract -> ST s (VM s)
+ EVM.UnitTest: initialUnitTestVm :: VMOps t => UnitTestOptions s -> SolcContract -> ST s (VM t s)
- EVM.UnitTest: initializeUnitTest :: UnitTestOptions s -> SolcContract -> Stepper s ()
+ EVM.UnitTest: initializeUnitTest :: UnitTestOptions s -> SolcContract -> Stepper Concrete s ()
- EVM.UnitTest: makeTxCall :: TestVMParams -> (Expr Buf, [Prop]) -> EVM s ()
+ EVM.UnitTest: makeTxCall :: VMOps t => TestVMParams -> (Expr Buf, [Prop]) -> EVM t s ()
- EVM.UnitTest: passOutput :: VM s -> UnitTestOptions s -> Text -> Text
+ EVM.UnitTest: passOutput :: VM t s -> UnitTestOptions s -> Text -> Text
- EVM.UnitTest: runUnitTestContract :: UnitTestOptions RealWorld -> Map Text SolcContract -> (Text, [Sig]) -> IO [Bool]
+ EVM.UnitTest: runUnitTestContract :: App m => UnitTestOptions RealWorld -> Map Text SolcContract -> (Text, [Sig]) -> m [Bool]
- EVM.UnitTest: symRun :: UnitTestOptions RealWorld -> VM RealWorld -> Sig -> IO (Text, Either Text Text)
+ EVM.UnitTest: symRun :: App m => UnitTestOptions RealWorld -> VM Concrete RealWorld -> Sig -> m (Text, Either Text Text)
- EVM.UnitTest: unitTest :: UnitTestOptions RealWorld -> Contracts -> IO Bool
+ EVM.UnitTest: unitTest :: App m => UnitTestOptions RealWorld -> Contracts -> m Bool
Files
- CHANGELOG.md +38/−0
- bench/bench.hs +11/−7
- cli/cli.hs +78/−64
- hevm.cabal +16/−6
- src/EVM.hs +2788/−2619
- src/EVM/ABI.hs +1/−1
- src/EVM/Effects.hs +123/−0
- src/EVM/Exec.hs +5/−5
- src/EVM/Expr.hs +223/−28
- src/EVM/Fetch.hs +14/−11
- src/EVM/Format.hs +83/−61
- src/EVM/Fuzz.hs +263/−0
- src/EVM/Keccak.hs +34/−19
- src/EVM/SMT.hs +100/−67
- src/EVM/Solidity.hs +22/−13
- src/EVM/Solvers.hs +107/−64
- src/EVM/Stepper.hs +30/−33
- src/EVM/SymExec.hs +156/−150
- src/EVM/Transaction.hs +1/−1
- src/EVM/Traversals.hs +4/−4
- src/EVM/Types.hs +133/−61
- src/EVM/UnitTest.hs +34/−35
- test/BlockchainTests.hs +6/−2
- test/EVM/Test/BlockchainTests.hs +60/−33
- test/EVM/Test/Tracing.hs +72/−65
- test/EVM/Test/Utils.hs +25/−6
- test/rpc.hs +25/−14
- test/test.hs +4237/−3801
CHANGELOG.md view
@@ -1,3 +1,5 @@+## [0.53.0] - 2024-02-23+ # Changelog All notable changes to this project will be documented in this file.@@ -5,6 +7,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Changed++- Minimum distance requirements are now asserted for Keccak function calls. They assert that it's hard to generate two Keccak's that are less than 256 afar.+- Keccak concretization is now done only after all simplifications are performed. This helps with simplification pre-concretization+- Added an IllegalOverflow error in case the system tries to allocate a large amount of memory during+ abstract gas execution but concrete running. In these cases, the interpreter can out-of-heap+ as the only check is that the size allocated is less than $2**{64}$, but that is too large to fit in memory. Now,+ we check more stringently, and still return an IllegalOverflow+- Fixed `--root` option for the `test` subcommand++## Added++- Optimized smt queries that significantly improve performance when dealing with solidity mappings and arrays+- Support for using Bitwuzla as a solver+- More efficient encoding for failure in ds-test style tests+- Symbolic tests now support statically sized arrays as parameters+- `hevm test` now has a `num-solvers` parameter that controls how many solver instances to spawn+- New solc-specific simplification rules that should make the final Props a lot more readable+- Prop is now correctly ordered, better BufLength and Max simplifications of Expr,+ and further solc-specific simplifications of Expr+- Simplify earlier and don't check reachability for things statically determined to be FALSE+- New concrete fuzzer that can be controlled via `--num-cex-fuzz`+- Partial support for dynamic jumps when the jump destination can be computed+ given already available information+- Added three forking cheatcodes: `createFork`, `selectFork`, and `activeFork`++## Fixed++- Traces now correctly perform source mapping to display contract details+- Event traces now correctly display indexed arguments and argument names+- JSON reading of foundry JSONs was dependent on locale and did not work with many locales.+- CVC5 needs `--incremental` flag to work properly in abstraction-refinement mode+ ## [0.52.0] - 2023-10-26 This is a major breaking release that removes several user facing features and includes non trivial@@ -71,6 +106,7 @@ - `test` now falls back to displaying an unecoded bytestring for calldata when the model returned by the solver has a different length the length of the arguments in the test signature. - we now generate correct counterexamples for branches where only a subset of input variables are referenced by the path conditions - `vm.prank` now works correctly when passed a symbolic address+- `vm.prank` now works correctly when the next call transfers value - storage layout information will now be parsed from the output of `forge build` if it is available ## API Changes@@ -109,9 +145,11 @@ - CopySlice+WriteWord+ConcreteBuf now truncates ConcreteBuf in special cases - Better simplification of Eq IR elements - Run a toplevel constant folding reasoning system on branch conditions+- Global config via a monad, which should allow for more refactoring - `evalProp` is renamed to `simplifyProp` for consistency - Mem explosion in `writeWord` function was possible in case `offset` was close to 2^256. Fixed. - BufLength was not simplified via bufLength function. Fixed.+- Add and Mul are associative, let's use that to make Expr more canonical - `VMOpts` no longer takes an initial store, and instead takes a `baseState` which can be either `EmptyBase` or `AbstractBase`. This controls whether storage should be inialized as empty or fully abstract. Regardless of this
bench/bench.hs view
@@ -2,6 +2,7 @@ import GHC.Natural import Control.Monad+import Control.Monad.IO.Unlift import Data.Maybe import System.Environment (getEnv) @@ -19,6 +20,7 @@ import EVM.SymExec import EVM.Solidity import EVM.Solvers+import EVM.Effects import EVM.Format (hexByteString) import qualified EVM.Stepper as Stepper import qualified EVM.Fetch as Fetch@@ -67,16 +69,18 @@ if n `elem` ignored then pure True else do- res <- runBCTest c+ res <- runApp $ runBCTest c pure $ acc && res ) True cases -- | executes a single test case and returns a boolean value representing its success-runBCTest :: BCTests.Case -> IO Bool+runBCTest :: App m => BCTests.Case -> m Bool runBCTest x = do- vm0 <- BCTests.vmForCase x+ vm0 <- liftIO $ BCTests.vmForCase x result <- Stepper.interpret (Fetch.zero 0 Nothing) vm0 Stepper.runFully+ writeTrace vm0+ maybeReason <- BCTests.checkExpectation False x result pure $ isNothing maybeReason @@ -84,7 +88,7 @@ --- Helpers ---------------------------------------------------------------------------------------- -findPanics :: Solver -> Natural -> Integer -> ByteString -> IO ()+findPanics :: App m => Solver -> Natural -> Integer -> ByteString -> m () findPanics solver count iters c = do _ <- withSolvers solver count Nothing $ \s -> do let opts = defaultVeriOpts@@ -92,7 +96,7 @@ , askSmtIters = iters + 1 } checkAssert s allPanicCodes c Nothing [] opts- putStrLn "done"+ liftIO $ putStrLn "done" -- constructs a benchmark suite that checks the given bytecode for reachable@@ -103,8 +107,8 @@ mkbench c name iters counts = localOption WallTime $ env c (bgroup name . bmarks) where bmarks c' = concat $ [- [ bench ("cvc5-" <> show i) $ nfIO $ findPanics CVC5 i iters c'- , bench ("z3-" <> show i) $ nfIO $ findPanics Z3 i iters c'+ [ bench ("cvc5-" <> show i) $ nfIO $ runApp $ findPanics CVC5 i iters c'+ , bench ("z3-" <> show i) $ nfIO $ runApp $ findPanics Z3 i iters c' ] | i <- counts ]
cli/cli.hs view
@@ -8,6 +8,7 @@ import Control.Monad (when, forM_, unless) import Control.Monad.ST (RealWorld, stToIO)+import Control.Monad.IO.Unlift import Data.ByteString (ByteString) import Data.DoubleWord (Word256) import Data.List (intersperse)@@ -22,8 +23,7 @@ import Witch (unsafeInto) import Options.Generic as Options import Paths_hevm qualified as Paths-import System.IO (stderr)-import System.Directory (withCurrentDirectory, getCurrentDirectory, doesDirectoryExist)+import System.Directory (withCurrentDirectory, getCurrentDirectory, doesDirectoryExist, makeAbsolute) import System.FilePath ((</>)) import System.Exit (exitFailure, exitWith, ExitCode(..)) @@ -35,14 +35,16 @@ import GitHash import EVM.FeeSchedule (feeSchedule) import EVM.Fetch qualified as Fetch-import EVM.Format (hexByteString, strip0x, showTraceTree, formatExpr)+import EVM.Format (hexByteString, strip0x, formatExpr) import EVM.Solidity import EVM.Solvers import EVM.Stepper qualified import EVM.SymExec import EVM.Transaction qualified-import EVM.Types hiding (word)+import EVM.Types hiding (word, Env, Symbolic)+import EVM.Types qualified import EVM.UnitTest+import EVM.Effects -- This record defines the program's command-line options -- automatically via the `optparse-generic` package.@@ -83,10 +85,13 @@ , showReachableTree :: w ::: Bool <?> "Print only reachable branches explored in tree view" , smttimeout :: w ::: Maybe Natural <?> "Timeout given to SMT solver in seconds (default: 300)" , maxIterations :: w ::: Maybe Integer <?> "Number of times we may revisit a particular branching point"- , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc5"+ , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default), cvc5, or bitwuzla" , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"+ , debug :: w ::: Bool <?> "Debug printing of internal behaviour"+ , trace :: w ::: Bool <?> "Dump trace" , assertions :: w ::: Maybe [Word256] <?> "Comma separated list of solc panic codes to check for (default: user defined assertion violations only)" , askSmtIterations :: w ::: Integer <!> "1" <?> "Number of times we may revisit a particular branching point before we consult the smt solver to check reachability (default: 1)"+ , numCexFuzz :: w ::: Integer <!> "3" <?> "Number of fuzzing tries to do to generate a counterexample (default: 3)" , numSolvers :: w ::: Maybe Natural <?> "Number of solver instances to use (default: number of cpu cores)" , loopDetectionHeuristic :: w ::: LoopHeuristic <!> "StackBased" <?> "Which heuristic should be used to determine if we are in a loop: StackBased (default) or Naive" , abstractArithmetic :: w ::: Bool <?> "Use abstraction-refinement for complicated arithmetic functions such as MulMod. This runs the solver first with abstraction turned on, and if it returns a potential counterexample, the counterexample is refined to make sure it is a counterexample for the actual (not the abstracted) problem"@@ -100,10 +105,13 @@ , calldata :: w ::: Maybe ByteString <?> "Tx: calldata" , smttimeout :: w ::: Maybe Natural <?> "Timeout given to SMT solver in seconds (default: 300)" , maxIterations :: w ::: Maybe Integer <?> "Number of times we may revisit a particular branching point"- , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc5"+ , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default), cvc5, or bitwuzla" , smtoutput :: w ::: Bool <?> "Print verbose smt output" , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"+ , debug :: w ::: Bool <?> "Debug printing of internal behaviour"+ , trace :: w ::: Bool <?> "Dump trace" , askSmtIterations :: w ::: Integer <!> "1" <?> "Number of times we may revisit a particular branching point before we consult the smt solver to check reachability (default: 1)"+ , numCexFuzz :: w ::: Integer <!> "3" <?> "Number of fuzzing tries to do to generate a counterexample (default: 3)" , loopDetectionHeuristic :: w ::: LoopHeuristic <!> "StackBased" <?> "Which heuristic should be used to determine if we are in a loop: StackBased (default) or Naive" , abstractArithmetic :: w ::: Bool <?> "Use abstraction-refinement for complicated arithmetic functions such as MulMod. This runs the solver first with abstraction turned on, and if it returns a potential counterexample, the counterexample is refined to make sure it is a counterexample for the actual (not the abstracted) problem" , abstractMemory :: w ::: Bool <?> "Use abstraction-refinement for Memory. This runs the solver first with abstraction turned on, and if it returns a potential counterexample, the counterexample is refined to make sure it is a counterexample for the actual (not the abstracted) problem"@@ -128,6 +136,7 @@ , maxcodesize :: w ::: Maybe W256 <?> "Block: max code size" , prevRandao :: w ::: Maybe W256 <?> "Block: prevRandao" , chainid :: w ::: Maybe W256 <?> "Env: chainId"+ , debug :: w ::: Bool <?> "Debug printing of internal behaviour" , trace :: w ::: Bool <?> "Dump trace" , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node" , block :: w ::: Maybe W256 <?> "Block state is be fetched from"@@ -142,14 +151,18 @@ , verbose :: w ::: Maybe Int <?> "Append call trace: {1} failures {2} all" , coverage :: w ::: Bool <?> "Coverage analysis" , match :: w ::: Maybe String <?> "Test case filter - only run methods matching regex"- , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc5"+ , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default), cvc5, or bitwuzla"+ , numSolvers :: w ::: Maybe Natural <?> "Number of solver instances to use (default: number of cpu cores)" , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"+ , debug :: w ::: Bool <?> "Debug printing of internal behaviour"+ , trace :: w ::: Bool <?> "Dump trace" , ffi :: w ::: Bool <?> "Allow the usage of the hevm.ffi() cheatcode (WARNING: this allows test authors to execute arbitrary code on your machine)" , smttimeout :: w ::: Maybe Natural <?> "Timeout given to SMT solver in seconds (default: 300)" , maxIterations :: w ::: Maybe Integer <?> "Number of times we may revisit a particular branching point" , loopDetectionHeuristic :: w ::: LoopHeuristic <!> "StackBased" <?> "Which heuristic should be used to determine if we are in a loop: StackBased (default) or Naive" , abstractArithmetic :: w ::: Bool <?> "Use abstraction-refinement for complicated arithmetic functions such as MulMod. This runs the solver first with abstraction turned on, and if it returns a potential counterexample, the counterexample is refined to make sure it is a counterexample for the actual (not the abstracted) problem" , abstractMemory :: w ::: Bool <?> "Use abstraction-refinement for Memory. This runs the solver first with abstraction turned on, and if it returns a potential counterexample, the counterexample is refined to make sure it is a counterexample for the actual (not the abstracted) problem"+ , numCexFuzz :: w ::: Integer <!> "3" <?> "Number of fuzzing tries to do to generate a counterexample (default: 3)" , askSmtIterations :: w ::: Integer <!> "1" <?> "Number of times we may revisit a particular branching point before we consult the smt solver to check reachability (default: 1)" } | Version@@ -186,54 +199,59 @@ main :: IO () main = do cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator"+ let env = Env { config = defaultConfig+ { dumpQueries = cmd.smtdebug+ , debug = cmd.debug+ , numCexFuzz = cmd.numCexFuzz+ , abstRefineMem = cmd.abstractMemory+ , abstRefineArith = cmd.abstractArithmetic+ , dumpTrace = cmd.trace+ } } case cmd of Version {} ->putStrLn getFullVersion Symbolic {} -> do root <- getRoot cmd- withCurrentDirectory root $ assert cmd- Equivalence {} -> equivalence cmd- Exec {} ->- launchExec cmd+ withCurrentDirectory root $ runEnv env $ assert cmd+ Equivalence {} -> runEnv env $ equivalence cmd+ Exec {} -> runEnv env $ launchExec cmd Test {} -> do root <- getRoot cmd- withCurrentDirectory root $ do- cores <- unsafeInto <$> getNumProcessors- solver <- getSolver cmd- withSolvers solver cores cmd.smttimeout $ \solvers -> do- buildOut <- readBuildOutput root (getProjectType cmd)- case buildOut of- Left e -> do- putStrLn $ "Error: " <> e- exitFailure- Right out -> do- -- TODO: which functions here actually require a BuildOutput, and which can take it as a Maybe?- testOpts <- unitTestOptions cmd solvers (Just out)- res <- unitTest testOpts out.contracts- unless res exitFailure+ solver <- getSolver cmd+ cores <- liftIO $ unsafeInto <$> getNumProcessors+ let solverCount = fromMaybe cores cmd.numSolvers+ runEnv env $ withSolvers solver solverCount cmd.smttimeout $ \solvers -> do+ buildOut <- liftIO $ readBuildOutput root (getProjectType cmd)+ case buildOut of+ Left e -> liftIO $ do+ putStrLn $ "Error: " <> e+ exitFailure+ Right out -> do+ -- TODO: which functions here actually require a BuildOutput, and which can take it as a Maybe?+ testOpts <- liftIO $ unitTestOptions cmd solvers (Just out)+ res <- unitTest testOpts out.contracts+ liftIO $ unless res exitFailure -equivalence :: Command Options.Unwrapped -> IO ()+equivalence :: App m => Command Options.Unwrapped -> m () equivalence cmd = do let bytecodeA = hexByteString "--code" . strip0x $ cmd.codeA bytecodeB = hexByteString "--code" . strip0x $ cmd.codeB veriOpts = VeriOpts { simp = True- , debug = False , maxIter = cmd.maxIterations , askSmtIters = cmd.askSmtIterations , loopHeuristic = cmd.loopDetectionHeuristic- , abstRefineConfig = AbstRefineConfig cmd.abstractArithmetic cmd.abstractMemory , rpcInfo = Nothing }- calldata <- buildCalldata cmd- solver <- getSolver cmd+ calldata <- liftIO $ buildCalldata cmd+ solver <- liftIO $ getSolver cmd withSolvers solver 3 Nothing $ \s -> do res <- equivalenceCheck s bytecodeA bytecodeB veriOpts calldata case any isCex res of- False -> do+ False -> liftIO $ do putStrLn "No discrepancies found" when (any isTimeout res) $ do putStrLn "But timeout(s) occurred" exitFailure- True -> do+ True -> liftIO $ do let cexs = mapMaybe getCex res T.putStrLn . T.unlines $ [ "Not equivalent. The following inputs result in differing behaviours:"@@ -247,6 +265,7 @@ Just s -> case T.unpack s of "z3" -> pure Z3 "cvc5" -> pure CVC5+ "bitwuzla" -> pure Bitwuzla input -> do putStrLn $ "unrecognised solver: " <> input exitFailure@@ -268,7 +287,7 @@ getProjectType cmd = fromMaybe Foundry cmd.projectType getRoot :: Command Options.Unwrapped -> IO FilePath-getRoot cmd = maybe getCurrentDirectory pure (cmd.root)+getRoot cmd = maybe getCurrentDirectory makeAbsolute (cmd.root) -- | Builds a buffer representing calldata based on the given cli arguments@@ -289,29 +308,27 @@ -- If function signatures are known, they should always be given for best results.-assert :: Command Options.Unwrapped -> IO ()+assert :: App m => Command Options.Unwrapped -> m () assert cmd = do let block' = maybe Fetch.Latest Fetch.BlockNumber cmd.block rpcinfo = (,) block' <$> cmd.rpc- calldata <- buildCalldata cmd- preState <- symvmFromCommand cmd calldata+ calldata <- liftIO $ buildCalldata cmd+ preState <- liftIO $ symvmFromCommand cmd calldata let errCodes = fromMaybe defaultPanicCodes cmd.assertions- cores <- unsafeInto <$> getNumProcessors+ cores <- liftIO $ unsafeInto <$> getNumProcessors let solverCount = fromMaybe cores cmd.numSolvers- solver <- getSolver cmd+ solver <- liftIO $ getSolver cmd withSolvers solver solverCount cmd.smttimeout $ \solvers -> do let opts = VeriOpts { simp = True- , debug = cmd.smtdebug , maxIter = cmd.maxIterations , askSmtIters = cmd.askSmtIterations , loopHeuristic = cmd.loopDetectionHeuristic- , abstRefineConfig = AbstRefineConfig cmd.abstractArithmetic cmd.abstractMemory , rpcInfo = rpcinfo } (expr, res) <- verify solvers opts preState (Just $ checkAssertions errCodes) case res of [Qed _] -> do- putStrLn "\nQED: No reachable property violations discovered\n"+ liftIO $ putStrLn "\nQED: No reachable property violations discovered\n" showExtras solvers cmd calldata expr _ -> do let cexs = snd <$> mapMaybe getCex res@@ -330,25 +347,26 @@ , "Could not determine reachability of the following end states:" , "" ] <> fmap (formatExpr) timeouts- T.putStrLn $ T.unlines (counterexamples <> unknowns)+ liftIO $ T.putStrLn $ T.unlines (counterexamples <> unknowns) showExtras solvers cmd calldata expr- exitFailure+ liftIO $ exitFailure -showExtras :: SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> IO ()+showExtras :: App m => SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> m () showExtras solvers cmd calldata expr = do- when cmd.showTree $ do+ when cmd.showTree $ liftIO $ do putStrLn "=== Expression ===\n" T.putStrLn $ formatExpr expr putStrLn "" when cmd.showReachableTree $ do reached <- reachable solvers expr- putStrLn "=== Reachable Expression ===\n"- T.putStrLn (formatExpr . snd $ reached)- putStrLn ""+ liftIO $ do+ putStrLn "=== Reachable Expression ===\n"+ T.putStrLn (formatExpr . snd $ reached)+ putStrLn "" when cmd.getModels $ do- putStrLn $ "=== Models for " <> show (Expr.numBranches expr) <> " branches ===\n"+ liftIO $ putStrLn $ "=== Models for " <> show (Expr.numBranches expr) <> " branches ===\n" ms <- produceModels solvers expr- forM_ ms (showModel (fst calldata))+ liftIO $ forM_ ms (showModel (fst calldata)) isTestOrLib :: Text -> Bool isTestOrLib file = T.isSuffixOf ".t.sol" file || areAnyPrefixOf ["src/test/", "src/tests/", "lib/"] file@@ -356,10 +374,10 @@ areAnyPrefixOf :: [Text] -> Text -> Bool areAnyPrefixOf prefixes t = any (flip T.isPrefixOf t) prefixes -launchExec :: Command Options.Unwrapped -> IO ()+launchExec :: App m => Command Options.Unwrapped -> m () launchExec cmd = do- dapp <- getSrcInfo cmd- vm <- vmFromCommand cmd+ dapp <- liftIO $ getSrcInfo cmd+ vm <- liftIO $ vmFromCommand cmd let block = maybe Fetch.Latest Fetch.BlockNumber cmd.block rpcinfo = (,) block <$> cmd.rpc@@ -367,21 +385,18 @@ -- TODO: we shouldn't need solvers to execute this code withSolvers Z3 0 Nothing $ \solvers -> do vm' <- EVM.Stepper.interpret (Fetch.oracle solvers rpcinfo) vm EVM.Stepper.runFully- when cmd.trace $ T.hPutStr stderr (showTraceTree dapp vm')+ writeTraceDapp dapp vm' case vm'.result of- Just (VMFailure (Revert msg)) -> do+ Just (VMFailure (Revert msg)) -> liftIO $ do let res = case msg of ConcreteBuf bs -> bs _ -> "<symbolic>" putStrLn $ "Revert: " <> (show $ ByteStringS res) exitWith (ExitFailure 2)- Just (VMFailure err) -> do+ Just (VMFailure err) -> liftIO $ do putStrLn $ "Error: " <> show err exitWith (ExitFailure 2)- Just (Unfinished p) -> do- putStrLn $ "Could not continue execution: " <> show p- exitWith (ExitFailure 2)- Just (VMSuccess buf) -> do+ Just (VMSuccess buf) -> liftIO $ do let msg = case buf of ConcreteBuf msg' -> msg' _ -> "<symbolic>"@@ -390,7 +405,7 @@ internalError "no EVM result" -- | Creates a (concrete) VM from command line options-vmFromCommand :: Command Options.Unwrapped -> IO (VM RealWorld)+vmFromCommand :: Command Options.Unwrapped -> IO (VM Concrete RealWorld) vmFromCommand cmd = do (miner,ts,baseFee,blockNum,prevRan) <- case cmd.rpc of Nothing -> pure (LitAddr 0,Lit 0,0,0,0)@@ -481,7 +496,7 @@ addr f def = maybe def LitAddr (f cmd) bytes f def = maybe def decipher (f cmd) -symvmFromCommand :: Command Options.Unwrapped -> (Expr Buf, [Prop]) -> IO (VM RealWorld)+symvmFromCommand :: Command Options.Unwrapped -> (Expr Buf, [Prop]) -> IO (VM EVM.Types.Symbolic RealWorld) symvmFromCommand cmd calldata = do (miner,blockNum,baseFee,prevRan) <- case cmd.rpc of Nothing -> pure (SymAddr "miner",0,0,0)@@ -539,7 +554,7 @@ , address = address , caller = caller , origin = origin- , gas = word64 (.gas) 0xffffffffffffffff+ , gas = () , gaslimit = word64 (.gaslimit) 0xffffffffffffffff , baseFee = baseFee , priorityFee = word (.priorityFee) 0@@ -585,7 +600,6 @@ Nothing -> Nothing , maxIter = cmd.maxIterations , askSmtIters = cmd.askSmtIterations- , smtDebug = cmd.smtdebug , smtTimeout = cmd.smttimeout , solver = cmd.solver , verbose = cmd.verbose
hevm.cabal view
@@ -2,7 +2,7 @@ name: hevm version:- 0.52.0+ 0.53.0 synopsis: Symbolic EVM Evaluator homepage:@@ -59,6 +59,8 @@ -Wno-orphans -Wno-ambiguous-fields -optc-Wno-ignored-attributes+ -fspecialize-aggressively+ -fexpose-all-unfoldings default-language: GHC2021 default-extensions: DuplicateRecordFields@@ -83,6 +85,7 @@ EVM.Expr, EVM.SMT, EVM.Solvers,+ EVM.Fuzz, EVM.Exec, EVM.Format, EVM.Fetch,@@ -100,6 +103,7 @@ EVM.Types, EVM.UnitTest, EVM.Sign,+ EVM.Effects, other-modules: Paths_hevm autogen-modules:@@ -178,7 +182,8 @@ stm >= 2.5.0 && < 2.6.0, spawn >= 0.3 && < 0.4, filepattern >= 0.1.2 && < 0.2,- witch >= 1.1 && < 1.3+ witch >= 1.1 && < 1.3,+ unliftio-core >= 0.2.1.0 if !os(windows) build-depends: brick >= 1.4 && < 2.0,@@ -230,7 +235,8 @@ spawn, optics-core, githash >= 0.1.6 && < 0.2,- witch+ witch,+ unliftio-core if os(windows) buildable: False @@ -281,7 +287,10 @@ operational, optics-core, optics-extra,- witch+ witch,+ unliftio-core,+ exceptions,+ MissingH library test-utils import:@@ -319,7 +328,7 @@ main-is: test.hs --- these tests require network access so we split them into a seperate test+-- these tests require network access so we split them into a separate test -- suite to make it easy to skip them when running nix-build test-suite rpc-tests import:@@ -367,4 +376,5 @@ filemanip, filepath, containers,- mtl+ mtl,+ unliftio-core
src/EVM.hs view
@@ -20,2622 +20,2791 @@ import EVM.Precompiled qualified import EVM.Solidity import EVM.Types-import EVM.Sign qualified-import EVM.Concrete qualified as Concrete--import Control.Monad.ST (ST)-import Control.Monad.State.Strict hiding (state)-import Data.Bits (FiniteBits, countLeadingZeros, finiteBitSize)-import Data.ByteArray qualified as BA-import Data.ByteString (ByteString)-import Data.ByteString qualified as BS-import Data.ByteString.Lazy (fromStrict)-import Data.ByteString.Lazy qualified as LS-import Data.ByteString.Char8 qualified as Char8-import Data.Foldable (toList)-import Data.List (find)-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Data.Maybe (fromMaybe, fromJust, isJust)-import Data.Set (insert, member, fromList)-import Data.Sequence (Seq)-import Data.Sequence qualified as Seq-import Data.Text (unpack, pack)-import Data.Text.Encoding (decodeUtf8)-import Data.Tree-import Data.Tree.Zipper qualified as Zipper-import Data.Typeable-import Data.Vector qualified as V-import Data.Vector.Storable qualified as SV-import Data.Vector.Storable.Mutable qualified as SV-import Data.Vector.Unboxed qualified as VUnboxed-import Data.Vector.Unboxed.Mutable qualified as VUnboxed.Mutable-import Data.Word (Word8, Word32, Word64)-import Witch (into, tryFrom, unsafeInto)--import Crypto.Hash (Digest, SHA256, RIPEMD160)-import Crypto.Hash qualified as Crypto-import Crypto.Number.ModArithmetic (expFast)--blankState :: ST s (FrameState s)-blankState = do- memory <- ConcreteMemory <$> VUnboxed.Mutable.new 0- pure $ FrameState- { contract = LitAddr 0- , codeContract = LitAddr 0- , code = RuntimeCode (ConcreteRuntimeCode "")- , pc = 0- , stack = mempty- , memory- , memorySize = 0- , calldata = mempty- , callvalue = Lit 0- , caller = LitAddr 0- , gas = 0- , returndata = mempty- , static = False- }---- | An "external" view of a contract's bytecode, appropriate for--- e.g. @EXTCODEHASH@.-bytecode :: Getter Contract (Maybe (Expr Buf))-bytecode = #code % to f- where f (InitCode _ _) = Just mempty- f (RuntimeCode (ConcreteRuntimeCode bs)) = Just $ ConcreteBuf bs- f (RuntimeCode (SymbolicRuntimeCode ops)) = Just $ Expr.fromList ops- f (UnknownCode _) = Nothing---- * Data accessors--currentContract :: VM s -> Maybe Contract-currentContract vm =- Map.lookup vm.state.codeContract vm.env.contracts---- * Data constructors--makeVm :: VMOpts -> ST s (VM s)-makeVm o = do- let txaccessList = o.txAccessList- txorigin = o.origin- txtoAddr = o.address- initialAccessedAddrs = fromList $- [txorigin, txtoAddr, o.coinbase]- ++ (fmap LitAddr [1..9])- ++ (Map.keys txaccessList)- initialAccessedStorageKeys = fromList $ foldMap (uncurry (map . (,))) (Map.toList txaccessList)- touched = if o.create then [txorigin] else [txorigin, txtoAddr]- memory <- ConcreteMemory <$> VUnboxed.Mutable.new 0- pure $ VM- { result = Nothing- , frames = mempty- , tx = TxState- { gasprice = o.gasprice- , gaslimit = o.gaslimit- , priorityFee = o.priorityFee- , origin = txorigin- , toAddr = txtoAddr- , value = o.value- , substate = SubState mempty touched initialAccessedAddrs initialAccessedStorageKeys mempty- , isCreate = o.create- , txReversion = Map.fromList ((o.address,o.contract):o.otherContracts)- }- , logs = []- , traces = Zipper.fromForest []- , block = Block- { coinbase = o.coinbase- , timestamp = o.timestamp- , number = o.number- , prevRandao = o.prevRandao- , maxCodeSize = o.maxCodeSize- , gaslimit = o.blockGaslimit- , baseFee = o.baseFee- , schedule = o.schedule- }- , state = FrameState- { pc = 0- , stack = mempty- , memory- , memorySize = 0- , code = o.contract.code- , contract = o.address- , codeContract = o.address- , calldata = fst o.calldata- , callvalue = o.value- , caller = o.caller- , gas = o.gas- , returndata = mempty- , static = False- }- , env = Env- { chainId = o.chainId- , contracts = Map.fromList ((o.address,o.contract):o.otherContracts)- , freshAddresses = 0- }- , cache = Cache mempty mempty- , burned = 0- , constraints = snd o.calldata- , keccakEqs = mempty- , iterations = mempty- , config = RuntimeConfig- { allowFFI = o.allowFFI- , overrideCaller = Nothing- , baseState = o.baseState- }- }---- | Initialize an abstract contract with unknown code-unknownContract :: Expr EAddr -> Contract-unknownContract addr = Contract- { code = UnknownCode addr- , storage = AbstractStore addr- , origStorage = AbstractStore addr- , balance = Balance addr- , nonce = Nothing- , codehash = hashcode (UnknownCode addr)- , opIxMap = mempty- , codeOps = mempty- , external = False- }---- | Initialize an abstract contract with known code-abstractContract :: ContractCode -> Expr EAddr -> Contract-abstractContract code addr = Contract- { code = code- , storage = AbstractStore addr- , origStorage = AbstractStore addr- , balance = Balance addr- , nonce = if isCreation code then Just 1 else Just 0- , codehash = hashcode code- , opIxMap = mkOpIxMap code- , codeOps = mkCodeOps code- , external = False- }---- | Initialize an empty contract without code-emptyContract :: Contract-emptyContract = initialContract (RuntimeCode (ConcreteRuntimeCode ""))---- | Initialize empty contract with given code-initialContract :: ContractCode -> Contract-initialContract code = Contract- { code = code- , storage = ConcreteStore mempty- , origStorage = ConcreteStore mempty- , balance = Lit 0- , nonce = if isCreation code then Just 1 else Just 0- , codehash = hashcode code- , opIxMap = mkOpIxMap code- , codeOps = mkCodeOps code- , external = False- }--isCreation :: ContractCode -> Bool-isCreation = \case- InitCode _ _ -> True- RuntimeCode _ -> False- UnknownCode _ -> False---- * Opcode dispatch (exec1)---- | Update program counter-next :: (?op :: Word8) => EVM s ()-next = modifying (#state % #pc) (+ (opSize ?op))---- | Executes the EVM one step-exec1 :: EVM s ()-exec1 = do- vm <- get-- let- -- Convenient aliases- stk = vm.state.stack- self = vm.state.contract- this = fromMaybe (internalError "state contract") (Map.lookup self vm.env.contracts)-- fees@FeeSchedule {..} = vm.block.schedule-- doStop = finishFrame (FrameReturned mempty)-- litSelf = maybeLitAddr self-- if isJust litSelf && (fromJust litSelf) > 0x0 && (fromJust litSelf) <= 0x9 then do- -- call to precompile- let ?op = 0x00 -- dummy value- case bufLength vm.state.calldata of- Lit calldatasize -> do- copyBytesToMemory vm.state.calldata (Lit calldatasize) (Lit 0) (Lit 0)- executePrecompile (fromJust litSelf) vm.state.gas 0 calldatasize 0 0 []- vmx <- get- case vmx.state.stack of- x:_ -> case x of- Lit 0 ->- fetchAccount self $ \_ -> do- touchAccount self- vmError PrecompileFailure- Lit _ ->- fetchAccount self $ \_ -> do- touchAccount self- out <- use (#state % #returndata)- finishFrame (FrameReturned out)- e -> partial $- UnexpectedSymbolicArg vmx.state.pc "precompile returned a symbolic value" (wrap [e])- _ ->- underrun- e -> partial $- UnexpectedSymbolicArg vm.state.pc "cannot call precompiles with symbolic data" (wrap [e])-- else if vm.state.pc >= opslen vm.state.code- then doStop-- else do- let ?op = case vm.state.code of- UnknownCode _ -> internalError "Cannot execute unknown code"- InitCode conc _ -> BS.index conc vm.state.pc- RuntimeCode (ConcreteRuntimeCode bs) -> BS.index bs vm.state.pc- RuntimeCode (SymbolicRuntimeCode ops) ->- fromMaybe (internalError "could not analyze symbolic code") $- maybeLitByte $ ops V.! vm.state.pc-- case getOp (?op) of-- OpPush0 -> do- limitStack 1 $- burn g_base $ do- next- pushSym (Lit 0)-- OpPush n' -> do- let n = into n'- !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 readWord (Lit 0) $ Expr.fromList $ padLeft' 32 bytes- limitStack 1 $- burn g_verylow $ do- next- pushSym xs-- OpDup i ->- case preview (ix (into i - 1)) stk of- Nothing -> underrun- Just y ->- limitStack 1 $- burn g_verylow $ do- next- pushSym y-- OpSwap i ->- if length stk < (into i) + 1- then underrun- else- burn g_verylow $ do- next- zoom (#state % #stack) $ do- assign (ix 0) (stk ^?! ix (into i))- assign (ix (into i)) (stk ^?! ix 0)-- OpLog n ->- notStatic $- case stk of- (xOffset':xSize':xs) ->- if length xs < (into n)- then underrun- else- forceConcrete2 (xOffset', xSize') "LOG" $ \(xOffset, xSize) -> do- bytes <- readMemory xOffset' xSize'- let (topics, xs') = splitAt (into n) xs- logs' = (LogEntry (WAddr self) bytes topics) : vm.logs- case (tryFrom xSize) of- (Right sz) ->- burn (g_log + g_logdata * sz + (into n) * g_logtopic) $- accessMemoryRange xOffset xSize $ do- traceTopLog logs'- next- assign (#state % #stack) xs'- assign #logs logs'- _ -> vmError IllegalOverflow- _ ->- underrun-- OpStop -> doStop-- OpAdd -> stackOp2 g_verylow Expr.add- OpMul -> stackOp2 g_low Expr.mul- OpSub -> stackOp2 g_verylow Expr.sub-- OpDiv -> stackOp2 g_low Expr.div-- OpSdiv -> stackOp2 g_low Expr.sdiv-- OpMod -> stackOp2 g_low Expr.mod-- OpSmod -> stackOp2 g_low Expr.smod- OpAddmod -> stackOp3 g_mid Expr.addmod- OpMulmod -> stackOp3 g_mid Expr.mulmod-- OpLt -> stackOp2 g_verylow Expr.lt- OpGt -> stackOp2 g_verylow Expr.gt- OpSlt -> stackOp2 g_verylow Expr.slt- OpSgt -> stackOp2 g_verylow Expr.sgt-- OpEq -> stackOp2 g_verylow Expr.eq- OpIszero -> stackOp1 g_verylow Expr.iszero-- OpAnd -> stackOp2 g_verylow Expr.and- OpOr -> stackOp2 g_verylow Expr.or- OpXor -> stackOp2 g_verylow Expr.xor- OpNot -> stackOp1 g_verylow Expr.not-- OpByte -> stackOp2 g_verylow (\i w -> Expr.padByte $ Expr.indexWord i w)-- OpShl -> stackOp2 g_verylow Expr.shl- OpShr -> stackOp2 g_verylow Expr.shr- OpSar -> stackOp2 g_verylow Expr.sar-- -- more accurately refered to as KECCAK- OpSha3 ->- case stk of- xOffset':xSize':xs ->- forceConcrete xOffset' "sha3 offset must be concrete" $- \xOffset -> forceConcrete xSize' "sha3 size must be concrete" $ \xSize ->- burn (g_sha3 + g_sha3word * ceilDiv (unsafeInto xSize) 32) $- accessMemoryRange xOffset xSize $ do- hash <- readMemory xOffset' xSize' >>= \case- ConcreteBuf bs -> do- let hash' = keccak' bs- eqs <- use #keccakEqs- assign #keccakEqs $- PEq (Lit hash') (Keccak (ConcreteBuf bs)):eqs- pure $ Lit hash'- buf -> pure $ Keccak buf- next- assign (#state % #stack) (hash : xs)- _ -> underrun-- OpAddress ->- limitStack 1 $- burn g_base (next >> pushAddr self)-- OpBalance ->- case stk of- x:xs -> forceAddr x "BALANCE" $ \a ->- accessAndBurn a $- fetchAccount a $ \c -> do- next- assign (#state % #stack) xs- pushSym c.balance- [] ->- underrun-- OpOrigin ->- limitStack 1 . burn g_base $- next >> pushAddr vm.tx.origin-- OpCaller ->- limitStack 1 . burn g_base $- next >> pushAddr vm.state.caller-- OpCallvalue ->- limitStack 1 . burn g_base $- next >> pushSym vm.state.callvalue-- OpCalldataload -> stackOp1 g_verylow $- \ind -> Expr.readWord ind vm.state.calldata-- OpCalldatasize ->- limitStack 1 . burn g_base $- next >> pushSym (bufLength vm.state.calldata)-- OpCalldatacopy ->- case stk of- xTo':xFrom:xSize':xs ->- forceConcrete2 (xTo', xSize') "CALLDATACOPY" $- \(xTo, xSize) ->- burn (g_verylow + g_copy * ceilDiv (unsafeInto xSize) 32) $- accessMemoryRange xTo xSize $ do- next- assign (#state % #stack) xs- copyBytesToMemory vm.state.calldata xSize' xFrom xTo'- _ -> underrun-- OpCodesize ->- limitStack 1 . burn g_base $- next >> pushSym (codelen vm.state.code)-- OpCodecopy ->- case stk of- memOffset':codeOffset:n':xs ->- forceConcrete2 (memOffset', n') "CODECOPY" $- \(memOffset,n) -> do- case toWord64 n of- Nothing -> vmError IllegalOverflow- Just n'' ->- if n'' <= ( (maxBound :: Word64) - g_verylow ) `div` g_copy * 32 then- burn (g_verylow + g_copy * ceilDiv (unsafeInto n) 32) $- accessMemoryRange memOffset n $ do- next- assign (#state % #stack) xs- case toBuf vm.state.code of- Just b -> copyBytesToMemory b n' codeOffset memOffset'- Nothing -> internalError "Cannot produce a buffer from UnknownCode"- else vmError IllegalOverflow- _ -> underrun-- OpGasprice ->- limitStack 1 . burn g_base $- next >> push vm.tx.gasprice-- OpExtcodesize ->- case stk of- x':xs -> forceAddr x' "EXTCODESIZE" $ \x -> do- let impl = accessAndBurn x $- fetchAccount x $ \c -> do- next- assign (#state % #stack) xs- case view bytecode c of- Just b -> pushSym (bufLength b)- Nothing -> pushSym $ CodeSize x- case x of- a@(LitAddr _) -> if a == cheatCode- then do- next- assign (#state % #stack) xs- pushSym (Lit 1)- else impl- _ -> impl- [] ->- underrun-- OpExtcodecopy ->- case stk of- extAccount':memOffset':codeOffset:codeSize':xs ->- forceConcrete2 (memOffset', codeSize') "EXTCODECOPY" $ \(memOffset, codeSize) -> do- forceAddr extAccount' "EXTCODECOPY" $ \extAccount -> do- acc <- accessAccountForGas extAccount- let cost = if acc then g_warm_storage_read else g_cold_account_access- burn (cost + g_copy * ceilDiv (unsafeInto codeSize) 32) $- accessMemoryRange memOffset codeSize $- fetchAccount extAccount $ \c -> do- next- assign (#state % #stack) xs- case view bytecode c of- Just b -> copyBytesToMemory b codeSize' codeOffset memOffset'- Nothing -> do- pc <- use (#state % #pc)- partial $ UnexpectedSymbolicArg pc "Cannot copy from unknown code at" (wrap [extAccount])- _ -> underrun-- OpReturndatasize ->- limitStack 1 . burn g_base $- next >> pushSym (bufLength vm.state.returndata)-- OpReturndatacopy ->- case stk of- xTo':xFrom:xSize':xs -> forceConcrete2 (xTo', xSize') "RETURNDATACOPY" $- \(xTo, xSize) ->- burn (g_verylow + g_copy * ceilDiv (unsafeInto xSize) 32) $- accessMemoryRange xTo xSize $ do- next- assign (#state % #stack) xs-- let jump True = vmError ReturnDataOutOfBounds- jump False = copyBytesToMemory vm.state.returndata xSize' xFrom xTo'-- case (xFrom, bufLength vm.state.returndata) of- (Lit f, Lit l) ->- jump $ l < f + xSize || f + xSize < f- _ -> do- let oob = Expr.lt (bufLength vm.state.returndata) (Expr.add xFrom xSize')- overflow = Expr.lt (Expr.add xFrom xSize') (xFrom)- branch (Expr.or oob overflow) jump- _ -> underrun-- OpExtcodehash ->- case stk of- x':xs -> forceAddr x' "EXTCODEHASH" $ \x ->- accessAndBurn x $ do- next- assign (#state % #stack) xs- fetchAccount x $ \c ->- if accountEmpty c- then push (W256 0)- else case view bytecode c of- Just b -> pushSym $ keccak b- Nothing -> pushSym $ CodeHash x- [] ->- underrun-- OpBlockhash -> do- -- We adopt the fake block hash scheme of the VMTests,- -- so that blockhash(i) is the hash of i as decimal ASCII.- stackOp1 g_blockhash $ \case- Lit i -> if i + 256 < vm.block.number || i >= vm.block.number- then Lit 0- else (into i :: Integer) & show & Char8.pack & keccak' & Lit- i -> BlockHash i-- OpCoinbase ->- limitStack 1 . burn g_base $- next >> pushAddr vm.block.coinbase-- OpTimestamp ->- limitStack 1 . burn g_base $- next >> pushSym vm.block.timestamp-- OpNumber ->- limitStack 1 . burn g_base $- next >> push vm.block.number-- OpPrevRandao -> do- limitStack 1 . burn g_base $- next >> push vm.block.prevRandao-- OpGaslimit ->- limitStack 1 . burn g_base $- next >> push (into vm.block.gaslimit)-- OpChainid ->- limitStack 1 . burn g_base $- next >> push vm.env.chainId-- OpSelfbalance ->- limitStack 1 . burn g_low $- next >> pushSym this.balance-- OpBaseFee ->- limitStack 1 . burn g_base $- next >> push vm.block.baseFee-- OpPop ->- case stk of- _:xs -> burn g_base (next >> assign (#state % #stack) xs)- _ -> underrun-- OpMload ->- case stk of- x':xs -> forceConcrete x' "MLOAD" $ \x ->- burn g_verylow $- accessMemoryWord x $ do- next- buf <- readMemory (Lit x) (Lit 32)- let w = Expr.readWordFromBytes (Lit 0) buf- assign (#state % #stack) (w : xs)- _ -> underrun-- OpMstore ->- case stk of- x':y:xs -> forceConcrete x' "MSTORE index" $ \x ->- burn g_verylow $- accessMemoryWord x $ do- next- gets (.state.memory) >>= \case- ConcreteMemory mem -> do- case y of- Lit w ->- writeMemory mem (unsafeInto x) (word256Bytes w)- _ -> do- -- copy out and move to symbolic memory- buf <- freezeMemory mem- assign (#state % #memory) (SymbolicMemory $ writeWord (Lit x) y buf)- SymbolicMemory mem ->- assign (#state % #memory) (SymbolicMemory $ writeWord (Lit x) y mem)- assign (#state % #stack) xs- _ -> underrun-- OpMstore8 ->- case stk of- x':y:xs -> forceConcrete x' "MSTORE8" $ \x ->- burn g_verylow $- accessMemoryRange x 1 $ do- let yByte = indexWord (Lit 31) y- next- gets (.state.memory) >>= \case- ConcreteMemory mem -> do- case yByte of- LitByte byte ->- writeMemory mem (unsafeInto x) (BS.pack [byte])- _ -> do- -- copy out and move to symbolic memory- buf <- freezeMemory mem- assign (#state % #memory) (SymbolicMemory $ writeByte (Lit x) yByte buf)- SymbolicMemory mem ->- assign (#state % #memory) (SymbolicMemory $ writeByte (Lit x) yByte mem)-- assign (#state % #stack) xs- _ -> underrun-- OpSload ->- case stk of- x:xs -> do- acc <- accessStorageForGas self x- let cost = if acc then g_warm_storage_read else g_cold_sload- burn cost $- accessStorage self x $ \y -> do- next- assign (#state % #stack) (y:xs)- _ -> underrun-- OpSstore ->- notStatic $- case stk of- x:new:xs ->- accessStorage self x $ \current -> do- availableGas <- use (#state % #gas)-- if availableGas <= g_callstipend then- finishFrame (FrameErrored (OutOfGas availableGas g_callstipend))- else do- let- original =- case Expr.simplify $ SLoad x this.origStorage of- Lit v -> v- _ -> 0- storage_cost =- case (maybeLitWord current, maybeLitWord new) of- (Just current', Just new') ->- if (current' == new') then g_sload- else if (current' == original) && (original == 0) then g_sset- else if (current' == original) then g_sreset- else g_sload-- -- if any of the arguments are symbolic,- -- assume worst case scenario- _-> g_sset-- acc <- accessStorageForGas self x- let cold_storage_cost = if acc then 0 else g_cold_sload- burn (storage_cost + cold_storage_cost) $ do- next- assign (#state % #stack) xs- modifying (#env % #contracts % ix self % #storage) (writeStorage x new)-- case (maybeLitWord current, maybeLitWord new) of- (Just current', Just new') ->- unless (current' == new') $- if current' == original then- when (original /= 0 && new' == 0) $- refund (g_sreset + g_access_list_storage_key)- else do- when (original /= 0) $- if current' == 0- then unRefund (g_sreset + g_access_list_storage_key)- else when (new' == 0) $ refund (g_sreset + g_access_list_storage_key)- when (original == new') $- if original == 0- then refund (g_sset - g_sload)- else refund (g_sreset - g_sload)- -- if any of the arguments are symbolic,- -- don't change the refund counter- _ -> noop- _ -> underrun-- OpJump ->- case stk of- x:xs ->- burn g_mid $ forceConcrete x "JUMP: symbolic jumpdest" $ \x' ->- case toInt x' of- Nothing -> vmError BadJumpDestination- Just i -> checkJump i xs- _ -> underrun-- OpJumpi -> do- case stk of- (x:y:xs) -> forceConcrete x "JUMPI: symbolic jumpdest" $ \x' ->- burn g_high $- let jump :: Bool -> EVM s ()- jump False = assign (#state % #stack) xs >> next- jump _ = case toInt x' of- Nothing -> vmError BadJumpDestination- Just i -> checkJump i xs- in branch y jump- _ -> underrun-- OpPc ->- limitStack 1 . burn g_base $- next >> push (unsafeInto vm.state.pc)-- OpMsize ->- limitStack 1 . burn g_base $- next >> push (into vm.state.memorySize)-- OpGas ->- limitStack 1 . burn g_base $- next >> push (into (vm.state.gas - g_base))-- OpJumpdest -> burn g_jumpdest next-- OpExp ->- -- NOTE: this can be done symbolically using unrolling like this:- -- https://hackage.haskell.org/package/sbv-9.0/docs/src/Data.SBV.Core.Model.html#.%5E- -- However, it requires symbolic gas, since the gas depends on the exponent- case stk of- base:exponent':xs -> forceConcrete exponent' "EXP: symbolic exponent" $ \exponent ->- let cost = if exponent == 0- then g_exp- else g_exp + g_expbyte * unsafeInto (ceilDiv (1 + log2 exponent) 8)- in burn cost $ do- next- (#state % #stack) .= Expr.exp base exponent' : xs- _ -> underrun-- OpSignextend -> stackOp2 g_low Expr.sex-- OpCreate ->- notStatic $- case stk of- xValue:xOffset':xSize':xs -> forceConcrete2 (xOffset', xSize') "CREATE" $- \(xOffset, xSize) -> do- accessMemoryRange xOffset xSize $ do- availableGas <- use (#state % #gas)- let- (cost, gas') = costOfCreate fees availableGas xSize False- newAddr <- createAddress self this.nonce- _ <- accessAccountForGas newAddr- burn cost $ do- initCode <- readMemory xOffset' xSize'- create self this xSize gas' xValue xs newAddr initCode- _ -> underrun-- OpCall ->- case stk of- xGas':xTo':xValue:xInOffset':xInSize':xOutOffset':xOutSize':xs ->- forceConcrete5 (xGas', xInOffset', xInSize', xOutOffset', xOutSize') "CALL" $- \(xGas, xInOffset, xInSize, xOutOffset, xOutSize) ->- branch (Expr.gt xValue (Lit 0)) $ \gt0 -> do- (if gt0 then notStatic else id) $- forceAddr xTo' "unable to determine a call target" $ \xTo ->- case tryFrom xGas of- Left _ -> vmError IllegalOverflow- Right gas ->- delegateCall this gas xTo xTo xValue xInOffset xInSize xOutOffset xOutSize xs $- \callee -> do- let from' = fromMaybe self vm.config.overrideCaller- zoom #state $ do- assign #callvalue xValue- assign #caller from'- assign #contract callee- assign (#config % #overrideCaller) Nothing- touchAccount from'- touchAccount callee- transfer from' callee xValue- _ ->- underrun-- OpCallcode ->- case stk of- xGas':xTo':xValue:xInOffset':xInSize':xOutOffset':xOutSize':xs ->- forceConcrete5 (xGas', xInOffset', xInSize', xOutOffset', xOutSize') "CALLCODE" $- \(xGas, xInOffset, xInSize, xOutOffset, xOutSize) ->- forceAddr xTo' "unable to determine a call target" $ \xTo ->- case tryFrom xGas of- Left _ -> vmError IllegalOverflow- Right gas ->- delegateCall this gas xTo self xValue xInOffset xInSize xOutOffset xOutSize xs $ \_ -> do- zoom #state $ do- assign #callvalue xValue- assign #caller $ fromMaybe self vm.config.overrideCaller- assign (#config % #overrideCaller) Nothing- touchAccount self- _ ->- underrun-- OpReturn ->- case stk of- xOffset':xSize':_ -> forceConcrete2 (xOffset', xSize') "RETURN" $ \(xOffset, xSize) ->- accessMemoryRange xOffset xSize $ do- output <- readMemory xOffset' xSize'- let- codesize = fromMaybe (internalError "processing opcode RETURN. Cannot return dynamically sized abstract data")- . maybeLitWord . bufLength $ output- maxsize = vm.block.maxCodeSize- creation = case vm.frames of- [] -> vm.tx.isCreate- frame:_ -> case frame.context of- CreationContext {} -> True- CallContext {} -> False- if creation- then- if codesize > maxsize- then- finishFrame (FrameErrored (MaxCodeSizeExceeded maxsize codesize))- else do- let frameReturned = burn (g_codedeposit * unsafeInto codesize) $- finishFrame (FrameReturned output)- frameErrored = finishFrame $ FrameErrored InvalidFormat- case readByte (Lit 0) output of- LitByte 0xef -> frameErrored- LitByte _ -> frameReturned- y -> branch (Expr.eqByte y (LitByte 0xef)) $ \case- True -> frameErrored- False -> frameReturned- else- finishFrame (FrameReturned output)- _ -> underrun-- OpDelegatecall ->- case stk of- xGas':xTo:xInOffset':xInSize':xOutOffset':xOutSize':xs ->- forceConcrete5 (xGas', xInOffset', xInSize', xOutOffset', xOutSize') "DELEGATECALL" $- \(xGas, xInOffset, xInSize, xOutOffset, xOutSize) ->- case wordToAddr xTo of- Nothing -> do- loc <- codeloc- let msg = "Unable to determine a call target"- partial $ UnexpectedSymbolicArg (snd loc) msg [SomeExpr xTo]- Just xTo' ->- case tryFrom xGas of- Left _ -> vmError IllegalOverflow- Right gas ->- delegateCall this gas xTo' self (Lit 0) xInOffset xInSize xOutOffset xOutSize xs $- \_ -> touchAccount self- _ -> underrun-- OpCreate2 -> notStatic $- case stk of- xValue:xOffset':xSize':xSalt':xs ->- forceConcrete3 (xOffset', xSize', xSalt') "CREATE2" $- \(xOffset, xSize, xSalt) ->- accessMemoryRange xOffset xSize $ do- availableGas <- use (#state % #gas)- buf <- readMemory xOffset' xSize'- forceConcreteBuf buf "CREATE2" $- \initCode -> do- let- (cost, gas') = costOfCreate fees availableGas xSize True- newAddr <- create2Address self xSalt initCode- _ <- accessAccountForGas newAddr- burn cost $- create self this xSize gas' xValue xs newAddr (ConcreteBuf initCode)- _ -> underrun-- OpStaticcall ->- case stk of- xGas':xTo:xInOffset':xInSize':xOutOffset':xOutSize':xs ->- forceConcrete5 (xGas', xInOffset', xInSize', xOutOffset', xOutSize') "STATICCALL" $- \(xGas, xInOffset, xInSize, xOutOffset, xOutSize) -> do- case wordToAddr xTo of- Nothing -> do- loc <- codeloc- let msg = "Unable to determine a call target"- partial $ UnexpectedSymbolicArg (snd loc) msg [SomeExpr xTo]- Just xTo' ->- case tryFrom xGas of- Left _ -> vmError IllegalOverflow- Right gas ->- delegateCall this gas xTo' xTo' (Lit 0) xInOffset xInSize xOutOffset xOutSize xs $- \callee -> do- zoom #state $ do- assign #callvalue (Lit 0)- assign #caller $ fromMaybe self (vm.config.overrideCaller)- assign #contract callee- assign #static True- assign (#config % #overrideCaller) Nothing- touchAccount self- touchAccount callee- _ ->- underrun-- OpSelfdestruct ->- notStatic $- case stk of- [] -> underrun- (xTo':_) -> forceAddr xTo' "SELFDESTRUCT" $ \case- xTo@(LitAddr _) -> do- acc <- accessAccountForGas xTo- let cost = if acc then 0 else g_cold_account_access- funds = this.balance- recipientExists = accountExists xTo vm- branch (Expr.iszero $ Expr.eq funds (Lit 0)) $ \hasFunds -> do- let c_new = if (not recipientExists) && hasFunds- then g_selfdestruct_newaccount- else 0- burn (g_selfdestruct + c_new + cost) $ do- selfdestruct self- touchAccount xTo-- if hasFunds- then fetchAccount xTo $ \_ -> do- #env % #contracts % ix xTo % #balance %= (Expr.add funds)- assign (#env % #contracts % ix self % #balance) (Lit 0)- doStop- else do- doStop- a -> do- pc <- use (#state % #pc)- partial $ UnexpectedSymbolicArg pc "trying to self destruct to a symbolic address" (wrap [a])-- OpRevert ->- case stk of- xOffset':xSize':_ -> forceConcrete2 (xOffset', xSize') "REVERT" $ \(xOffset, xSize) ->- accessMemoryRange xOffset xSize $ do- output <- readMemory xOffset' xSize'- finishFrame (FrameReverted output)- _ -> underrun-- OpUnknown xxx ->- vmError $ UnrecognizedOpcode xxx--transfer :: Expr EAddr -> Expr EAddr -> Expr EWord -> EVM s ()-transfer _ _ (Lit 0) = pure ()-transfer src dst val = do- sb <- preuse $ #env % #contracts % ix src % #balance- db <- preuse $ #env % #contracts % ix dst % #balance- baseState <- use (#config % #baseState)- let mkc = case baseState of- AbstractBase -> unknownContract- EmptyBase -> const emptyContract- case (sb, db) of- -- both sender and recipient in state- (Just srcBal, Just _) ->- branch (Expr.gt val srcBal) $ \case- True -> vmError $ BalanceTooLow val srcBal- False -> do- (#env % #contracts % ix src % #balance) %= (`Expr.sub` val)- (#env % #contracts % ix dst % #balance) %= (`Expr.add` val)- -- sender not in state- (Nothing, Just _) -> do- case src of- LitAddr _ -> do- (#env % #contracts) %= (Map.insert src (mkc src))- transfer src dst val- SymAddr _ -> do- pc <- use (#state % #pc)- partial $ UnexpectedSymbolicArg pc "Attempting to transfer eth from a symbolic address that is not present in the state" (wrap [src])- GVar _ -> internalError "Unexpected GVar"- -- recipient not in state- (_ , Nothing) -> do- case dst of- LitAddr _ -> do- (#env % #contracts) %= (Map.insert dst (mkc dst))- transfer src dst val- SymAddr _ -> do- pc <- use (#state % #pc)- partial $ UnexpectedSymbolicArg pc "Attempting to transfer eth to a symbolic address that is not present in the state" (wrap [dst])- GVar _ -> internalError "Unexpected GVar"---- | Checks a *CALL for failure; OOG, too many callframes, memory access etc.-callChecks- :: (?op :: Word8)- => Contract -> Word64 -> Expr EAddr -> Expr EAddr -> Expr EWord -> W256 -> W256 -> W256 -> W256 -> [Expr EWord]- -- continuation with gas available for call- -> (Word64 -> EVM s ())- -> EVM s ()-callChecks this xGas xContext xTo xValue xInOffset xInSize xOutOffset xOutSize xs continue = do- vm <- get- let fees = vm.block.schedule- accessMemoryRange xInOffset xInSize $- accessMemoryRange xOutOffset xOutSize $ do- availableGas <- use (#state % #gas)- let recipientExists = accountExists xContext vm- (cost, gas') <- costOfCall fees recipientExists xValue availableGas xGas xTo- burn (cost - gas') $- branch (Expr.gt xValue this.balance) $ \case- True -> do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- pushTrace $ ErrorTrace (BalanceTooLow xValue this.balance)- next- False ->- if length vm.frames >= 1024- then do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- pushTrace $ ErrorTrace CallDepthLimitReached- next- else continue gas'--precompiledContract- :: (?op :: Word8)- => Contract- -> Word64- -> Addr- -> Addr- -> Expr EWord- -> W256 -> W256 -> W256 -> W256- -> [Expr EWord]- -> EVM s ()-precompiledContract this xGas precompileAddr recipient xValue inOffset inSize outOffset outSize xs- = callChecks this xGas (LitAddr recipient) (LitAddr precompileAddr) xValue inOffset inSize outOffset outSize xs $ \gas' ->- do- executePrecompile precompileAddr gas' inOffset inSize outOffset outSize xs- self <- use (#state % #contract)- stk <- use (#state % #stack)- pc' <- use (#state % #pc)- result' <- use #result- case result' of- Nothing -> case stk of- x:_ -> case maybeLitWord x of- Just 0 ->- pure ()- Just 1 ->- fetchAccount (LitAddr recipient) $ \_ -> do- touchAccount self- touchAccount (LitAddr recipient)- transfer self (LitAddr recipient) xValue- _ -> partial $- UnexpectedSymbolicArg pc' "unexpected return value from precompile" (wrap [x])- _ -> underrun- _ -> pure ()--executePrecompile- :: (?op :: Word8)- => Addr- -> Word64 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord]- -> EVM s ()-executePrecompile preCompileAddr gasCap inOffset inSize outOffset outSize xs = do- vm <- get- input <- readMemory (Lit inOffset) (Lit inSize)- let fees = vm.block.schedule- cost = costOfPrecompile fees preCompileAddr input- notImplemented = internalError $ "precompile at address " <> show preCompileAddr <> " not yet implemented"- precompileFail = burn (gasCap - cost) $ do- assign (#state % #stack) (Lit 0 : xs)- pushTrace $ ErrorTrace PrecompileFailure- next- if cost > gasCap then- burn gasCap $ do- assign (#state % #stack) (Lit 0 : xs)- next- else burn cost $- case preCompileAddr of- -- ECRECOVER- 0x1 ->- -- TODO: support symbolic variant- forceConcreteBuf input "ECRECOVER" $ \input' -> do- case EVM.Precompiled.execute 0x1 (truncpadlit 128 input') 32 of- Nothing -> do- -- return no output for invalid signature- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) mempty- next- Just output -> do- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) (ConcreteBuf output)- copyBytesToMemory (ConcreteBuf output) (Lit outSize) (Lit 0) (Lit outOffset)- next-- -- SHA2-256- 0x2 ->- forceConcreteBuf input "SHA2-256" $ \input' -> do- let- hash = sha256Buf input'- sha256Buf x = ConcreteBuf $ BA.convert (Crypto.hash x :: Digest SHA256)- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) hash- copyBytesToMemory hash (Lit outSize) (Lit 0) (Lit outOffset)- next-- -- RIPEMD-160- 0x3 ->- -- TODO: support symbolic variant- forceConcreteBuf input "RIPEMD160" $ \input' -> do- let- padding = BS.pack $ replicate 12 0- hash' = BA.convert (Crypto.hash input' :: Digest RIPEMD160)- hash = ConcreteBuf $ padding <> hash'- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) hash- copyBytesToMemory hash (Lit outSize) (Lit 0) (Lit outOffset)- next-- -- IDENTITY- 0x4 -> do- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) input- copyCallBytesToMemory input (Lit outSize) (Lit outOffset)- next-- -- MODEXP- 0x5 ->- -- TODO: support symbolic variant- forceConcreteBuf input "MODEXP" $ \input' -> do- let- (lenb, lene, lenm) = parseModexpLength input'-- output = ConcreteBuf $- if isZero (96 + lenb + lene) lenm input'- then truncpadlit (unsafeInto lenm) (asBE (0 :: Int))- else- let- b = asInteger $ lazySlice 96 lenb input'- e = asInteger $ lazySlice (96 + lenb) lene input'- m = asInteger $ lazySlice (96 + lenb + lene) lenm input'- in- padLeft (unsafeInto lenm) (asBE (expFast b e m))- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) output- copyBytesToMemory output (Lit outSize) (Lit 0) (Lit outOffset)- next-- -- ECADD- 0x6 ->- -- TODO: support symbolic variant- forceConcreteBuf input "ECADD" $ \input' ->- case EVM.Precompiled.execute 0x6 (truncpadlit 128 input') 64 of- Nothing -> precompileFail- Just output -> do- let truncpaddedOutput = ConcreteBuf $ truncpadlit 64 output- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) truncpaddedOutput- copyBytesToMemory truncpaddedOutput (Lit outSize) (Lit 0) (Lit outOffset)- next-- -- ECMUL- 0x7 ->- -- TODO: support symbolic variant- forceConcreteBuf input "ECMUL" $ \input' ->- case EVM.Precompiled.execute 0x7 (truncpadlit 96 input') 64 of- Nothing -> precompileFail- Just output -> do- let truncpaddedOutput = ConcreteBuf $ truncpadlit 64 output- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) truncpaddedOutput- copyBytesToMemory truncpaddedOutput (Lit outSize) (Lit 0) (Lit outOffset)- next-- -- ECPAIRING- 0x8 ->- -- TODO: support symbolic variant- forceConcreteBuf input "ECPAIR" $ \input' ->- case EVM.Precompiled.execute 0x8 input' 32 of- Nothing -> precompileFail- Just output -> do- let truncpaddedOutput = ConcreteBuf $ truncpadlit 32 output- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) truncpaddedOutput- copyBytesToMemory truncpaddedOutput (Lit outSize) (Lit 0) (Lit outOffset)- next-- -- BLAKE2- 0x9 ->- -- TODO: support symbolic variant- forceConcreteBuf input "BLAKE2" $ \input' -> do- case (BS.length input', 1 >= BS.last input') of- (213, True) -> case EVM.Precompiled.execute 0x9 input' 64 of- Just output -> do- let truncpaddedOutput = ConcreteBuf $ truncpadlit 64 output- assign (#state % #stack) (Lit 1 : xs)- assign (#state % #returndata) truncpaddedOutput- copyBytesToMemory truncpaddedOutput (Lit outSize) (Lit 0) (Lit outOffset)- next- Nothing -> precompileFail- _ -> precompileFail-- _ -> notImplemented--truncpadlit :: Int -> ByteString -> ByteString-truncpadlit n xs = if m > n then BS.take n xs- else BS.append xs (BS.replicate (n - m) 0)- where m = BS.length xs--lazySlice :: W256 -> W256 -> ByteString -> LS.ByteString-lazySlice offset size bs =- let bs' = LS.take (unsafeInto size) (LS.drop (unsafeInto offset) (fromStrict bs))- in bs' <> LS.replicate (unsafeInto size - LS.length bs') 0--parseModexpLength :: ByteString -> (W256, W256, W256)-parseModexpLength input =- let lenb = word $ LS.toStrict $ lazySlice 0 32 input- lene = word $ LS.toStrict $ lazySlice 32 64 input- lenm = word $ LS.toStrict $ lazySlice 64 96 input- in (lenb, lene, lenm)----- checks if a range of ByteString bs starting at offset and length size is all zeros.-isZero :: W256 -> W256 -> ByteString -> Bool-isZero offset size bs =- LS.all (== 0) $- LS.take (unsafeInto size) $- LS.drop (unsafeInto offset) $- fromStrict bs--asInteger :: LS.ByteString -> Integer-asInteger xs = if xs == mempty then 0- else 256 * asInteger (LS.init xs)- + into (LS.last xs)---- * Opcode helper actions--noop :: Monad m => m ()-noop = pure ()--pushTo :: MonadState s m => Lens s s [a] [a] -> a -> m ()-pushTo f x = f %= (x :)--pushToSequence :: MonadState s m => Setter s s (Seq a) (Seq a) -> a -> m ()-pushToSequence f x = f %= (Seq.|> x)--getCodeLocation :: VM s -> CodeLocation-getCodeLocation vm = (vm.state.contract, vm.state.pc)--query :: Query s -> EVM s ()-query = assign #result . Just . HandleEffect . Query--choose :: Choose s -> EVM s ()-choose = assign #result . Just . HandleEffect . Choose--branch :: forall s. Expr EWord -> (Bool -> EVM s ()) -> EVM s ()-branch cond continue = do- loc <- codeloc- pathconds <- use #constraints- query $ PleaseAskSMT cond pathconds (choosePath loc)- where- condSimp = Expr.simplify cond- choosePath :: CodeLocation -> BranchCondition -> EVM s ()- choosePath loc (Case v) = do- assign #result Nothing- pushTo #constraints $ if v then Expr.simplifyProp (condSimp ./= Lit 0) else Expr.simplifyProp (condSimp .== Lit 0)- (iteration, _) <- use (#iterations % at loc % non (0,[]))- stack <- use (#state % #stack)- assign (#cache % #path % at (loc, iteration)) (Just v)- assign (#iterations % at loc) (Just (iteration + 1, stack))- continue v- -- Both paths are possible; we ask for more input- choosePath loc Unknown =- choose . PleaseChoosePath condSimp $ choosePath loc . Case---- | Construct RPC Query and halt execution until resolved-fetchAccount :: Expr EAddr -> (Contract -> EVM s ()) -> EVM s ()-fetchAccount addr continue =- use (#env % #contracts % at addr) >>= \case- Just c -> continue c- Nothing -> case addr of- SymAddr _ -> do- pc <- use (#state % #pc)- partial $ UnexpectedSymbolicArg pc "trying to access a symbolic address that isn't already present in storage" (wrap [addr])- LitAddr a -> do- use (#cache % #fetched % at a) >>= \case- Just c -> do- assign (#env % #contracts % at addr) (Just c)- continue c- Nothing -> do- base <- use (#config % #baseState)- assign (#result) . Just . HandleEffect . Query $- PleaseFetchContract a base- (\c -> do assign (#cache % #fetched % at a) (Just c)- assign (#env % #contracts % at addr) (Just c)- assign #result Nothing- continue c)- GVar _ -> internalError "Unexpected GVar"--accessStorage- :: Expr EAddr- -> Expr EWord- -> (Expr EWord -> EVM s ())- -> EVM s ()-accessStorage addr slot continue = do- use (#env % #contracts % at addr) >>= \case- Just c ->- case readStorage slot c.storage of- Just x ->- continue x- Nothing ->- if c.external then- forceConcreteAddr addr "cannot read storage from symbolic addresses via rpc" $ \addr' ->- forceConcrete slot "cannot read symbolic slots via RPC" $ \slot' -> do- -- check if the slot is cached- contract <- preuse (#cache % #fetched % ix addr')- case contract of- Nothing -> internalError "contract marked external not found in cache"- Just fetched -> case readStorage (Lit slot') fetched.storage of- Nothing -> mkQuery addr' slot'- Just val -> continue val- else do- modifying (#env % #contracts % ix addr % #storage) (writeStorage slot (Lit 0))- continue $ Lit 0- Nothing ->- fetchAccount addr $ \_ ->- accessStorage addr slot continue- where- mkQuery a s = query $- PleaseFetchSlot a s- (\x -> do- modifying (#cache % #fetched % ix a % #storage) (writeStorage (Lit s) (Lit x))- modifying (#env % #contracts % ix (LitAddr a) % #storage) (writeStorage (Lit s) (Lit x))- assign #result Nothing- continue (Lit x))--accountExists :: Expr EAddr -> VM s -> Bool-accountExists addr vm =- case Map.lookup addr vm.env.contracts of- Just c -> not (accountEmpty c)- Nothing -> False---- EIP 161-accountEmpty :: Contract -> Bool-accountEmpty c =- case c.code of- RuntimeCode (ConcreteRuntimeCode "") -> True- RuntimeCode (SymbolicRuntimeCode b) -> null b- _ -> False- && c.nonce == (Just 0)- -- TODO: handle symbolic balance...- && c.balance == Lit 0---- * How to finalize a transaction-finalize :: EVM s ()-finalize = do- let- revertContracts = use (#tx % #txReversion) >>= assign (#env % #contracts)- revertSubstate = assign (#tx % #substate) (SubState mempty mempty mempty mempty mempty)-- use #result >>= \case- Just (VMFailure (Revert _)) -> do- revertContracts- revertSubstate- Just (VMFailure _) -> do- -- burn remaining gas- assign (#state % #gas) 0- revertContracts- revertSubstate- Just (VMSuccess output) -> do- -- deposit the code from a creation tx- pc' <- use (#state % #pc)- creation <- use (#tx % #isCreate)- createe <- use (#state % #contract)- createeExists <- (Map.member createe) <$> use (#env % #contracts)- let onContractCode contractCode =- when (creation && createeExists) $ replaceCode createe contractCode- case output of- ConcreteBuf bs ->- onContractCode $ RuntimeCode (ConcreteRuntimeCode bs)- _ ->- case Expr.toList output of- Nothing ->- partial $- UnexpectedSymbolicArg pc' "runtime code cannot have an abstract lentgh" (wrap [output])- Just ops ->- onContractCode $ RuntimeCode (SymbolicRuntimeCode ops)- _ ->- internalError "Finalising an unfinished tx."-- -- compute and pay the refund to the caller and the- -- corresponding payment to the miner- block <- use #block- tx <- use #tx- gasRemaining <- use (#state % #gas)-- let- sumRefunds = sum (snd <$> tx.substate.refunds)- gasUsed = tx.gaslimit - gasRemaining- cappedRefund = min (quot gasUsed 5) sumRefunds- originPay = (into $ gasRemaining + cappedRefund) * tx.gasprice- minerPay = tx.priorityFee * (into gasUsed)-- modifying (#env % #contracts)- (Map.adjust (over #balance (Expr.add (Lit originPay))) tx.origin)- modifying (#env % #contracts)- (Map.adjust (over #balance (Expr.add (Lit minerPay))) block.coinbase)- touchAccount block.coinbase-- -- perform state trie clearing (EIP 161), of selfdestructs- -- and touched accounts. addresses are cleared if they have- -- a) selfdestructed, or- -- b) been touched and- -- c) are empty.- -- (see Yellow Paper "Accrued Substate")- --- -- remove any destructed addresses- destroyedAddresses <- use (#tx % #substate % #selfdestructs)- modifying (#env % #contracts)- (Map.filterWithKey (\k _ -> (k `notElem` destroyedAddresses)))- -- then, clear any remaining empty and touched addresses- touchedAddresses <- use (#tx % #substate % #touchedAccounts)- modifying (#env % #contracts)- (Map.filterWithKey- (\k a -> not ((k `elem` touchedAddresses) && accountEmpty a)))---- | Loads the selected contract as the current contract to execute-loadContract :: Expr EAddr -> State (VM s) ()-loadContract target =- preuse (#env % #contracts % ix target % #code) >>=- \case- Nothing ->- internalError "Call target doesn't exist"- Just targetCode -> do- assign (#state % #contract) target- assign (#state % #code) targetCode- assign (#state % #codeContract) target--limitStack :: Int -> EVM s () -> EVM s ()-limitStack n continue = do- stk <- use (#state % #stack)- if length stk + n > 1024- then vmError StackLimitExceeded- else continue--notStatic :: EVM s () -> EVM s ()-notStatic continue = do- bad <- use (#state % #static)- if bad- then vmError StateChangeWhileStatic- else continue---- | Burn gas, failing if insufficient gas is available-burn :: Word64 -> EVM s () -> EVM s ()-burn n continue = do- available <- use (#state % #gas)- if n <= available- then do- #state % #gas %= (subtract n)- #burned %= (+ n)- continue- else- vmError (OutOfGas available n)--forceAddr :: Expr EWord -> String -> (Expr EAddr -> EVM s ()) -> EVM s ()-forceAddr n msg continue = case wordToAddr n of- Nothing -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n])- Just c -> continue c--forceConcrete :: Expr EWord -> String -> (W256 -> EVM s ()) -> EVM s ()-forceConcrete n msg continue = case maybeLitWord n of- Nothing -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n])- Just c -> continue c--forceConcreteAddr :: Expr EAddr -> String -> (Addr -> EVM s ()) -> EVM s ()-forceConcreteAddr n msg continue = case maybeLitAddr n of- Nothing -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n])- Just c -> continue c--forceConcreteAddr2 :: (Expr EAddr, Expr EAddr) -> String -> ((Addr, Addr) -> EVM s ()) -> EVM s ()-forceConcreteAddr2 (n,m) msg continue = case (maybeLitAddr n, maybeLitAddr m) of- (Just c, Just d) -> continue (c,d)- _ -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n, m])--forceConcrete2 :: (Expr EWord, Expr EWord) -> String -> ((W256, W256) -> EVM s ()) -> EVM s ()-forceConcrete2 (n,m) msg continue = case (maybeLitWord n, maybeLitWord m) of- (Just c, Just d) -> continue (c, d)- _ -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n, m])--forceConcrete3 :: (Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256) -> EVM s ()) -> EVM s ()-forceConcrete3 (k,n,m) msg continue = case (maybeLitWord k, maybeLitWord n, maybeLitWord m) of- (Just c, Just d, Just f) -> continue (c, d, f)- _ -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [k, n, m])--forceConcrete4 :: (Expr EWord, Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256, W256) -> EVM s ()) -> EVM s ()-forceConcrete4 (k,l,n,m) msg continue = case (maybeLitWord k, maybeLitWord l, maybeLitWord n, maybeLitWord m) of- (Just b, Just c, Just d, Just f) -> continue (b, c, d, f)- _ -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [k, l, n, m])--forceConcrete5 :: (Expr EWord, Expr EWord, Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256, W256, W256) -> EVM s ()) -> EVM s ()-forceConcrete5 (k,l,m,n,o) msg continue = case (maybeLitWord k, maybeLitWord l, maybeLitWord m, maybeLitWord n, maybeLitWord o) of- (Just a, Just b, Just c, Just d, Just e) -> continue (a, b, c, d, e)- _ -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [k, l, m, n, o])--forceConcrete6 :: (Expr EWord, Expr EWord, Expr EWord, Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256, W256, W256, W256) -> EVM s ()) -> EVM s ()-forceConcrete6 (k,l,m,n,o,p) msg continue = case (maybeLitWord k, maybeLitWord l, maybeLitWord m, maybeLitWord n, maybeLitWord o, maybeLitWord p) of- (Just a, Just b, Just c, Just d, Just e, Just f) -> continue (a, b, c, d, e, f)- _ -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [k, l, m, n, o, p])--forceConcreteBuf :: Expr Buf -> String -> (ByteString -> EVM s ()) -> EVM s ()-forceConcreteBuf (ConcreteBuf b) _ continue = continue b-forceConcreteBuf b msg _ = do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [b])---- * Substate manipulation-refund :: Word64 -> EVM s ()-refund n = do- self <- use (#state % #contract)- pushTo (#tx % #substate % #refunds) (self, n)--unRefund :: Word64 -> EVM s ()-unRefund n = do- self <- use (#state % #contract)- refs <- use (#tx % #substate % #refunds)- assign (#tx % #substate % #refunds)- (filter (\(a,b) -> not (a == self && b == n)) refs)--touchAccount :: Expr EAddr -> EVM s ()-touchAccount = pushTo ((#tx % #substate) % #touchedAccounts)--selfdestruct :: Expr EAddr -> EVM s ()-selfdestruct = pushTo ((#tx % #substate) % #selfdestructs)--accessAndBurn :: Expr EAddr -> EVM s () -> EVM s ()-accessAndBurn x cont = do- FeeSchedule {..} <- use (#block % #schedule)- acc <- accessAccountForGas x- let cost = if acc then g_warm_storage_read else g_cold_account_access- burn cost cont---- | returns a wrapped boolean- if true, this address has been touched before in the txn (warm gas cost as in EIP 2929)--- otherwise cold-accessAccountForGas :: Expr EAddr -> EVM s Bool-accessAccountForGas addr = do- accessedAddrs <- use (#tx % #substate % #accessedAddresses)- let accessed = member addr accessedAddrs- assign (#tx % #substate % #accessedAddresses) (insert addr accessedAddrs)- pure accessed---- | returns a wrapped boolean- if true, this slot has been touched before in the txn (warm gas cost as in EIP 2929)--- otherwise cold-accessStorageForGas :: Expr EAddr -> Expr EWord -> EVM s Bool-accessStorageForGas addr key = do- accessedStrkeys <- use (#tx % #substate % #accessedStorageKeys)- case maybeLitWord key of- Just litword -> do- let accessed = member (addr, litword) accessedStrkeys- assign (#tx % #substate % #accessedStorageKeys) (insert (addr, litword) accessedStrkeys)- pure accessed- _ -> return False---- * Cheat codes---- The cheat code is 7109709ecfa91a80626ff3989d68f67f5b1dd12d.--- Call this address using one of the cheatActions below to do--- special things, e.g. changing the block timestamp. Beware that--- these are necessarily hevm specific.-cheatCode :: Expr EAddr-cheatCode = LitAddr $ unsafeInto (keccak' "hevm cheat code")--cheat- :: (?op :: Word8)- => (W256, W256) -> (W256, W256)- -> EVM s ()-cheat (inOffset, inSize) (outOffset, outSize) = do- vm <- get- input <- readMemory (Lit $ inOffset + 4) (Lit $ inSize - 4)- abi <- readBytes 4 (Lit 0) <$> readMemory (Lit inOffset) (Lit 4)- pushTrace $ FrameTrace (CallContext cheatCode cheatCode inOffset inSize (Lit 0) (maybeLitWord abi) input vm.env.contracts vm.tx.substate)- case maybeLitWord abi of- Nothing -> partial $ UnexpectedSymbolicArg vm.state.pc "symbolic cheatcode selector" (wrap [abi])- Just (unsafeInto -> abi') ->- case Map.lookup abi' cheatActions of- Nothing ->- vmError (BadCheatCode abi')- Just action -> do- action (Lit outOffset) (Lit outSize) input- popTrace- next- push 1--type CheatAction s = Expr EWord -> Expr EWord -> Expr Buf -> EVM s ()--cheatActions :: Map FunctionSelector (CheatAction s)-cheatActions =- Map.fromList- [ action "ffi(string[])" $- \sig outOffset outSize input -> do- vm <- get- if vm.config.allowFFI then- case decodeBuf [AbiArrayDynamicType AbiStringType] input of- CAbi valsArr -> case valsArr of- [AbiArrayDynamic AbiStringType strsV] ->- let- cmd = fmap- (\case- (AbiString a) -> unpack $ decodeUtf8 a- _ -> "")- (V.toList strsV)- cont bs = do- let encoded = ConcreteBuf bs- assign (#state % #returndata) encoded- copyBytesToMemory encoded outSize (Lit 0) outOffset- assign #result Nothing- in query (PleaseDoFFI cmd cont)- _ -> vmError (BadCheatCode sig)- _ -> vmError (BadCheatCode sig)- else- let msg = "ffi disabled: run again with --ffi if you want to allow tests to call external scripts"- in partial $ UnexpectedSymbolicArg vm.state.pc msg [],-- action "warp(uint256)" $- \sig _ _ input -> case decodeStaticArgs 0 1 input of- [x] -> assign (#block % #timestamp) x- _ -> vmError (BadCheatCode sig),-- action "deal(address,uint256)" $- \sig _ _ input -> case decodeStaticArgs 0 2 input of- [a, amt] ->- forceAddr a "vm.deal: cannot decode target into an address" $ \usr ->- fetchAccount usr $ \_ -> do- assign (#env % #contracts % ix usr % #balance) amt- _ -> vmError (BadCheatCode sig),-- action "assume(bool)" $- \sig _ _ input -> case decodeStaticArgs 0 1 input of- [c] -> modifying #constraints ((:) (PEq c (Lit 1)))- _ -> vmError (BadCheatCode sig),-- action "roll(uint256)" $- \sig _ _ input -> case decodeStaticArgs 0 1 input of- [x] -> forceConcrete x "cannot roll to a symbolic block number" (assign (#block % #number))- _ -> vmError (BadCheatCode sig),-- action "store(address,bytes32,bytes32)" $- \sig _ _ input -> case decodeStaticArgs 0 3 input of- [a, slot, new] -> case wordToAddr a of- Just a'@(LitAddr _) -> fetchAccount a' $ \_ ->- modifying (#env % #contracts % ix a' % #storage) (writeStorage slot new)- _ -> vmError (BadCheatCode sig)- _ -> vmError (BadCheatCode sig),-- action "load(address,bytes32)" $- \sig outOffset _ input -> case decodeStaticArgs 0 2 input of- [a, slot] -> case wordToAddr a of- Just a'@(LitAddr _) -> fetchAccount a' $ \_ ->- accessStorage a' slot $ \res -> do- assign (#state % #returndata % word256At (Lit 0)) res- let buf = writeWord (Lit 0) res (ConcreteBuf "")- copyBytesToMemory buf (Lit 32) (Lit 0) outOffset- _ -> vmError (BadCheatCode sig)- _ -> vmError (BadCheatCode sig),-- action "sign(uint256,bytes32)" $- \sig outOffset _ input -> case decodeStaticArgs 0 2 input of- [sk, hash] ->- forceConcrete2 (sk, hash) "cannot sign symbolic data" $ \(sk', hash') -> do- let (v,r,s) = EVM.Sign.sign hash' (toInteger sk')- encoded = encodeAbiValue $- AbiTuple (V.fromList- [ AbiUInt 8 $ into v- , AbiBytes 32 (word256Bytes r)- , AbiBytes 32 (word256Bytes s)- ])- assign (#state % #returndata) (ConcreteBuf encoded)- copyBytesToMemory (ConcreteBuf encoded) (Lit . unsafeInto . BS.length $ encoded) (Lit 0) outOffset- _ -> vmError (BadCheatCode sig),-- action "addr(uint256)" $- \sig outOffset _ input -> case decodeStaticArgs 0 1 input of- [sk] -> forceConcrete sk "cannot derive address for a symbolic key" $ \sk' -> do- let a = EVM.Sign.deriveAddr $ into sk'- case a of- Nothing -> vmError (BadCheatCode sig)- Just address -> do- let expAddr = litAddr address- assign (#state % #returndata % word256At (Lit 0)) expAddr- let buf = ConcreteBuf $ word256Bytes (into address)- copyBytesToMemory buf (Lit 32) (Lit 0) outOffset- _ -> vmError (BadCheatCode sig),-- action "prank(address)" $- \sig _ _ input -> case decodeStaticArgs 0 1 input of- [addr] -> case wordToAddr addr of- Just a -> assign (#config % #overrideCaller) (Just a)- Nothing -> vmError (BadCheatCode sig)- _ -> vmError (BadCheatCode sig)-- ]- where- action s f = (abiKeccak s, f (abiKeccak s))---- * General call implementation ("delegateCall")--- note that the continuation is ignored in the precompile case-delegateCall- :: (?op :: Word8)- => Contract -> Word64 -> Expr EAddr -> Expr EAddr -> Expr EWord -> W256 -> W256 -> W256 -> W256- -> [Expr EWord]- -> (Expr EAddr -> EVM s ())- -> EVM s ()-delegateCall this gasGiven xTo xContext xValue xInOffset xInSize xOutOffset xOutSize xs continue- | isPrecompileAddr xTo- = forceConcreteAddr2 (xTo, xContext) "Cannot call precompile with symbolic addresses" $- \(xTo', xContext') ->- precompiledContract this gasGiven xTo' xContext' xValue xInOffset xInSize xOutOffset xOutSize xs- | xTo == cheatCode = do- assign (#state % #stack) xs- cheat (xInOffset, xInSize) (xOutOffset, xOutSize)- | otherwise =- callChecks this gasGiven xContext xTo xValue xInOffset xInSize xOutOffset xOutSize xs $- \xGas -> do- vm0 <- get- fetchAccount xTo $ \target -> case target.code of- UnknownCode _ -> do- pc <- use (#state % #pc)- partial $ UnexpectedSymbolicArg pc "call target has unknown code" (wrap [xTo])- _ -> do- burn xGas $ do- calldata <- readMemory (Lit xInOffset) (Lit xInSize)- abi <- maybeLitWord . readBytes 4 (Lit 0) <$> readMemory (Lit xInOffset) (Lit 4)- let newContext = CallContext- { target = xTo- , context = xContext- , offset = xOutOffset- , size = xOutSize- , codehash = target.codehash- , callreversion = vm0.env.contracts- , subState = vm0.tx.substate- , abi- , calldata- }- pushTrace (FrameTrace newContext)- next- vm1 <- get-- pushTo #frames $ Frame- { state = vm1.state { stack = xs }- , context = newContext- }-- let clearInitCode = \case- (InitCode _ _) -> InitCode mempty mempty- a -> a-- newMemory <- ConcreteMemory <$> VUnboxed.Mutable.new 0- zoom #state $ do- assign #gas xGas- assign #pc 0- assign #code (clearInitCode target.code)- assign #codeContract xTo- assign #stack mempty- assign #memory newMemory- assign #memorySize 0- assign #returndata mempty- assign #calldata calldata- continue xTo---- -- * Contract creation---- EIP 684-collision :: Maybe Contract -> Bool-collision c' = case c' of- Just c -> c.nonce /= Just 0 || case c.code of- RuntimeCode (ConcreteRuntimeCode "") -> False- RuntimeCode (SymbolicRuntimeCode b) -> not $ null b- _ -> True- Nothing -> False--create :: (?op :: Word8)- => Expr EAddr -> Contract- -> W256 -> Word64 -> Expr EWord -> [Expr EWord] -> Expr EAddr -> Expr Buf -> EVM s ()-create self this xSize xGas xValue xs newAddr initCode = do- vm0 <- get- if xSize > vm0.block.maxCodeSize * 2- then do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- vmError $ MaxInitCodeSizeExceeded (vm0.block.maxCodeSize * 2) xSize- else if this.nonce == Just maxBound- then do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- pushTrace $ ErrorTrace NonceOverflow- next- else if length vm0.frames >= 1024- then do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- pushTrace $ ErrorTrace CallDepthLimitReached- next- else if collision $ Map.lookup newAddr vm0.env.contracts- then burn xGas $ do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- modifying (#env % #contracts % ix self % #nonce) (fmap ((+) 1))- next- -- do we have enough balance- else branch (Expr.gt xValue this.balance) $ \case- True -> do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- pushTrace $ ErrorTrace $ BalanceTooLow xValue this.balance- next- touchAccount self- touchAccount newAddr- -- are we overflowing the nonce- False -> burn xGas $ do- case parseInitCode initCode of- Nothing ->- partial $ UnexpectedSymbolicArg vm0.state.pc "initcode must have a concrete prefix" []- Just c -> do- let- newContract = initialContract c- newContext =- CreationContext { address = newAddr- , codehash = newContract.codehash- , createreversion = vm0.env.contracts- , substate = vm0.tx.substate- }-- zoom (#env % #contracts) $ do- oldAcc <- use (at newAddr)- let oldBal = maybe (Lit 0) (.balance) oldAcc-- assign (at newAddr) (Just (newContract & #balance .~ oldBal))- modifying (ix self % #nonce) (fmap ((+) 1))-- let- resetStorage :: Expr Storage -> Expr Storage- resetStorage = \case- ConcreteStore _ -> ConcreteStore mempty- AbstractStore a -> AbstractStore a- SStore _ _ p -> resetStorage p- GVar _ -> error "unexpected global variable"-- modifying (#env % #contracts % ix newAddr % #storage) resetStorage- modifying (#env % #contracts % ix newAddr % #origStorage) resetStorage-- transfer self newAddr xValue-- pushTrace (FrameTrace newContext)- next- vm1 <- get- pushTo #frames $ Frame- { context = newContext- , state = vm1.state { stack = xs }- }-- state <- lift blankState- assign #state $ state- { contract = newAddr- , codeContract = newAddr- , code = c- , callvalue = xValue- , caller = self- , gas = xGas- }---- | Parses a raw Buf into an InitCode------ solidity implements constructor args by appending them to the end of--- the initcode. we support this internally by treating initCode as a--- concrete region (initCode) followed by a potentially symbolic region--- (arguments).------ when constructing a contract that has symbolic construcor args, we--- need to apply some heuristics to convert the (unstructured) initcode--- in memory into this structured representation. The (unsound, bad,--- hacky) way that we do this, is by: looking for the first potentially--- symbolic byte in the input buffer and then splitting it there into code / data.-parseInitCode :: Expr Buf -> Maybe ContractCode-parseInitCode (ConcreteBuf b) = Just (InitCode b mempty)-parseInitCode buf = if V.null conc- then Nothing- else Just $ InitCode (BS.pack $ V.toList conc) sym- where- conc = Expr.concretePrefix buf- -- unsafeInto: findIndex will always be positive- sym = Expr.drop (unsafeInto (V.length conc)) buf---- | Replace a contract's code, like when CREATE returns--- from the constructor code.-replaceCode :: Expr EAddr -> ContractCode -> EVM s ()-replaceCode target newCode =- zoom (#env % #contracts % at target) $- get >>= \case- Just now -> case now.code of- InitCode _ _ ->- put . Just $- ((initialContract newCode) :: Contract)- { balance = now.balance- , nonce = now.nonce- , storage = now.storage- }- RuntimeCode _ ->- internalError $ "Can't replace code of deployed contract " <> show target- UnknownCode _ ->- internalError "Can't replace unknown code"- Nothing ->- internalError "Can't replace code of nonexistent contract"--replaceCodeOfSelf :: ContractCode -> EVM s ()-replaceCodeOfSelf newCode = do- vm <- get- replaceCode vm.state.contract newCode--resetState :: EVM s ()-resetState = do- state <- lift blankState- modify' $ \vm -> vm { result = Nothing, frames = [], state }---- * VM error implementation--vmError :: EvmError -> EVM s ()-vmError e = finishFrame (FrameErrored e)--partial :: PartialExec -> EVM s ()-partial e = assign #result (Just (Unfinished e))--wrap :: Typeable a => [Expr a] -> [SomeExpr]-wrap = fmap SomeExpr--underrun :: EVM s ()-underrun = vmError StackUnderrun---- | A stack frame can be popped in three ways.-data FrameResult- = FrameReturned (Expr Buf) -- ^ STOP, RETURN, or no more code- | FrameReverted (Expr Buf) -- ^ REVERT- | FrameErrored EvmError -- ^ Any other error- deriving Show---- | This function defines how to pop the current stack frame in either of--- the ways specified by 'FrameResult'.------ It also handles the case when the current stack frame is the only one;--- in this case, we set the final '_result' of the VM execution.-finishFrame :: FrameResult -> EVM s ()-finishFrame how = do- oldVm <- get-- case oldVm.frames of- -- Is the current frame the only one?- [] -> do- case how of- FrameReturned output -> assign #result . Just $ VMSuccess output- FrameReverted buffer -> assign #result . Just $ VMFailure (Revert buffer)- FrameErrored e -> assign #result . Just $ VMFailure e- finalize-- -- Are there some remaining frames?- nextFrame : remainingFrames -> do-- -- Insert a debug trace.- insertTrace $- case how of- FrameErrored e ->- ErrorTrace e- FrameReverted e ->- ErrorTrace (Revert e)- FrameReturned output ->- ReturnTrace output nextFrame.context- -- Pop to the previous level of the debug trace stack.- popTrace-- -- Pop the top frame.- assign #frames remainingFrames- -- Install the state of the frame to which we shall return.- assign #state nextFrame.state-- -- When entering a call, the gas allowance is counted as burned- -- in advance; this unburns the remainder and adds it to the- -- parent frame.- let remainingGas = oldVm.state.gas- reclaimRemainingGasAllowance = do- modifying #burned (subtract remainingGas)- modifying (#state % #gas) (+ remainingGas)-- -- Now dispatch on whether we were creating or calling,- -- and whether we shall return, revert, or internalError(six cases).- case nextFrame.context of-- -- Were we calling?- CallContext _ _ (Lit -> outOffset) (Lit -> outSize) _ _ _ reversion substate' -> do-- -- Excerpt K.1. from the yellow paper:- -- K.1. Deletion of an Account Despite Out-of-gas.- -- At block 2675119, in the transaction 0xcf416c536ec1a19ed1fb89e4ec7ffb3cf73aa413b3aa9b77d60e4fd81a4296ba,- -- an account at address 0x03 was called and an out-of-gas occurred during the call.- -- Against the equation (197), this added 0x03 in the set of touched addresses, and this transaction turned σ[0x03] into ∅.-- -- In other words, we special case address 0x03 and keep it in the set of touched accounts during revert- touched <- use (#tx % #substate % #touchedAccounts)-- let- substate'' = over #touchedAccounts (maybe id cons (find (LitAddr 3 ==) touched)) substate'- revertContracts = assign (#env % #contracts) reversion- revertSubstate = assign (#tx % #substate) substate''-- case how of- -- Case 1: Returning from a call?- FrameReturned output -> do- assign (#state % #returndata) output- copyCallBytesToMemory output outSize outOffset- reclaimRemainingGasAllowance- push 1-- -- Case 2: Reverting during a call?- FrameReverted output -> do- revertContracts- revertSubstate- assign (#state % #returndata) output- copyCallBytesToMemory output outSize outOffset- reclaimRemainingGasAllowance- push 0-- -- Case 3: Error during a call?- FrameErrored _ -> do- revertContracts- revertSubstate- assign (#state % #returndata) mempty- push 0- -- Or were we creating?- CreationContext _ _ reversion substate' -> do- creator <- use (#state % #contract)- let- createe = oldVm.state.contract- revertContracts = assign (#env % #contracts) reversion'- revertSubstate = assign (#tx % #substate) substate'-- -- persist the nonce through the reversion- reversion' = (Map.adjust (over #nonce (fmap ((+) 1))) creator) reversion-- case how of- -- Case 4: Returning during a creation?- FrameReturned output -> do- let onContractCode contractCode = do- replaceCode createe contractCode- assign (#state % #returndata) mempty- reclaimRemainingGasAllowance- pushAddr createe- case output of- ConcreteBuf bs ->- onContractCode $ RuntimeCode (ConcreteRuntimeCode bs)- _ ->- case Expr.toList output of- Nothing -> partial $- UnexpectedSymbolicArg- oldVm.state.pc- "runtime code cannot have an abstract length"- (wrap [output])- Just newCode -> do- onContractCode $ RuntimeCode (SymbolicRuntimeCode newCode)-- -- Case 5: Reverting during a creation?- FrameReverted output -> do- revertContracts- revertSubstate- assign (#state % #returndata) output- reclaimRemainingGasAllowance- push 0-- -- Case 6: Error during a creation?- FrameErrored _ -> do- revertContracts- revertSubstate- assign (#state % #returndata) mempty- push 0----- * Memory helpers--accessUnboundedMemoryRange- :: Word64- -> Word64- -> EVM s ()- -> EVM s ()-accessUnboundedMemoryRange _ 0 continue = continue-accessUnboundedMemoryRange f l continue = do- m0 <- use (#state % #memorySize)- fees <- gets (.block.schedule)- let m1 = 32 * ceilDiv (max m0 (f + l)) 32- burn (memoryCost fees m1 - memoryCost fees m0) $ do- assign (#state % #memorySize) m1- continue--accessMemoryRange- :: W256- -> W256- -> EVM s ()- -> EVM s ()-accessMemoryRange _ 0 continue = continue-accessMemoryRange offs sz continue =- case (,) <$> toWord64 offs <*> toWord64 sz of- Nothing -> vmError IllegalOverflow- Just (offs64, sz64) ->- if offs64 + sz64 < sz64- then vmError IllegalOverflow- else accessUnboundedMemoryRange offs64 sz64 continue--accessMemoryWord- :: W256 -> EVM s () -> EVM s ()-accessMemoryWord x = accessMemoryRange x 32--copyBytesToMemory- :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM s ()-copyBytesToMemory bs size srcOffset memOffset =- if size == Lit 0 then noop- else do- gets (.state.memory) >>= \case- ConcreteMemory mem ->- case (bs, size, srcOffset, memOffset) of- (ConcreteBuf b, Lit size', Lit srcOffset', Lit memOffset') -> do- let src =- if srcOffset' >= unsafeInto (BS.length b) then- BS.replicate (unsafeInto size') 0- else- BS.take (unsafeInto size') $- padRight (unsafeInto size') $- BS.drop (unsafeInto srcOffset') b-- writeMemory mem (unsafeInto memOffset') src- _ -> do- -- copy out and move to symbolic memory- buf <- freezeMemory mem- assign (#state % #memory) $- SymbolicMemory $ copySlice srcOffset memOffset size bs buf- SymbolicMemory mem ->- assign (#state % #memory) $- SymbolicMemory $ copySlice srcOffset memOffset size bs mem--copyCallBytesToMemory- :: Expr Buf -> Expr EWord -> Expr EWord -> EVM s ()-copyCallBytesToMemory bs size yOffset =- copyBytesToMemory bs (Expr.min size (bufLength bs)) (Lit 0) yOffset--readMemory :: Expr EWord -> Expr EWord -> EVM s (Expr Buf)-readMemory offset' size' = do- vm <- get- case vm.state.memory of- ConcreteMemory mem -> do- case (offset', size') of- (Lit offset, Lit size) -> do- let memSize :: Word64 = unsafeInto (VUnboxed.Mutable.length mem)- if size > Expr.maxBytes ||- offset + size > Expr.maxBytes ||- offset >= into memSize then- -- reads past memory are all zeros- pure $ ConcreteBuf $ BS.replicate (unsafeInto size) 0- else do- let pastEnd = (unsafeInto offset + unsafeInto size) - unsafeInto memSize- let fromMemSize = if pastEnd > 0 then unsafeInto size - pastEnd else unsafeInto size-- buf <- VUnboxed.freeze $ VUnboxed.Mutable.slice (unsafeInto offset) fromMemSize mem- let dataFromMem = BS.pack $ VUnboxed.toList buf- pure $ ConcreteBuf $ dataFromMem <> BS.replicate pastEnd 0- _ -> do- buf <- freezeMemory mem- pure $ copySlice offset' (Lit 0) size' buf mempty- SymbolicMemory mem ->- pure $ copySlice offset' (Lit 0) size' mem mempty---- * Tracing--withTraceLocation :: TraceData -> EVM s Trace-withTraceLocation x = do- vm <- get- let this = fromJust $ currentContract vm- pure Trace- { tracedata = x- , contract = this- , opIx = fromMaybe 0 $ this.opIxMap SV.!? vm.state.pc- }--pushTrace :: TraceData -> EVM s ()-pushTrace x = do- trace <- withTraceLocation x- modifying #traces $- \t -> Zipper.children $ Zipper.insert (Node trace []) t--insertTrace :: TraceData -> EVM s ()-insertTrace x = do- trace <- withTraceLocation x- modifying #traces $- \t -> Zipper.nextSpace $ Zipper.insert (Node trace []) t--popTrace :: EVM s ()-popTrace =- modifying #traces $- \t -> case Zipper.parent t of- Nothing -> internalError "internal internalError(trace root)"- Just t' -> Zipper.nextSpace t'--zipperRootForest :: Zipper.TreePos Zipper.Empty a -> Forest a-zipperRootForest z =- case Zipper.parent z of- Nothing -> Zipper.toForest z- Just z' -> zipperRootForest (Zipper.nextSpace z')--traceForest :: VM s -> Forest Trace-traceForest vm = zipperRootForest vm.traces--traceForest' :: Expr End -> Forest Trace-traceForest' (Success _ (Traces f _) _ _) = f-traceForest' (Partial _ (Traces f _) _) = f-traceForest' (Failure _ (Traces f _) _) = f-traceForest' (ITE {}) = internalError"Internal Error: ITE does not contain a trace"-traceForest' (GVar {}) = internalError"Internal Error: Unexpected GVar"--traceContext :: Expr End -> Map (Expr EAddr) Contract-traceContext (Success _ (Traces _ c) _ _) = c-traceContext (Partial _ (Traces _ c) _) = c-traceContext (Failure _ (Traces _ c) _) = c-traceContext (ITE {}) = internalError"Internal Error: ITE does not contain a trace"-traceContext (GVar {}) = internalError"Internal Error: Unexpected GVar"--traceTopLog :: [Expr Log] -> EVM s ()-traceTopLog [] = noop-traceTopLog ((LogEntry addr bytes topics) : _) = do- trace <- withTraceLocation (EventTrace addr bytes topics)- modifying #traces $- \t -> Zipper.nextSpace (Zipper.insert (Node trace []) t)-traceTopLog ((GVar _) : _) = internalError "unexpected global variable"---- * Stack manipulation--push :: W256 -> EVM s ()-push = pushSym . Lit--pushSym :: Expr EWord -> EVM s ()-pushSym x = #state % #stack %= (x :)--pushAddr :: Expr EAddr -> EVM s ()-pushAddr (LitAddr x) = #state % #stack %= (Lit (into x) :)-pushAddr x@(SymAddr _) = #state % #stack %= (WAddr x :)-pushAddr (GVar _) = internalError "Unexpected GVar"--stackOp1- :: (?op :: Word8)- => Word64- -> (Expr EWord -> Expr EWord)- -> EVM s ()-stackOp1 cost f =- use (#state % #stack) >>= \case- x:xs ->- burn cost $ do- next- let !y = f x- #state % #stack .= y : xs- _ ->- underrun--stackOp2- :: (?op :: Word8)- => Word64- -> (Expr EWord -> Expr EWord -> Expr EWord)- -> EVM s ()-stackOp2 cost f =- use (#state % #stack) >>= \case- x:y:xs ->- burn cost $ do- next- #state % #stack .= f x y : xs- _ ->- underrun--stackOp3- :: (?op :: Word8)- => Word64- -> (Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord)- -> EVM s ()-stackOp3 cost f =- use (#state % #stack) >>= \case- x:y:z:xs ->- burn cost $ do- next- (#state % #stack) .= f x y z : xs- _ ->- underrun---- * Bytecode data functions--use' :: (VM s -> a) -> EVM s a-use' f = do- vm <- get- pure (f vm)--checkJump :: Int -> [Expr EWord] -> EVM s ()-checkJump x xs = noJumpIntoInitData x $ do- vm <- get- case isValidJumpDest vm x of- True -> do- #state % #stack .= xs- #state % #pc .= x- False -> vmError BadJumpDestination---- fails with partial if we're trying to jump into the symbolic region of an `InitCode`-noJumpIntoInitData :: Int -> EVM s () -> EVM s ()-noJumpIntoInitData idx cont = do- vm <- get- case vm.state.code of- -- init code is totally concrete, so we don't return partial if we're- -- jumping beyond the range of `ops`- InitCode _ (ConcreteBuf "") -> cont- -- init code has a symbolic region, so check if we're trying to jump into- -- the symbolic region and return partial if we are- InitCode ops _ -> if idx > BS.length ops- then partial $ JumpIntoSymbolicCode vm.state.pc idx- else cont- -- we're not executing init code, so nothing to check here- _ -> cont--isValidJumpDest :: VM s -> Int -> Bool-isValidJumpDest vm x = let- code = vm.state.code- self = vm.state.codeContract- contract = fromMaybe- (internalError "self not found in current contracts")- (Map.lookup self vm.env.contracts)- op = case code of- UnknownCode _ -> internalError "Cannot analyze jumpdests for unknown code"- InitCode ops _ -> BS.indexMaybe ops x- RuntimeCode (ConcreteRuntimeCode ops) -> BS.indexMaybe ops x- RuntimeCode (SymbolicRuntimeCode ops) -> ops V.!? x >>= maybeLitByte- in case op of- Nothing -> False- Just b -> 0x5b == b && OpJumpdest == snd (contract.codeOps V.! (contract.opIxMap SV.! x))--opSize :: Word8 -> Int-opSize x | x >= 0x60 && x <= 0x7f = into x - 0x60 + 2-opSize _ = 1---- i of the resulting vector contains the operation index for--- the program counter value i. This is needed because source map--- entries are per operation, not per byte.-mkOpIxMap :: ContractCode -> SV.Vector Int-mkOpIxMap (UnknownCode _) = internalError "Cannot build opIxMap for unknown code"-mkOpIxMap (InitCode conc _)- = SV.create $ SV.new (BS.length conc) >>= \v ->- -- Loop over the byte string accumulating a vector-mutating action.- -- This is somewhat obfuscated, but should be fast.- let (_, _, _, m) = BS.foldl' (go v) (0 :: Word8, 0, 0, pure ()) conc- in m >> pure v- where- -- concrete case- go v (0, !i, !j, !m) x | x >= 0x60 && x <= 0x7f =- {- Start of PUSH op. -} (x - 0x60 + 1, i + 1, j, m >> SV.write v i j)- go v (1, !i, !j, !m) _ =- {- End of PUSH op. -} (0, i + 1, j + 1, m >> SV.write v i j)- go v (0, !i, !j, !m) _ =- {- Other op. -} (0, i + 1, j + 1, m >> SV.write v i j)- go v (n, !i, !j, !m) _ =- {- PUSH data. -} (n - 1, i + 1, j, m >> SV.write v i j)--mkOpIxMap (RuntimeCode (ConcreteRuntimeCode ops)) =- mkOpIxMap (InitCode ops mempty) -- a bit hacky--mkOpIxMap (RuntimeCode (SymbolicRuntimeCode ops))- = SV.create $ SV.new (length ops) >>= \v ->- let (_, _, _, m) = foldl (go v) (0, 0, 0, pure ()) (stripBytecodeMetadataSym $ V.toList ops)- in m >> pure v- where- go v (0, !i, !j, !m) x = case maybeLitByte x of- Just x' -> if x' >= 0x60 && x' <= 0x7f- -- start of PUSH op --- then (x' - 0x60 + 1, i + 1, j, m >> SV.write v i j)- -- other data --- else (0, i + 1, j + 1, m >> SV.write v i j)- _ -> internalError $ "cannot analyze symbolic code:\nx: " <> show x <> " i: " <> show i <> " j: " <> show j-- go v (1, !i, !j, !m) _ =- {- End of PUSH op. -} (0, i + 1, j + 1, m >> SV.write v i j)- go v (n, !i, !j, !m) _ =- {- PUSH data. -} (n - 1, i + 1, j, m >> SV.write v i j)---vmOp :: VM s -> Maybe Op-vmOp vm =- let i = vm ^. #state % #pc- code' = vm ^. #state % #code- (op, pushdata) = case code' of- UnknownCode _ -> internalError "cannot get op from unknown code"- InitCode xs' _ ->- (BS.index xs' i, fmap LitByte $ BS.unpack $ BS.drop i xs')- RuntimeCode (ConcreteRuntimeCode xs') ->- (BS.index xs' i, fmap LitByte $ BS.unpack $ BS.drop i xs')- RuntimeCode (SymbolicRuntimeCode xs') ->- ( fromMaybe (internalError "unexpected symbolic code") . maybeLitByte $ xs' V.! i , V.toList $ V.drop i xs')- in if (opslen code' < i)- then Nothing- else Just (readOp op pushdata)--vmOpIx :: VM s -> Maybe Int-vmOpIx vm =- do self <- currentContract vm- self.opIxMap SV.!? vm.state.pc---- Maps operation indicies into a pair of (bytecode index, operation)-mkCodeOps :: ContractCode -> V.Vector (Int, Op)-mkCodeOps contractCode =- let l = case contractCode of- UnknownCode _ -> internalError "Cannot make codeOps for unknown code"- InitCode bytes _ ->- LitByte <$> (BS.unpack bytes)- RuntimeCode (ConcreteRuntimeCode ops) ->- LitByte <$> (BS.unpack $ stripBytecodeMetadata ops)- RuntimeCode (SymbolicRuntimeCode ops) ->- stripBytecodeMetadataSym $ V.toList ops- in V.fromList . toList $ go 0 l- where- go !i !xs =- case uncons xs of- Nothing ->- mempty- Just (x, xs') ->- let x' = fromMaybe (internalError "unexpected symbolic code argument") $ maybeLitByte x- j = opSize x'- in (i, readOp x' xs') Seq.<| go (i + j) (drop j xs)---- * Gas cost calculation helpers---- Gas cost function for CALL, transliterated from the Yellow Paper.-costOfCall- :: FeeSchedule Word64- -> Bool -> Expr EWord -> Word64 -> Word64 -> Expr EAddr- -> EVM s (Word64, Word64)-costOfCall (FeeSchedule {..}) recipientExists (Lit xValue) availableGas xGas target = do- acc <- accessAccountForGas target- let call_base_gas = if acc then g_warm_storage_read else g_cold_account_access- c_new = if not recipientExists && xValue /= 0- then g_newaccount- else 0- c_xfer = if xValue /= 0 then g_callvalue else 0- c_extra = call_base_gas + c_xfer + c_new- c_gascap = if availableGas >= c_extra- then min xGas (allButOne64th (availableGas - c_extra))- else xGas- c_callgas = if xValue /= 0 then c_gascap + g_callstipend else c_gascap- pure (c_gascap + c_extra, c_callgas)--- calls are free if value is symbolic :)-costOfCall _ _ _ _ _ _ = pure (0,0)---- Gas cost of create, including hash cost if needed-costOfCreate- :: FeeSchedule Word64- -> Word64 -> W256 -> Bool -> (Word64, Word64)-costOfCreate (FeeSchedule {..}) availableGas size hashNeeded = (createCost, initGas)- where- byteCost = if hashNeeded then g_sha3word + g_initcodeword else g_initcodeword- createCost = g_create + codeCost- codeCost = byteCost * (ceilDiv (unsafeInto size) 32)- initGas = allButOne64th (availableGas - createCost)--concreteModexpGasFee :: ByteString -> Word64-concreteModexpGasFee input =- if lenb < into (maxBound :: Word32) &&- (lene < into (maxBound :: Word32) || (lenb == 0 && lenm == 0)) &&- lenm < into (maxBound :: Word64)- then- max 200 ((multiplicationComplexity * iterCount) `div` 3)- else- maxBound -- TODO: this is not 100% correct, return Nothing on overflow- where- (lenb, lene, lenm) = parseModexpLength input- ez = isZero (96 + lenb) lene input- e' = word $ LS.toStrict $- lazySlice (96 + lenb) (min 32 lene) input- nwords :: Word64- nwords = ceilDiv (unsafeInto $ max lenb lenm) 8- multiplicationComplexity = nwords * nwords- iterCount' :: Word64- iterCount' | lene <= 32 && ez = 0- | lene <= 32 = unsafeInto (log2 e')- | e' == 0 = 8 * (unsafeInto lene - 32)- | otherwise = unsafeInto (log2 e') + 8 * (unsafeInto lene - 32)- iterCount = max iterCount' 1---- Gas cost of precompiles-costOfPrecompile :: FeeSchedule Word64 -> Addr -> Expr Buf -> Word64-costOfPrecompile (FeeSchedule {..}) precompileAddr input =- let errorDynamicSize = internalError "precompile input cannot have a dynamic size"- inputLen = case input of- ConcreteBuf bs -> unsafeInto $ BS.length bs- AbstractBuf _ -> errorDynamicSize- buf -> case bufLength buf of- Lit l -> unsafeInto l -- TODO: overflow- _ -> errorDynamicSize- in case precompileAddr of- -- ECRECOVER- 0x1 -> 3000- -- SHA2-256- 0x2 -> (((inputLen + 31) `div` 32) * 12) + 60- -- RIPEMD-160- 0x3 -> (((inputLen + 31) `div` 32) * 120) + 600- -- IDENTITY- 0x4 -> (((inputLen + 31) `div` 32) * 3) + 15- -- MODEXP- 0x5 -> case input of- ConcreteBuf i -> concreteModexpGasFee i- _ -> internalError "Unsupported symbolic modexp gas calc "- -- ECADD- 0x6 -> g_ecadd- -- ECMUL- 0x7 -> g_ecmul- -- ECPAIRING- 0x8 -> (inputLen `div` 192) * g_pairing_point + g_pairing_base- -- BLAKE2- 0x9 -> case input of- ConcreteBuf i -> g_fround * (unsafeInto $ asInteger $ lazySlice 0 4 i)- _ -> internalError "Unsupported symbolic blake2 gas calc"- _ -> internalError $ "unimplemented precompiled contract " ++ show precompileAddr---- Gas cost of memory expansion-memoryCost :: FeeSchedule Word64 -> Word64 -> Word64-memoryCost FeeSchedule{..} byteCount =- let- wordCount = ceilDiv byteCount 32- linearCost = g_memory * wordCount- quadraticCost = div (wordCount * wordCount) 512- in- linearCost + quadraticCost--hashcode :: ContractCode -> Expr EWord-hashcode (UnknownCode a) = CodeHash a-hashcode (InitCode ops args) = keccak $ (ConcreteBuf ops) <> args-hashcode (RuntimeCode (ConcreteRuntimeCode ops)) = keccak (ConcreteBuf ops)-hashcode (RuntimeCode (SymbolicRuntimeCode ops)) = keccak . Expr.fromList $ ops---- | The length of the code ignoring any constructor args.--- This represents the region that can contain executable opcodes-opslen :: ContractCode -> Int-opslen (UnknownCode _) = internalError "Cannot produce concrete opslen for unknown code"-opslen (InitCode ops _) = BS.length ops-opslen (RuntimeCode (ConcreteRuntimeCode ops)) = BS.length ops-opslen (RuntimeCode (SymbolicRuntimeCode ops)) = length ops---- | The length of the code including any constructor args.--- This can return an abstract value-codelen :: ContractCode -> Expr EWord-codelen (UnknownCode a) = CodeSize a-codelen c@(InitCode {}) = case toBuf c of- Just b -> bufLength b- Nothing -> internalError "impossible"--- these are never going to be negative so unsafeInto is fine here-codelen (RuntimeCode (ConcreteRuntimeCode ops)) = Lit . unsafeInto $ BS.length ops-codelen (RuntimeCode (SymbolicRuntimeCode ops)) = Lit . unsafeInto $ length ops--toBuf :: ContractCode -> Maybe (Expr Buf)-toBuf (UnknownCode _) = Nothing-toBuf (InitCode ops args) = Just $ ConcreteBuf ops <> args-toBuf (RuntimeCode (ConcreteRuntimeCode ops)) = Just $ ConcreteBuf ops-toBuf (RuntimeCode (SymbolicRuntimeCode ops)) = Just $ Expr.fromList ops--codeloc :: EVM s CodeLocation-codeloc = do- vm <- get- pure (vm.state.contract, vm.state.pc)--createAddress :: Expr EAddr -> Maybe W64 -> EVM s (Expr EAddr)-createAddress (LitAddr a) (Just n) = pure $ Concrete.createAddress a n-createAddress (GVar _) _ = internalError "Unexpected GVar"-createAddress _ _ = freshSymAddr--create2Address :: Expr EAddr -> W256 -> ByteString -> EVM s (Expr EAddr)-create2Address (LitAddr a) s b = pure $ Concrete.create2Address a s b-create2Address (SymAddr _) _ _ = freshSymAddr-create2Address (GVar _) _ _ = internalError "Unexpected GVar"--freshSymAddr :: EVM s (Expr EAddr)-freshSymAddr = do- modifying (#env % #freshAddresses) (+ 1)- n <- use (#env % #freshAddresses)- pure $ SymAddr ("freshSymAddr" <> (pack $ show n))--isPrecompileAddr :: Expr EAddr -> Bool-isPrecompileAddr = \case- LitAddr a -> 0x0 < a && a <= 0x09- SymAddr _ -> False- GVar _ -> internalError "Unexpected GVar"---- * Arithmetic--ceilDiv :: (Num a, Integral a) => a -> a -> a-ceilDiv m n = div (m + n - 1) n--allButOne64th :: (Num a, Integral a) => a -> a-allButOne64th n = n - div n 64--log2 :: FiniteBits b => b -> Int-log2 x = finiteBitSize x - 1 - countLeadingZeros x--writeMemory :: MutableMemory s -> Int -> ByteString -> EVM s ()-writeMemory memory offset buf = do- memory' <- expandMemory (offset + BS.length buf)- mapM_ (uncurry (VUnboxed.Mutable.write memory'))- (zip [offset..] (BS.unpack buf))- where- expandMemory targetSize = do- let toAlloc = targetSize - VUnboxed.Mutable.length memory- if toAlloc > 0 then do- memory' <- VUnboxed.Mutable.grow memory toAlloc- assign (#state % #memory) (ConcreteMemory memory')- pure memory'- else- pure memory--freezeMemory :: MutableMemory s -> EVM s (Expr Buf)-freezeMemory memory =- ConcreteBuf . BS.pack . VUnboxed.toList <$> VUnboxed.freeze memory+import EVM.Types qualified as Expr (Expr(Gas))+import EVM.Sign qualified+import EVM.Concrete qualified as Concrete++import Control.Monad.ST (ST)+import Control.Monad.State.Strict hiding (state)+import Data.Bits (FiniteBits, countLeadingZeros, finiteBitSize)+import Data.ByteArray qualified as BA+import Data.ByteString (ByteString)+import Data.ByteString qualified as BS+import Data.ByteString.Lazy (fromStrict)+import Data.ByteString.Lazy qualified as LS+import Data.ByteString.Char8 qualified as Char8+import Data.Foldable (toList)+import Data.List (find)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Maybe (fromMaybe, fromJust, isJust)+import Data.Set (insert, member, fromList)+import Data.Sequence (Seq)+import Data.Sequence qualified as Seq+import Data.Text (unpack, pack)+import Data.Text.Encoding (decodeUtf8)+import Data.Tree+import Data.Tree.Zipper qualified as Zipper+import Data.Typeable+import Data.Vector qualified as V+import Data.Vector.Storable qualified as SV+import Data.Vector.Storable.Mutable qualified as SV+import Data.Vector.Unboxed qualified as VUnboxed+import Data.Vector.Unboxed.Mutable qualified as VUnboxed.Mutable+import Data.Word (Word8, Word32, Word64)+import Witch (into, tryFrom, unsafeInto)++import Crypto.Hash (Digest, SHA256, RIPEMD160)+import Crypto.Hash qualified as Crypto+import Crypto.Number.ModArithmetic (expFast)++blankState :: VMOps t => ST s (FrameState t s)+blankState = do+ memory <- ConcreteMemory <$> VUnboxed.Mutable.new 0+ pure $ FrameState+ { contract = LitAddr 0+ , codeContract = LitAddr 0+ , code = RuntimeCode (ConcreteRuntimeCode "")+ , pc = 0+ , stack = mempty+ , memory+ , memorySize = 0+ , calldata = mempty+ , callvalue = Lit 0+ , caller = LitAddr 0+ , gas = initialGas+ , returndata = mempty+ , static = False+ }++-- | An "external" view of a contract's bytecode, appropriate for+-- e.g. @EXTCODEHASH@.+bytecode :: Getter Contract (Maybe (Expr Buf))+bytecode = #code % to f+ where f (InitCode _ _) = Just mempty+ f (RuntimeCode (ConcreteRuntimeCode bs)) = Just $ ConcreteBuf bs+ f (RuntimeCode (SymbolicRuntimeCode ops)) = Just $ Expr.fromList ops+ f (UnknownCode _) = Nothing++-- * Data accessors++currentContract :: VM t s -> Maybe Contract+currentContract vm =+ Map.lookup vm.state.codeContract vm.env.contracts++-- * Data constructors++makeVm :: VMOps t => VMOpts t -> ST s (VM t s)+makeVm o = do+ let txaccessList = o.txAccessList+ txorigin = o.origin+ txtoAddr = o.address+ initialAccessedAddrs = fromList $+ [txorigin, txtoAddr, o.coinbase]+ ++ (fmap LitAddr [1..9])+ ++ (Map.keys txaccessList)+ initialAccessedStorageKeys = fromList $ foldMap (uncurry (map . (,))) (Map.toList txaccessList)+ touched = if o.create then [txorigin] else [txorigin, txtoAddr]+ memory <- ConcreteMemory <$> VUnboxed.Mutable.new 0+ pure $ VM+ { result = Nothing+ , frames = mempty+ , tx = TxState+ { gasprice = o.gasprice+ , gaslimit = o.gaslimit+ , priorityFee = o.priorityFee+ , origin = txorigin+ , toAddr = txtoAddr+ , value = o.value+ , substate = SubState mempty touched initialAccessedAddrs initialAccessedStorageKeys mempty+ , isCreate = o.create+ , txReversion = Map.fromList ((o.address,o.contract):o.otherContracts)+ }+ , logs = []+ , traces = Zipper.fromForest []+ , block = block+ , state = FrameState+ { pc = 0+ , stack = mempty+ , memory+ , memorySize = 0+ , code = o.contract.code+ , contract = o.address+ , codeContract = o.address+ , calldata = fst o.calldata+ , callvalue = o.value+ , caller = o.caller+ , gas = o.gas+ , returndata = mempty+ , static = False+ }+ , env = env+ , cache = cache+ , burned = initialGas+ , constraints = snd o.calldata+ , iterations = mempty+ , config = RuntimeConfig+ { allowFFI = o.allowFFI+ , overrideCaller = Nothing+ , baseState = o.baseState+ }+ , forks = Seq.singleton (ForkState env block cache "")+ , currentFork = 0+ }+ where+ env = Env+ { chainId = o.chainId+ , contracts = Map.fromList ((o.address,o.contract):o.otherContracts)+ , freshAddresses = 0+ , freshGasVals = 0+ }+ block = Block+ { coinbase = o.coinbase+ , timestamp = o.timestamp+ , number = o.number+ , prevRandao = o.prevRandao+ , maxCodeSize = o.maxCodeSize+ , gaslimit = o.blockGaslimit+ , baseFee = o.baseFee+ , schedule = o.schedule+ }+ cache = Cache mempty mempty++-- | Initialize an abstract contract with unknown code+unknownContract :: Expr EAddr -> Contract+unknownContract addr = Contract+ { code = UnknownCode addr+ , storage = AbstractStore addr Nothing+ , origStorage = AbstractStore addr Nothing+ , balance = Balance addr+ , nonce = Nothing+ , codehash = hashcode (UnknownCode addr)+ , opIxMap = mempty+ , codeOps = mempty+ , external = False+ }++-- | Initialize an abstract contract with known code+abstractContract :: ContractCode -> Expr EAddr -> Contract+abstractContract code addr = Contract+ { code = code+ , storage = AbstractStore addr Nothing+ , origStorage = AbstractStore addr Nothing+ , balance = Balance addr+ , nonce = if isCreation code then Just 1 else Just 0+ , codehash = hashcode code+ , opIxMap = mkOpIxMap code+ , codeOps = mkCodeOps code+ , external = False+ }++-- | Initialize an empty contract without code+emptyContract :: Contract+emptyContract = initialContract (RuntimeCode (ConcreteRuntimeCode ""))++-- | Initialize empty contract with given code+initialContract :: ContractCode -> Contract+initialContract code = Contract+ { code = code+ , storage = ConcreteStore mempty+ , origStorage = ConcreteStore mempty+ , balance = Lit 0+ , nonce = if isCreation code then Just 1 else Just 0+ , codehash = hashcode code+ , opIxMap = mkOpIxMap code+ , codeOps = mkCodeOps code+ , external = False+ }++isCreation :: ContractCode -> Bool+isCreation = \case+ InitCode _ _ -> True+ RuntimeCode _ -> False+ UnknownCode _ -> False++-- * Opcode dispatch (exec1)++-- | Update program counter+next :: (?op :: Word8) => EVM t s ()+next = modifying (#state % #pc) (+ (opSize ?op))++-- | Executes the EVM one step+exec1 :: forall (t :: VMType) s. VMOps t => EVM t s ()+exec1 = do+ vm <- get++ let+ -- Convenient aliases+ stk = vm.state.stack+ self = vm.state.contract+ this = fromMaybe (internalError "state contract") (Map.lookup self vm.env.contracts)++ fees@FeeSchedule {..} = vm.block.schedule++ doStop = finishFrame (FrameReturned mempty)++ litSelf = maybeLitAddr self++ if isJust litSelf && (fromJust litSelf) > 0x0 && (fromJust litSelf) <= 0x9 then do+ -- call to precompile+ let ?op = 0x00 -- dummy value+ let calldatasize = bufLength vm.state.calldata+ copyBytesToMemory vm.state.calldata calldatasize (Lit 0) (Lit 0)+ executePrecompile (fromJust litSelf) vm.state.gas (Lit 0) calldatasize (Lit 0) (Lit 0) []+ vmx <- get+ case vmx.state.stack of+ x:_ -> case x of+ Lit 0 ->+ fetchAccount self $ \_ -> do+ touchAccount self+ vmError PrecompileFailure+ Lit _ ->+ fetchAccount self $ \_ -> do+ touchAccount self+ out <- use (#state % #returndata)+ finishFrame (FrameReturned out)+ e -> partial $+ UnexpectedSymbolicArg vmx.state.pc "precompile returned a symbolic value" (wrap [e])+ _ ->+ underrun++ else if vm.state.pc >= opslen vm.state.code+ then doStop++ else do+ let ?op = case vm.state.code of+ UnknownCode _ -> internalError "Cannot execute unknown code"+ InitCode conc _ -> BS.index conc vm.state.pc+ RuntimeCode (ConcreteRuntimeCode bs) -> BS.index bs vm.state.pc+ RuntimeCode (SymbolicRuntimeCode ops) ->+ fromMaybe (internalError "could not analyze symbolic code") $+ maybeLitByte $ ops V.! vm.state.pc++ case getOp (?op) of++ OpPush0 -> do+ limitStack 1 $+ burn g_base $ do+ next+ pushSym (Lit 0)++ OpPush n' -> do+ let n = into n'+ !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 readWord (Lit 0) $ Expr.fromList $ padLeft' 32 bytes+ limitStack 1 $+ burn g_verylow $ do+ next+ pushSym xs++ OpDup i ->+ case preview (ix (into i - 1)) stk of+ Nothing -> underrun+ Just y ->+ limitStack 1 $+ burn g_verylow $ do+ next+ pushSym y++ OpSwap i ->+ if length stk < (into i) + 1+ then underrun+ else+ burn g_verylow $ do+ next+ zoom (#state % #stack) $ do+ assign (ix 0) (stk ^?! ix (into i))+ assign (ix (into i)) (stk ^?! ix 0)++ OpLog n ->+ notStatic $+ case stk of+ (xOffset:xSize:xs) ->+ if length xs < (into n)+ then underrun+ else do+ bytes <- readMemory xOffset xSize+ let (topics, xs') = splitAt (into n) xs+ logs' = (LogEntry (WAddr self) bytes topics) : vm.logs+ burnLog xSize n $+ accessMemoryRange xOffset xSize $ do+ traceTopLog logs'+ next+ assign (#state % #stack) xs'+ assign #logs logs'+ _ ->+ underrun++ OpStop -> doStop++ OpAdd -> stackOp2 g_verylow Expr.add+ OpMul -> stackOp2 g_low Expr.mul+ OpSub -> stackOp2 g_verylow Expr.sub++ OpDiv -> stackOp2 g_low Expr.div++ OpSdiv -> stackOp2 g_low Expr.sdiv++ OpMod -> stackOp2 g_low Expr.mod++ OpSmod -> stackOp2 g_low Expr.smod+ OpAddmod -> stackOp3 g_mid Expr.addmod+ OpMulmod -> stackOp3 g_mid Expr.mulmod++ OpLt -> stackOp2 g_verylow Expr.lt+ OpGt -> stackOp2 g_verylow Expr.gt+ OpSlt -> stackOp2 g_verylow Expr.slt+ OpSgt -> stackOp2 g_verylow Expr.sgt++ OpEq -> stackOp2 g_verylow Expr.eq+ OpIszero -> stackOp1 g_verylow Expr.iszero++ OpAnd -> stackOp2 g_verylow Expr.and+ OpOr -> stackOp2 g_verylow Expr.or+ OpXor -> stackOp2 g_verylow Expr.xor+ OpNot -> stackOp1 g_verylow Expr.not++ OpByte -> stackOp2 g_verylow (\i w -> Expr.padByte $ Expr.indexWord i w)++ OpShl -> stackOp2 g_verylow Expr.shl+ OpShr -> stackOp2 g_verylow Expr.shr+ OpSar -> stackOp2 g_verylow Expr.sar++ -- more accurately referred to as KECCAK+ OpSha3 ->+ case stk of+ xOffset:xSize:xs ->+ burnSha3 xSize $+ accessMemoryRange xOffset xSize $ do+ hash <- readMemory xOffset xSize >>= \case+ orig@(ConcreteBuf bs) ->+ whenSymbolicElse+ (pure $ Keccak orig)+ (pure $ Lit (keccak' bs))+ buf -> pure $ Keccak buf+ next+ assign (#state % #stack) (hash : xs)+ _ -> underrun++ OpAddress ->+ limitStack 1 $+ burn g_base (next >> pushAddr self)++ OpBalance ->+ case stk of+ x:xs -> forceAddr x "BALANCE" $ \a ->+ accessAndBurn a $+ fetchAccount a $ \c -> do+ next+ assign (#state % #stack) xs+ pushSym c.balance+ [] ->+ underrun++ OpOrigin ->+ limitStack 1 . burn g_base $+ next >> pushAddr vm.tx.origin++ OpCaller ->+ limitStack 1 . burn g_base $+ next >> pushAddr vm.state.caller++ OpCallvalue ->+ limitStack 1 . burn g_base $+ next >> pushSym vm.state.callvalue++ OpCalldataload -> stackOp1 g_verylow $+ \ind -> Expr.readWord ind vm.state.calldata++ OpCalldatasize ->+ limitStack 1 . burn g_base $+ next >> pushSym (bufLength vm.state.calldata)++ OpCalldatacopy ->+ case stk of+ xTo:xFrom:xSize:xs ->+ burnCalldatacopy xSize $+ accessMemoryRange xTo xSize $ do+ next+ assign (#state % #stack) xs+ copyBytesToMemory vm.state.calldata xSize xFrom xTo+ _ -> underrun++ OpCodesize ->+ limitStack 1 . burn g_base $+ next >> pushSym (codelen vm.state.code)++ OpCodecopy ->+ case stk of+ memOffset:codeOffset:n:xs ->+ burnCodecopy n $ do+ accessMemoryRange memOffset n $ do+ next+ assign (#state % #stack) xs+ case toBuf vm.state.code of+ Just b -> copyBytesToMemory b n codeOffset memOffset+ Nothing -> internalError "Cannot produce a buffer from UnknownCode"+ _ -> underrun++ OpGasprice ->+ limitStack 1 . burn g_base $+ next >> push vm.tx.gasprice++ OpExtcodesize ->+ case stk of+ x':xs -> forceAddr x' "EXTCODESIZE" $ \x -> do+ let impl = accessAndBurn x $+ fetchAccount x $ \c -> do+ next+ assign (#state % #stack) xs+ case view bytecode c of+ Just b -> pushSym (bufLength b)+ Nothing -> pushSym $ CodeSize x+ case x of+ a@(LitAddr _) -> if a == cheatCode+ then do+ next+ assign (#state % #stack) xs+ pushSym (Lit 1)+ else impl+ _ -> impl+ [] ->+ underrun++ OpExtcodecopy ->+ case stk of+ extAccount':memOffset:codeOffset:codeSize:xs ->+ forceAddr extAccount' "EXTCODECOPY" $ \extAccount -> do+ burnExtcodecopy extAccount codeSize $+ accessMemoryRange memOffset codeSize $+ fetchAccount extAccount $ \c -> do+ next+ assign (#state % #stack) xs+ case view bytecode c of+ Just b -> copyBytesToMemory b codeSize codeOffset memOffset+ Nothing -> do+ pc <- use (#state % #pc)+ partial $ UnexpectedSymbolicArg pc "Cannot copy from unknown code at" (wrap [extAccount])+ _ -> underrun++ OpReturndatasize ->+ limitStack 1 . burn g_base $+ next >> pushSym (bufLength vm.state.returndata)++ OpReturndatacopy ->+ case stk of+ xTo:xFrom:xSize:xs ->+ burnReturndatacopy xSize $+ accessMemoryRange xTo xSize $ do+ next+ assign (#state % #stack) xs++ let jump True = vmError ReturnDataOutOfBounds+ jump False = copyBytesToMemory vm.state.returndata xSize xFrom xTo++ case (xFrom, bufLength vm.state.returndata, xSize) of+ (Lit f, Lit l, Lit sz) ->+ jump $ l < f + sz || f + sz < f+ _ -> do+ let oob = Expr.lt (bufLength vm.state.returndata) (Expr.add xFrom xSize)+ overflow = Expr.lt (Expr.add xFrom xSize) (xFrom)+ branch (Expr.or oob overflow) jump+ _ -> underrun++ OpExtcodehash ->+ case stk of+ x':xs -> forceAddr x' "EXTCODEHASH" $ \x ->+ accessAndBurn x $ do+ next+ assign (#state % #stack) xs+ fetchAccount x $ \c ->+ if accountEmpty c+ then push (W256 0)+ else case view bytecode c of+ Just b -> pushSym $ keccak b+ Nothing -> pushSym $ CodeHash x+ [] ->+ underrun++ OpBlockhash -> do+ -- We adopt the fake block hash scheme of the VMTests,+ -- so that blockhash(i) is the hash of i as decimal ASCII.+ stackOp1 g_blockhash $ \case+ Lit i -> if i + 256 < vm.block.number || i >= vm.block.number+ then Lit 0+ else (into i :: Integer) & show & Char8.pack & keccak' & Lit+ i -> BlockHash i++ OpCoinbase ->+ limitStack 1 . burn g_base $+ next >> pushAddr vm.block.coinbase++ OpTimestamp ->+ limitStack 1 . burn g_base $+ next >> pushSym vm.block.timestamp++ OpNumber ->+ limitStack 1 . burn g_base $+ next >> push vm.block.number++ OpPrevRandao -> do+ limitStack 1 . burn g_base $+ next >> push vm.block.prevRandao++ OpGaslimit ->+ limitStack 1 . burn g_base $+ next >> push (into vm.block.gaslimit)++ OpChainid ->+ limitStack 1 . burn g_base $+ next >> push vm.env.chainId++ OpSelfbalance ->+ limitStack 1 . burn g_low $+ next >> pushSym this.balance++ OpBaseFee ->+ limitStack 1 . burn g_base $+ next >> push vm.block.baseFee++ OpPop ->+ case stk of+ _:xs -> burn g_base (next >> assign (#state % #stack) xs)+ _ -> underrun++ OpMload ->+ 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++ OpMstore ->+ 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 $ writeWord x y buf)+ SymbolicMemory mem ->+ assign (#state % #memory) (SymbolicMemory $ writeWord x y mem)+ assign (#state % #stack) xs+ _ -> underrun++ OpMstore8 ->+ case stk of+ x:y:xs ->+ burn g_verylow $+ accessMemoryRange x (Lit 1) $ do+ let yByte = indexWord (Lit 31) y+ next+ gets (.state.memory) >>= \case+ ConcreteMemory mem -> do+ case yByte of+ LitByte byte ->+ copyBytesToMemory (ConcreteBuf (BS.pack [byte])) (Lit 1) (Lit 0) x+ _ -> do+ -- copy out and move to symbolic memory+ buf <- freezeMemory mem+ assign (#state % #memory) (SymbolicMemory $ writeByte x yByte buf)+ SymbolicMemory mem ->+ assign (#state % #memory) (SymbolicMemory $ writeByte x yByte mem)++ assign (#state % #stack) xs+ _ -> underrun++ OpSload ->+ case stk of+ x:xs -> do+ acc <- accessStorageForGas self x+ let cost = if acc then g_warm_storage_read else g_cold_sload+ burn cost $+ accessStorage self x $ \y -> do+ next+ assign (#state % #stack) (y:xs)+ _ -> underrun++ OpSstore ->+ notStatic $+ case stk of+ x:new:xs ->+ accessStorage self x $ \current -> do+ ensureGas g_callstipend $ do+ let+ original =+ case Expr.concKeccakSimpExpr $ SLoad x this.origStorage of+ Lit v -> v+ _ -> 0+ storage_cost =+ case (maybeLitWord current, maybeLitWord new) of+ (Just current', Just new') ->+ if (current' == new') then g_sload+ else if (current' == original) && (original == 0) then g_sset+ else if (current' == original) then g_sreset+ else g_sload++ -- if any of the arguments are symbolic,+ -- assume worst case scenario+ _-> g_sset++ acc <- accessStorageForGas self x+ let cold_storage_cost = if acc then 0 else g_cold_sload+ burn (storage_cost + cold_storage_cost) $ do+ next+ assign (#state % #stack) xs+ modifying (#env % #contracts % ix self % #storage) (writeStorage x new)++ case (maybeLitWord current, maybeLitWord new) of+ (Just current', Just new') ->+ unless (current' == new') $+ if current' == original then+ when (original /= 0 && new' == 0) $+ refund (g_sreset + g_access_list_storage_key)+ else do+ when (original /= 0) $+ if current' == 0+ then unRefund (g_sreset + g_access_list_storage_key)+ else when (new' == 0) $ refund (g_sreset + g_access_list_storage_key)+ when (original == new') $+ if original == 0+ then refund (g_sset - g_sload)+ else refund (g_sreset - g_sload)+ -- if any of the arguments are symbolic,+ -- don't change the refund counter+ _ -> noop+ _ -> underrun++ OpJump ->+ case stk of+ x:xs ->+ burn g_mid $ forceConcrete x "JUMP: symbolic jumpdest" $ \x' ->+ case toInt x' of+ Nothing -> vmError BadJumpDestination+ Just i -> checkJump i xs+ _ -> underrun++ OpJumpi ->+ case stk of+ x:y:xs -> forceConcrete x "JUMPI: symbolic jumpdest" $ \x' ->+ burn g_high $+ let jump :: Bool -> EVM t s ()+ jump False = assign (#state % #stack) xs >> next+ jump _ = case toInt x' of+ Nothing -> vmError BadJumpDestination+ Just i -> checkJump i xs+ in branch y jump+ _ -> underrun++ OpPc ->+ limitStack 1 . burn g_base $+ next >> push (unsafeInto vm.state.pc)++ OpMsize ->+ limitStack 1 . burn g_base $+ next >> push (into vm.state.memorySize)++ OpGas ->+ limitStack 1 . burn g_base $+ next >> pushGas++ OpJumpdest -> burn g_jumpdest next++ OpExp ->+ -- NOTE: this can be done symbolically using unrolling like this:+ -- https://hackage.haskell.org/package/sbv-9.0/docs/src/Data.SBV.Core.Model.html#.%5E+ -- However, it requires symbolic gas, since the gas depends on the exponent+ case stk of+ base:exponent:xs ->+ burnExp exponent $ do+ next+ (#state % #stack) .= Expr.exp base exponent : xs+ _ -> underrun++ OpSignextend -> stackOp2 g_low Expr.sex++ OpCreate ->+ notStatic $+ case stk of+ xValue:xOffset:xSize:xs ->+ accessMemoryRange xOffset xSize $ do+ availableGas <- use (#state % #gas)+ let+ (cost, gas') = costOfCreate fees availableGas xSize False+ newAddr <- createAddress self this.nonce+ _ <- accessAccountForGas newAddr+ burn' cost $ do+ initCode <- readMemory xOffset xSize+ create self this xSize gas' xValue xs newAddr initCode+ _ -> underrun++ OpCall ->+ case stk of+ xGas:xTo':xValue:xInOffset:xInSize:xOutOffset:xOutSize:xs ->+ branch (Expr.gt xValue (Lit 0)) $ \gt0 -> do+ (if gt0 then notStatic else id) $+ forceAddr xTo' "unable to determine a call target" $ \xTo ->+ case gasTryFrom xGas of+ Left _ -> vmError IllegalOverflow+ Right gas ->+ delegateCall this gas xTo xTo xValue xInOffset xInSize xOutOffset xOutSize xs $+ \callee -> do+ let from' = fromMaybe self vm.config.overrideCaller+ zoom #state $ do+ assign #callvalue xValue+ assign #caller from'+ assign #contract callee+ assign (#config % #overrideCaller) Nothing+ touchAccount from'+ touchAccount callee+ transfer from' callee xValue+ _ ->+ underrun++ OpCallcode ->+ case stk of+ xGas:xTo':xValue:xInOffset:xInSize:xOutOffset:xOutSize:xs ->+ forceAddr xTo' "unable to determine a call target" $ \xTo ->+ case gasTryFrom xGas of+ Left _ -> vmError IllegalOverflow+ Right gas ->+ delegateCall this gas xTo self xValue xInOffset xInSize xOutOffset xOutSize xs $ \_ -> do+ zoom #state $ do+ assign #callvalue xValue+ assign #caller $ fromMaybe self vm.config.overrideCaller+ assign (#config % #overrideCaller) Nothing+ touchAccount self+ _ ->+ underrun++ OpReturn ->+ case stk of+ xOffset:xSize:_ ->+ accessMemoryRange xOffset xSize $ do+ output <- readMemory xOffset xSize+ let+ codesize = fromMaybe (internalError "processing opcode RETURN. Cannot return dynamically sized abstract data")+ . maybeLitWord . bufLength $ output+ maxsize = vm.block.maxCodeSize+ creation = case vm.frames of+ [] -> vm.tx.isCreate+ frame:_ -> case frame.context of+ CreationContext {} -> True+ CallContext {} -> False+ if creation+ then+ if codesize > maxsize+ then+ finishFrame (FrameErrored (MaxCodeSizeExceeded maxsize codesize))+ else do+ let frameReturned = burn (g_codedeposit * unsafeInto codesize) $+ finishFrame (FrameReturned output)+ frameErrored = finishFrame $ FrameErrored InvalidFormat+ case readByte (Lit 0) output of+ LitByte 0xef -> frameErrored+ LitByte _ -> frameReturned+ y -> branch (Expr.eqByte y (LitByte 0xef)) $ \case+ True -> frameErrored+ False -> frameReturned+ else+ finishFrame (FrameReturned output)+ _ -> underrun++ OpDelegatecall ->+ case stk of+ xGas:xTo:xInOffset:xInSize:xOutOffset:xOutSize:xs ->+ case wordToAddr xTo of+ Nothing -> do+ loc <- codeloc+ let msg = "Unable to determine a call target"+ partial $ UnexpectedSymbolicArg (snd loc) msg [SomeExpr xTo]+ Just xTo' ->+ case gasTryFrom xGas of+ Left _ -> vmError IllegalOverflow+ Right gas ->+ delegateCall this gas xTo' self (Lit 0) xInOffset xInSize xOutOffset xOutSize xs $+ \_ -> touchAccount self+ _ -> underrun++ OpCreate2 -> notStatic $+ case stk of+ xValue:xOffset:xSize:xSalt':xs ->+ forceConcrete xSalt' "CREATE2" $ \(xSalt) ->+ accessMemoryRange xOffset xSize $ do+ availableGas <- use (#state % #gas)+ buf <- readMemory xOffset xSize+ forceConcreteBuf buf "CREATE2" $+ \initCode -> do+ let+ (cost, gas') = costOfCreate fees availableGas xSize True+ newAddr <- create2Address self xSalt initCode+ _ <- accessAccountForGas newAddr+ burn' cost $+ create self this xSize gas' xValue xs newAddr (ConcreteBuf initCode)+ _ -> underrun++ OpStaticcall ->+ case stk of+ xGas:xTo:xInOffset:xInSize:xOutOffset:xOutSize:xs ->+ case wordToAddr xTo of+ Nothing -> do+ loc <- codeloc+ let msg = "Unable to determine a call target"+ partial $ UnexpectedSymbolicArg (snd loc) msg [SomeExpr xTo]+ Just xTo' ->+ case gasTryFrom xGas of+ Left _ -> vmError IllegalOverflow+ Right gas ->+ delegateCall this gas xTo' xTo' (Lit 0) xInOffset xInSize xOutOffset xOutSize xs $+ \callee -> do+ zoom #state $ do+ assign #callvalue (Lit 0)+ assign #caller $ fromMaybe self (vm.config.overrideCaller)+ assign #contract callee+ assign #static True+ assign (#config % #overrideCaller) Nothing+ touchAccount self+ touchAccount callee+ _ ->+ underrun++ OpSelfdestruct ->+ notStatic $+ case stk of+ [] -> underrun+ (xTo':_) -> forceAddr xTo' "SELFDESTRUCT" $ \case+ xTo@(LitAddr _) -> do+ acc <- accessAccountForGas xTo+ let cost = if acc then 0 else g_cold_account_access+ funds = this.balance+ recipientExists = accountExists xTo vm+ branch (Expr.iszero $ Expr.eq funds (Lit 0)) $ \hasFunds -> do+ let c_new = if (not recipientExists) && hasFunds+ then g_selfdestruct_newaccount+ else 0+ burn (g_selfdestruct + c_new + cost) $ do+ selfdestruct self+ touchAccount xTo++ if hasFunds+ then fetchAccount xTo $ \_ -> do+ #env % #contracts % ix xTo % #balance %= (Expr.add funds)+ assign (#env % #contracts % ix self % #balance) (Lit 0)+ doStop+ else do+ doStop+ a -> do+ pc <- use (#state % #pc)+ partial $ UnexpectedSymbolicArg pc "trying to self destruct to a symbolic address" (wrap [a])++ OpRevert ->+ case stk of+ xOffset:xSize:_ ->+ accessMemoryRange xOffset xSize $ do+ output <- readMemory xOffset xSize+ finishFrame (FrameReverted output)+ _ -> underrun++ OpUnknown xxx ->+ vmError $ UnrecognizedOpcode xxx++transfer :: VMOps t => Expr EAddr -> Expr EAddr -> Expr EWord -> EVM t s ()+transfer _ _ (Lit 0) = pure ()+transfer src dst val = do+ sb <- preuse $ #env % #contracts % ix src % #balance+ db <- preuse $ #env % #contracts % ix dst % #balance+ baseState <- use (#config % #baseState)+ let mkc = case baseState of+ AbstractBase -> unknownContract+ EmptyBase -> const emptyContract+ case (sb, db) of+ -- both sender and recipient in state+ (Just srcBal, Just _) ->+ branch (Expr.gt val srcBal) $ \case+ True -> vmError $ BalanceTooLow val srcBal+ False -> do+ (#env % #contracts % ix src % #balance) %= (`Expr.sub` val)+ (#env % #contracts % ix dst % #balance) %= (`Expr.add` val)+ -- sender not in state+ (Nothing, Just _) -> do+ case src of+ LitAddr _ -> do+ (#env % #contracts) %= (Map.insert src (mkc src))+ transfer src dst val+ SymAddr _ -> do+ pc <- use (#state % #pc)+ partial $ UnexpectedSymbolicArg pc "Attempting to transfer eth from a symbolic address that is not present in the state" (wrap [src])+ GVar _ -> internalError "Unexpected GVar"+ -- recipient not in state+ (_ , Nothing) -> do+ case dst of+ LitAddr _ -> do+ (#env % #contracts) %= (Map.insert dst (mkc dst))+ transfer src dst val+ SymAddr _ -> do+ pc <- use (#state % #pc)+ partial $ UnexpectedSymbolicArg pc "Attempting to transfer eth to a symbolic address that is not present in the state" (wrap [dst])+ GVar _ -> internalError "Unexpected GVar"++-- | Checks a *CALL for failure; OOG, too many callframes, memory access etc.+callChecks+ :: forall (t :: VMType) s. (?op :: Word8, VMOps t)+ => Contract+ -> Gas t+ -> Expr EAddr+ -> Expr EAddr+ -> Expr EWord+ -> Expr EWord+ -> Expr EWord+ -> Expr EWord+ -> Expr EWord+ -> [Expr EWord]+ -- continuation with gas available for call+ -> (Gas t -> EVM t s ())+ -> EVM t s ()+callChecks this xGas xContext xTo xValue xInOffset xInSize xOutOffset xOutSize xs continue = do+ vm <- get+ let fees = vm.block.schedule+ accessMemoryRange xInOffset xInSize $+ accessMemoryRange xOutOffset xOutSize $ do+ availableGas <- use (#state % #gas)+ let recipientExists = accountExists xContext vm+ let from = fromMaybe vm.state.contract vm.config.overrideCaller+ fromBal <- preuse $ #env % #contracts % ix from % #balance+ costOfCall fees recipientExists xValue availableGas xGas xTo $ \cost gas' -> do+ let checkCallDepth =+ if length vm.frames >= 1024+ then do+ assign (#state % #stack) (Lit 0 : xs)+ assign (#state % #returndata) mempty+ pushTrace $ ErrorTrace CallDepthLimitReached+ next+ else continue (toGas gas')+ case (fromBal, xValue) of+ -- we're not transferring any value, and can skip the balance check+ (_, Lit 0) -> burn (cost - gas') checkCallDepth++ -- from is in the state, we check if they have enough balance+ (Just fb, _) -> do+ burn (cost - gas') $+ branch (Expr.gt xValue fb) $ \case+ True -> do+ assign (#state % #stack) (Lit 0 : xs)+ assign (#state % #returndata) mempty+ pushTrace $ ErrorTrace (BalanceTooLow xValue this.balance)+ next+ False -> checkCallDepth++ -- from is not in the state, we insert it if safe to do so and run the checks again+ (Nothing, _) -> case from of+ LitAddr _ -> do+ -- insert an entry in the state+ let contract = case vm.config.baseState of+ AbstractBase -> unknownContract from+ EmptyBase -> emptyContract+ (#env % #contracts) %= (Map.insert from contract)+ -- run callChecks again+ callChecks this xGas xContext xTo xValue xInOffset xInSize xOutOffset xOutSize xs continue++ -- adding a symbolic address into the state here would be unsound (due to potential aliasing)+ SymAddr _ -> do+ pc <- use (#state % #pc)+ partial $ UnexpectedSymbolicArg pc "Attempting to transfer eth from a symbolic address that is not present in the state" (wrap [from])+ GVar _ -> internalError "Unexpected GVar"+++++precompiledContract+ :: (?op :: Word8, VMOps t)+ => Contract+ -> Gas t+ -> Addr+ -> Addr+ -> Expr EWord+ -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord+ -> [Expr EWord]+ -> EVM t s ()+precompiledContract this xGas precompileAddr recipient xValue inOffset inSize outOffset outSize xs+ = callChecks this xGas (LitAddr recipient) (LitAddr precompileAddr) xValue inOffset inSize outOffset outSize xs $ \gas' ->+ do+ executePrecompile precompileAddr gas' inOffset inSize outOffset outSize xs+ self <- use (#state % #contract)+ stk <- use (#state % #stack)+ pc' <- use (#state % #pc)+ result' <- use #result+ case result' of+ Nothing -> case stk of+ x:_ -> case maybeLitWord x of+ Just 0 ->+ pure ()+ Just 1 ->+ fetchAccount (LitAddr recipient) $ \_ -> do+ touchAccount self+ touchAccount (LitAddr recipient)+ transfer self (LitAddr recipient) xValue+ _ -> partial $+ UnexpectedSymbolicArg pc' "unexpected return value from precompile" (wrap [x])+ _ -> underrun+ _ -> pure ()++executePrecompile+ :: (?op :: Word8, VMOps t)+ => Addr+ -> Gas t -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> [Expr EWord]+ -> EVM t s ()+executePrecompile preCompileAddr gasCap inOffset inSize outOffset outSize xs = do+ vm <- get+ input <- readMemory inOffset inSize+ let fees = vm.block.schedule+ cost = costOfPrecompile fees preCompileAddr input+ notImplemented = internalError $ "precompile at address " <> show preCompileAddr <> " not yet implemented"+ precompileFail = burn' (subGas gasCap cost) $ do+ assign (#state % #stack) (Lit 0 : xs)+ pushTrace $ ErrorTrace PrecompileFailure+ next+ if not (enoughGas cost gasCap) then+ burn' gasCap $ do+ assign (#state % #stack) (Lit 0 : xs)+ next+ else burn cost $+ case preCompileAddr of+ -- ECRECOVER+ 0x1 ->+ -- TODO: support symbolic variant+ forceConcreteBuf input "ECRECOVER" $ \input' -> do+ case EVM.Precompiled.execute 0x1 (truncpadlit 128 input') 32 of+ Nothing -> do+ -- return no output for invalid signature+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) mempty+ next+ Just output -> do+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) (ConcreteBuf output)+ copyBytesToMemory (ConcreteBuf output) outSize (Lit 0) outOffset+ next++ -- SHA2-256+ 0x2 ->+ forceConcreteBuf input "SHA2-256" $ \input' -> do+ let+ hash = sha256Buf input'+ sha256Buf x = ConcreteBuf $ BA.convert (Crypto.hash x :: Digest SHA256)+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) hash+ copyBytesToMemory hash outSize (Lit 0) outOffset+ next++ -- RIPEMD-160+ 0x3 ->+ -- TODO: support symbolic variant+ forceConcreteBuf input "RIPEMD160" $ \input' -> do+ let+ padding = BS.pack $ replicate 12 0+ hash' = BA.convert (Crypto.hash input' :: Digest RIPEMD160)+ hash = ConcreteBuf $ padding <> hash'+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) hash+ copyBytesToMemory hash outSize (Lit 0) outOffset+ next++ -- IDENTITY+ 0x4 -> do+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) input+ copyCallBytesToMemory input outSize outOffset+ next++ -- MODEXP+ 0x5 ->+ -- TODO: support symbolic variant+ forceConcreteBuf input "MODEXP" $ \input' -> do+ let+ (lenb, lene, lenm) = parseModexpLength input'++ output = ConcreteBuf $+ if isZero (96 + lenb + lene) lenm input'+ then truncpadlit (unsafeInto lenm) (asBE (0 :: Int))+ else+ let+ b = asInteger $ lazySlice 96 lenb input'+ e = asInteger $ lazySlice (96 + lenb) lene input'+ m = asInteger $ lazySlice (96 + lenb + lene) lenm input'+ in+ padLeft (unsafeInto lenm) (asBE (expFast b e m))+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) output+ copyBytesToMemory output outSize (Lit 0) outOffset+ next++ -- ECADD+ 0x6 ->+ -- TODO: support symbolic variant+ forceConcreteBuf input "ECADD" $ \input' ->+ case EVM.Precompiled.execute 0x6 (truncpadlit 128 input') 64 of+ Nothing -> precompileFail+ Just output -> do+ let truncpaddedOutput = ConcreteBuf $ truncpadlit 64 output+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) truncpaddedOutput+ copyBytesToMemory truncpaddedOutput outSize (Lit 0) outOffset+ next++ -- ECMUL+ 0x7 ->+ -- TODO: support symbolic variant+ forceConcreteBuf input "ECMUL" $ \input' ->+ case EVM.Precompiled.execute 0x7 (truncpadlit 96 input') 64 of+ Nothing -> precompileFail+ Just output -> do+ let truncpaddedOutput = ConcreteBuf $ truncpadlit 64 output+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) truncpaddedOutput+ copyBytesToMemory truncpaddedOutput outSize (Lit 0) outOffset+ next++ -- ECPAIRING+ 0x8 ->+ -- TODO: support symbolic variant+ forceConcreteBuf input "ECPAIR" $ \input' ->+ case EVM.Precompiled.execute 0x8 input' 32 of+ Nothing -> precompileFail+ Just output -> do+ let truncpaddedOutput = ConcreteBuf $ truncpadlit 32 output+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) truncpaddedOutput+ copyBytesToMemory truncpaddedOutput outSize (Lit 0) outOffset+ next++ -- BLAKE2+ 0x9 ->+ -- TODO: support symbolic variant+ forceConcreteBuf input "BLAKE2" $ \input' -> do+ case (BS.length input', 1 >= BS.last input') of+ (213, True) -> case EVM.Precompiled.execute 0x9 input' 64 of+ Just output -> do+ let truncpaddedOutput = ConcreteBuf $ truncpadlit 64 output+ assign (#state % #stack) (Lit 1 : xs)+ assign (#state % #returndata) truncpaddedOutput+ copyBytesToMemory truncpaddedOutput outSize (Lit 0) outOffset+ next+ Nothing -> precompileFail+ _ -> precompileFail++ _ -> notImplemented++truncpadlit :: Int -> ByteString -> ByteString+truncpadlit n xs = if m > n then BS.take n xs+ else BS.append xs (BS.replicate (n - m) 0)+ where m = BS.length xs++lazySlice :: W256 -> W256 -> ByteString -> LS.ByteString+lazySlice offset size bs =+ let bs' = LS.take (unsafeInto size) (LS.drop (unsafeInto offset) (fromStrict bs))+ in bs' <> LS.replicate (unsafeInto size - LS.length bs') 0++parseModexpLength :: ByteString -> (W256, W256, W256)+parseModexpLength input =+ let lenb = word $ LS.toStrict $ lazySlice 0 32 input+ lene = word $ LS.toStrict $ lazySlice 32 64 input+ lenm = word $ LS.toStrict $ lazySlice 64 96 input+ in (lenb, lene, lenm)++--- checks if a range of ByteString bs starting at offset and length size is all zeros.+isZero :: W256 -> W256 -> ByteString -> Bool+isZero offset size bs =+ LS.all (== 0) $+ LS.take (unsafeInto size) $+ LS.drop (unsafeInto offset) $+ fromStrict bs++asInteger :: LS.ByteString -> Integer+asInteger xs = if xs == mempty then 0+ else 256 * asInteger (LS.init xs)+ + into (LS.last xs)++-- * Opcode helper actions++noop :: Monad m => m ()+noop = pure ()++pushTo :: MonadState s m => Lens s s [a] [a] -> a -> m ()+pushTo f x = f %= (x :)++pushToSequence :: MonadState s m => Setter s s (Seq a) (Seq a) -> a -> m ()+pushToSequence f x = f %= (Seq.|> x)++getCodeLocation :: VM t s -> CodeLocation+getCodeLocation vm = (vm.state.contract, vm.state.pc)++query :: Query t s -> EVM t s ()+query = assign #result . Just . HandleEffect . Query++choose :: Choose s -> EVM Symbolic s ()+choose = assign #result . Just . HandleEffect . Choose++-- | Construct RPC Query and halt execution until resolved+fetchAccount :: VMOps t => Expr EAddr -> (Contract -> EVM t s ()) -> EVM t s ()+fetchAccount addr continue =+ use (#env % #contracts % at addr) >>= \case+ Just c -> continue c+ Nothing -> case addr of+ SymAddr _ -> do+ pc <- use (#state % #pc)+ partial $ UnexpectedSymbolicArg pc "trying to access a symbolic address that isn't already present in storage" (wrap [addr])+ LitAddr a -> do+ use (#cache % #fetched % at a) >>= \case+ Just c -> do+ assign (#env % #contracts % at addr) (Just c)+ continue c+ Nothing -> do+ base <- use (#config % #baseState)+ assign (#result) . Just . HandleEffect . Query $+ PleaseFetchContract a base+ (\c -> do assign (#cache % #fetched % at a) (Just c)+ assign (#env % #contracts % at addr) (Just c)+ assign #result Nothing+ continue c)+ GVar _ -> internalError "Unexpected GVar"++accessStorage+ :: VMOps t => Expr EAddr+ -> Expr EWord+ -> (Expr EWord -> EVM t s ())+ -> EVM t s ()+accessStorage addr slot continue = do+ let slotConc = Expr.concKeccakSimpExpr slot+ use (#env % #contracts % at addr) >>= \case+ Just c ->+ -- Try first without concretization. Then if we get a Just, with concretization+ -- if both give a Just, should we `continue`.+ -- --> This is because readStorage can do smart rewrites, but only in case+ -- the expression is of a particular format, which can be destroyed by simplification.+ -- However, without concretization, it may not find things that are actually in the storage+ case readStorage slot c.storage of+ Just x -> case readStorage slotConc c.storage of+ Just _ -> continue x+ Nothing -> rpcCall c slotConc+ Nothing -> rpcCall c slotConc+ Nothing ->+ fetchAccount addr $ \_ ->+ accessStorage addr slot continue+ where+ rpcCall c slotConc = if c.external+ then forceConcreteAddr addr "cannot read storage from symbolic addresses via rpc" $ \addr' ->+ forceConcrete slotConc "cannot read symbolic slots via RPC" $ \slot' -> do+ -- check if the slot is cached+ contract <- preuse (#cache % #fetched % ix addr')+ case contract of+ Nothing -> internalError "contract marked external not found in cache"+ Just fetched -> case readStorage (Lit slot') fetched.storage of+ Nothing -> mkQuery addr' slot'+ Just val -> continue val+ else do+ modifying (#env % #contracts % ix addr % #storage) (writeStorage slot (Lit 0))+ continue $ Lit 0+ mkQuery a s = query $+ PleaseFetchSlot a s+ (\x -> do+ modifying (#cache % #fetched % ix a % #storage) (writeStorage (Lit s) (Lit x))+ modifying (#env % #contracts % ix (LitAddr a) % #storage) (writeStorage (Lit s) (Lit x))+ assign #result Nothing+ continue (Lit x))++accountExists :: Expr EAddr -> VM t s -> Bool+accountExists addr vm =+ case Map.lookup addr vm.env.contracts of+ Just c -> not (accountEmpty c)+ Nothing -> False++-- EIP 161+accountEmpty :: Contract -> Bool+accountEmpty c =+ case c.code of+ RuntimeCode (ConcreteRuntimeCode "") -> True+ RuntimeCode (SymbolicRuntimeCode b) -> null b+ _ -> False+ && c.nonce == (Just 0)+ -- TODO: handle symbolic balance...+ && c.balance == Lit 0++-- * How to finalize a transaction+finalize :: VMOps t => EVM t s ()+finalize = do+ let+ revertContracts = use (#tx % #txReversion) >>= assign (#env % #contracts)+ revertSubstate = assign (#tx % #substate) (SubState mempty mempty mempty mempty mempty)++ use #result >>= \case+ Just (VMFailure (Revert _)) -> do+ revertContracts+ revertSubstate+ Just (VMFailure _) -> do+ -- burn remaining gas+ assign (#state % #gas) initialGas+ revertContracts+ revertSubstate+ Just (VMSuccess output) -> do+ -- deposit the code from a creation tx+ pc' <- use (#state % #pc)+ creation <- use (#tx % #isCreate)+ createe <- use (#state % #contract)+ createeExists <- (Map.member createe) <$> use (#env % #contracts)+ let onContractCode contractCode =+ when (creation && createeExists) $ replaceCode createe contractCode+ case output of+ ConcreteBuf bs ->+ onContractCode $ RuntimeCode (ConcreteRuntimeCode bs)+ _ ->+ case Expr.toList output of+ Nothing ->+ partial $+ UnexpectedSymbolicArg pc' "runtime code cannot have an abstract length" (wrap [output])+ Just ops ->+ onContractCode $ RuntimeCode (SymbolicRuntimeCode ops)+ _ ->+ internalError "Finalising an unfinished tx."++ -- compute and pay the refund to the caller and the+ -- corresponding payment to the miner+ block <- use #block++ payRefunds++ touchAccount block.coinbase++ -- perform state trie clearing (EIP 161), of selfdestructs+ -- and touched accounts. addresses are cleared if they have+ -- a) selfdestructed, or+ -- b) been touched and+ -- c) are empty.+ -- (see Yellow Paper "Accrued Substate")+ --+ -- remove any destructed addresses+ destroyedAddresses <- use (#tx % #substate % #selfdestructs)+ modifying (#env % #contracts)+ (Map.filterWithKey (\k _ -> (k `notElem` destroyedAddresses)))+ -- then, clear any remaining empty and touched addresses+ touchedAddresses <- use (#tx % #substate % #touchedAccounts)+ modifying (#env % #contracts)+ (Map.filterWithKey+ (\k a -> not ((k `elem` touchedAddresses) && accountEmpty a)))++-- | Loads the selected contract as the current contract to execute+loadContract :: Expr EAddr -> State (VM t s) ()+loadContract target =+ preuse (#env % #contracts % ix target % #code) >>=+ \case+ Nothing ->+ internalError "Call target doesn't exist"+ Just targetCode -> do+ assign (#state % #contract) target+ assign (#state % #code) targetCode+ assign (#state % #codeContract) target++limitStack :: VMOps t => Int -> EVM (t :: VMType) s () -> EVM t s ()+limitStack n continue = do+ stk <- use (#state % #stack)+ if length stk + n > 1024+ then vmError StackLimitExceeded+ else continue++notStatic :: VMOps t => EVM t s () -> EVM t s ()+notStatic continue = do+ bad <- use (#state % #static)+ if bad+ then vmError StateChangeWhileStatic+ else continue++forceAddr :: VMOps t => Expr EWord -> String -> (Expr EAddr -> EVM t s ()) -> EVM t s ()+forceAddr n msg continue = case wordToAddr n of+ Nothing -> do+ vm <- get+ partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n])+ Just c -> continue c++forceConcrete :: VMOps t => Expr EWord -> String -> (W256 -> EVM t s ()) -> EVM t s ()+forceConcrete n msg continue = case maybeLitWord n of+ Nothing -> do+ vm <- get+ partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n])+ Just c -> continue c++forceConcreteAddr :: VMOps t => Expr EAddr -> String -> (Addr -> EVM t s ()) -> EVM t s ()+forceConcreteAddr n msg continue = case maybeLitAddr n of+ Nothing -> do+ vm <- get+ partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n])+ Just c -> continue c++forceConcreteAddr2 :: VMOps t => (Expr EAddr, Expr EAddr) -> String -> ((Addr, Addr) -> EVM t s ()) -> EVM t s ()+forceConcreteAddr2 (n,m) msg continue = case (maybeLitAddr n, maybeLitAddr m) of+ (Just c, Just d) -> continue (c,d)+ _ -> do+ vm <- get+ partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n, m])++forceConcrete2 :: VMOps t => (Expr EWord, Expr EWord) -> String -> ((W256, W256) -> EVM t s ()) -> EVM t s ()+forceConcrete2 (n,m) msg continue = case (maybeLitWord n, maybeLitWord m) of+ (Just c, Just d) -> continue (c, d)+ _ -> do+ vm <- get+ partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n, m])++forceConcreteBuf :: VMOps t => Expr Buf -> String -> (ByteString -> EVM t s ()) -> EVM t s ()+forceConcreteBuf (ConcreteBuf b) _ continue = continue b+forceConcreteBuf b msg _ = do+ vm <- get+ partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [b])++-- * Substate manipulation+refund :: Word64 -> EVM t s ()+refund n = do+ self <- use (#state % #contract)+ pushTo (#tx % #substate % #refunds) (self, n)++unRefund :: Word64 -> EVM t s ()+unRefund n = do+ self <- use (#state % #contract)+ refs <- use (#tx % #substate % #refunds)+ assign (#tx % #substate % #refunds)+ (filter (\(a,b) -> not (a == self && b == n)) refs)++touchAccount :: Expr EAddr -> EVM t s ()+touchAccount = pushTo ((#tx % #substate) % #touchedAccounts)++selfdestruct :: Expr EAddr -> EVM t s ()+selfdestruct = pushTo ((#tx % #substate) % #selfdestructs)++accessAndBurn :: VMOps t => Expr EAddr -> EVM t s () -> EVM t s ()+accessAndBurn x cont = do+ FeeSchedule {..} <- use (#block % #schedule)+ acc <- accessAccountForGas x+ let cost = if acc then g_warm_storage_read else g_cold_account_access+ burn cost cont++-- | returns a wrapped boolean- if true, this address has been touched before in the txn (warm gas cost as in EIP 2929)+-- otherwise cold+accessAccountForGas :: Expr EAddr -> EVM t s Bool+accessAccountForGas addr = do+ accessedAddrs <- use (#tx % #substate % #accessedAddresses)+ let accessed = member addr accessedAddrs+ assign (#tx % #substate % #accessedAddresses) (insert addr accessedAddrs)+ pure accessed++-- | returns a wrapped boolean- if true, this slot has been touched before in the txn (warm gas cost as in EIP 2929)+-- otherwise cold+accessStorageForGas :: Expr EAddr -> Expr EWord -> EVM t s Bool+accessStorageForGas addr key = do+ accessedStrkeys <- use (#tx % #substate % #accessedStorageKeys)+ case maybeLitWord key of+ Just litword -> do+ let accessed = member (addr, litword) accessedStrkeys+ assign (#tx % #substate % #accessedStorageKeys) (insert (addr, litword) accessedStrkeys)+ pure accessed+ _ -> pure False++-- * Cheat codes++-- The cheat code is 7109709ecfa91a80626ff3989d68f67f5b1dd12d.+-- Call this address using one of the cheatActions below to do+-- special things, e.g. changing the block timestamp. Beware that+-- these are necessarily hevm specific.+cheatCode :: Expr EAddr+cheatCode = LitAddr $ unsafeInto (keccak' "hevm cheat code")++cheat+ :: (?op :: Word8, VMOps t)+ => (Expr EWord, Expr EWord) -> (Expr EWord, Expr EWord)+ -> EVM t s ()+cheat (inOffset, inSize) (outOffset, outSize) = do+ vm <- get+ input <- readMemory (Expr.add inOffset (Lit 4)) (Expr.sub inSize (Lit 4))+ abi <- readBytes 4 (Lit 0) <$> readMemory inOffset (Lit 4)+ pushTrace $ FrameTrace (CallContext cheatCode cheatCode inOffset inSize (Lit 0) (maybeLitWord abi) input vm.env.contracts vm.tx.substate)+ case maybeLitWord abi of+ Nothing -> partial $ UnexpectedSymbolicArg vm.state.pc "symbolic cheatcode selector" (wrap [abi])+ Just (unsafeInto -> abi') ->+ case Map.lookup abi' cheatActions of+ Nothing ->+ vmError (BadCheatCode abi')+ Just action -> do+ action outOffset outSize input+ popTrace+ next+ push 1++type CheatAction t s = Expr EWord -> Expr EWord -> Expr Buf -> EVM t s ()++cheatActions :: VMOps t => Map FunctionSelector (CheatAction t s)+cheatActions =+ Map.fromList+ [ action "ffi(string[])" $+ \sig outOffset outSize input -> do+ vm <- get+ if vm.config.allowFFI then+ case decodeBuf [AbiArrayDynamicType AbiStringType] input of+ CAbi valsArr -> case valsArr of+ [AbiArrayDynamic AbiStringType strsV] ->+ let+ cmd = fmap+ (\case+ (AbiString a) -> unpack $ decodeUtf8 a+ _ -> "")+ (V.toList strsV)+ cont bs = do+ let encoded = ConcreteBuf bs+ assign (#state % #returndata) encoded+ copyBytesToMemory encoded outSize (Lit 0) outOffset+ assign #result Nothing+ in query (PleaseDoFFI cmd cont)+ _ -> vmError (BadCheatCode sig)+ _ -> vmError (BadCheatCode sig)+ else+ let msg = "ffi disabled: run again with --ffi if you want to allow tests to call external scripts"+ in partial $ UnexpectedSymbolicArg vm.state.pc msg [],++ action "warp(uint256)" $+ \sig _ _ input -> case decodeStaticArgs 0 1 input of+ [x] -> assign (#block % #timestamp) x+ _ -> vmError (BadCheatCode sig),++ action "deal(address,uint256)" $+ \sig _ _ input -> case decodeStaticArgs 0 2 input of+ [a, amt] ->+ forceAddr a "vm.deal: cannot decode target into an address" $ \usr ->+ fetchAccount usr $ \_ -> do+ assign (#env % #contracts % ix usr % #balance) amt+ _ -> vmError (BadCheatCode sig),++ action "assume(bool)" $+ \sig _ _ input -> case decodeStaticArgs 0 1 input of+ [c] -> modifying #constraints ((:) (PEq c (Lit 1)))+ _ -> vmError (BadCheatCode sig),++ action "roll(uint256)" $+ \sig _ _ input -> case decodeStaticArgs 0 1 input of+ [x] -> forceConcrete x "cannot roll to a symbolic block number" (assign (#block % #number))+ _ -> vmError (BadCheatCode sig),++ action "store(address,bytes32,bytes32)" $+ \sig _ _ input -> case decodeStaticArgs 0 3 input of+ [a, slot, new] -> case wordToAddr a of+ Just a'@(LitAddr _) -> fetchAccount a' $ \_ ->+ modifying (#env % #contracts % ix a' % #storage) (writeStorage slot new)+ _ -> vmError (BadCheatCode sig)+ _ -> vmError (BadCheatCode sig),++ action "load(address,bytes32)" $+ \sig outOffset _ input -> case decodeStaticArgs 0 2 input of+ [a, slot] -> case wordToAddr a of+ Just a'@(LitAddr _) -> fetchAccount a' $ \_ ->+ accessStorage a' slot $ \res -> do+ assign (#state % #returndata % word256At (Lit 0)) res+ let buf = writeWord (Lit 0) res (ConcreteBuf "")+ copyBytesToMemory buf (Lit 32) (Lit 0) outOffset+ _ -> vmError (BadCheatCode sig)+ _ -> vmError (BadCheatCode sig),++ action "sign(uint256,bytes32)" $+ \sig outOffset _ input -> case decodeStaticArgs 0 2 input of+ [sk, hash] ->+ forceConcrete2 (sk, hash) "cannot sign symbolic data" $ \(sk', hash') -> do+ let (v,r,s) = EVM.Sign.sign hash' (into sk')+ encoded = encodeAbiValue $+ AbiTuple (V.fromList+ [ AbiUInt 8 $ into v+ , AbiBytes 32 (word256Bytes r)+ , AbiBytes 32 (word256Bytes s)+ ])+ assign (#state % #returndata) (ConcreteBuf encoded)+ copyBytesToMemory (ConcreteBuf encoded) (Lit . unsafeInto . BS.length $ encoded) (Lit 0) outOffset+ _ -> vmError (BadCheatCode sig),++ action "addr(uint256)" $+ \sig outOffset _ input -> case decodeStaticArgs 0 1 input of+ [sk] -> forceConcrete sk "cannot derive address for a symbolic key" $ \sk' -> do+ let a = EVM.Sign.deriveAddr $ into sk'+ case a of+ Nothing -> vmError (BadCheatCode sig)+ Just address -> do+ let expAddr = litAddr address+ assign (#state % #returndata % word256At (Lit 0)) expAddr+ let buf = ConcreteBuf $ word256Bytes (into address)+ copyBytesToMemory buf (Lit 32) (Lit 0) outOffset+ _ -> vmError (BadCheatCode sig),++ action "prank(address)" $+ \sig _ _ input -> case decodeStaticArgs 0 1 input of+ [addr] -> case wordToAddr addr of+ Just a -> assign (#config % #overrideCaller) (Just a)+ Nothing -> vmError (BadCheatCode sig)+ _ -> vmError (BadCheatCode sig),++ action "createFork(string)" $+ \sig outOffset _ input -> case decodeBuf [AbiStringType] input of+ CAbi valsArr -> case valsArr of+ [AbiString bytes] -> do+ forkId <- length <$> gets (.forks)+ let urlOrAlias = Char8.unpack bytes+ modify' $ \vm -> vm { forks = vm.forks Seq.|> ForkState vm.env vm.block vm.cache urlOrAlias }+ let encoded = encodeAbiValue $ AbiUInt 256 (fromIntegral forkId)+ assign (#state % #returndata) (ConcreteBuf encoded)+ copyBytesToMemory (ConcreteBuf encoded) (Lit . unsafeInto . BS.length $ encoded) (Lit 0) outOffset+ _ -> vmError (BadCheatCode sig)+ _ -> vmError (BadCheatCode sig),++ action "selectFork(uint256)" $+ \sig _ _ input -> case decodeStaticArgs 0 1 input of+ [forkId] ->+ forceConcrete forkId "forkId must be concrete" $ \(fromIntegral -> forkId') -> do+ saved <- Seq.lookup forkId' <$> gets (.forks)+ case saved of+ Just forkState -> do+ vm <- get+ let contractAddr = vm.state.contract+ let callerAddr = vm.state.caller+ fetchAccount contractAddr $ \contractAcct -> fetchAccount callerAddr $ \callerAcct -> do+ let+ -- the current contract is persisted across forks+ newContracts = Map.insert callerAddr callerAcct $+ Map.insert contractAddr contractAcct forkState.env.contracts+ newEnv = (forkState.env :: Env) { contracts = newContracts }++ when (vm.currentFork /= forkId') $ do+ modify' $ \vm' -> vm'+ { env = newEnv+ , block = forkState.block+ , forks = Seq.adjust' (\state -> (state :: ForkState)+ { env = vm.env, block = vm.block, cache = vm.cache }+ ) vm.currentFork vm.forks+ , currentFork = forkId'+ }+ Nothing ->+ vmError (NonexistentFork forkId')+ _ -> vmError (BadCheatCode sig),++ action "activeFork()" $+ \_ outOffset _ _ -> do+ vm <- get+ let encoded = encodeAbiValue $ AbiUInt 256 (fromIntegral vm.currentFork)+ assign (#state % #returndata) (ConcreteBuf encoded)+ copyBytesToMemory (ConcreteBuf encoded) (Lit . unsafeInto . BS.length $ encoded) (Lit 0) outOffset+ ]+ where+ action s f = (abiKeccak s, f (abiKeccak s))++-- * General call implementation ("delegateCall")+-- note that the continuation is ignored in the precompile case+delegateCall+ :: (VMOps t, ?op :: Word8)+ => Contract+ -> Gas t+ -> Expr EAddr+ -> Expr EAddr+ -> Expr EWord+ -> Expr EWord+ -> Expr EWord+ -> Expr EWord+ -> Expr EWord+ -> [Expr EWord]+ -> (Expr EAddr -> EVM t s ())+ -> EVM t s ()+delegateCall this gasGiven xTo xContext xValue xInOffset xInSize xOutOffset xOutSize xs continue+ | isPrecompileAddr xTo+ = forceConcreteAddr2 (xTo, xContext) "Cannot call precompile with symbolic addresses" $+ \(xTo', xContext') ->+ precompiledContract this gasGiven xTo' xContext' xValue xInOffset xInSize xOutOffset xOutSize xs+ | xTo == cheatCode = do+ assign (#state % #stack) xs+ cheat (xInOffset, xInSize) (xOutOffset, xOutSize)+ | otherwise =+ callChecks this gasGiven xContext xTo xValue xInOffset xInSize xOutOffset xOutSize xs $+ \xGas -> do+ vm0 <- get+ fetchAccount xTo $ \target -> case target.code of+ UnknownCode _ -> do+ pc <- use (#state % #pc)+ partial $ UnexpectedSymbolicArg pc "call target has unknown code" (wrap [xTo])+ _ -> do+ burn' xGas $ do+ calldata <- readMemory xInOffset xInSize+ abi <- maybeLitWord . readBytes 4 (Lit 0) <$> readMemory xInOffset (Lit 4)+ let newContext = CallContext+ { target = xTo+ , context = xContext+ , offset = xOutOffset+ , size = xOutSize+ , codehash = target.codehash+ , callreversion = vm0.env.contracts+ , subState = vm0.tx.substate+ , abi+ , calldata+ }+ pushTrace (FrameTrace newContext)+ next+ vm1 <- get++ pushTo #frames $ Frame+ { state = vm1.state { stack = xs }+ , context = newContext+ }++ let clearInitCode = \case+ (InitCode _ _) -> InitCode mempty mempty+ a -> a++ newMemory <- ConcreteMemory <$> VUnboxed.Mutable.new 0+ zoom #state $ do+ assign #gas xGas+ assign #pc 0+ assign #code (clearInitCode target.code)+ assign #codeContract xTo+ assign #stack mempty+ assign #memory newMemory+ assign #memorySize 0+ assign #returndata mempty+ assign #calldata calldata+ continue xTo++-- -- * Contract creation++-- EIP 684+collision :: Maybe Contract -> Bool+collision c' = case c' of+ Just c -> c.nonce /= Just 0 || case c.code of+ RuntimeCode (ConcreteRuntimeCode "") -> False+ RuntimeCode (SymbolicRuntimeCode b) -> not $ null b+ _ -> True+ Nothing -> False++create :: forall t s. (?op :: Word8, VMOps t)+ => Expr EAddr -> Contract+ -> Expr EWord -> Gas t -> Expr EWord -> [Expr EWord] -> Expr EAddr -> Expr Buf -> EVM t s ()+create self this xSize xGas xValue xs newAddr initCode = do+ vm0 <- get+ if xSize > Lit (vm0.block.maxCodeSize * 2)+ then do+ assign (#state % #stack) (Lit 0 : xs)+ assign (#state % #returndata) mempty+ vmError $ MaxInitCodeSizeExceeded (vm0.block.maxCodeSize * 2) xSize+ else if this.nonce == Just maxBound+ then do+ assign (#state % #stack) (Lit 0 : xs)+ assign (#state % #returndata) mempty+ pushTrace $ ErrorTrace NonceOverflow+ next+ else if length vm0.frames >= 1024+ then do+ assign (#state % #stack) (Lit 0 : xs)+ assign (#state % #returndata) mempty+ pushTrace $ ErrorTrace CallDepthLimitReached+ next+ else if collision $ Map.lookup newAddr vm0.env.contracts+ then burn' xGas $ do+ assign (#state % #stack) (Lit 0 : xs)+ assign (#state % #returndata) mempty+ modifying (#env % #contracts % ix self % #nonce) (fmap ((+) 1))+ next+ -- do we have enough balance+ else branch (Expr.gt xValue this.balance) $ \case+ True -> do+ assign (#state % #stack) (Lit 0 : xs)+ assign (#state % #returndata) mempty+ pushTrace $ ErrorTrace $ BalanceTooLow xValue this.balance+ next+ touchAccount self+ touchAccount newAddr+ -- are we overflowing the nonce+ False -> burn' xGas $ do+ case parseInitCode initCode of+ Nothing ->+ partial $ UnexpectedSymbolicArg vm0.state.pc "initcode must have a concrete prefix" []+ Just c -> do+ let+ newContract = initialContract c+ newContext =+ CreationContext { address = newAddr+ , codehash = newContract.codehash+ , createreversion = vm0.env.contracts+ , substate = vm0.tx.substate+ }++ zoom (#env % #contracts) $ do+ oldAcc <- use (at newAddr)+ let oldBal = maybe (Lit 0) (.balance) oldAcc++ assign (at newAddr) (Just (newContract & #balance .~ oldBal))+ modifying (ix self % #nonce) (fmap ((+) 1))++ let+ resetStorage :: Expr Storage -> Expr Storage+ resetStorage = \case+ ConcreteStore _ -> ConcreteStore mempty+ AbstractStore a Nothing -> AbstractStore a Nothing+ SStore _ _ p -> resetStorage p+ AbstractStore _ (Just _) -> internalError "unexpected logical store in EVM.hs"+ GVar _ -> internalError "unexpected global variable"++ modifying (#env % #contracts % ix newAddr % #storage) resetStorage+ modifying (#env % #contracts % ix newAddr % #origStorage) resetStorage++ transfer self newAddr xValue++ pushTrace (FrameTrace newContext)+ next+ vm1 <- get+ pushTo #frames $ Frame+ { context = newContext+ , state = vm1.state { stack = xs }+ }++ state :: FrameState t s <- lift blankState+ assign #state $ state+ { contract = newAddr+ , codeContract = newAddr+ , code = c+ , callvalue = xValue+ , caller = self+ , gas = xGas+ }++-- | Parses a raw Buf into an InitCode+--+-- solidity implements constructor args by appending them to the end of+-- the initcode. we support this internally by treating initCode as a+-- concrete region (initCode) followed by a potentially symbolic region+-- (arguments).+--+-- when constructing a contract that has symbolic constructor args, we+-- need to apply some heuristics to convert the (unstructured) initcode+-- in memory into this structured representation. The (unsound, bad,+-- hacky) way that we do this, is by: looking for the first potentially+-- symbolic byte in the input buffer and then splitting it there into code / data.+parseInitCode :: Expr Buf -> Maybe ContractCode+parseInitCode (ConcreteBuf b) = Just (InitCode b mempty)+parseInitCode buf = if V.null conc+ then Nothing+ else Just $ InitCode (BS.pack $ V.toList conc) sym+ where+ conc = Expr.concretePrefix buf+ -- unsafeInto: findIndex will always be positive+ sym = Expr.drop (unsafeInto (V.length conc)) buf++-- | Replace a contract's code, like when CREATE returns+-- from the constructor code.+replaceCode :: Expr EAddr -> ContractCode -> EVM t s ()+replaceCode target newCode =+ zoom (#env % #contracts % at target) $+ get >>= \case+ Just now -> case now.code of+ InitCode _ _ ->+ put . Just $+ ((initialContract newCode) :: Contract)+ { balance = now.balance+ , nonce = now.nonce+ , storage = now.storage+ }+ RuntimeCode _ ->+ internalError $ "Can't replace code of deployed contract " <> show target+ UnknownCode _ ->+ internalError "Can't replace unknown code"+ Nothing ->+ internalError "Can't replace code of nonexistent contract"++replaceCodeOfSelf :: ContractCode -> EVM t s ()+replaceCodeOfSelf newCode = do+ vm <- get+ replaceCode vm.state.contract newCode++resetState :: VMOps t => EVM t s ()+resetState = do+ state <- lift blankState+ modify' $ \vm -> vm { result = Nothing, frames = [], state }++-- * VM error implementation++vmError :: VMOps t => EvmError -> EVM t s ()+vmError e = finishFrame (FrameErrored e)++wrap :: Typeable a => [Expr a] -> [SomeExpr]+wrap = fmap SomeExpr++underrun :: VMOps t => EVM t s ()+underrun = vmError StackUnderrun++-- | A stack frame can be popped in three ways.+data FrameResult+ = FrameReturned (Expr Buf) -- ^ STOP, RETURN, or no more code+ | FrameReverted (Expr Buf) -- ^ REVERT+ | FrameErrored EvmError -- ^ Any other error+ deriving Show++-- | This function defines how to pop the current stack frame in either of+-- the ways specified by 'FrameResult'.+--+-- It also handles the case when the current stack frame is the only one;+-- in this case, we set the final '_result' of the VM execution.+finishFrame :: VMOps t => FrameResult -> EVM t s ()+finishFrame how = do+ oldVm <- get++ case oldVm.frames of+ -- Is the current frame the only one?+ [] -> do+ case how of+ FrameReturned output -> assign #result . Just $ VMSuccess output+ FrameReverted buffer -> assign #result . Just $ VMFailure (Revert buffer)+ FrameErrored e -> assign #result . Just $ VMFailure e+ finalize++ -- Are there some remaining frames?+ nextFrame : remainingFrames -> do++ -- Insert a debug trace.+ insertTrace $+ case how of+ FrameErrored e ->+ ErrorTrace e+ FrameReverted e ->+ ErrorTrace (Revert e)+ FrameReturned output ->+ ReturnTrace output nextFrame.context+ -- Pop to the previous level of the debug trace stack.+ popTrace++ -- Pop the top frame.+ assign #frames remainingFrames+ -- Install the state of the frame to which we shall return.+ assign #state nextFrame.state++ -- Now dispatch on whether we were creating or calling,+ -- and whether we shall return, revert, or internalError(six cases).+ case nextFrame.context of++ -- Were we calling?+ CallContext _ _ outOffset outSize _ _ _ reversion substate' -> do++ -- Excerpt K.1. from the yellow paper:+ -- K.1. Deletion of an Account Despite Out-of-gas.+ -- At block 2675119, in the transaction 0xcf416c536ec1a19ed1fb89e4ec7ffb3cf73aa413b3aa9b77d60e4fd81a4296ba,+ -- an account at address 0x03 was called and an out-of-gas occurred during the call.+ -- Against the equation (197), this added 0x03 in the set of touched addresses, and this transaction turned σ[0x03] into ∅.++ -- In other words, we special case address 0x03 and keep it in the set of touched accounts during revert+ touched <- use (#tx % #substate % #touchedAccounts)++ let+ substate'' = over #touchedAccounts (maybe id cons (find (LitAddr 3 ==) touched)) substate'+ revertContracts = assign (#env % #contracts) reversion+ revertSubstate = assign (#tx % #substate) substate''++ case how of+ -- Case 1: Returning from a call?+ FrameReturned output -> do+ assign (#state % #returndata) output+ copyCallBytesToMemory output outSize outOffset+ reclaimRemainingGasAllowance oldVm+ push 1++ -- Case 2: Reverting during a call?+ FrameReverted output -> do+ revertContracts+ revertSubstate+ assign (#state % #returndata) output+ copyCallBytesToMemory output outSize outOffset+ reclaimRemainingGasAllowance oldVm+ push 0++ -- Case 3: Error during a call?+ FrameErrored _ -> do+ revertContracts+ revertSubstate+ assign (#state % #returndata) mempty+ push 0+ -- Or were we creating?+ CreationContext _ _ reversion substate' -> do+ creator <- use (#state % #contract)+ let+ createe = oldVm.state.contract+ revertContracts = assign (#env % #contracts) reversion'+ revertSubstate = assign (#tx % #substate) substate'++ -- persist the nonce through the reversion+ reversion' = (Map.adjust (over #nonce (fmap ((+) 1))) creator) reversion++ case how of+ -- Case 4: Returning during a creation?+ FrameReturned output -> do+ let onContractCode contractCode = do+ replaceCode createe contractCode+ assign (#state % #returndata) mempty+ reclaimRemainingGasAllowance oldVm+ pushAddr createe+ case output of+ ConcreteBuf bs ->+ onContractCode $ RuntimeCode (ConcreteRuntimeCode bs)+ _ ->+ case Expr.toList output of+ Nothing -> partial $+ UnexpectedSymbolicArg+ oldVm.state.pc+ "runtime code cannot have an abstract length"+ (wrap [output])+ Just newCode -> do+ onContractCode $ RuntimeCode (SymbolicRuntimeCode newCode)++ -- Case 5: Reverting during a creation?+ FrameReverted output -> do+ revertContracts+ revertSubstate+ assign (#state % #returndata) output+ reclaimRemainingGasAllowance oldVm+ push 0++ -- Case 6: Error during a creation?+ FrameErrored _ -> do+ revertContracts+ revertSubstate+ assign (#state % #returndata) mempty+ push 0+++-- * Memory helpers++accessUnboundedMemoryRange+ :: VMOps t => Word64+ -> Word64+ -> EVM t s ()+ -> EVM t s ()+accessUnboundedMemoryRange _ 0 continue = continue+accessUnboundedMemoryRange f l continue = do+ m0 <- use (#state % #memorySize)+ fees <- gets (.block.schedule)+ let m1 = 32 * ceilDiv (max m0 (f + l)) 32+ burn (memoryCost fees m1 - memoryCost fees m0) $ do+ assign (#state % #memorySize) m1+ continue++accessMemoryRange+ :: VMOps t+ => Expr EWord+ -> Expr EWord+ -> EVM t s ()+ -> EVM t s ()+accessMemoryRange _ (Lit 0) continue = continue+accessMemoryRange (Lit offs) (Lit sz) continue =+ case (,) <$> toWord64 offs <*> toWord64 sz of+ Nothing -> vmError IllegalOverflow+ Just (offs64, sz64) ->+ if offs64 + sz64 < sz64+ then vmError IllegalOverflow+ -- we need to limit these to <256MB because otherwise we could run out of memory+ -- in e.g. OpCalldatacopy and subsequent memory allocation when running with abstract gas.+ -- In these cases, the system would try to allocate a large (but <2**64 bytes) memory+ -- that leads to out-of-heap. Real-world scenarios cannot allocate 256MB of memory due to gas+ else if offs64 >= 0x0fffffff || sz64 >= 0x0fffffff+ then vmError IllegalOverflow+ else accessUnboundedMemoryRange offs64 sz64 continue+-- we just ignore gas if we get symbolic inputs+accessMemoryRange _ _ continue = continue++accessMemoryWord+ :: VMOps t => Expr EWord -> EVM t s () -> EVM t s ()+accessMemoryWord x = accessMemoryRange x (Lit 32)++copyBytesToMemory+ :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM t s ()+copyBytesToMemory bs size srcOffset memOffset =+ if size == Lit 0 then noop+ else do+ gets (.state.memory) >>= \case+ ConcreteMemory mem ->+ case (bs, size, srcOffset, memOffset) of+ (ConcreteBuf b, Lit size', Lit srcOffset', Lit memOffset') -> do+ let src =+ if srcOffset' >= unsafeInto (BS.length b) then+ BS.replicate (unsafeInto size') 0+ else+ BS.take (unsafeInto size') $+ padRight (unsafeInto size') $+ BS.drop (unsafeInto srcOffset') b++ writeMemory mem (unsafeInto memOffset') src+ _ -> do+ -- copy out and move to symbolic memory+ buf <- freezeMemory mem+ assign (#state % #memory) $+ SymbolicMemory $ copySlice srcOffset memOffset size bs buf+ SymbolicMemory mem ->+ assign (#state % #memory) $+ SymbolicMemory $ copySlice srcOffset memOffset size bs mem++copyCallBytesToMemory+ :: Expr Buf -> Expr EWord -> Expr EWord -> EVM t s ()+copyCallBytesToMemory bs size yOffset =+ copyBytesToMemory bs (Expr.min size (bufLength bs)) (Lit 0) yOffset++readMemory :: Expr EWord -> Expr EWord -> EVM t s (Expr Buf)+readMemory offset' size' = do+ vm <- get+ case vm.state.memory of+ ConcreteMemory mem -> do+ case (offset', size') of+ (Lit offset, Lit size) -> do+ let memSize :: Word64 = unsafeInto (VUnboxed.Mutable.length mem)+ if size > Expr.maxBytes ||+ offset + size > Expr.maxBytes ||+ offset >= into memSize then+ -- reads past memory are all zeros+ pure $ ConcreteBuf $ BS.replicate (unsafeInto size) 0+ else do+ let pastEnd = (unsafeInto offset + unsafeInto size) - unsafeInto memSize+ let fromMemSize = if pastEnd > 0 then unsafeInto size - pastEnd else unsafeInto size++ buf <- VUnboxed.freeze $ VUnboxed.Mutable.slice (unsafeInto offset) fromMemSize mem+ let dataFromMem = BS.pack $ VUnboxed.toList buf+ pure $ ConcreteBuf $ dataFromMem <> BS.replicate pastEnd 0+ _ -> do+ buf <- freezeMemory mem+ pure $ copySlice offset' (Lit 0) size' buf mempty+ SymbolicMemory mem ->+ pure $ copySlice offset' (Lit 0) size' mem mempty++-- * Tracing++withTraceLocation :: TraceData -> EVM t s Trace+withTraceLocation x = do+ vm <- get+ let this = fromJust $ currentContract vm+ pure Trace+ { tracedata = x+ , contract = this+ , opIx = fromMaybe 0 $ this.opIxMap SV.!? vm.state.pc+ }++pushTrace :: TraceData -> EVM t s ()+pushTrace x = do+ trace <- withTraceLocation x+ modifying #traces $+ \t -> Zipper.children $ Zipper.insert (Node trace []) t++insertTrace :: TraceData -> EVM t s ()+insertTrace x = do+ trace <- withTraceLocation x+ modifying #traces $+ \t -> Zipper.nextSpace $ Zipper.insert (Node trace []) t++popTrace :: EVM t s ()+popTrace =+ modifying #traces $+ \t -> case Zipper.parent t of+ Nothing -> internalError "internal internalError(trace root)"+ Just t' -> Zipper.nextSpace t'++zipperRootForest :: Zipper.TreePos Zipper.Empty a -> Forest a+zipperRootForest z =+ case Zipper.parent z of+ Nothing -> Zipper.toForest z+ Just z' -> zipperRootForest (Zipper.nextSpace z')++traceForest :: VM t s -> Forest Trace+traceForest vm = zipperRootForest vm.traces++traceForest' :: Expr End -> Forest Trace+traceForest' (Success _ (Traces f _) _ _) = f+traceForest' (Partial _ (Traces f _) _) = f+traceForest' (Failure _ (Traces f _) _) = f+traceForest' (ITE {}) = internalError"Internal Error: ITE does not contain a trace"+traceForest' (GVar {}) = internalError"Internal Error: Unexpected GVar"++traceContext :: Expr End -> Map (Expr EAddr) Contract+traceContext (Success _ (Traces _ c) _ _) = c+traceContext (Partial _ (Traces _ c) _) = c+traceContext (Failure _ (Traces _ c) _) = c+traceContext (ITE {}) = internalError"Internal Error: ITE does not contain a trace"+traceContext (GVar {}) = internalError"Internal Error: Unexpected GVar"++traceTopLog :: [Expr Log] -> EVM t s ()+traceTopLog [] = noop+traceTopLog ((LogEntry addr bytes topics) : _) = do+ trace <- withTraceLocation (EventTrace addr bytes topics)+ modifying #traces $+ \t -> Zipper.nextSpace (Zipper.insert (Node trace []) t)+traceTopLog ((GVar _) : _) = internalError "unexpected global variable"++-- * Stack manipulation++push :: W256 -> EVM t s ()+push = pushSym . Lit++pushSym :: Expr EWord -> EVM t s ()+pushSym x = #state % #stack %= (x :)++pushAddr :: Expr EAddr -> EVM t s ()+pushAddr (LitAddr x) = #state % #stack %= (Lit (into x) :)+pushAddr x@(SymAddr _) = #state % #stack %= (WAddr x :)+pushAddr (GVar _) = internalError "Unexpected GVar"++stackOp1+ :: (?op :: Word8, VMOps t)+ => Word64+ -> (Expr EWord -> Expr EWord)+ -> EVM t s ()+stackOp1 cost f =+ use (#state % #stack) >>= \case+ x:xs ->+ burn cost $ do+ next+ let !y = f x+ #state % #stack .= y : xs+ _ ->+ underrun++stackOp2+ :: (?op :: Word8, VMOps t)+ => Word64+ -> (Expr EWord -> Expr EWord -> Expr EWord)+ -> EVM t s ()+stackOp2 cost f =+ use (#state % #stack) >>= \case+ x:y:xs ->+ burn cost $ do+ next+ #state % #stack .= f x y : xs+ _ ->+ underrun++stackOp3+ :: (?op :: Word8, VMOps t)+ => Word64+ -> (Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord)+ -> EVM t s ()+stackOp3 cost f =+ use (#state % #stack) >>= \case+ x:y:z:xs ->+ burn cost $ do+ next+ (#state % #stack) .= f x y z : xs+ _ ->+ underrun++-- * Bytecode data functions++checkJump :: VMOps t => Int -> [Expr EWord] -> EVM t s ()+checkJump x xs = noJumpIntoInitData x $ do+ vm <- get+ case isValidJumpDest vm x of+ True -> do+ #state % #stack .= xs+ #state % #pc .= x+ False -> vmError BadJumpDestination++-- fails with partial if we're trying to jump into the symbolic region of an `InitCode`+noJumpIntoInitData :: VMOps t => Int -> EVM t s () -> EVM t s ()+noJumpIntoInitData idx cont = do+ vm <- get+ case vm.state.code of+ -- init code is totally concrete, so we don't return partial if we're+ -- jumping beyond the range of `ops`+ InitCode _ (ConcreteBuf "") -> cont+ -- init code has a symbolic region, so check if we're trying to jump into+ -- the symbolic region and return partial if we are+ InitCode ops _ -> if idx > BS.length ops+ then partial $ JumpIntoSymbolicCode vm.state.pc idx+ else cont+ -- we're not executing init code, so nothing to check here+ _ -> cont++isValidJumpDest :: VM t s -> Int -> Bool+isValidJumpDest vm x = let+ code = vm.state.code+ self = vm.state.codeContract+ contract = fromMaybe+ (internalError "self not found in current contracts")+ (Map.lookup self vm.env.contracts)+ op = case code of+ UnknownCode _ -> internalError "Cannot analyze jumpdests for unknown code"+ InitCode ops _ -> BS.indexMaybe ops x+ RuntimeCode (ConcreteRuntimeCode ops) -> BS.indexMaybe ops x+ RuntimeCode (SymbolicRuntimeCode ops) -> ops V.!? x >>= maybeLitByte+ in case op of+ Nothing -> False+ Just b -> 0x5b == b && OpJumpdest == snd (contract.codeOps V.! (contract.opIxMap SV.! x))++opSize :: Word8 -> Int+opSize x | x >= 0x60 && x <= 0x7f = into x - 0x60 + 2+opSize _ = 1++-- i of the resulting vector contains the operation index for+-- the program counter value i. This is needed because source map+-- entries are per operation, not per byte.+mkOpIxMap :: ContractCode -> SV.Vector Int+mkOpIxMap (UnknownCode _) = internalError "Cannot build opIxMap for unknown code"+mkOpIxMap (InitCode conc _)+ = SV.create $ SV.new (BS.length conc) >>= \v ->+ -- Loop over the byte string accumulating a vector-mutating action.+ -- This is somewhat obfuscated, but should be fast.+ let (_, _, _, m) = BS.foldl' (go v) (0 :: Word8, 0, 0, pure ()) conc+ in m >> pure v+ where+ -- concrete case+ go v (0, !i, !j, !m) x | x >= 0x60 && x <= 0x7f =+ {- Start of PUSH op. -} (x - 0x60 + 1, i + 1, j, m >> SV.write v i j)+ go v (1, !i, !j, !m) _ =+ {- End of PUSH op. -} (0, i + 1, j + 1, m >> SV.write v i j)+ go v (0, !i, !j, !m) _ =+ {- Other op. -} (0, i + 1, j + 1, m >> SV.write v i j)+ go v (n, !i, !j, !m) _ =+ {- PUSH data. -} (n - 1, i + 1, j, m >> SV.write v i j)++mkOpIxMap (RuntimeCode (ConcreteRuntimeCode ops)) =+ mkOpIxMap (InitCode ops mempty) -- a bit hacky++mkOpIxMap (RuntimeCode (SymbolicRuntimeCode ops))+ = SV.create $ SV.new (length ops) >>= \v ->+ let (_, _, _, m) = foldl (go v) (0, 0, 0, pure ()) (stripBytecodeMetadataSym $ V.toList ops)+ in m >> pure v+ where+ go v (0, !i, !j, !m) x = case maybeLitByte x of+ Just x' -> if x' >= 0x60 && x' <= 0x7f+ -- start of PUSH op --+ then (x' - 0x60 + 1, i + 1, j, m >> SV.write v i j)+ -- other data --+ else (0, i + 1, j + 1, m >> SV.write v i j)+ _ -> internalError $ "cannot analyze symbolic code:\nx: " <> show x <> " i: " <> show i <> " j: " <> show j++ go v (1, !i, !j, !m) _ =+ {- End of PUSH op. -} (0, i + 1, j + 1, m >> SV.write v i j)+ go v (n, !i, !j, !m) _ =+ {- PUSH data. -} (n - 1, i + 1, j, m >> SV.write v i j)+++vmOp :: VM t s -> Maybe Op+vmOp vm =+ let i = vm ^. #state % #pc+ code' = vm ^. #state % #code+ (op, pushdata) = case code' of+ UnknownCode _ -> internalError "cannot get op from unknown code"+ InitCode xs' _ ->+ (BS.index xs' i, fmap LitByte $ BS.unpack $ BS.drop i xs')+ RuntimeCode (ConcreteRuntimeCode xs') ->+ (BS.index xs' i, fmap LitByte $ BS.unpack $ BS.drop i xs')+ RuntimeCode (SymbolicRuntimeCode xs') ->+ ( fromMaybe (internalError "unexpected symbolic code") . maybeLitByte $ xs' V.! i , V.toList $ V.drop i xs')+ in if (opslen code' < i)+ then Nothing+ else Just (readOp op pushdata)++vmOpIx :: VM t s -> Maybe Int+vmOpIx vm =+ do self <- currentContract vm+ self.opIxMap SV.!? vm.state.pc++-- Maps operation indices into a pair of (bytecode index, operation)+mkCodeOps :: ContractCode -> V.Vector (Int, Op)+mkCodeOps contractCode =+ let l = case contractCode of+ UnknownCode _ -> internalError "Cannot make codeOps for unknown code"+ InitCode bytes _ ->+ LitByte <$> (BS.unpack bytes)+ RuntimeCode (ConcreteRuntimeCode ops) ->+ LitByte <$> (BS.unpack $ stripBytecodeMetadata ops)+ RuntimeCode (SymbolicRuntimeCode ops) ->+ stripBytecodeMetadataSym $ V.toList ops+ in V.fromList . toList $ go 0 l+ where+ go !i !xs =+ case uncons xs of+ Nothing ->+ mempty+ Just (x, xs') ->+ let x' = fromMaybe (internalError "unexpected symbolic code argument") $ maybeLitByte x+ j = opSize x'+ in (i, readOp x' xs') Seq.<| go (i + j) (drop j xs)++-- * Gas cost calculation helpers++concreteModexpGasFee :: ByteString -> Word64+concreteModexpGasFee input =+ if lenb < into (maxBound :: Word32) &&+ (lene < into (maxBound :: Word32) || (lenb == 0 && lenm == 0)) &&+ lenm < into (maxBound :: Word64)+ then+ max 200 ((multiplicationComplexity * iterCount) `div` 3)+ else+ maxBound -- TODO: this is not 100% correct, return Nothing on overflow+ where+ (lenb, lene, lenm) = parseModexpLength input+ ez = isZero (96 + lenb) lene input+ e' = word $ LS.toStrict $+ lazySlice (96 + lenb) (min 32 lene) input+ nwords :: Word64+ nwords = ceilDiv (unsafeInto $ max lenb lenm) 8+ multiplicationComplexity = nwords * nwords+ iterCount' :: Word64+ iterCount' | lene <= 32 && ez = 0+ | lene <= 32 = unsafeInto (log2 e')+ | e' == 0 = 8 * (unsafeInto lene - 32)+ | otherwise = unsafeInto (log2 e') + 8 * (unsafeInto lene - 32)+ iterCount = max iterCount' 1++-- Gas cost of precompiles+costOfPrecompile :: FeeSchedule Word64 -> Addr -> Expr Buf -> Word64+costOfPrecompile (FeeSchedule {..}) precompileAddr input =+ let errorDynamicSize = internalError "precompile input cannot have a dynamic size"+ inputLen = case input of+ ConcreteBuf bs -> unsafeInto $ BS.length bs+ AbstractBuf _ -> errorDynamicSize+ buf -> case bufLength buf of+ Lit l -> unsafeInto l -- TODO: overflow+ _ -> errorDynamicSize+ in case precompileAddr of+ -- ECRECOVER+ 0x1 -> 3000+ -- SHA2-256+ 0x2 -> (((inputLen + 31) `div` 32) * 12) + 60+ -- RIPEMD-160+ 0x3 -> (((inputLen + 31) `div` 32) * 120) + 600+ -- IDENTITY+ 0x4 -> (((inputLen + 31) `div` 32) * 3) + 15+ -- MODEXP+ 0x5 -> case input of+ ConcreteBuf i -> concreteModexpGasFee i+ _ -> internalError "Unsupported symbolic modexp gas calc "+ -- ECADD+ 0x6 -> g_ecadd+ -- ECMUL+ 0x7 -> g_ecmul+ -- ECPAIRING+ 0x8 -> (inputLen `div` 192) * g_pairing_point + g_pairing_base+ -- BLAKE2+ 0x9 -> case input of+ ConcreteBuf i -> g_fround * (unsafeInto $ asInteger $ lazySlice 0 4 i)+ _ -> internalError "Unsupported symbolic blake2 gas calc"+ _ -> internalError $ "unimplemented precompiled contract " ++ show precompileAddr++-- Gas cost of memory expansion+memoryCost :: FeeSchedule Word64 -> Word64 -> Word64+memoryCost FeeSchedule{..} byteCount =+ let+ wordCount = ceilDiv byteCount 32+ linearCost = g_memory * wordCount+ quadraticCost = div (wordCount * wordCount) 512+ in+ linearCost + quadraticCost++hashcode :: ContractCode -> Expr EWord+hashcode (UnknownCode a) = CodeHash a+hashcode (InitCode ops args) = keccak $ (ConcreteBuf ops) <> args+hashcode (RuntimeCode (ConcreteRuntimeCode ops)) = keccak (ConcreteBuf ops)+hashcode (RuntimeCode (SymbolicRuntimeCode ops)) = keccak . Expr.fromList $ ops++-- | The length of the code ignoring any constructor args.+-- This represents the region that can contain executable opcodes+opslen :: ContractCode -> Int+opslen (UnknownCode _) = internalError "Cannot produce concrete opslen for unknown code"+opslen (InitCode ops _) = BS.length ops+opslen (RuntimeCode (ConcreteRuntimeCode ops)) = BS.length ops+opslen (RuntimeCode (SymbolicRuntimeCode ops)) = length ops++-- | The length of the code including any constructor args.+-- This can return an abstract value+codelen :: ContractCode -> Expr EWord+codelen (UnknownCode a) = CodeSize a+codelen c@(InitCode {}) = case toBuf c of+ Just b -> bufLength b+ Nothing -> internalError "impossible"+-- these are never going to be negative so unsafeInto is fine here+codelen (RuntimeCode (ConcreteRuntimeCode ops)) = Lit . unsafeInto $ BS.length ops+codelen (RuntimeCode (SymbolicRuntimeCode ops)) = Lit . unsafeInto $ length ops++toBuf :: ContractCode -> Maybe (Expr Buf)+toBuf (UnknownCode _) = Nothing+toBuf (InitCode ops args) = Just $ ConcreteBuf ops <> args+toBuf (RuntimeCode (ConcreteRuntimeCode ops)) = Just $ ConcreteBuf ops+toBuf (RuntimeCode (SymbolicRuntimeCode ops)) = Just $ Expr.fromList ops++codeloc :: EVM t s CodeLocation+codeloc = do+ vm <- get+ pure (vm.state.contract, vm.state.pc)++createAddress :: Expr EAddr -> Maybe W64 -> EVM t s (Expr EAddr)+createAddress (LitAddr a) (Just n) = pure $ Concrete.createAddress a n+createAddress (GVar _) _ = internalError "Unexpected GVar"+createAddress _ _ = freshSymAddr++create2Address :: Expr EAddr -> W256 -> ByteString -> EVM t s (Expr EAddr)+create2Address (LitAddr a) s b = pure $ Concrete.create2Address a s b+create2Address (SymAddr _) _ _ = freshSymAddr+create2Address (GVar _) _ _ = internalError "Unexpected GVar"++freshSymAddr :: EVM t s (Expr EAddr)+freshSymAddr = do+ modifying (#env % #freshAddresses) (+ 1)+ n <- use (#env % #freshAddresses)+ pure $ SymAddr ("freshSymAddr" <> (pack $ show n))++isPrecompileAddr :: Expr EAddr -> Bool+isPrecompileAddr = \case+ LitAddr a -> 0x0 < a && a <= 0x09+ SymAddr _ -> False+ GVar _ -> internalError "Unexpected GVar"++-- * Arithmetic++ceilDiv :: (Num a, Integral a) => a -> a -> a+ceilDiv m n = div (m + n - 1) n++allButOne64th :: (Num a, Integral a) => a -> a+allButOne64th n = n - div n 64++log2 :: FiniteBits b => b -> Int+log2 x = finiteBitSize x - 1 - countLeadingZeros x++writeMemory :: MutableMemory s -> Int -> ByteString -> EVM t s ()+writeMemory memory offset buf = do+ memory' <- expandMemory (offset + BS.length buf)+ mapM_ (uncurry (VUnboxed.Mutable.write memory'))+ (zip [offset..] (BS.unpack buf))+ where+ expandMemory targetSize = do+ let toAlloc = targetSize - VUnboxed.Mutable.length memory+ if toAlloc > 0 then do+ memory' <- VUnboxed.Mutable.grow memory toAlloc+ assign (#state % #memory) (ConcreteMemory memory')+ pure memory'+ else+ pure memory++freezeMemory :: MutableMemory s -> EVM t s (Expr Buf)+freezeMemory memory =+ ConcreteBuf . BS.pack . VUnboxed.toList <$> VUnboxed.freeze memory+++instance VMOps Symbolic where+ burn' _ continue = continue+ burnExp _ continue = continue+ burnSha3 _ continue = continue+ burnCalldatacopy _ continue = continue+ burnCodecopy _ continue = continue+ burnExtcodecopy _ _ continue = continue+ burnReturndatacopy _ continue = continue+ burnLog _ _ continue = continue++ initialGas = ()+ ensureGas _ continue = continue+ gasTryFrom _ = Right ()+ costOfCreate _ _ _ _ = ((), ())+ costOfCall _ _ _ _ _ _ continue = continue 0 0+ reclaimRemainingGasAllowance _ = pure ()+ payRefunds = pure ()+ pushGas = do+ modifying (#env % #freshGasVals) (+ 1)+ n <- use (#env % #freshGasVals)+ pushSym $ Expr.Gas n+ enoughGas _ _ = True+ subGas _ _ = ()+ toGas _ = ()+ whenSymbolicElse a _ = a++ partial e = assign #result (Just (Unfinished e))+ branch cond continue = do+ loc <- codeloc+ pathconds <- use #constraints+ query $ PleaseAskSMT cond pathconds (choosePath loc)+ where+ condSimp = Expr.simplify cond+ condSimpConc = Expr.concKeccakSimpExpr condSimp+ choosePath loc (Case v) = do+ assign #result Nothing+ pushTo #constraints $ if v then Expr.simplifyProp (condSimpConc ./= Lit 0)+ else Expr.simplifyProp (condSimpConc .== Lit 0)+ (iteration, _) <- use (#iterations % at loc % non (0,[]))+ stack <- use (#state % #stack)+ assign (#cache % #path % at (loc, iteration)) (Just v)+ assign (#iterations % at loc) (Just (iteration + 1, stack))+ continue v+ -- Both paths are possible; we ask for more input+ choosePath loc Unknown =+ choose . PleaseChoosePath condSimp $ choosePath loc . Case++instance VMOps Concrete where+ burn' n continue = do+ available <- use (#state % #gas)+ if n <= available+ then do+ #state % #gas %= (subtract n)+ #burned %= (+ n)+ continue+ else+ vmError (OutOfGas available n)++ burnExp (forceLit -> exponent) continue = do+ FeeSchedule {..} <- gets (.block.schedule)+ let cost = if exponent == 0+ then g_exp+ else g_exp + g_expbyte * unsafeInto (ceilDiv (1 + log2 exponent) 8)+ burn cost continue++ burnSha3 (forceLit -> xSize) continue = do+ FeeSchedule {..} <- gets (.block.schedule)+ burn (g_sha3 + g_sha3word * ceilDiv (unsafeInto xSize) 32) continue++ burnCalldatacopy (forceLit -> xSize) continue = do+ FeeSchedule {..} <- gets (.block.schedule)+ burn (g_verylow + g_copy * ceilDiv (unsafeInto xSize) 32) continue++ burnCodecopy (forceLit -> n) continue =+ case tryFrom n of+ Left _ -> vmError IllegalOverflow+ Right n' -> do+ FeeSchedule {..} <- gets (.block.schedule)+ if n' <= ( (maxBound :: Word64) - g_verylow ) `div` g_copy * 32 then+ burn (g_verylow + g_copy * ceilDiv (unsafeInto n) 32) continue+ else vmError IllegalOverflow++ burnExtcodecopy extAccount (forceLit -> codeSize) continue = do+ FeeSchedule {..} <- gets (.block.schedule)+ acc <- accessAccountForGas extAccount+ let cost = if acc then g_warm_storage_read else g_cold_account_access+ burn (cost + g_copy * ceilDiv (unsafeInto codeSize) 32) continue++ burnReturndatacopy (forceLit -> xSize) continue = do+ FeeSchedule {..} <- gets (.block.schedule)+ burn (g_verylow + g_copy * ceilDiv (unsafeInto xSize) 32) continue++ burnLog (forceLit -> xSize) n continue = do+ case tryFrom xSize of+ Right sz -> do+ FeeSchedule {..} <- gets (.block.schedule)+ burn (g_log + g_logdata * sz + (into n) * g_logtopic) continue+ _ -> vmError IllegalOverflow++ initialGas = 0++ ensureGas amount continue = do+ availableGas <- use (#state % #gas)++ if availableGas <= amount then+ vmError (OutOfGas availableGas amount)+ else continue++ gasTryFrom (forceLit -> w256) =+ case tryFrom w256 of+ Left _ -> Left ()+ Right a -> Right a++ -- Gas cost of create, including hash cost if needed+ costOfCreate (FeeSchedule {..}) availableGas size hashNeeded = (createCost, initGas)+ where+ byteCost = if hashNeeded then g_sha3word + g_initcodeword else g_initcodeword+ createCost = g_create + codeCost+ codeCost = byteCost * (ceilDiv (unsafeInto (forceLit size)) 32)+ initGas = allButOne64th (availableGas - createCost)++ -- Gas cost function for CALL, transliterated from the Yellow Paper.+ costOfCall (FeeSchedule {..}) recipientExists (forceLit -> xValue) availableGas xGas target continue = do+ acc <- accessAccountForGas target+ let call_base_gas = if acc then g_warm_storage_read else g_cold_account_access+ c_new = if not recipientExists && xValue /= 0+ then g_newaccount+ else 0+ c_xfer = if xValue /= 0 then g_callvalue else 0+ c_extra = call_base_gas + c_xfer + c_new+ c_gascap = if availableGas >= c_extra+ then min xGas (allButOne64th (availableGas - c_extra))+ else xGas+ c_callgas = if xValue /= 0 then c_gascap + g_callstipend else c_gascap+ let (cost, gas') = (c_gascap + c_extra, c_callgas)+ continue cost gas'++ -- When entering a call, the gas allowance is counted as burned+ -- in advance; this unburns the remainder and adds it to the+ -- parent frame.+ reclaimRemainingGasAllowance oldVm = do+ let remainingGas = oldVm.state.gas+ modifying #burned (subtract remainingGas)+ modifying (#state % #gas) (+ remainingGas)++ payRefunds = do+ -- compute and pay the refund to the caller and the+ -- corresponding payment to the miner+ block <- use #block+ tx <- use #tx+ gasRemaining <- use (#state % #gas)++ let+ sumRefunds = sum (snd <$> tx.substate.refunds)+ gasUsed = tx.gaslimit - gasRemaining+ cappedRefund = min (quot gasUsed 5) sumRefunds+ originPay = (into $ gasRemaining + cappedRefund) * tx.gasprice+ minerPay = tx.priorityFee * (into gasUsed)++ modifying (#env % #contracts)+ (Map.adjust (over #balance (Expr.add (Lit originPay))) tx.origin)+ modifying (#env % #contracts)+ (Map.adjust (over #balance (Expr.add (Lit minerPay))) block.coinbase)++ pushGas = do+ vm <- get+ push (into vm.state.gas)++ enoughGas cost gasCap = cost <= gasCap++ subGas gasCap cost = gasCap - cost++ toGas = id++ whenSymbolicElse _ a = a++ partial _ = internalError "won't happen during concrete exec"++ branch (forceLit -> cond) continue = continue (cond > 0)++-- Create symbolic VM from concrete VM+symbolify :: VM Concrete s -> VM Symbolic s+symbolify vm =+ vm { result = symbolifyResult <$> vm.result+ , state = symbolifyFrameState vm.state+ , frames = symbolifyFrame <$> vm.frames+ , burned = ()+ }++symbolifyFrameState :: FrameState Concrete s -> FrameState Symbolic s+symbolifyFrameState state = state { gas = () }++symbolifyFrame :: Frame Concrete s -> Frame Symbolic s+symbolifyFrame frame = frame { state = symbolifyFrameState frame.state }++symbolifyResult :: VMResult Concrete s -> VMResult Symbolic s+symbolifyResult result =+ case result of+ HandleEffect _ -> internalError "shouldn't happen"+ VMFailure e -> VMFailure e+ VMSuccess b -> VMSuccess b++forceLit :: Expr EWord -> W256+forceLit (Lit w) = w+forceLit _ = internalError "concrete vm, shouldn't ever happen"++burn :: VMOps t => Word64 -> EVM t s () -> EVM t s ()+burn = burn' . toGas
src/EVM/ABI.hs view
@@ -24,7 +24,7 @@ Nested sequences are encoded recursively with no special treatment. - Calldata args are encoded as heterogenous sequences sans length prefix.+ Calldata args are encoded as heterogeneous sequences sans length prefix. -} module EVM.ABI
+ src/EVM/Effects.hs view
@@ -0,0 +1,123 @@+{-|+Module : Effects+Description : Domain specific effects++This module contains custom app specific mtl style effects for hevm+These are written in the style of the ReaderT over IO pattern [1].+Right now we only have a single `ReadConfig` effect, but over time hope to+migrate most usages of IO into custom effects here.++This framework would allow us to have multiple interpretations for effects+(e.g. a pure version for tests), but for now we interpret everything in IO+only.++[1]: https://www.fpcomplete.com/blog/readert-design-pattern/+-}++{-# Language RankNTypes #-}+{-# Language FlexibleInstances #-}+{-# Language KindSignatures #-}+{-# Language DataKinds #-}+{-# Language GADTs #-}+{-# Language DerivingStrategies #-}+{-# Language DuplicateRecordFields #-}+{-# Language NoFieldSelectors #-}+{-# Language ConstraintKinds #-}++module EVM.Effects where++import Control.Monad.Reader+import Control.Monad.IO.Unlift+import EVM.Dapp (DappInfo)+import EVM.Types (VM(..))+import Control.Monad.ST (RealWorld)+import Data.Text (Text)+import Data.Text.IO qualified as T+import System.IO (stderr)+import EVM.Format (showTraceTree)+import EVM (traceForest)+++-- Abstract Effects --------------------------------------------------------------------------------+-- Here we define the abstract interface for the effects that we wish to model+++-- Read from global config+class Monad m => ReadConfig m where+ readConfig :: m Config++data Config = Config+ { dumpQueries :: Bool+ , dumpExprs :: Bool+ , dumpEndStates :: Bool+ , debug :: Bool+ , abstRefineArith :: Bool+ , abstRefineMem :: Bool+ , dumpTrace :: Bool+ , numCexFuzz :: Integer+ -- Used to debug fuzzer in test.hs. It disables the SMT solver+ -- and uses the fuzzer ONLY to try to find a counterexample.+ -- Returns Unknown if the Cex cannot be found via fuzzing+ , onlyCexFuzz :: Bool+ }+ deriving (Show, Eq)++defaultConfig :: Config+defaultConfig = Config+ { dumpQueries = False+ , dumpExprs = False+ , dumpEndStates = False+ , debug = False+ , abstRefineArith = False+ , abstRefineMem = False+ , dumpTrace = False+ , numCexFuzz = 10+ , onlyCexFuzz = False+ }++-- Write to the console+class Monad m => TTY m where+ writeOutput :: Text -> m ()+ writeErr :: Text -> m ()++writeTraceDapp :: App m => DappInfo -> VM t RealWorld -> m ()+writeTraceDapp dapp vm = do+ conf <- readConfig+ liftIO $ when conf.dumpTrace $ T.writeFile "VM.trace" (showTraceTree dapp vm)++writeTrace :: App m => VM t RealWorld -> m ()+writeTrace vm = do+ conf <- readConfig+ liftIO $ when conf.dumpTrace $ writeFile "VM.trace" (show $ traceForest vm)+++-- IO Interpretation -------------------------------------------------------------------------------+++newtype Env = Env+ { config :: Config+ }++defaultEnv :: Env+defaultEnv = Env { config = defaultConfig }++instance Monad m => ReadConfig (ReaderT Env m) where+ readConfig = do+ e <- ask+ pure e.config++instance (Monad m, MonadIO m) => TTY (ReaderT Env m) where+ writeOutput txt = liftIO $ T.putStrLn txt+ writeErr txt = liftIO $ T.hPutStr stderr txt++runEnv :: Env -> ReaderT Env m a -> m a+runEnv e a = runReaderT a e+++-- Helpful Aliases ---------------------------------------------------------------------------------+++type App m = (MonadUnliftIO m, ReadConfig m, TTY m)++runApp :: ReaderT Env m a -> m a+runApp = runEnv defaultEnv
src/EVM/Exec.hs view
@@ -14,7 +14,7 @@ ethrunAddress :: Addr ethrunAddress = Addr 0x00a329c0648769a73afac7f9381e08fb43dbea72 -vmForEthrunCreation :: ByteString -> ST s (VM s)+vmForEthrunCreation :: VMOps t => ByteString -> ST s (VM t s) vmForEthrunCreation creationCode = (makeVm $ VMOpts { contract = initialContract (InitCode creationCode mempty)@@ -31,7 +31,7 @@ , blockGaslimit = 0 , gasprice = 0 , prevRandao = 42069- , gas = 0xffffffffffffffff+ , gas = toGas 0xffffffffffffffff , gaslimit = 0xffffffffffffffff , baseFee = 0 , priorityFee = 0@@ -44,21 +44,21 @@ }) <&> set (#env % #contracts % at (LitAddr ethrunAddress)) (Just (initialContract (RuntimeCode (ConcreteRuntimeCode "")))) -exec :: EVM s (VMResult s)+exec :: VMOps t => EVM t s (VMResult t s) exec = do vm <- get case vm.result of Nothing -> exec1 >> exec Just r -> pure r -run :: EVM s (VM s)+run :: VMOps t => EVM t s (VM t s) run = do vm <- get case vm.result of Nothing -> exec1 >> run Just _ -> pure vm -execWhile :: (VM s -> Bool) -> State (VM s) Int+execWhile :: (VM t s -> Bool) -> State (VM t s) Int execWhile p = go 0 where go i = do
src/EVM/Expr.hs view
@@ -205,11 +205,11 @@ -- | Extracts the byte at a given index from a Buf. -- -- We do our best to return a concrete value wherever possible, but fallback to--- an abstract expresion if nescessary. Note that a Buf is an infinite+-- an abstract expression if necessary. Note that a Buf is an infinite -- structure, so reads outside of the bounds of a ConcreteBuf return 0. This is -- inline with the semantics of calldata and memory, but not of returndata. --- fuly concrete reads+-- fully concrete reads readByte :: Expr EWord -> Expr Buf -> Expr Byte readByte (Lit x) (ConcreteBuf b) = if x <= unsafeInto (maxBound :: Int) && i < BS.length b@@ -235,7 +235,7 @@ then readByte (Lit $ x - (dstOffset - srcOffset)) src else readByte i dst readByte i@(Lit x) buf@(CopySlice _ (Lit dstOffset) (Lit size) _ dst)- -- the byte we are trying to read is compeletely outside of the sliced region+ -- the byte we are trying to read is completely outside of the sliced region = if x - dstOffset >= size then readByte i dst else ReadByte (Lit x) buf@@ -269,7 +269,7 @@ readWord (Lit idx) b@(CopySlice (Lit srcOff) (Lit dstOff) (Lit size) src dst) -- the region we are trying to read is enclosed in the sliced region | (idx - dstOff) < size && 32 <= size - (idx - dstOff) = readWord (Lit $ srcOff + (idx - dstOff)) src- -- the region we are trying to read is compeletely outside of the sliced region+ -- the region we are trying to read is completely outside of the sliced region | (idx - dstOff) >= size && (idx - dstOff) <= (maxBound :: W256) - 31 = readWord (Lit idx) dst -- the region we are trying to read partially overlaps the sliced region | otherwise = readWordFromBytes (Lit idx) b@@ -343,7 +343,7 @@ -- copying 32 bytes can be rewritten to a WriteWord on dst (e.g. CODECOPY of args during constructors) copySlice srcOffset dstOffset (Lit 32) src dst = writeWord dstOffset (readWord srcOffset src) dst --- concrete indicies & abstract src (may produce a concrete result if we are+-- concrete indices & abstract src (may produce a concrete result if we are -- copying from a concrete region of src) copySlice s@(Lit srcOffset) d@(Lit dstOffset) sz@(Lit size) src ds@(ConcreteBuf dst) | dstOffset < maxBytes, size < maxBytes, srcOffset + (size-1) > srcOffset = let@@ -355,7 +355,7 @@ else CopySlice s d sz src ds | otherwise = CopySlice s d sz src ds --- abstract indicies+-- abstract indices copySlice srcOffset dstOffset size src dst = CopySlice srcOffset dstOffset size src dst @@ -412,7 +412,7 @@ -- | Returns the length of a given buffer -- -- If there are any writes to abstract locations, or CopySlices with an--- abstract size or dstOffset, an abstract expresion will be returned.+-- abstract size or dstOffset, an abstract expression will be returned. bufLength :: Expr Buf -> Expr EWord bufLength = bufLengthEnv mempty False @@ -421,7 +421,7 @@ where go :: Expr EWord -> Expr Buf -> Expr EWord go l (ConcreteBuf b) = EVM.Expr.max l (Lit (unsafeInto . BS.length $ b))- go l (AbstractBuf b) = Max l (BufLength (AbstractBuf b))+ go l (AbstractBuf b) = EVM.Expr.max l (BufLength (AbstractBuf b)) go l (WriteWord idx _ b) = go (EVM.Expr.max l (add idx (Lit 32))) b go l (WriteByte idx _ b) = go (EVM.Expr.max l (add idx (Lit 1))) b go l (CopySlice _ dstOffset size _ dst) = go (EVM.Expr.max l (add dstOffset size)) dst@@ -473,12 +473,12 @@ inputLen = case bufLength b of Lit s -> if s > maxIdx then internalError "concretePrefix: input buffer size exceeds 500mb"- -- unafeInto: s is <= 500,000,000+ -- unsafeInto: s is <= 500,000,000 else Just (unsafeInto s) _ -> Nothing - -- recursively reads succesive bytes from `b` until we reach a symbolic- -- byte returns the larged index read from and a reference to the mutable+ -- recursively reads successive bytes from `b` until we reach a symbolic+ -- byte returns the large index read from and a reference to the mutable -- vec (might not be the same as the input because of the call to grow) go :: forall s . Int -> MVector s Word8 -> ST s (Int, MVector s Word8) go i v@@ -529,7 +529,7 @@ fromList :: V.Vector (Expr Byte) -> Expr Buf fromList bs = case Prelude.and (fmap isLitByte bs) of True -> ConcreteBuf . BS.pack . V.toList . V.mapMaybe maybeLitByte $ bs- -- we want to minimize the size of the resulting expresion, so we do two passes:+ -- we want to minimize the size of the resulting expression, so we do two passes: -- 1. write all concrete bytes to some base buffer -- 2. write all symbolic writes on top of this buffer -- this is safe because each write in the input vec is to a single byte at a distinct location@@ -612,7 +612,7 @@ where go :: Expr EWord -> Expr Storage -> Maybe (Expr EWord) go _ (GVar _) = internalError "Can't read from a GVar"- go slot s@(AbstractStore _) = Just $ SLoad slot s+ go slot s@(AbstractStore _ _) = Just $ SLoad slot s go (Lit l) (ConcreteStore s) = Lit <$> Map.lookup l s go slot store@(ConcreteStore _) = Just $ SLoad slot store go slot s@(SStore prevSlot val prev) = case (prevSlot, slot) of@@ -640,12 +640,16 @@ -- the chance of adding a value <= 2^32 to any given keccack output -- leading to an overflow is effectively zero. the chance of an overflow- -- occuring here is 2^32/2^256 = 2^-224, which is close enough to zero+ -- occurring here is 2^32/2^256 = 2^-224, which is close enough to zero -- for our purposes. This lets us completely simplify reads from write -- chains involving writes to arrays at literal offsets. (Lit a, Add (Lit b) (Keccak _) ) | a < 256, b < maxW32 -> go slot prev- (Add (Lit a) (Keccak _) , Lit b) | a < 256, b < maxW32 -> go slot prev+ (Add (Lit a) (Keccak _) , Lit b) | b < 256, a < maxW32 -> go slot prev + --- NOTE these are needed to succeed in rewriting arrays with a variable index+ -- (Lit a, Add (Keccak _) (Var _) ) | a < 256 -> go slot prev+ -- (Add (Keccak _) (Var _) , Lit b) | b < 256 -> go slot prev+ -- Finding two Keccaks that are < 256 away from each other should be VERY hard -- This simplification allows us to deal with maps of structs (Add (Lit a2) (Keccak _), Add (Lit b2) (Keccak _)) | a2 /= b2, abs(a2-b2) < 256 -> go slot prev@@ -690,6 +694,7 @@ pattern Keccak64Bytes <- Keccak (CopySlice (Lit 0) (Lit 0) (Lit 64) _ (ConcreteBuf "")) -- storage slots for arrays are determined by (keccak(bytes32(id)) + offset)+-- note that `normArgs` puts the Lit as the 2nd argument to `Add` pattern ArraySlotWithOffset :: ByteString -> Expr EWord -> Expr EWord pattern ArraySlotWithOffset id offset = Add (Keccak (ConcreteBuf id)) offset @@ -755,14 +760,138 @@ getAddr :: Expr Storage -> Maybe (Expr EAddr) getAddr (SStore _ _ p) = getAddr p-getAddr (AbstractStore a) = Just a+getAddr (AbstractStore a _) = Just a getAddr (ConcreteStore _) = Nothing getAddr (GVar _) = internalError "cannot determine addr of a GVar" +getLogicalIdx :: Expr Storage -> Maybe W256+getLogicalIdx (SStore _ _ p) = getLogicalIdx p+getLogicalIdx (AbstractStore _ idx) = idx+getLogicalIdx (ConcreteStore _) = Nothing+getLogicalIdx (GVar _) = internalError "cannot determine addr of a GVar" + -- ** Whole Expression Simplification ** ----------------------------------------------------------- +data StorageType = SmallSlot | Array | Map | Mixed | UNK+ deriving (Show, Eq)++safeToDecompose :: Expr a -> Bool+safeToDecompose inp = result /= Mixed+ where+ result = execState (safeToDecomposeRunner inp) UNK++ safeToDecomposeRunner :: forall a. Expr a -> State StorageType ()+ safeToDecomposeRunner a = go a++ go :: forall b. Expr b -> State StorageType ()+ go e@(SLoad (MappingSlot {}) _) = setMap e+ go e@(SLoad (ArraySlotZero {}) _) = setArray e+ go e@(SLoad (ArraySlotWithOffset {}) _) = setArray e+ go e@(SLoad (Lit x) _) | x < 256 = setSmall e+ go e@(SLoad _ _) = setMixed e+ go e@(SStore (MappingSlot {}) _ _) = setMap e+ go e@(SStore (ArraySlotZero {}) _ _) = setArray e+ go e@(SStore (ArraySlotWithOffset {}) _ _) = setArray e+ go e@(SStore (Lit x) _ _) | x < 256 = setSmall e+ go e@(SStore _ _ _) = setMixed e+ go _ = pure ()++ -- Helper functions for detecting mixed load/store+ setMixed _ = do+ put Mixed+ pure ()+ setMap _ = do+ s <- get+ case s of+ Array -> put Mixed+ SmallSlot -> put Mixed+ UNK -> put Map+ _ -> pure ()+ pure ()+ setArray _ = do+ s <- get+ case s of+ Map -> put Mixed+ SmallSlot -> put Mixed+ UNK -> put Array+ _ -> pure ()+ pure ()+ setSmall _ = do+ s <- get+ case s of+ Map -> put Mixed+ Array -> put Mixed+ UNK -> put SmallSlot+ _ -> pure ()+ pure ()++-- | Splits storage into logical sub-stores if (1) all SLoad->SStore* chains are one of:+-- (1a) Lit < 256, (1b) MappingSlot, (1c) ArraySlotWithOffset, (1d) ArraySlotZero+-- and (2) there is no mixing of different types (e.g. Map with Array) within+-- the same SStore -> SLoad* chain, and (3) there is no mixing of different array/map slots.+--+-- Mixing (2) and (3) are attempted to be prevented (if possible) as part of the rewrites+-- done by the `readStorage` function that is ran before this. If there is still mixing here,+-- we abort with a Nothing.+--+-- We do NOT rewrite stand-alone `SStore`-s (i.e. SStores that are not read), since+-- they are often used to describe a post-state, and are not dispatched as-is to+-- the solver+decomposeStorage :: Expr a -> Maybe (Expr a)+decomposeStorage = go+ where+ go :: Expr a -> Maybe (Expr a)+ go e@(SLoad origKey store) = if Prelude.not (safeToDecompose e) then Nothing else tryRewrite origKey store+ go e = Just e++ tryRewrite :: Expr EWord -> Expr Storage -> Maybe (Expr EWord)+ tryRewrite origKey store = case inferLogicalIdx origKey of+ Just (idx, key) -> do+ base <- setLogicalBase idx store+ pure (SLoad key base)+ _ -> Nothing++ -- NOTE: we use (Maybe W256) for idx here, because for small slot numbers we want to keep the+ -- Logical Store value a Nothing+ inferLogicalIdx :: Expr EWord -> Maybe (Maybe W256, Expr EWord)+ inferLogicalIdx = \case+ Lit a | a >= 256 -> Nothing+ Lit a -> Just (Nothing, Lit a)+ (MappingSlot idx key) | BS.length idx == 64 -> Just (Just $ idxToWord idx, key)+ (ArraySlotWithOffset idx offset) | BS.length idx == 32 -> Just (Just $ idxToWord64 idx, offset)+ (ArraySlotZero idx) | BS.length idx == 32 -> Just (Just $ idxToWord64 idx, Lit 0)+ _ -> Nothing++ idxToWord :: ByteString -> W256+ idxToWord = W256 . word256 . (BS.takeEnd 32)+ -- Arrays take the whole `id` and keccak it. It's supposed to be 64B+ idxToWord64 :: ByteString -> W256+ idxToWord64 = W256 . word256 . (BS.takeEnd 64)++ -- Updates the logical base store of the given expression if it is safe to do so+ setLogicalBase :: Maybe W256 -> Expr Storage -> Maybe (Expr Storage)++ -- abstract bases get their logical idx set to the new value+ setLogicalBase idx (AbstractStore addr _) = Just $ AbstractStore addr idx+ setLogicalBase idx (SStore i v s) = do+ b <- setLogicalBase idx s+ (idx2, key2) <- inferLogicalIdx i+ if idx == idx2 then Just (SStore key2 v b) else Nothing++ -- empty concrete base is safe to reuse without any rewriting+ setLogicalBase _ s@(ConcreteStore m) | Map.null m = Just s++ -- if the existing base is concrete but we have writes to only keys < 256+ -- then we can safely rewrite the base to an empty ConcreteStore (safe because we assume keccack(x) > 256)+ setLogicalBase _ (ConcreteStore store) =+ if all (< 256) (Map.keys store)+ then Just (ConcreteStore mempty)+ else Nothing+ setLogicalBase _ (GVar _) = internalError "Unexpected GVar"++ -- | Simple recursive match based AST simplification -- Note: may not terminate! simplify :: Expr a -> Expr a@@ -780,6 +909,9 @@ go (CopySlice (Lit 0x0) (Lit 0x0) (Lit 0x0) _ dst) = dst -- simplify storage+ go (Keccak (ConcreteBuf bs)) = case BS.length bs == 64 of+ True -> MappingSlot bs (Lit $ word bs)+ False -> Keccak (ConcreteBuf bs) go (SLoad slot store) = readStorage' slot store go (SStore slot val store) = writeStorage slot val store @@ -832,6 +964,8 @@ go (EVM.Types.GEq a b) = leq b a go (EVM.Types.LEq a b) = EVM.Types.IsZero (gt a b) go (IsZero a) = iszero a+ go (SLT a@(Lit _) b@(Lit _)) = slt a b+ go (SGT a b) = SLT b a -- syntactic Eq reduction go (Eq (Lit a) (Lit b))@@ -870,10 +1004,21 @@ -- (a+(c-b)) -- In other words, subtraction is just adding a much larger number. -- So 3-1 mod 6 = 3+(6-1) mod 6 = 3+5 mod 6 = 5+3 mod 6 = 2- go (Sub (Sub orig (Lit x)) (Lit y)) = Sub orig (Lit (y+x))- go (Sub (Add orig (Lit x)) (Lit y)) = Sub orig (Lit (y-x))- go (Add (Add orig (Lit x)) (Lit y)) = Add orig (Lit (y+x))- go (Add (Sub orig (Lit x)) (Lit y)) = Add orig (Lit (y-x))+ -- Notice: all Add is normalized, hence the 1st argument is+ -- expected to be Lit, if any. Hence `orig` needs to be the+ -- 2nd argument for Add. However, Sub is not normalized+ go (Add (Lit x) (Add (Lit y) orig)) = add (Lit (x+y)) orig+ -- add + sub NOTE: every combination of Sub is needed (2)+ go (Add (Lit x) (Sub (Lit y) orig)) = sub (Lit (x+y)) orig+ go (Add (Lit x) (Sub orig (Lit y))) = add (Lit (x-y)) orig+ -- sub + sub NOTE: every combination of Sub is needed (2x2)+ go (Sub (Lit x) (Sub (Lit y) orig)) = add (Lit (x-y)) orig+ go (Sub (Lit x) (Sub orig (Lit y))) = sub (Lit (x+y)) orig+ go (Sub (Sub (Lit x) orig) (Lit y)) = sub (Lit (x-y)) orig+ go (Sub (Sub orig (Lit x)) (Lit y)) = sub orig (Lit (x+y))+ -- sub + add NOTE: every combination of Sub is needed (2)+ go (Sub (Lit x) (Add (Lit y) orig)) = sub (Lit (x-y)) orig+ go (Sub (Add (Lit x) orig) (Lit y) ) = add (Lit (x-y)) orig -- redundant add / sub go (Sub (Add a b) c)@@ -881,6 +1026,12 @@ | b == c = a | otherwise = sub (add a b) c + -- Add is associative. We are doing left-growing trees because LIT is+ -- arranged to the left. This way, they accumulate in all combinations.+ -- See `sim-assoc-add` test cases in test.hs+ go (Add a (Add b c)) = add (add a b) c+ go (Add (Add (Lit a) x) (Lit b)) = add (Lit (a+b)) x+ -- add / sub identities go (Add a b) | b == (Lit 0) = a@@ -940,18 +1091,23 @@ go (EVM.Types.Not (EVM.Types.Not a)) = a -- Some trivial min / max eliminations- go (Max a b) = case (a, b) of- (Lit 0, _) -> b- _ -> EVM.Expr.max a b+ go (Max a b) = EVM.Expr.max a b go (Min a b) = case (a, b) of (Lit 0, _) -> Lit 0 _ -> EVM.Expr.min a b + -- Mul is associative. We are doing left-growing trees because LIT is+ -- arranged to the left. This way, they accumulate in all combinations.+ -- See `sim-assoc-add` test cases in test.hs+ go (Mul a (Mul b c)) = mul (mul a b) c+ go (Mul (Mul (Lit a) x) (Lit b)) = mul (Lit (a*b)) x+ -- Some trivial mul eliminations go (Mul a b) = case (a, b) of (Lit 0, _) -> Lit 0 (Lit 1, _) -> b _ -> mul a b+ -- Some trivial div eliminations go (Div (Lit 0) _) = Lit 0 -- divide 0 by anything (including 0) is zero in EVM go (Div _ (Lit 0)) = Lit 0 -- divide anything by 0 is zero in EVM@@ -983,6 +1139,7 @@ canBeSat = constFoldProp simplified -- | Evaluate the provided proposition down to its most concrete result+-- Also simplifies the inner Expr, if it exists simplifyProp :: Prop -> Prop simplifyProp prop = let new = mapProp' go (simpInnerExpr prop)@@ -990,7 +1147,7 @@ where go :: Prop -> Prop - -- LT/LEq comparisions+ -- LT/LEq comparisons go (PLT (Var _) (Lit 0)) = PBool False go (PLEq (Lit 0) (Var _)) = PBool True go (PLT (Lit val) (Var _)) | val == maxLit = PBool False@@ -999,12 +1156,16 @@ go (PLEq (Lit l) (Lit r)) = PBool (l <= r) go (PLEq a (Max b _)) | a == b = PBool True go (PLEq a (Max _ b)) | a == b = PBool True+ go (PLT (Max (Lit a) b) (Lit c)) | a < c = PLT b (Lit c)+ go (PLT (Lit 0) (Eq a b)) = PEq a b -- negations go (PNeg (PBool b)) = PBool (Prelude.not b) go (PNeg (PNeg a)) = a -- solc specific stuff+ go (PEq (IsZero (Eq a b)) (Lit 0)) = PEq a b+ go (PEq (IsZero (IsZero (Eq a b))) (Lit 0)) = PNeg (PEq a b) -- iszero(a) -> (a == 0) -- iszero(iszero(a))) -> ~(a == 0) -> a > 0@@ -1071,7 +1232,7 @@ -- removes redundant (constant True/False) props remRedundantProps :: [Prop] -> [Prop]-remRedundantProps p = collapseFalse . filter (\x -> x /= PBool True) . nubOrd $ p+remRedundantProps p = nubOrd $ collapseFalse . filter (\x -> x /= PBool True) $ p where collapseFalse ps = if isJust $ find (== PBool False) ps then [PBool False] else ps @@ -1137,7 +1298,7 @@ -- Simplify masked reads: -- ----- reads across the mask boundry+-- reads across the mask boundary -- return an abstract expression -- │ -- │@@ -1161,7 +1322,7 @@ -- indexWord 31 reads from the LSB -- indexWord i@(Lit idx) e@(And (Lit mask) w)- -- if the mask is all 1s then read from the undelrying word+ -- if the mask is all 1s then read from the underlying word -- we need this case to avoid overflow | mask == fullWordMask = indexWord (Lit idx) w -- if the index is a read from the masked region then read from the underlying word@@ -1176,7 +1337,7 @@ , isByteAligned mask , idx < unmaskedBytes = LitByte 0- -- if the mask is not a power of 2, or it does not align with a byte boundry return an abstract expression+ -- if the mask is not a power of 2, or it does not align with a byte boundary return an abstract expression | idx <= 31 = IndexWord i e -- reads outside the range of the source word return 0 | otherwise = LitByte 0@@ -1270,6 +1431,8 @@ min x y = normArgs Min Prelude.min x y max :: Expr EWord -> Expr EWord -> Expr EWord+max (Lit 0) y = y+max x (Lit 0) = x max x y = normArgs Max Prelude.max x y numBranches :: Expr End -> Int@@ -1309,6 +1472,7 @@ oneRun ps2 startState = (execState (mapM (go . simplifyProp) ps2) startState).canBeSat go :: Prop -> State ConstState () go x = case x of+ -- PEq PEq (Lit l) a -> do s <- get case Map.lookup a s.values of@@ -1319,6 +1483,16 @@ let vs' = Map.insert a l s.values put $ s{values=vs'} PEq a b@(Lit _) -> go (PEq b a)+ -- PNeg+ PNeg (PEq (Lit l) a) -> do+ s <- get+ case Map.lookup a s.values of+ Just l2 -> case l==l2 of+ True -> put ConstState {canBeSat=False, values=mempty}+ False -> pure ()+ Nothing -> pure()+ PNeg (PEq a b@(Lit _)) -> go $ PNeg (PEq b a)+ -- Others PAnd a b -> do go a go b@@ -1333,3 +1507,24 @@ put $ s{canBeSat=(s2.canBeSat && (v1 || v2))} PBool False -> put $ ConstState {canBeSat=False, values=mempty} _ -> pure ()++-- Concretize & simplify Keccak expressions until fixed-point.+concKeccakSimpExpr :: Expr a -> Expr a+concKeccakSimpExpr orig = untilFixpoint ((mapExpr concKeccakOnePass) . simplify) orig++-- Only concretize Keccak in Props+-- Needed because if it also simplified, we may not find some simplification errors, as+-- simplification would always be ON+concKeccakProps :: [Prop] -> [Prop]+concKeccakProps orig = untilFixpoint (map (mapProp concKeccakOnePass)) orig++-- Simplifies in case the input to the Keccak is of specific array/map format and+-- can be simplified into a concrete value+-- Turns (Keccak ConcreteBuf) into a Lit+concKeccakOnePass :: Expr a -> Expr a+concKeccakOnePass (Keccak (ConcreteBuf bs)) = Lit (keccak' bs)+concKeccakOnePass orig@(Keccak (CopySlice (Lit 0) (Lit 0) (Lit 64) orig2@(WriteWord (Lit 0) _ (ConcreteBuf bs)) (ConcreteBuf ""))) =+ case (BS.length bs, (copySlice (Lit 0) (Lit 0) (Lit 64) (simplify orig2) (ConcreteBuf ""))) of+ (64, ConcreteBuf a) -> Lit (keccak' a)+ _ -> orig+concKeccakOnePass x = x
src/EVM/Fetch.hs view
@@ -27,6 +27,8 @@ import Network.Wreq.Session qualified as Session import Numeric.Natural (Natural) import System.Process+import Control.Monad.IO.Class+import EVM.Effects -- | Abstract representation of an RPC fetch request data RpcQuery a where@@ -182,23 +184,23 @@ sess <- Session.newAPISession fetchQuery Latest (fetchWithSession url sess) QueryChainId -http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher s+http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher t m s http smtjobs smttimeout n url q = withSolvers Z3 smtjobs smttimeout $ \s -> oracle s (Just (n, url)) q -zero :: Natural -> Maybe Natural -> Fetcher s+zero :: Natural -> Maybe Natural -> Fetcher t m s zero smtjobs smttimeout q = withSolvers Z3 smtjobs smttimeout $ \s -> oracle s Nothing q -- smtsolving + (http or zero)-oracle :: SolverGroup -> RpcInfo -> Fetcher s+oracle :: SolverGroup -> RpcInfo -> Fetcher t m s oracle solvers info q = do case q of PleaseDoFFI vals continue -> case vals of cmd : args -> do- (_, stdout', _) <- readProcessWithExitCode cmd args ""+ (_, stdout', _) <- liftIO $ readProcessWithExitCode cmd args "" pure . continue . encodeAbiValue $ AbiTuple (RegularVector.fromList [ AbiBytesDynamic . hexText . pack $ stdout']) _ -> internalError (show vals)@@ -215,7 +217,7 @@ AbstractBase -> unknownContract (LitAddr addr) EmptyBase -> emptyContract in pure $ Just c- Just (n, url) -> fetchContractFrom n url addr+ Just (n, url) -> liftIO $ fetchContractFrom n url addr case contract of Just x -> pure $ continue x Nothing -> internalError $ "oracle error: " ++ show q@@ -224,27 +226,28 @@ case info of Nothing -> pure (continue 0) Just (n, url) ->- fetchSlotFrom n url addr slot >>= \case+ liftIO $ fetchSlotFrom n url addr slot >>= \case Just x -> pure (continue x) Nothing -> internalError $ "oracle error: " ++ show q -type Fetcher s = Query s -> IO (EVM s ())+type Fetcher t m s = App m => Query t s -> m (EVM t s ()) -- | Checks which branches are satisfiable, checking the pathconditions for consistency -- if the third argument is true. -- When in debug mode, we do not want to be able to navigate to dead paths, -- but for normal execution paths with inconsistent pathconditions -- will be pruned anyway.-checkBranch :: SolverGroup -> Prop -> Prop -> IO BranchCondition+checkBranch :: App m => SolverGroup -> Prop -> Prop -> m BranchCondition checkBranch solvers branchcondition pathconditions = do- checkSat solvers (assertProps abstRefineDefault [(branchcondition .&& pathconditions)]) >>= \case+ conf <- readConfig+ liftIO $ checkSat solvers (assertProps conf [(branchcondition .&& pathconditions)]) >>= \case -- the condition is unsatisfiable Unsat -> -- if pathconditions are consistent then the condition must be false pure $ Case False -- Sat means its possible for condition to hold- Sat _ -> -- is its negation also possible?- checkSat solvers (assertProps abstRefineDefault [(pathconditions .&& (PNeg branchcondition))]) >>= \case+ Sat _ -> do -- is its negation also possible?+ checkSat solvers (assertProps conf [(pathconditions .&& (PNeg branchcondition))]) >>= \case -- No. The condition must hold Unsat -> pure $ Case True -- Yes. Both branches possible
src/EVM/Format.hs view
@@ -5,6 +5,7 @@ ( formatExpr , formatSomeExpr , formatPartial+ , formatProp , contractNamePart , contractPathPart , showError@@ -17,7 +18,6 @@ , showWordExact , showWordExplanation , parenthesise- , unindexed , showValue , textValues , showAbiValue@@ -38,7 +38,7 @@ import EVM.Types import EVM (traceForest, traceForest', traceContext, cheatCode) import EVM.ABI (getAbiSeq, parseTypeName, AbiValue(..), AbiType(..), SolError(..), Indexed(..), Event(..))-import EVM.Dapp (DappContext(..), DappInfo(..), showTraceLocation)+import EVM.Dapp (DappContext(..), DappInfo(..), findSrc, showTraceLocation) import EVM.Expr qualified as Expr import EVM.Solidity (SolcContract(..), Method(..), contractName, abiMap) @@ -53,10 +53,10 @@ import Data.Char qualified as Char import Data.DoubleWord (signedWord) import Data.Foldable (toList)-import Data.List (isPrefixOf)+import Data.List (isPrefixOf, sort) import Data.Map (Map) import Data.Map qualified as Map-import Data.Maybe (catMaybes, fromMaybe, fromJust)+import Data.Maybe (catMaybes, fromMaybe) import Data.Text (Text, pack, unpack, intercalate, dropEnd, splitOn) import Data.Text qualified as T import Data.Text.Encoding qualified as T@@ -111,15 +111,9 @@ showAbiValue (AbiBytesDynamic bs) = formatBytes bs showAbiValue (AbiBytes _ bs) = formatBinary bs showAbiValue (AbiAddress addr) =- let dappinfo = ?context.info- contracts = ?context.env- name = case Map.lookup (LitAddr addr) contracts of+ let name = case Map.lookup (LitAddr addr) ?context.env of Nothing -> ""- Just contract ->- let hash = maybeLitWord contract.codehash- in case hash of- Just h -> maybeContractName' (preview (ix h % _2) dappinfo.solcByHash)- Nothing -> ""+ Just contract -> maybeContractName' (findSrc contract ?context.info) in name <> "@" <> (pack $ show addr) showAbiValue v = pack $ show v@@ -190,7 +184,7 @@ formatSBinary (AbstractBuf t) = "<" <> t <> " abstract buf>" formatSBinary _ = internalError "formatSBinary: implement me" -showTraceTree :: DappInfo -> VM s -> Text+showTraceTree :: DappInfo -> VM t s -> Text showTraceTree dapp vm = let forest = traceForest vm traces = fmap (fmap (unpack . showTrace dapp (vm.env.contracts))) forest@@ -203,9 +197,6 @@ traces = fmap (fmap (unpack . showTrace dapp (traceContext leaf))) forest in pack $ concatMap showTree traces -unindexed :: [(Text, AbiType, Indexed)] -> [AbiType]-unindexed ts = [t | (_, t, NotIndexed) <- ts]- showTrace :: DappInfo -> Map (Expr EAddr) Contract -> Trace -> Text showTrace dapp env trace = let ?context = DappContext { info = dapp, env = env }@@ -214,37 +205,17 @@ case showTraceLocation dapp trace of Left x -> " \x1b[1m" <> x <> "\x1b[0m" Right x -> " \x1b[1m(" <> x <> ")\x1b[0m"- fullAbiMap = dapp.abiMap in case trace.tracedata of EventTrace _ bytes topics ->- let logn = mconcat- [ "\x1b[36m"- , "log" <> (pack (show (length topics)))- , parenthesise ((map (pack . show) topics) ++ [formatSBinary bytes])- , "\x1b[0m"- ] <> pos- knownTopic name types = mconcat- [ "\x1b[36m"- , name- , showValues (unindexed types) bytes- -- todo: show indexed- , "\x1b[0m"- ] <> pos- lognote sig usr = mconcat- [ "\x1b[36m"- , "LogNote"- , parenthesise [sig, usr, "..."]- , "\x1b[0m"- ] <> pos- in case topics of+ case topics of [] -> logn- (t1:_) ->- case maybeLitWord t1 of+ firstTopic:restTopics ->+ case maybeLitWord firstTopic of Just topic -> case Map.lookup topic dapp.eventMap of- Just (Event name _ types) ->- knownTopic name types+ Just (Event name _ argInfos) ->+ showEvent name argInfos restTopics Nothing -> case topics of [_, t2, _, _] ->@@ -273,7 +244,56 @@ logn Nothing -> logn+ where+ logn = mconcat+ [ "\x1b[36m"+ , "log" <> (pack (show (length topics)))+ , parenthesise ((map (pack . show) topics) ++ [formatSBinary bytes])+ , "\x1b[0m"+ ] <> pos + showEvent eventName argInfos indexedTopics = mconcat+ [ "\x1b[36m"+ , eventName+ , parenthesise (snd <$> sort (unindexedArgs <> indexedArgs))+ , "\x1b[0m"+ ] <> pos+ where+ -- We maintain the original position of event arguments since indexed+ -- and not indexed arguments can be interleaved.+ unindexedArgs :: [(Int, Text)]+ unindexedArgs =+ let (positions, names, abiTypes) = unzip3 (filterArgInfos NotIndexed)+ in zip positions (zipWith withName names (textValues abiTypes bytes))++ indexedArgs :: [(Int, Text)]+ indexedArgs =+ let (positions, names, abiTypes) = unzip3 (filterArgInfos Indexed)+ in zip positions (zipWith withName names (zipWith showTopic abiTypes indexedTopics))+ where+ showTopic :: AbiType -> Expr EWord -> Text+ showTopic abiType topic =+ case maybeLitWord (Expr.concKeccakSimpExpr topic) of+ Just w -> head $ textValues [abiType] (ConcreteBuf (word256Bytes w))+ _ -> "<symbolic>"++ withName :: Text -> Text -> Text+ withName "" value = value+ withName argName value = argName <> "=" <> value++ filterArgInfos :: Indexed -> [(Int, Text, AbiType)]+ filterArgInfos which =+ [ (i, argName, argType) | (i, (argName, argType, indexed)) <- zip [0..] argInfos+ , indexed == which+ ]++ lognote sig usr = mconcat+ [ "\x1b[36m"+ , "LogNote"+ , parenthesise [sig, usr, "..."]+ , "\x1b[0m"+ ] <> pos+ ErrorTrace e -> case e of Revert out ->@@ -281,9 +301,9 @@ _ -> "\x1b[91merror\x1b[0m " <> pack (show e) <> pos - ReturnTrace out (CallContext _ _ _ _ _ (Just abi) _ _ _) ->+ ReturnTrace out (CallContext { abi = Just abi }) -> "← " <>- case Map.lookup (unsafeInto abi) fullAbiMap of+ case Map.lookup (unsafeInto abi) dapp.abiMap of Just m -> case unzip m.output of ([], []) ->@@ -299,29 +319,23 @@ in "← " <> formatExpr l <> " bytes of code" EntryTrace t -> t- FrameTrace (CreationContext addr (Lit hash) _ _ ) -> -- FIXME: irrefutable pattern- "create "- <> maybeContractName (preview (ix hash % _2) dapp.solcByHash)- <> "@" <> formatAddr addr- <> pos- FrameTrace (CreationContext addr _ _ _ ) ->+ FrameTrace (CreationContext { address }) -> "create "- <> "<unknown contract>"- <> "@" <> formatAddr addr+ <> maybeContractName (findSrcFromAddr address)+ <> "@" <> formatAddr address <> pos- FrameTrace (CallContext target context _ _ hash abi calldata _ _) ->+ FrameTrace (CallContext { target, context, abi, calldata }) -> let calltype = if target == context then "call " else "delegatecall "- hash' = fromJust $ maybeLitWord hash- in case preview (ix hash' % _2) dapp.solcByHash of+ in case findSrcFromAddr target of Nothing -> calltype <> case target of LitAddr 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D -> "HEVM" _ -> formatAddr target <> pack "::"- <> case Map.lookup (unsafeInto (fromMaybe 0x00 abi)) fullAbiMap of+ <> case Map.lookup (unsafeInto (fromMaybe 0x00 abi)) dapp.abiMap of Just m -> "\x1b[1m" <> m.name@@ -344,6 +358,10 @@ (abi >>= fmap getAbiTypes . maybeAbiName solc) <> "\x1b[0m" <> pos+ where+ findSrcFromAddr addr = do+ contract <- Map.lookup addr env+ findSrc contract dapp formatAddr :: Expr EAddr -> Text formatAddr = \case@@ -397,6 +415,7 @@ ReturnDataOutOfBounds -> "Return data out of bounds" NonceOverflow -> "Nonce overflow" BadCheatCode a -> "Bad cheat code: sig: " <> show a+ NonexistentFork a -> "Fork ID does not exist: " <> show a prettyvmresult :: Expr End -> String prettyvmresult (Failure _ _ (Revert (ConcreteBuf ""))) = "Revert"@@ -668,12 +687,13 @@ , indent 2 $ formatExpr slot , "val:" , indent 2 $ formatExpr val+ , "store:"+ , indent 2 $ formatExpr prev ] , ")"- , formatExpr prev ]- AbstractStore a ->- "(AbstractStore " <> formatExpr a <> ")"+ AbstractStore a idx ->+ "(AbstractStore " <> formatExpr a <> " " <> T.pack (show idx) <> ")" ConcreteStore s -> if null s then "(ConcreteStore <empty>)" else T.unlines@@ -695,9 +715,10 @@ , "size: " <> formatExpr size , "src:" , indent 2 $ formatExpr src+ , "dst:"+ , indent 2 $ formatExpr dst ] , ")"- , formatExpr dst ] WriteWord idx val buf -> T.unlines [ "(WriteWord"@@ -706,18 +727,19 @@ , indent 2 $ formatExpr idx , "val:" , indent 2 $ formatExpr val+ , "buf:"+ , indent 2 $ formatExpr buf ] , ")"- , formatExpr buf ] WriteByte idx val buf -> T.unlines [ "(WriteByte" , indent 2 $ T.unlines [ "idx: " <> formatExpr idx , "val: " <> formatExpr val+ , "buf: " <> formatExpr buf ] , ")"- , formatExpr buf ] ConcreteBuf bs -> case bs of "" -> "(ConcreteBuf \"\")"
+ src/EVM/Fuzz.hs view
@@ -0,0 +1,263 @@+{-# 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
src/EVM/Keccak.hs view
@@ -14,31 +14,32 @@ import EVM.Types import EVM.Expr -newtype BuilderState = BuilderState- { keccaks :: Set (Expr EWord) }++newtype KeccakStore = KeccakStore+ { keccakEqs :: Set (Expr EWord) } deriving (Show) -initState :: BuilderState-initState = BuilderState { keccaks = Set.empty }+initState :: KeccakStore+initState = KeccakStore { keccakEqs = Set.empty } -go :: forall a. Expr a -> State BuilderState (Expr a)-go = \case+keccakFinder :: forall a. Expr a -> State KeccakStore (Expr a)+keccakFinder = \case e@(Keccak _) -> do s <- get- put $ s{keccaks=Set.insert e s.keccaks}+ put $ s{keccakEqs=Set.insert e s.keccakEqs} pure e e -> pure e -findKeccakExpr :: forall a. Expr a -> State BuilderState (Expr a)-findKeccakExpr e = mapExprM go e+findKeccakExpr :: forall a. Expr a -> State KeccakStore (Expr a)+findKeccakExpr e = mapExprM keccakFinder e -findKeccakProp :: Prop -> State BuilderState Prop-findKeccakProp p = mapPropM go p+findKeccakProp :: Prop -> State KeccakStore Prop+findKeccakProp p = mapPropM keccakFinder p -findKeccakPropsExprs :: [Prop] -> [Expr Buf] -> [Expr Storage]-> State BuilderState ()+findKeccakPropsExprs :: [Prop] -> [Expr Buf] -> [Expr Storage]-> State KeccakStore () findKeccakPropsExprs ps bufs stores = do- mapM_ findKeccakProp ps;- mapM_ findKeccakExpr bufs;+ mapM_ findKeccakProp ps+ mapM_ findKeccakExpr bufs mapM_ findKeccakExpr stores @@ -54,25 +55,39 @@ minProp k@(Keccak _) = PGT k (Lit 256) minProp _ = internalError "expected keccak expression" +concVal :: Expr EWord -> Prop+concVal k@(Keccak (ConcreteBuf bs)) = PEq (Lit (keccak' bs)) k+concVal _ = PBool True+ injProp :: (Expr EWord, Expr EWord) -> Prop injProp (k1@(Keccak b1), k2@(Keccak b2)) = POr ((b1 .== b2) .&& (bufLength b1 .== bufLength b2)) (PNeg (PEq k1 k2)) injProp _ = internalError "expected keccak expression" --- Takes a list of props, find all keccak occurences and generates two kinds of assumptions:++-- Takes a list of props, find all keccak occurrences and generates two kinds of assumptions: -- 1. Minimum output value: That the output of the invocation is greater than--- 50 (needed to avoid spurious counterexamples due to storage collisions+-- 256 (needed to avoid spurious counterexamples due to storage collisions -- with solidity mappings & value type storage slots) -- 2. Injectivity: That keccak is an injective function (we avoid quantifiers -- here by making this claim for each unique pair of keccak invocations -- discovered in the input expressions) keccakAssumptions :: [Prop] -> [Expr Buf] -> [Expr Storage] -> [Prop]-keccakAssumptions ps bufs stores = injectivity <> minValue+keccakAssumptions ps bufs stores = injectivity <> minValue <> minDiffOfPairs <> concValues where (_, st) = runState (findKeccakPropsExprs ps bufs stores) initState - injectivity = fmap injProp $ combine (Set.toList st.keccaks)- minValue = fmap minProp (Set.toList st.keccaks)+ injectivity = fmap injProp $ combine (Set.toList st.keccakEqs)+ concValues = fmap concVal (Set.toList st.keccakEqs)+ minValue = fmap minProp (Set.toList st.keccakEqs)+ minDiffOfPairs = map minDistance $ filter (uncurry (/=)) [(a,b) | a<-(Set.elems st.keccakEqs), b<-(Set.elems st.keccakEqs)]+ where+ minDistance :: (Expr EWord, Expr EWord) -> Prop+ minDistance (ka@(Keccak a), kb@(Keccak b)) = PImpl (a ./= b) (PAnd req1 req2)+ where+ req1 = (PGEq (Sub ka kb) (Lit 256))+ req2 = (PGEq (Sub kb ka) (Lit 256))+ minDistance _ = internalError "expected Keccak expression" compute :: forall a. Expr a -> [Prop] compute = \case
src/EVM/SMT.hs view
@@ -31,7 +31,8 @@ import Language.SMT2.Syntax (Symbol, SpecConstant(..), GeneralRes(..), Term(..), QualIdentifier(..), Identifier(..), Sort(..), Index(..), VarBinding(..)) import Numeric (readHex, readBin) import Witch (into, unsafeInto)-import Control.Monad.State+import Control.Monad.State (State, runState, get, put)+import EVM.Format (formatProp) import EVM.CSE import EVM.Expr (writeByte, bufLengthEnv, containsNode, bufLength, minLength, inRange)@@ -39,6 +40,7 @@ import EVM.Keccak (keccakAssumptions, keccakCompute) import EVM.Traversals import EVM.Types+import EVM.Effects -- ** Encoding ** ----------------------------------------------------------------------------------@@ -53,7 +55,7 @@ -- | buffer names and guesses at their maximum size , buffers :: Map Text (Expr EWord) -- | reads from abstract storage- , storeReads :: Map (Expr EAddr) (Set (Expr EWord))+ , storeReads :: Map (Expr EAddr, Maybe W256) (Set (Expr EWord)) -- | the names of any block context variables , blockContext :: [Text] -- | the names of any tx context variables@@ -103,15 +105,21 @@ -- | Used for abstraction-refinement of the SMT formula. Contains assertions that make our query fully precise. These will be added to the assertion stack if we get `sat` with the abstracted query.-newtype RefinementEqs = RefinementEqs [Builder]- deriving (Eq, Show, Monoid, Semigroup)+data RefinementEqs = RefinementEqs [Builder] [Prop]+ deriving (Eq, Show) +instance Semigroup RefinementEqs where+ (RefinementEqs a b) <> (RefinementEqs a2 b2) = RefinementEqs (a <> a2) (b <> b2)++instance Monoid RefinementEqs where+ mempty = RefinementEqs mempty mempty+ flattenBufs :: SMTCex -> Maybe SMTCex flattenBufs cex = do bs <- mapM collapse cex.buffers pure $ cex{ buffers = bs } --- | Attemps to collapse a compressed buffer representation down to a flattened one+-- | Attempts to collapse a compressed buffer representation down to a flattened one collapse :: BufModel -> Maybe BufModel collapse model = case toBuf model of Just (ConcreteBuf b) -> Just $ Flat b@@ -125,17 +133,20 @@ getVar :: SMTCex -> TS.Text -> W256 getVar cex name = fromJust $ Map.lookup (Var name) cex.vars -data SMT2 = SMT2 [Builder] RefinementEqs CexVars+data SMT2 = SMT2 [Builder] RefinementEqs CexVars [Prop] deriving (Eq, Show) instance Semigroup SMT2 where- (SMT2 a (RefinementEqs b) c) <> (SMT2 a2 (RefinementEqs b2) c2) = SMT2 (a <> a2) (RefinementEqs (b <> b2)) (c <> c2)+ (SMT2 a (RefinementEqs b refps) c d) <> (SMT2 a2 (RefinementEqs b2 refps2) c2 d2) = SMT2 (a <> a2) (RefinementEqs (b <> b2) (refps <> refps2)) (c <> c2) (d <> d2) instance Monoid SMT2 where- mempty = SMT2 mempty mempty mempty+ mempty = SMT2 mempty mempty mempty mempty formatSMT2 :: SMT2 -> Text-formatSMT2 (SMT2 ls _ _) = T.unlines (fmap toLazyText ls)+formatSMT2 (SMT2 ls _ _ ps) = expr <> smt2+ where+ expr = T.concat [T.singleton ';', T.replace "\n" "\n;" $ T.pack . TS.unpack $ TS.unlines (fmap formatProp ps)]+ smt2 = T.unlines (fmap toLazyText ls) -- | Reads all intermediate variables from the builder state and produces SMT declaring them as constants declareIntermediates :: BufEnv -> StoreEnv -> SMT2@@ -144,16 +155,16 @@ encBs = Map.mapWithKey encodeBuf bufs sorted = List.sortBy compareFst $ Map.toList $ encSs <> encBs decls = fmap snd sorted- smt2 = (SMT2 [fromText "; intermediate buffers & stores"] mempty mempty):decls- in foldr (<>) (SMT2[""] mempty mempty) smt2+ smt2 = (SMT2 [fromText "; intermediate buffers & stores"] mempty mempty mempty):decls+ in foldr (<>) (SMT2[""] mempty mempty mempty) smt2 where compareFst (l, _) (r, _) = compare l r encodeBuf n expr =- SMT2 ["(define-const buf" <> (fromString . show $ n) <> " Buf " <> exprToSMT expr <> ")\n"] mempty mempty <> encodeBufLen n expr+ SMT2 ["(define-fun buf" <> (fromString . show $ n) <> "() Buf " <> exprToSMT expr <> ")\n"] mempty mempty mempty <> encodeBufLen n expr encodeBufLen n expr =- SMT2 ["(define-const buf" <> (fromString . show $ n) <>"_length" <> " (_ BitVec 256) " <> exprToSMT (bufLengthEnv bufs True expr) <> ")"] mempty mempty+ SMT2 ["(define-fun buf" <> (fromString . show $ n) <>"_length () (_ BitVec 256) " <> exprToSMT (bufLengthEnv bufs True expr) <> ")"] mempty mempty mempty encodeStore n expr =- SMT2 ["(define-const store" <> (fromString . show $ n) <> " Storage " <> exprToSMT expr <> ")"] mempty mempty+ SMT2 ["(define-fun store" <> (fromString . show $ n) <> " () Storage " <> exprToSMT expr <> ")"] mempty mempty mempty data AbstState = AbstState { words :: Map (Expr EWord) Int@@ -161,21 +172,21 @@ } deriving (Show) -abstractAwayProps :: AbstRefineConfig -> [Prop] -> ([Prop], AbstState)-abstractAwayProps abstRefineConfig ps = runState (mapM abstrAway ps) (AbstState mempty 0)+abstractAwayProps :: Config -> [Prop] -> ([Prop], AbstState)+abstractAwayProps conf ps = runState (mapM abstrAway ps) (AbstState mempty 0) where abstrAway :: Prop -> State AbstState Prop abstrAway prop = mapPropM go prop go :: Expr a -> State AbstState (Expr a) go x = case x of- e@(Mod{}) | abstRefineConfig.arith -> abstrExpr e- e@(SMod{}) | abstRefineConfig.arith -> abstrExpr e- e@(MulMod{}) | abstRefineConfig.arith -> abstrExpr e- e@(AddMod{}) | abstRefineConfig.arith -> abstrExpr e- e@(Mul{}) | abstRefineConfig.arith -> abstrExpr e- e@(Div{}) | abstRefineConfig.arith -> abstrExpr e- e@(SDiv {}) | abstRefineConfig.arith -> abstrExpr e- e@(ReadWord {}) | abstRefineConfig.mem -> abstrExpr e+ e@(Mod{}) | conf.abstRefineArith -> abstrExpr e+ e@(SMod{}) | conf.abstRefineArith -> abstrExpr e+ e@(MulMod{}) | conf.abstRefineArith -> abstrExpr e+ e@(AddMod{}) | conf.abstRefineArith -> abstrExpr e+ e@(Mul{}) | conf.abstRefineArith -> abstrExpr e+ e@(Div{}) | conf.abstRefineArith -> abstrExpr e+ e@(SDiv {}) | conf.abstRefineArith -> abstrExpr e+ e@(ReadWord {}) | conf.abstRefineMem -> abstrExpr e e -> pure e where abstrExpr e = do@@ -190,16 +201,22 @@ pure $ Var (TS.pack ("abst_" ++ show next)) smt2Line :: Builder -> SMT2-smt2Line txt = SMT2 [txt] mempty mempty+smt2Line txt = SMT2 [txt] mempty mempty mempty -assertProps :: AbstRefineConfig -> [Prop] -> SMT2-assertProps conf ps = assertPropsNoSimp conf (Expr.simplifyProps ps)+assertProps :: Config -> [Prop] -> SMT2+-- simplify to rewrite sload/sstore combos+-- notice: it is VERY important not to concretize, because Keccak assumptions+-- will be generated by assertPropsNoSimp, and that needs unconcretized Props+assertProps conf ps = assertPropsNoSimp conf (decompose . Expr.simplifyProps $ ps)+ where+ decompose :: [Prop] -> [Prop]+ decompose props = fromMaybe props (mapM (mapPropM Expr.decomposeStorage) props) --- Note: we need a version that does NOT call simplify or simplifyProps,+-- Note: we need a version that does NOT call simplify, -- because we make use of it to verify the correctness of our simplification -- passes through property-based testing.-assertPropsNoSimp :: AbstRefineConfig -> [Prop] -> SMT2-assertPropsNoSimp abstRefineConfig ps =+assertPropsNoSimp :: Config -> [Prop] -> SMT2+assertPropsNoSimp config psPreConc = let encs = map propToSMT psElimAbst abstSMT = map propToSMT abstProps intermediates = declareIntermediates bufs stores in@@ -208,7 +225,7 @@ <> smt2Line "" <> (declareAddrs addresses) <> smt2Line ""- <> (declareBufs psElim bufs stores)+ <> (declareBufs toDeclarePsElim bufs stores) <> smt2Line "" <> (declareVars . nubOrd $ foldl (<>) [] allVars) <> smt2Line ""@@ -218,17 +235,19 @@ <> smt2Line "" <> intermediates <> smt2Line ""- <> keccakAssumes+ <> keccakAssertions <> readAssumes <> smt2Line ""- <> SMT2 (fmap (\p -> "(assert " <> p <> ")") encs) mempty mempty- <> SMT2 mempty (RefinementEqs $ fmap (\p -> "(assert " <> p <> ")") abstSMT) mempty- <> SMT2 mempty mempty mempty { storeReads = storageReads }+ <> SMT2 (fmap (\p -> "(assert " <> p <> ")") encs) mempty mempty mempty+ <> SMT2 mempty (RefinementEqs (fmap (\p -> "(assert " <> p <> ")") abstSMT) (psElimAbst <> abstProps)) mempty mempty+ <> SMT2 mempty mempty mempty { storeReads = storageReads } mempty+ <> SMT2 mempty mempty mempty psPreConc where+ ps = Expr.concKeccakProps psPreConc (psElim, bufs, stores) = eliminateProps ps- (psElimAbst, abst@(AbstState abstExprToInt _)) = if abstRefineConfig.arith || abstRefineConfig.mem- then abstractAwayProps abstRefineConfig psElim+ (psElimAbst, abst@(AbstState abstExprToInt _)) = if config.abstRefineArith || config.abstRefineMem+ then abstractAwayProps config psElim else (psElim, AbstState mempty 0) abstProps = map toProp (Map.toList abstExprToInt)@@ -236,34 +255,45 @@ toProp :: (Expr EWord, Int) -> Prop toProp (e, num) = PEq e (Var (TS.pack ("abst_" ++ (show num)))) - allVars = fmap referencedVars psElim <> fmap referencedVars bufVals <> fmap referencedVars storeVals <> [abstrVars abst]- frameCtx = fmap referencedFrameContext psElim <> fmap referencedFrameContext bufVals <> fmap referencedFrameContext storeVals- blockCtx = fmap referencedBlockContext psElim <> fmap referencedBlockContext bufVals <> fmap referencedBlockContext storeVals+ -- Props storing info that need declaration(s)+ toDeclarePs = ps <> keccAssump <> keccComp+ toDeclarePsElim = psElim <> keccAssump <> keccComp + -- vars, frames, and block contexts in need of declaration+ allVars = fmap referencedVars toDeclarePsElim <> fmap referencedVars bufVals <> fmap referencedVars storeVals <> [abstrVars abst]+ frameCtx = fmap referencedFrameContext toDeclarePsElim <> fmap referencedFrameContext bufVals <> fmap referencedFrameContext storeVals+ blockCtx = fmap referencedBlockContext toDeclarePsElim <> fmap referencedBlockContext bufVals <> fmap referencedBlockContext storeVals+ abstrVars :: AbstState -> [Builder] abstrVars (AbstState b _) = map ((\v->fromString ("abst_" ++ show v)) . snd) (Map.toList b) + -- Buf, Storage, etc. declarations needed bufVals = Map.elems bufs storeVals = Map.elems stores- storageReads = Map.unionsWith (<>) $ fmap findStorageReads ps- abstractStores = Set.toList $ Set.unions (fmap referencedAbstractStores ps)- addresses = Set.toList $ Set.unions (fmap referencedWAddrs ps)+ storageReads = Map.unionsWith (<>) $ fmap findStorageReads toDeclarePs+ abstractStores = Set.toList $ Set.unions (fmap referencedAbstractStores toDeclarePs)+ addresses = Set.toList $ Set.unions (fmap referencedWAddrs toDeclarePs) - keccakAssumes+ -- Keccak assertions: concrete values, distance between pairs, injectivity, etc.+ -- This will make sure concrete values of Keccak are asserted, if they can be computed (i.e. can be concretized)+ keccAssump = keccakAssumptions psPreConc bufVals storeVals+ keccComp = keccakCompute psPreConc bufVals storeVals+ keccakAssertions = smt2Line "; keccak assumptions"- <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakAssumptions psElim bufVals storeVals)) mempty mempty+ <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") keccAssump) mempty mempty mempty <> smt2Line "; keccak computations"- <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakCompute psElim bufVals storeVals)) mempty mempty+ <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") keccComp) mempty mempty mempty + -- assert that reads beyond size of buffer & storage is zero readAssumes = smt2Line "; read assumptions"- <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (assertReads psElim bufs stores)) mempty mempty+ <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (assertReads psElim bufs stores)) mempty mempty mempty referencedAbstractStores :: TraversableTerm a => a -> Set Builder referencedAbstractStores term = foldTerm go mempty term where go = \case- AbstractStore s -> Set.singleton (storeName s)+ AbstractStore s idx -> Set.singleton (storeName s idx) _ -> mempty referencedWAddrs :: TraversableTerm a => a -> Set Builder@@ -320,16 +350,16 @@ -- the store (e.g, SLoad addr idx (SStore addr idx val AbstractStore)). -- However, we expect that most of such reads will have been -- simplified away.-findStorageReads :: Prop -> Map (Expr EAddr) (Set (Expr EWord))+findStorageReads :: Prop -> Map (Expr EAddr, Maybe W256) (Set (Expr EWord)) findStorageReads p = Map.fromListWith (<>) $ foldProp go mempty p where- go :: Expr a -> [(Expr EAddr, Set (Expr EWord))]+ go :: Expr a -> [((Expr EAddr, Maybe W256), Set (Expr EWord))] go = \case SLoad slot store ->- [((fromMaybe (error $ "Internal Error: could not extract address from: " <> show store) (Expr.getAddr store)), Set.singleton slot) | containsNode isAbstractStore store]+ [((fromMaybe (internalError $ "could not extract address from: " <> show store) (Expr.getAddr store), Expr.getLogicalIdx store), Set.singleton slot) | containsNode isAbstractStore store] _ -> [] - isAbstractStore (AbstractStore _) = True+ isAbstractStore (AbstractStore _ _) = True isAbstractStore _ = False findBufferAccess :: TraversableTerm a => [a] -> [(Expr EWord, Expr EWord, Expr Buf)]@@ -395,7 +425,7 @@ -- | Returns an SMT2 object with all buffers referenced from the input props declared, and with the appropriate cex extraction metadata attached declareBufs :: [Prop] -> BufEnv -> StoreEnv -> SMT2-declareBufs props bufEnv storeEnv = SMT2 ("; buffers" : fmap declareBuf allBufs <> ("; buffer lengths" : fmap declareLength allBufs)) mempty cexvars+declareBufs props bufEnv storeEnv = SMT2 ("; buffers" : fmap declareBuf allBufs <> ("; buffer lengths" : fmap declareLength allBufs)) mempty cexvars mempty where cexvars = (mempty :: CexVars){ buffers = discoverMaxReads props bufEnv storeEnv } allBufs = fmap fromLazyText $ Map.keys cexvars.buffers@@ -404,39 +434,39 @@ -- Given a list of variable names, create an SMT2 object with the variables declared declareVars :: [Builder] -> SMT2-declareVars names = SMT2 (["; variables"] <> fmap declare names) mempty cexvars+declareVars names = SMT2 (["; variables"] <> fmap declare names) mempty cexvars mempty where declare n = "(declare-const " <> n <> " (_ BitVec 256))" cexvars = (mempty :: CexVars){ calldata = fmap toLazyText names } -- Given a list of variable names, create an SMT2 object with the variables declared declareAddrs :: [Builder] -> SMT2-declareAddrs names = SMT2 (["; symbolic addresseses"] <> fmap declare names) mempty cexvars+declareAddrs names = SMT2 (["; symbolic addresseses"] <> fmap declare names) mempty cexvars mempty where declare n = "(declare-const " <> n <> " Addr)" cexvars = (mempty :: CexVars){ addrs = fmap toLazyText names } declareFrameContext :: [(Builder, [Prop])] -> SMT2-declareFrameContext names = SMT2 (["; frame context"] <> concatMap declare names) mempty cexvars+declareFrameContext names = SMT2 (["; frame context"] <> concatMap declare names) mempty cexvars mempty where declare (n,props) = [ "(declare-const " <> n <> " (_ BitVec 256))" ] <> fmap (\p -> "(assert " <> propToSMT p <> ")") props cexvars = (mempty :: CexVars){ txContext = fmap (toLazyText . fst) names } declareAbstractStores :: [Builder] -> SMT2-declareAbstractStores names = SMT2 (["; abstract base stores"] <> fmap declare names) mempty mempty+declareAbstractStores names = SMT2 (["; abstract base stores"] <> fmap declare names) mempty mempty mempty where declare n = "(declare-const " <> n <> " Storage)" declareBlockContext :: [(Builder, [Prop])] -> SMT2-declareBlockContext names = SMT2 (["; block context"] <> concatMap declare names) mempty cexvars+declareBlockContext names = SMT2 (["; block context"] <> concatMap declare names) mempty cexvars mempty where declare (n, props) = [ "(declare-const " <> n <> " (_ BitVec 256))" ] <> fmap (\p -> "(assert " <> propToSMT p <> ")") props cexvars = (mempty :: CexVars){ blockContext = fmap (toLazyText . fst) names } prelude :: SMT2-prelude = SMT2 src mempty mempty+prelude = SMT2 src mempty mempty mempty where src = fmap (fromLazyText . T.drop 2) . T.lines $ [i| ; logic@@ -666,7 +696,7 @@ Mul a b -> op2 "bvmul" a b Exp a b -> case b of Lit b' -> expandExp a b'- _ -> internalError "cannot encode symbolic exponentation into SMT"+ _ -> internalError "cannot encode symbolic exponentiation into SMT" Min a b -> let aenc = exprToSMT a benc = exprToSMT b in@@ -788,8 +818,10 @@ "(writeWord " <> encIdx `sp` encVal `sp` encPrev <> ")" CopySlice srcIdx dstIdx size src dst -> copySlice srcIdx dstIdx size (exprToSMT src) (exprToSMT dst)++ -- we need to do a bit of processing here. ConcreteStore s -> encodeConcreteStore s- AbstractStore a -> storeName a+ AbstractStore a idx -> storeName a idx SStore idx val prev -> let encIdx = exprToSMT idx encVal = exprToSMT val@@ -909,8 +941,9 @@ encVal = exprToSMT (Lit val) in "(store " <> prev `sp` encKey `sp` encVal <> ")" -storeName :: Expr EAddr -> Builder-storeName a = fromString ("baseStore_") <> formatEAddr a+storeName :: Expr EAddr -> Maybe W256 -> Builder+storeName a Nothing = fromString ("baseStore_") <> formatEAddr a+storeName a (Just idx) = fromString ("baseStore_") <> formatEAddr a <> "_" <> (fromString $ show idx) formatEAddr :: Expr EAddr -> Builder formatEAddr = \case@@ -1066,15 +1099,15 @@ p -> parseErr p -- | Takes a Map containing all reads from a store with an abstract base, as--- well as the conrete part of the storage prestate and returns a fully+-- well as the concrete part of the storage prestate and returns a fully -- concretized storage getStore :: (Text -> IO Text)- -> Map (Expr EAddr) (Set (Expr EWord))+ -> Map (Expr EAddr, Maybe W256) (Set (Expr EWord)) -> IO (Map (Expr EAddr) (Map W256 W256)) getStore getVal abstractReads =- fmap Map.fromList $ forM (Map.toList abstractReads) $ \(addr, slots) -> do- let name = toLazyText (storeName addr)+ fmap Map.fromList $ forM (Map.toList abstractReads) $ \((addr, idx), slots) -> do+ let name = toLazyText (storeName addr idx) raw <- getVal name let parsed = case parseCommentFreeFileMsg getValueRes (T.toStrict raw) of Right (ResSpecific (valParsed :| [])) -> valParsed
src/EVM/Solidity.hs view
@@ -48,13 +48,14 @@ import Control.Applicative import Control.Monad+import Control.Monad.IO.Unlift import Data.Aeson hiding (json) import Data.Aeson.Types import Data.Aeson.Optics import Data.Aeson.Key qualified as Key import Data.Aeson.KeyMap qualified as KeyMap import Data.Scientific-import Data.ByteString (ByteString)+import Data.ByteString (ByteString, readFile) import Data.ByteString qualified as BS import Data.ByteString.Base16 qualified as BS16 import Data.ByteString.Lazy (toStrict)@@ -72,7 +73,6 @@ import Data.Text (pack, intercalate) import Data.Text qualified as T import Data.Text.Encoding (encodeUtf8, decodeUtf8)-import Data.Text.IO (readFile) import Data.Vector (Vector) import Data.Vector qualified as Vector import Data.Word (Word8)@@ -184,7 +184,7 @@ mempty = BuildOutput mempty mempty -- | The various project types understood by hevm-data ProjectType = DappTools | CombinedJSON | Foundry+data ProjectType = DappTools | CombinedJSON | Foundry | FoundryStdLib deriving (Eq, Show, Read, ParseField) data SourceCache = SourceCache@@ -282,7 +282,7 @@ go c (xs, state, p) = (xs, internalError ("srcmap: y u " ++ show c ++ " in state" ++ show state ++ "?!?"), p) --- | Reads all solc ouput json files found under the provided filepath and returns them merged into a BuildOutput+-- | Reads all solc output json files found under the provided filepath and returns them merged into a BuildOutput readBuildOutput :: FilePath -> ProjectType -> IO (Either String BuildOutput) readBuildOutput root DappTools = do let outDir = root </> "out"@@ -298,7 +298,7 @@ [x] -> readSolc CombinedJSON root (outDir </> x) [] -> pure . Left $ "no json files found in: " <> outDir _ -> pure . Left $ "multiple json files found in: " <> outDir-readBuildOutput root Foundry = do+readBuildOutput root _ = do let outDir = root </> "out" jsons <- findJsonFiles outDir case (filterMetadata jsons) of@@ -309,7 +309,8 @@ -- | Finds all json files under the provided filepath, searches recursively findJsonFiles :: FilePath -> IO [FilePath]-findJsonFiles root = getDirectoryFiles root ["**/*.json"]+findJsonFiles root = filter (not . isInfixOf "kompiled") -- HACK: this gets added to `out` by `kontrol`+ <$> getDirectoryFiles root ["**/*.json"] -- | Filters out metadata json files filterMetadata :: [FilePath] -> [FilePath]@@ -342,8 +343,12 @@ else Just (s1 - s2, min (s2 + n2 - s1) n1) readSolc :: ProjectType -> FilePath -> FilePath -> IO (Either String BuildOutput)-readSolc pt root fp =- (readJSON pt (T.pack $ takeBaseName fp) <$> readFile fp) >>=+readSolc pt root fp = do+ -- NOTE: we cannot and must not use Data.Text.IO.readFile because that takes the locale+ -- and may fail with very strange errors when the JSON it's reading+ -- contains any UTF-8 character -- which it will with foundry+ let fileContents = fmap Data.Text.Encoding.decodeUtf8 (Data.ByteString.readFile fp)+ (readJSON pt (T.pack $ takeBaseName fp) <$> fileContents) >>= \case Nothing -> pure . Left $ "unable to parse: " <> fp Just (contracts, asts, sources) -> do@@ -366,14 +371,18 @@ bytecode = c ^?! key "evm" ^?! key "deployedBytecode" ^?! key "object" % _String pure $ (toCode contractName) <$> (Just bytecode) -solidity :: Text -> Text -> IO (Maybe ByteString)-solidity contract src = do+solidity+ :: (MonadUnliftIO m)+ => Text -> Text -> m (Maybe ByteString)+solidity contract src = liftIO $ do json <- solc Solidity src let (Contracts sol, _, _) = fromJust $ readStdJSON json pure $ Map.lookup ("hevm.sol:" <> contract) sol <&> (.creationCode) -solcRuntime :: Text -> Text -> IO (Maybe ByteString)-solcRuntime contract src = do+solcRuntime+ :: (MonadUnliftIO m)+ => Text -> Text -> m (Maybe ByteString)+solcRuntime contract src = liftIO $ do json <- solc Solidity src case readStdJSON json of Just (Contracts sol, _, _) -> pure $ Map.lookup ("hevm.sol:" <> contract) sol <&> (.runtimeCode)@@ -395,7 +404,7 @@ readJSON :: ProjectType -> Text -> Text -> Maybe (Contracts, Asts, Sources) readJSON DappTools _ json = readStdJSON json readJSON CombinedJSON _ json = readCombinedJSON json-readJSON Foundry contractName json = readFoundryJSON contractName json+readJSON _ contractName json = readFoundryJSON contractName json -- | Reads a foundry json output readFoundryJSON :: Text -> Text -> Maybe (Contracts, Asts, Sources)
src/EVM/Solvers.hs view
@@ -12,17 +12,20 @@ import Control.Concurrent (forkIO, killThread) import Control.Monad import Control.Monad.State.Strict+import Control.Monad.IO.Unlift import Data.Char (isSpace) import Data.Map (Map) import Data.Map qualified as Map-import Data.Maybe (fromMaybe)+import Data.Maybe (fromMaybe, isJust, fromJust) import Data.Text qualified as TS import Data.Text.Lazy (Text) import Data.Text.Lazy qualified as T import Data.Text.Lazy.IO qualified as T import Data.Text.Lazy.Builder-import System.Process (createProcess, cleanupProcess, proc, ProcessHandle, std_in, std_out, std_err, StdStream(..))+import System.Process (createProcess, cleanupProcess, proc, ProcessHandle, std_in, std_out, std_err, StdStream(..), createPipe) import Witch (into)+import EVM.Effects+import EVM.Fuzz (tryCexFuzz) import EVM.SMT import EVM.Types (W256, Expr(AbstractBuf), internalError)@@ -46,7 +49,6 @@ { solvertype :: Solver , stdin :: Handle , stdout :: Handle- , stderr :: Handle , process :: ProcessHandle } @@ -88,59 +90,84 @@ -- collect result readChan resChan -withSolvers :: Solver -> Natural -> Maybe Natural -> (SolverGroup -> IO a) -> IO a+writeSMT2File :: SMT2 -> Int -> String -> IO ()+writeSMT2File smt2 count abst =+ do+ let content = formatSMT2 smt2 <> "\n\n(check-sat)"+ T.writeFile ("query-" <> (show count) <> "-" <> abst <> ".smt2") content++withSolvers :: App m => Solver -> Natural -> Maybe Natural -> (SolverGroup -> m a) -> m a withSolvers solver count timeout cont = do- -- spawn solvers- instances <- mapM (const $ spawnSolver solver timeout) [1..count]- -- spawn orchestration thread- taskQueue <- newChan- availableInstances <- newChan- forM_ instances (writeChan availableInstances)- orchestrateId <- forkIO $ orchestrate taskQueue availableInstances+ -- spawn solvers+ instances <- mapM (const $ liftIO $ spawnSolver solver timeout) [1..count]+ -- spawn orchestration thread+ taskQueue <- liftIO newChan+ availableInstances <- liftIO newChan+ liftIO $ forM_ instances (writeChan availableInstances)+ orchestrate' <- toIO $ orchestrate taskQueue availableInstances 0+ orchestrateId <- liftIO $ forkIO orchestrate' - -- run continuation with task queue- res <- cont (SolverGroup taskQueue)+ -- run continuation with task queue+ res <- cont (SolverGroup taskQueue) - -- cleanup and return results- mapM_ stopSolver instances- killThread orchestrateId- pure res+ -- cleanup and return results+ liftIO $ mapM_ (stopSolver) instances+ liftIO $ killThread orchestrateId+ pure res where- orchestrate queue avail = do- task <- readChan queue- inst <- readChan avail- _ <- forkIO $ runTask task inst avail- orchestrate queue avail+ orchestrate :: App m => Chan Task -> Chan SolverInstance -> Int -> m b+ orchestrate queue avail fileCounter = do+ task <- liftIO $ readChan queue+ inst <- liftIO $ readChan avail+ runTask' <- toIO $ runTask task inst avail fileCounter+ _ <- liftIO $ forkIO runTask'+ orchestrate queue avail (fileCounter + 1) - runTask (Task (SMT2 cmds (RefinementEqs refineEqs) cexvars) r) inst availableInstances = do- -- reset solver and send all lines of provided script- out <- sendScript inst (SMT2 ("(reset)" : cmds) mempty mempty)- case out of- -- if we got an error then return it- Left e -> writeChan r (Error ("error while writing SMT to solver: " <> T.toStrict e))- -- otherwise call (check-sat), parse the result, and send it down the result channel- Right () -> do- sat <- sendLine inst "(check-sat)"- res <- do- case sat of- "unsat" -> pure Unsat- "timeout" -> pure Unknown- "unknown" -> pure Unknown- "sat" -> if null refineEqs then Sat <$> getModel inst cexvars- else do- _ <- sendScript inst (SMT2 refineEqs mempty mempty)- sat2 <- sendLine inst "(check-sat)"- case sat2 of- "unsat" -> pure Unsat- "timeout" -> pure Unknown- "unknown" -> pure Unknown- "sat" -> Sat <$> getModel inst cexvars- _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> sat2- _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> sat- writeChan r res+ runTask :: (MonadIO m, ReadConfig m) => Task -> SolverInstance -> Chan SolverInstance -> Int -> m ()+ runTask (Task smt2@(SMT2 cmds (RefinementEqs refineEqs refps) cexvars ps) r) inst availableInstances fileCounter = do+ conf <- readConfig+ let fuzzResult = tryCexFuzz ps conf.numCexFuzz+ liftIO $ do+ when (conf.dumpQueries) $ writeSMT2File smt2 fileCounter "abstracted"+ if (isJust fuzzResult)+ then do+ when (conf.debug) $ putStrLn $ "Cex found via fuzzing:" <> (show fuzzResult)+ writeChan r (Sat $ fromJust fuzzResult)+ else if not conf.onlyCexFuzz then do+ when (conf.debug) $ putStrLn "Fuzzing failed to find a Cex"+ -- reset solver and send all lines of provided script+ out <- sendScript inst (SMT2 ("(reset)" : cmds) mempty mempty ps)+ case out of+ -- if we got an error then return it+ Left e -> writeChan r (Error ("error while writing SMT to solver: " <> T.toStrict e))+ -- otherwise call (check-sat), parse the result, and send it down the result channel+ Right () -> do+ sat <- sendLine inst "(check-sat)"+ res <- do+ case sat of+ "unsat" -> pure Unsat+ "timeout" -> pure Unknown+ "unknown" -> pure Unknown+ "sat" -> if null refineEqs then Sat <$> getModel inst cexvars+ else do+ let refinedSMT2 = SMT2 refineEqs mempty mempty (ps <> refps)+ writeSMT2File refinedSMT2 fileCounter "refined"+ _ <- sendScript inst refinedSMT2+ sat2 <- sendLine inst "(check-sat)"+ case sat2 of+ "unsat" -> pure Unsat+ "timeout" -> pure Unknown+ "unknown" -> pure Unknown+ "sat" -> Sat <$> getModel inst cexvars+ _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> sat2+ _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> sat+ writeChan r res+ else do+ when (conf.debug) $ putStrLn "Fuzzing failed to find a Cex, not trying SMT due to onlyCexFuzz"+ writeChan r Unknown - -- put the instance back in the list of available instances- writeChan availableInstances inst+ -- put the instance back in the list of available instances+ writeChan availableInstances inst getModel :: SolverInstance -> CexVars -> IO SMTCex getModel inst cexvars = do@@ -193,7 +220,7 @@ shrinkBuf buf hint = do let encBound = "(_ bv" <> (T.pack $ show (into hint :: Integer)) <> " 256)" sat <- liftIO $ do- checkCommand inst "(push)"+ checkCommand inst "(push 1)" checkCommand inst $ "(assert (bvule " <> buf <> "_length " <> encBound <> "))" sendLine inst "(check-sat)" case sat of@@ -201,7 +228,7 @@ model <- liftIO getRaw put model "unsat" -> do- liftIO $ checkCommand inst "(pop)"+ liftIO $ checkCommand inst "(pop 1)" shrinkBuf buf (if hint == 0 then hint + 1 else hint * 2) e -> internalError $ "Unexpected solver output: " <> (T.unpack e) @@ -226,10 +253,15 @@ Nothing -> 300 :: Natural Just t' -> t' --- | Arguments used when spawing a solver instance+-- | Arguments used when spawning a solver instance solverArgs :: Solver -> Maybe Natural -> [Text] solverArgs solver timeout = case solver of- Bitwuzla -> internalError "TODO: Bitwuzla args"+ Bitwuzla ->+ [ "--lang=smt2"+ , "--produce-models"+ , "--time-limit-per=" <> mkTimeout timeout+ , "--bv-solver=preprop"+ ] Z3 -> [ "-in" ] CVC5 ->@@ -237,6 +269,7 @@ , "--produce-models" , "--print-success" , "--interactive"+ , "--incremental" , "--tlimit-per=" <> mkTimeout timeout ] Custom _ -> []@@ -244,25 +277,35 @@ -- | Spawns a solver instance, and sets the various global config options that we use for our queries spawnSolver :: Solver -> Maybe (Natural) -> IO SolverInstance spawnSolver solver timeout = do- let cmd = (proc (show solver) (fmap T.unpack $ solverArgs solver timeout)) { std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe }- (Just stdin, Just stdout, Just stderr, process) <- createProcess cmd+ (readout, writeout) <- createPipe+ let cmd+ = (proc (show solver) (fmap T.unpack $ solverArgs solver timeout))+ { std_in = CreatePipe+ , std_out = UseHandle writeout+ , std_err = UseHandle writeout+ }+ (Just stdin, Nothing, Nothing, process) <- createProcess cmd hSetBuffering stdin (BlockBuffering (Just 1000000))- let solverInstance = SolverInstance solver stdin stdout stderr process+ let solverInstance = SolverInstance solver stdin readout process case solver of CVC5 -> pure solverInstance- _ -> do+ Bitwuzla -> do+ _ <- sendLine solverInstance "(set-option :print-success true)"+ pure solverInstance+ Z3 -> do _ <- sendLine' solverInstance $ "(set-option :timeout " <> mkTimeout timeout <> ")" _ <- sendLine solverInstance "(set-option :print-success true)" pure solverInstance+ Custom _ -> pure solverInstance --- | Cleanly shutdown a running solver instnace+-- | Cleanly shutdown a running solver instance stopSolver :: SolverInstance -> IO ()-stopSolver (SolverInstance _ stdin stdout stderr process) = cleanupProcess (Just stdin, Just stdout, Just stderr, process)+stopSolver (SolverInstance _ stdin stdout process) = cleanupProcess (Just stdin, Just stdout, Nothing, process) -- | Sends a list of commands to the solver. Returns the first error, if there was one. sendScript :: SolverInstance -> SMT2 -> IO (Either Text ())-sendScript solver (SMT2 cmds _ _) = do+sendScript solver (SMT2 cmds _ _ _) = do let sexprs = splitSExpr $ fmap toLazyText cmds go sexprs where@@ -292,20 +335,20 @@ -- | Sends a string to the solver and appends a newline, returns the first available line from the output buffer sendLine :: SolverInstance -> Text -> IO Text-sendLine (SolverInstance _ stdin stdout _ _) cmd = do+sendLine (SolverInstance _ stdin stdout _) cmd = do T.hPutStr stdin (T.append cmd "\n") hFlush stdin T.hGetLine stdout -- | Sends a string to the solver and appends a newline, doesn't return stdout sendLine' :: SolverInstance -> Text -> IO ()-sendLine' (SolverInstance _ stdin _ _ _) cmd = do+sendLine' (SolverInstance _ stdin _ _) cmd = do T.hPutStr stdin (T.append cmd "\n") hFlush stdin -- | Returns a string representation of the model for the requested variable getValue :: SolverInstance -> Text -> IO Text-getValue (SolverInstance _ stdin stdout _ _) var = do+getValue (SolverInstance _ stdin stdout _) var = do T.hPutStr stdin (T.append (T.append "(get-value (" var) "))\n") hFlush stdin fmap (T.unlines . reverse) (readSExpr stdout)
src/EVM/Stepper.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-} module EVM.Stepper ( Action (..)@@ -31,65 +32,63 @@ import EVM.Fetch qualified as Fetch import EVM.Types import Control.Monad.ST (stToIO, RealWorld)+import Control.Monad.IO.Class+import EVM.Effects -- | The instruction type of the operational monad-data Action s a where+data Action t s a where -- | Keep executing until an intermediate result is reached- Exec :: Action s (VMResult s)+ Exec :: Action t s (VMResult t s) -- | Wait for a query to be resolved- Wait :: Query s -> Action s ()+ Wait :: Query t s -> Action t s () -- | Multiple things can happen- Ask :: Choose s -> Action s ()+ Ask :: Choose s -> Action Symbolic s () -- | Embed a VM state transformation- EVM :: EVM s a -> Action s a+ EVM :: EVM t s a -> Action t s a -- | Perform an IO action- IOAct :: IO a -> Action s a+ IOAct :: IO a -> Action t s a -- | Type alias for an operational monad of @Action@-type Stepper s a = Program (Action s) a+type Stepper t s a = Program (Action t s) a -- Singleton actions -exec :: Stepper s (VMResult s)+exec :: Stepper t s (VMResult t s) exec = singleton Exec -run :: Stepper s (VM s)+run :: Stepper t s (VM t s) run = exec >> evm get -wait :: Query s -> Stepper s ()+wait :: Query t s -> Stepper t s () wait = singleton . Wait -ask :: Choose s -> Stepper s ()+ask :: Choose s -> Stepper Symbolic s () ask = singleton . Ask -evm :: EVM s a -> Stepper s a+evm :: EVM t s a -> Stepper t s a evm = singleton . EVM -evmIO :: IO a -> Stepper s a+evmIO :: IO a -> Stepper t s a evmIO = singleton . IOAct -- | Run the VM until final result, resolving all queries-execFully :: Stepper s (Either EvmError (Expr Buf))+execFully :: Stepper Concrete s (Either EvmError (Expr Buf)) execFully = exec >>= \case HandleEffect (Query q) -> wait q >> execFully- HandleEffect (Choose q) ->- ask q >> execFully VMFailure x -> pure (Left x) VMSuccess x -> pure (Right x)- Unfinished x- -> internalError $ "partial execution encountered during concrete execution: " <> show x -- | Run the VM until its final state-runFully :: Stepper s (VM s)+runFully :: Stepper t s (VM t s) runFully = do vm <- run case vm.result of@@ -101,33 +100,31 @@ Just _ -> pure vm -enter :: Text -> Stepper s ()+enter :: Text -> Stepper t s () enter t = evm (EVM.pushTrace (EntryTrace t)) -interpret :: Fetch.Fetcher RealWorld -> VM RealWorld -> Stepper RealWorld a -> IO a+interpret+ :: forall m a . (App m)+ => Fetch.Fetcher Concrete m RealWorld+ -> VM Concrete RealWorld+ -> Stepper Concrete RealWorld a+ -> m a interpret fetcher vm = eval . view where- eval :: ProgramView (Action RealWorld) a -> IO a+ eval :: ProgramView (Action Concrete RealWorld) a -> m a eval (Return x) = pure x eval (action :>>= k) = case action of Exec -> do- (r, vm') <- stToIO $ runStateT EVM.Exec.exec vm- interpret fetcher vm' (k r)- Wait (PleaseAskSMT (Lit c) _ continue) -> do- (r, vm') <- stToIO $ runStateT (continue (Case (c > 0))) vm+ (r, vm') <- liftIO $ stToIO $ runStateT EVM.Exec.exec vm interpret fetcher vm' (k r)- Wait (PleaseAskSMT c _ _) ->- error $ "cannot handle symbolic branch conditions in this interpreter: " <> show c Wait q -> do m <- fetcher q- vm' <- stToIO $ execStateT m vm+ vm' <- liftIO $ stToIO $ execStateT m vm interpret fetcher vm' (k ())- Ask _ ->- internalError "cannot make choices with this interpreter" IOAct m -> do- r <- m+ r <- liftIO m interpret fetcher vm (k r) EVM m -> do- (r, vm') <- stToIO $ runStateT m vm+ (r, vm') <- liftIO $ stToIO $ runStateT m vm interpret fetcher vm' (k r)
src/EVM/SymExec.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE ScopedTypeVariables #-} module EVM.SymExec where @@ -24,8 +25,6 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO qualified as T-import Data.Text.Lazy qualified as TL-import Data.Text.Lazy.IO qualified as TL import Data.Tree.Zipper qualified as Zipper import Data.Tuple (swap) import EVM (makeVm, abstractContract, initialContract, getCodeLocation, isValidJumpDest)@@ -34,7 +33,7 @@ import EVM.ABI import EVM.Expr qualified as Expr import EVM.Format (formatExpr, formatPartial, showVal, bsToHex)-import EVM.SMT (SMTCex(..), SMT2(..), assertProps, formatSMT2)+import EVM.SMT (SMTCex(..), SMT2(..), assertProps) import EVM.SMT qualified as SMT import EVM.Solvers import EVM.Stepper (Stepper)@@ -48,6 +47,8 @@ import Optics.Core import Options.Generic (ParseField, ParseFields, ParseRecord) import Witch (into, unsafeInto)+import EVM.Effects+import Control.Monad.IO.Unlift data LoopHeuristic = Naive@@ -73,11 +74,9 @@ data VeriOpts = VeriOpts { simp :: Bool- , debug :: Bool , maxIter :: Maybe Integer , askSmtIters :: Integer , loopHeuristic :: LoopHeuristic- , abstRefineConfig :: AbstRefineConfig , rpcInfo :: Fetch.RpcInfo } deriving (Eq, Show)@@ -85,23 +84,15 @@ defaultVeriOpts :: VeriOpts defaultVeriOpts = VeriOpts { simp = True- , debug = False , maxIter = Nothing , askSmtIters = 1 , loopHeuristic = StackBased- , abstRefineConfig = abstRefineDefault , rpcInfo = Nothing } rpcVeriOpts :: (Fetch.BlockNumber, Text) -> VeriOpts rpcVeriOpts info = defaultVeriOpts { rpcInfo = Just info } -debugVeriOpts :: VeriOpts-debugVeriOpts = defaultVeriOpts { debug = True }--debugAbstVeriOpts :: VeriOpts-debugAbstVeriOpts = defaultVeriOpts { abstRefineConfig = AbstRefineConfig True True }- extractCex :: VerifyResult -> Maybe (Expr End, SMTCex) extractCex (Cex c) = Just c extractCex _ = Nothing@@ -169,6 +160,7 @@ [] -> acc (hd:tl) -> case hd of St _ _ -> go (Expr.add acc (Lit 32)) tl+ Comp xs | all isSt xs -> go acc (xs <> tl) _ -> internalError "unsupported" writeSelector :: Expr Buf -> Text -> Expr Buf@@ -185,16 +177,24 @@ go _ [] acc = acc go idx (f:rest) (buf, ps) = case f of+ -- static fragments get written as a word in place St p w -> go (Expr.add idx (Lit 32)) rest (Expr.writeWord idx w buf, p <> ps)+ -- compound fragments that contain only static fragments get written in place+ Comp xs | all isSt xs -> go idx (xs <> rest) (buf,ps)+ -- dynamic fragments are not yet supported... :/ s -> internalError $ "unsupported cd fragment: " <> show s +isSt :: CalldataFragment -> Bool+isSt (St {}) = True+isSt _ = False + abstractVM :: (Expr Buf, [Prop]) -> ByteString -> Maybe (Precondition s) -> Bool- -> ST s (VM s)+ -> ST s (VM Symbolic s) abstractVM cd contractCode maybepre create = do let value = TxValue let code = if create then InitCode contractCode (fst cd) else RuntimeCode (ConcreteRuntimeCode contractCode)@@ -209,7 +209,7 @@ -> Expr EWord -> (Expr Buf, [Prop]) -> Bool- -> ST s (VM s)+ -> ST s (VM Symbolic s) loadSymVM x callvalue cd create = (makeVm $ VMOpts { contract = if create then initialContract x else abstractContract x (SymAddr "entrypoint")@@ -226,7 +226,7 @@ , blockGaslimit = 0 , gasprice = 0 , prevRandao = 42069- , gas = 0xffffffffffffffff+ , gas = () , gaslimit = 0xffffffffffffffff , baseFee = 0 , priorityFee = 0@@ -241,63 +241,62 @@ -- | Interpreter which explores all paths at branching points. Returns an -- 'Expr End' representing the possible executions. interpret- :: Fetch.Fetcher RealWorld+ :: forall m . App m+ => Fetch.Fetcher Symbolic m RealWorld -> Maybe Integer -- max iterations -> Integer -- ask smt iterations -> LoopHeuristic- -> VM RealWorld- -> Stepper RealWorld (Expr End)- -> IO (Expr End)+ -> VM Symbolic RealWorld+ -> Stepper Symbolic RealWorld (Expr End)+ -> m (Expr End) interpret fetcher maxIter askSmtIters heuristic vm = eval . Operational.view where eval- :: Operational.ProgramView (Stepper.Action RealWorld) (Expr End)- -> IO (Expr End)+ :: Operational.ProgramView (Stepper.Action Symbolic RealWorld) (Expr End)+ -> m (Expr End) eval (Operational.Return x) = pure x eval (action Operational.:>>= k) = case action of Stepper.Exec -> do- (r, vm') <- stToIO $ runStateT exec vm+ (r, vm') <- liftIO $ stToIO $ runStateT exec vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) Stepper.IOAct q -> do- r <- q+ r <- liftIO q interpret fetcher maxIter askSmtIters heuristic vm (k r) Stepper.Ask (PleaseChoosePath cond continue) -> do- (a, b) <- concurrently- (do- (ra, vma) <- stToIO $ runStateT (continue True) vm { result = Nothing }- interpret fetcher maxIter askSmtIters heuristic vma (k ra)- )- (do- (rb, vmb) <- stToIO $ runStateT (continue False) vm { result = Nothing }- interpret fetcher maxIter askSmtIters heuristic vmb (k rb)- )+ evalLeft <- toIO $ do+ (ra, vma) <- liftIO $ stToIO $ runStateT (continue True) vm { result = Nothing }+ interpret fetcher maxIter askSmtIters heuristic vma (k ra)+ evalRight <- toIO $ do+ (rb, vmb) <- liftIO $ stToIO $ runStateT (continue False) vm { result = Nothing }+ interpret fetcher maxIter askSmtIters heuristic vmb (k rb)+ (a, b) <- liftIO $ concurrently evalLeft evalRight pure $ ITE cond a b Stepper.Wait q -> do let performQuery = do- m <- liftIO (fetcher q)- (r, vm') <- stToIO $ runStateT m vm+ m <- fetcher q+ (r, vm') <- liftIO$ stToIO $ runStateT m vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) case q of PleaseAskSMT cond preconds continue -> do let- simpCond = Expr.simplify cond- simpProps = Expr.simplifyProps ((simpCond ./= Lit 0):preconds)- case simpCond of+ -- no concretiziation here, or we may lose information+ simpProps = Expr.simplifyProps ((cond ./= Lit 0):preconds)+ case Expr.concKeccakSimpExpr cond of -- is the condition concrete? Lit c -> -- have we reached max iterations, are we inside a loop? case (maxIterationsReached vm maxIter, isLoopHead heuristic vm) of -- Yes. return a partial leaf (Just _, Just True) ->- pure $ Partial vm.keccakEqs (Traces (Zipper.toForest vm.traces) vm.env.contracts) $ MaxIterationsReached vm.state.pc vm.state.contract+ pure $ Partial [] (Traces (Zipper.toForest vm.traces) vm.env.contracts) $ MaxIterationsReached vm.state.pc vm.state.contract -- No. keep executing _ -> do- (r, vm') <- stToIO $ runStateT (continue (Case (c > 0))) vm+ (r, vm') <- liftIO $ stToIO $ runStateT (continue (Case (c > 0))) vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) -- the condition is symbolic@@ -308,9 +307,9 @@ (Just True, _, Just n) -> do -- continue execution down the opposite branch than the one that -- got us to this point and return a partial leaf for the other side- (r, vm') <- stToIO $ runStateT (continue (Case $ not n)) vm+ (r, vm') <- liftIO $ stToIO $ runStateT (continue (Case $ not n)) vm a <- interpret fetcher maxIter askSmtIters heuristic vm' (k r)- pure $ ITE cond a (Partial vm.keccakEqs (Traces (Zipper.toForest vm.traces) vm.env.contracts) (MaxIterationsReached vm.state.pc vm.state.contract))+ pure $ ITE cond a (Partial [] (Traces (Zipper.toForest vm.traces) vm.env.contracts) (MaxIterationsReached vm.state.pc vm.state.contract)) -- we're in a loop and askSmtIters has been reached (Just True, True, _) -> -- ask the smt solver about the loop condition@@ -318,17 +317,17 @@ _ -> do (r, vm') <- case simpProps of -- if we can statically determine unsatisfiability then we skip exploring the jump- [PBool False] -> stToIO $ runStateT (continue (Case False)) vm+ [PBool False] -> liftIO $ stToIO $ runStateT (continue (Case False)) vm -- otherwise we explore both branches- _ -> stToIO $ runStateT (continue EVM.Types.Unknown) vm+ _ -> liftIO $ stToIO $ runStateT (continue EVM.Types.Unknown) vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) _ -> performQuery Stepper.EVM m -> do- (r, vm') <- stToIO $ runStateT m vm+ (r, vm') <- liftIO $ stToIO $ runStateT m vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) -maxIterationsReached :: VM s -> Maybe Integer -> Maybe Bool+maxIterationsReached :: VM Symbolic s -> Maybe Integer -> Maybe Bool maxIterationsReached _ Nothing = Nothing maxIterationsReached vm (Just maxIter) = let codelocation = getCodeLocation vm@@ -337,7 +336,7 @@ then Map.lookup (codelocation, iters - 1) vm.cache.path else Nothing -askSmtItersReached :: VM s -> Integer -> Bool+askSmtItersReached :: VM Symbolic s -> Integer -> Bool askSmtItersReached vm askSmtIters = let codelocation = getCodeLocation vm (iters, _) = view (at codelocation % non (0, [])) vm.iterations@@ -351,7 +350,7 @@ This heuristic is not perfect, and can certainly be tricked, but should generally be good enough for most compiler generated and non pathological user generated loops. -}-isLoopHead :: LoopHeuristic -> VM s -> Maybe Bool+isLoopHead :: LoopHeuristic -> VM Symbolic s -> Maybe Bool isLoopHead Naive _ = Just True isLoopHead StackBased vm = let loc = getCodeLocation vm@@ -362,29 +361,31 @@ Just (_, oldStack) -> Just $ filter isValid oldStack == filter isValid vm.state.stack Nothing -> Nothing -type Precondition s = VM s -> Prop-type Postcondition s = VM s -> Expr End -> Prop+type Precondition s = VM Symbolic s -> Prop+type Postcondition s = VM Symbolic s -> Expr End -> Prop checkAssert- :: SolverGroup+ :: App m+ => SolverGroup -> [Word256] -> ByteString -> Maybe Sig -> [String] -> VeriOpts- -> IO (Expr End, [VerifyResult])+ -> m (Expr End, [VerifyResult]) checkAssert solvers errs c signature' concreteArgs opts = verifyContract solvers c signature' concreteArgs opts Nothing (Just $ checkAssertions errs) getExpr- :: SolverGroup+ :: App m+ => SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts- -> IO (Expr End)+ -> m (Expr End) getExpr solvers c signature' concreteArgs opts = do- preState <- stToIO $ abstractVM (mkCalldata signature' concreteArgs) c Nothing False+ preState <- liftIO $ stToIO $ abstractVM (mkCalldata signature' concreteArgs) c Nothing False exprInter <- interpret (Fetch.oracle solvers opts.rpcInfo) opts.maxIter opts.askSmtIters opts.loopHeuristic preState runExpr if opts.simp then (pure $ Expr.simplify exprInter) else pure exprInter @@ -439,28 +440,28 @@ symCalldata name types args (AbstractBuf "txdata") verifyContract- :: SolverGroup+ :: App m+ => SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> Maybe (Precondition RealWorld) -> Maybe (Postcondition RealWorld)- -> IO (Expr End, [VerifyResult])+ -> m (Expr End, [VerifyResult]) verifyContract solvers theCode signature' concreteArgs opts maybepre maybepost = do- preState <- stToIO $ abstractVM (mkCalldata signature' concreteArgs) theCode maybepre False+ preState <- liftIO $ stToIO $ abstractVM (mkCalldata signature' concreteArgs) theCode maybepre False verify solvers opts preState maybepost -- | Stepper that parses the result of Stepper.runFully into an Expr End-runExpr :: Stepper.Stepper RealWorld (Expr End)+runExpr :: Stepper.Stepper Symbolic RealWorld (Expr End) runExpr = do vm <- Stepper.runFully- let asserts = vm.keccakEqs <> vm.constraints- traces = Traces (Zipper.toForest vm.traces) vm.env.contracts+ let traces = Traces (Zipper.toForest vm.traces) vm.env.contracts pure $ case vm.result of- Just (VMSuccess buf) -> Success asserts traces buf (fmap toEContract vm.env.contracts)- Just (VMFailure e) -> Failure asserts traces e- Just (Unfinished p) -> Partial asserts traces p+ Just (VMSuccess buf) -> Success vm.constraints traces buf (fmap toEContract vm.env.contracts)+ Just (VMFailure e) -> Failure vm.constraints traces e+ Just (Unfinished p) -> Partial vm.constraints traces p _ -> internalError "vm in intermediate state after call to runFully" toEContract :: Contract -> Expr EContract@@ -488,9 +489,10 @@ -- the incremental nature of the task at hand. Introducing support for -- incremental queries might let us go even faster here. -- TODO: handle errors properly-reachable :: SolverGroup -> Expr End -> IO ([SMT2], Expr End)+reachable :: App m => SolverGroup -> Expr End -> m ([SMT2], Expr End) reachable solvers e = do- res <- go [] e+ conf <- readConfig+ res <- liftIO $ go conf [] e pure $ second (fromMaybe (internalError "no reachable paths found")) res where {-@@ -499,12 +501,12 @@ If reachable return the expr wrapped in a Just. If not return Nothing. When walking back up the tree drop unreachable subbranches. -}- go :: [Prop] -> Expr End -> IO ([SMT2], Maybe (Expr End))- go pcs = \case+ go :: Config -> [Prop] -> Expr End -> IO ([SMT2], Maybe (Expr End))+ go conf pcs = \case ITE c t f -> do (tres, fres) <- concurrently- (go (PEq (Lit 1) c : pcs) t)- (go (PEq (Lit 0) c : pcs) f)+ (go conf (PEq (Lit 1) c : pcs) t)+ (go conf (PEq (Lit 0) c : pcs) f) let subexpr = case (snd tres, snd fres) of (Just t', Just f') -> Just $ ITE c t' f' (Just t', Nothing) -> Just t'@@ -512,14 +514,14 @@ (Nothing, Nothing) -> Nothing pure (fst tres <> fst fres, subexpr) leaf -> do- let query = assertProps abstRefineDefault pcs+ let query = assertProps conf pcs res <- checkSat solvers query case res of Sat _ -> pure ([query], Just leaf) Unsat -> pure ([query], Nothing) r -> internalError $ "Invalid solver result: " <> show r --- | Extract contraints stored in Expr End nodes+-- | Extract constraints stored in Expr End nodes extractProps :: Expr End -> [Prop] extractProps = \case ITE _ _ _ -> []@@ -543,58 +545,56 @@ -- | Symbolically execute the VM and check all endstates against the -- postcondition, if available. verify- :: SolverGroup+ :: App m+ => SolverGroup -> VeriOpts- -> VM RealWorld+ -> VM Symbolic RealWorld -> Maybe (Postcondition RealWorld)- -> IO (Expr End, [VerifyResult])+ -> m (Expr End, [VerifyResult]) verify solvers opts preState maybepost = do- putStrLn "Exploring contract"+ liftIO $ putStrLn "Exploring contract" exprInter <- interpret (Fetch.oracle solvers opts.rpcInfo) opts.maxIter opts.askSmtIters opts.loopHeuristic preState runExpr- when opts.debug $ T.writeFile "unsimplified.expr" (formatExpr exprInter)-- putStrLn "Simplifying expression"- let expr = if opts.simp then (Expr.simplify exprInter) else exprInter- when opts.debug $ T.writeFile "simplified.expr" (formatExpr expr)-- putStrLn $ "Explored contract (" <> show (Expr.numBranches expr) <> " branches)"+ conf <- readConfig+ when conf.dumpExprs $ liftIO $ T.writeFile "unsimplified.expr" (formatExpr exprInter)+ liftIO $ do+ putStrLn "Simplifying expression"+ let expr = if opts.simp then (Expr.simplify exprInter) else exprInter+ when conf.dumpExprs $ T.writeFile "simplified.expr" (formatExpr expr) - let flattened = flattenExpr expr- when (any isPartial flattened) $ do- T.putStrLn ""- T.putStrLn "WARNING: hevm was only able to partially explore the given contract due to the following issues:"- T.putStrLn ""- T.putStrLn . T.unlines . fmap (indent 2 . ("- " <>)) . fmap formatPartial . getPartials $ flattened+ putStrLn $ "Explored contract (" <> show (Expr.numBranches expr) <> " branches)" - case maybepost of- Nothing -> pure (expr, [Qed ()])- Just post -> do- let- -- Filter out any leaves that can be statically shown to be safe- canViolate = flip filter flattened $- \leaf -> case Expr.simplifyProp (post preState leaf) of- PBool True -> False- _ -> True- assumes = preState.constraints- withQueries = canViolate <&> \leaf ->- (assertProps opts.abstRefineConfig (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf)- putStrLn $ "Checking for reachability of "- <> show (length withQueries)- <> " potential property violation(s)"+ let flattened = flattenExpr expr+ when (any isPartial flattened) $ do+ T.putStrLn ""+ T.putStrLn "WARNING: hevm was only able to partially explore the given contract due to the following issues:"+ T.putStrLn ""+ T.putStrLn . T.unlines . fmap (indent 2 . ("- " <>)) . fmap formatPartial . getPartials $ flattened - when opts.debug $ forM_ (zip [(1 :: Int)..] withQueries) $ \(idx, (q, leaf)) -> do- TL.writeFile- ("query-" <> show idx <> ".smt2")- ("; " <> (TL.pack $ show leaf) <> "\n\n" <> formatSMT2 q <> "\n\n(check-sat)")+ case maybepost of+ Nothing -> pure (expr, [Qed ()])+ Just post -> liftIO $ do+ let+ -- Filter out any leaves from `flattened` that can be statically shown to be safe+ tocheck = flip map flattened $ \leaf -> (toPropsFinal leaf preState.constraints post, leaf)+ withQueries = filter canBeSat tocheck <&> \(a, leaf) -> (assertProps conf a, leaf)+ putStrLn $ "Checking for reachability of "+ <> show (length withQueries)+ <> " potential property violation(s)" - -- Dispatch the remaining branches to the solver to check for violations- results <- flip mapConcurrently withQueries $ \(query, leaf) -> do- res <- checkSat solvers query- pure (res, leaf)- let cexs = filter (\(res, _) -> not . isUnsat $ res) results- pure $ if Prelude.null cexs then (expr, [Qed ()]) else (expr, fmap toVRes cexs)+ -- Dispatch the remaining branches to the solver to check for violations+ results <- flip mapConcurrently withQueries $ \(query, leaf) -> do+ res <- checkSat solvers query+ pure (res, leaf)+ let cexs = filter (\(res, _) -> not . isUnsat $ res) results+ pure $ if Prelude.null cexs then (expr, [Qed ()]) else (expr, fmap toVRes cexs) where+ toProps leaf constr post = PNeg (post preState leaf) : constr <> extractProps leaf+ toPropsFinal leaf constr post = if opts.simp then Expr.simplifyProps $ toProps leaf constr post+ else toProps leaf constr post+ canBeSat (a, _) = case a of+ [PBool False] -> False+ _ -> True toVRes :: (CheckSatResult, Expr End) -> VerifyResult toVRes (res, leaf) = case res of Sat model -> Cex (leaf, expandCex preState model)@@ -602,7 +602,7 @@ Unsat -> Qed () Error e -> internalError $ "solver responded with error: " <> show e -expandCex :: VM s -> SMTCex -> SMTCex+expandCex :: VM Symbolic s -> SMTCex -> SMTCex expandCex prestate c = c { store = Map.union c.store concretePreStore } where concretePreStore = Map.mapMaybe (maybeConcreteStore . (.storage))@@ -623,54 +623,62 @@ -- equivalence break, and since we run this check for every pair of end states, -- the check is exhaustive. equivalenceCheck- :: SolverGroup -> ByteString -> ByteString -> VeriOpts -> (Expr Buf, [Prop])- -> IO [EquivResult]+ :: forall m . App m+ => SolverGroup+ -> ByteString+ -> ByteString+ -> VeriOpts+ -> (Expr Buf, [Prop])+ -> m [EquivResult] equivalenceCheck solvers bytecodeA bytecodeB opts calldata = do case bytecodeA == bytecodeB of- True -> do+ True -> liftIO $ do putStrLn "bytecodeA and bytecodeB are identical" pure [Qed ()] False -> do branchesA <- getBranches bytecodeA branchesB <- getBranches bytecodeB- equivalenceCheck' solvers branchesA branchesB opts+ equivalenceCheck' solvers branchesA branchesB where -- decompiles the given bytecode into a list of branches- getBranches :: ByteString -> IO [Expr End]+ getBranches :: ByteString -> m [Expr End] getBranches bs = do let bytecode = if BS.null bs then BS.pack [0] else bs- prestate <- stToIO $ abstractVM calldata bytecode Nothing False+ prestate <- liftIO $ stToIO $ abstractVM calldata bytecode Nothing False expr <- interpret (Fetch.oracle solvers Nothing) opts.maxIter opts.askSmtIters opts.loopHeuristic prestate runExpr let simpl = if opts.simp then (Expr.simplify expr) else expr pure $ flattenExpr simpl -equivalenceCheck' :: SolverGroup -> [Expr End] -> [Expr End] -> VeriOpts -> IO [EquivResult]-equivalenceCheck' solvers branchesA branchesB opts = do- when (any isPartial branchesA || any isPartial branchesB) $ do+equivalenceCheck'+ :: forall m . App m+ => SolverGroup -> [Expr End] -> [Expr End] -> m [EquivResult]+equivalenceCheck' solvers branchesA branchesB = do+ when (any isPartial branchesA || any isPartial branchesB) $ liftIO $ do putStrLn "" putStrLn "WARNING: hevm was only able to partially explore the given contract due to the following issues:" putStrLn "" T.putStrLn . T.unlines . fmap (indent 2 . ("- " <>)) . fmap formatPartial . nubOrd $ ((getPartials branchesA) <> (getPartials branchesB)) let allPairs = [(a,b) | a <- branchesA, b <- branchesB]- putStrLn $ "Found " <> show (length allPairs) <> " total pairs of endstates"+ liftIO $ putStrLn $ "Found " <> show (length allPairs) <> " total pairs of endstates" - when opts.debug $+ conf <- readConfig+ when conf.dumpEndStates $ liftIO $ putStrLn $ "endstates in bytecodeA: " <> show (length branchesA) <> "\nendstates in bytecodeB: " <> show (length branchesB) let differingEndStates = sortBySize (mapMaybe (uncurry distinct) allPairs)- putStrLn $ "Asking the SMT solver for " <> (show $ length differingEndStates) <> " pairs"- when opts.debug $ forM_ (zip differingEndStates [(1::Integer)..]) (\(x, i) ->- T.writeFile ("prop-checked-" <> show i) (T.pack $ show x))+ liftIO $ putStrLn $ "Asking the SMT solver for " <> (show $ length differingEndStates) <> " pairs"+ when conf.dumpEndStates $ forM_ (zip differingEndStates [(1::Integer)..]) (\(x, i) ->+ liftIO $ T.writeFile ("prop-checked-" <> show i <> ".prop") (T.pack $ show x)) - knownUnsat <- newTVarIO []- procs <- getNumProcessors+ knownUnsat <- liftIO $ newTVarIO []+ procs <- liftIO getNumProcessors results <- checkAll differingEndStates knownUnsat procs let useful = foldr (\(_, b) n -> if b then n+1 else n) (0::Integer) results- putStrLn $ "Reuse of previous queries was Useful in " <> (show useful) <> " cases"+ liftIO $ putStrLn $ "Reuse of previous queries was Useful in " <> (show useful) <> " cases" case all isQed . fmap fst $ results of True -> pure [Qed ()] False -> pure $ filter (/= Qed ()) . fmap fst $ results@@ -688,13 +696,9 @@ -- the solver if we can determine unsatisfiability from the cache already -- the last element of the returned tuple indicates whether the cache was -- used or not- check :: UnsatCache -> (Set Prop) -> Int -> IO (EquivResult, Bool)- check knownUnsat props idx = do- let smt = assertProps opts.abstRefineConfig (Set.toList props)- -- if debug is on, write the query to a file- let filename = "equiv-query-" <> show idx <> ".smt2"- when opts.debug $ TL.writeFile filename (formatSMT2 smt <> "\n\n(check-sat)")-+ check :: Config -> UnsatCache -> (Set Prop) -> IO (EquivResult, Bool)+ check conf knownUnsat props = do+ let smt = assertProps conf (Set.toList props) ku <- readTVarIO knownUnsat res <- if subsetAny props ku then pure (True, Unsat)@@ -710,16 +714,17 @@ atomically $ readTVar knownUnsat >>= writeTVar knownUnsat . (props :) pure (Qed (), False) (_, EVM.Solvers.Unknown) -> pure (Timeout (), False)- (_, Error txt) -> internalError $ "solver returned: " <> (T.unpack txt) <> if opts.debug then "\n SMT file was: " <> filename <> "" else ""+ (_, Error txt) -> internalError $ "solver returned: " <> (T.unpack txt) -- Allows us to run it in parallel. Note that this (seems to) run it -- from left-to-right, and with a max of K threads. This is in contrast to -- mapConcurrently which would spawn as many threads as there are jobs, and -- run them in a random order. We ordered them correctly, though so that'd be bad- checkAll :: [(Set Prop)] -> UnsatCache -> Int -> IO [(EquivResult, Bool)]+ checkAll :: App m => [(Set Prop)] -> UnsatCache -> Int -> m [(EquivResult, Bool)] checkAll input cache numproc = do- wrap <- pool numproc- parMapIO (wrap . (uncurry $ check cache)) $ zip input [1..]+ conf <- readConfig+ wrap <- liftIO $ pool numproc+ liftIO $ parMapIO (wrap . (check conf cache)) input -- Takes two branches and returns a set of props that will need to be@@ -786,11 +791,12 @@ both' :: (a -> b) -> (a, a) -> (b, b) both' f (x, y) = (f x, f y) -produceModels :: SolverGroup -> Expr End -> IO [(Expr End, CheckSatResult)]+produceModels :: App m => SolverGroup -> Expr End -> m [(Expr End, CheckSatResult)] produceModels solvers expr = do let flattened = flattenExpr expr- withQueries = fmap (\e -> ((assertProps abstRefineDefault) . extractProps $ e, e)) flattened- results <- flip mapConcurrently withQueries $ \(query, leaf) -> do+ withQueries conf = fmap (\e -> ((assertProps conf) . extractProps $ e, e)) flattened+ conf <- readConfig+ results <- liftIO $ (flip mapConcurrently) (withQueries conf) $ \(query, leaf) -> do res <- checkSat solvers query pure (res, leaf) pure $ fmap swap $ filter (\(res, _) -> not . isUnsat $ res) results@@ -832,13 +838,13 @@ where -- we attempt to produce a model for calldata by substituting all variables -- and buffers provided by the model into the original calldata expression.- -- If we have a concrete result then we diplay it, otherwise we diplay+ -- If we have a concrete result then we display it, otherwise we display -- `Any`. This is a little bit of a hack (and maybe unsound?), but we need -- it for branches that do not refer to calldata at all (e.g. the top level -- callvalue check inserted by solidity in contracts that don't have any -- payable functions). cd' = case sig of- Nothing -> prettyBuf . Expr.simplify . defaultSymbolicValues $ subModel m cd+ Nothing -> prettyBuf . Expr.concKeccakSimpExpr . defaultSymbolicValues $ subModel m cd Just (Sig n ts) -> prettyCalldata m cd n ts storeCex :: [Text]@@ -909,7 +915,7 @@ -- | If the expression contains any symbolic values, default them to some -- concrete value The intuition here is that if we still have symbolic values--- in our calldata expression after substituing in our cex, then they can have+-- in our calldata expression after substituting in our cex, then they can have -- any value and we can safely pick a random value. This is a bit unsatisfying, -- we should really be doing smth like: https://github.com/ethereum/hevm/issues/334 -- but it's probably good enough for now@@ -1002,7 +1008,7 @@ where go :: Expr a -> Expr a go = \case- v@(AbstractStore a)+ v@(AbstractStore a _) -> if a == var then ConcreteStore val else v
src/EVM/Transaction.hs view
@@ -246,7 +246,7 @@ -- | Given a valid tx loaded into the vm state, -- subtract gas payment from the origin, increment the nonce -- and pay receiving address-initTx :: VM s -> VM s+initTx :: VM t s -> VM t s initTx vm = let toAddr = vm.state.contract origin = vm.tx.origin
src/EVM/Traversals.hs view
@@ -164,7 +164,7 @@ -- frame context - e@(Gas _ _) -> f e+ e@(Gas _) -> f e e@(Balance {}) -> f e -- code@@ -185,7 +185,7 @@ -- storage e@(ConcreteStore _) -> f e- e@(AbstractStore _) -> f e+ e@(AbstractStore _ _) -> f e e@(SLoad a b) -> f e <> (go a) <> (go b) e@(SStore a b c) -> f e <> (go a) <> (go b) <> (go c) @@ -487,7 +487,7 @@ -- frame context - Gas a b -> f (Gas a b)+ Gas a -> f (Gas a) Balance a -> do a' <- mapExprM f a f (Balance a')@@ -512,7 +512,7 @@ -- storage ConcreteStore b -> f (ConcreteStore b)- AbstractStore a -> f (AbstractStore a)+ AbstractStore a b -> f (AbstractStore a b) SLoad a b -> do a' <- mapExprM f a b' <- mapExprM f b
src/EVM/Types.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wno-inline-rule-shadowing #-}+{-# LANGUAGE TypeFamilyDependencies #-} module EVM.Types where @@ -36,6 +37,7 @@ import Data.Map qualified as Map import Data.Maybe (fromMaybe) import Data.Set (Set)+import Data.Sequence (Seq) import Data.Sequence qualified as Seq import Data.Serialize qualified as Cereal import Data.Text qualified as T@@ -67,7 +69,7 @@ -- Conversions ------------------------------------------------------------------------------------- --- We ignore hlint to supress the warnings about `fromIntegral` and friends here+-- We ignore hlint to suppress the warnings about `fromIntegral` and friends here #ifndef __HLINT__ instance From Addr Integer where from = fromIntegral@@ -129,7 +131,7 @@ | End deriving (Typeable) --- Variables refering to a global environment+-- Variables referring to a global environment data GVar (a :: EType) where BufVar :: Int -> GVar Buf StoreVar :: Int -> GVar Storage@@ -139,7 +141,7 @@ deriving instance Ord (GVar a) {- |- Expr implements an abstract respresentation of an EVM program+ Expr implements an abstract representation of an EVM program This type can give insight into the provenance of a term which is useful, both for the aesthetic purpose of printing terms in a richer way, but also to@@ -284,7 +286,6 @@ Balance :: Expr EAddr -> Expr EWord Gas :: Int -- frame idx- -> Int -- PC -> Expr EWord -- code@@ -326,7 +327,9 @@ -- storage ConcreteStore :: (Map W256 W256) -> Expr Storage- AbstractStore :: Expr EAddr -> Expr Storage+ AbstractStore :: Expr EAddr -- which contract is this store for?+ -> Maybe W256 -- which logical store does this refer to? (e.g. solidity mappings / arrays)+ -> Expr Storage SLoad :: Expr EWord -- key -> Expr Storage -- storage@@ -479,19 +482,32 @@ PBool a <= PBool b = a <= b PEq (a :: Expr x) (b :: Expr x) <= PEq (c :: Expr y) (d :: Expr y) = case eqT @x @y of- Just Refl -> a <= c && b <= d- Nothing -> False- PLT a b <= PLT c d = a <= c && b <= d- PGT a b <= PGT c d = a <= c && b <= d- PGEq a b <= PGEq c d = a <= c && b <= d- PLEq a b <= PLEq c d = a <= c && b <= d+ Just Refl -> a <= c || b <= d+ Nothing -> toNum a <= toNum c+ PLT a b <= PLT c d = a <= c || b <= d+ PGT a b <= PGT c d = a <= c || b <= d+ PGEq a b <= PGEq c d = a <= c || b <= d+ PLEq a b <= PLEq c d = a <= c || b <= d PNeg a <= PNeg b = a <= b- PAnd a b <= PAnd c d = a <= c && b <= d- POr a b <= POr c d = a <= c && b <= d- PImpl a b <= PImpl c d = a <= c && b <= d- _ <= _ = False+ PAnd a b <= PAnd c d = a <= c || b <= d+ POr a b <= POr c d = a <= c || b <= d+ PImpl a b <= PImpl c d = a <= c || b <= d+ a <= b = asNum a <= asNum b+ where+ asNum :: Prop -> Int+ asNum (PBool {}) = 0+ asNum (PEq {}) = 1+ asNum (PLT {}) = 2+ asNum (PGT {}) = 3+ asNum (PGEq {}) = 4+ asNum (PLEq {}) = 5+ asNum (PNeg {}) = 6+ asNum (PAnd {}) = 7+ asNum (POr {}) = 8+ asNum (PImpl {}) = 9 + isPBool :: Prop -> Bool isPBool (PBool _) = True isPBool _ = False@@ -515,12 +531,13 @@ | InvalidMemoryAccess | CallDepthLimitReached | MaxCodeSizeExceeded W256 W256- | MaxInitCodeSizeExceeded W256 W256+ | MaxInitCodeSizeExceeded W256 (Expr EWord) | InvalidFormat | PrecompileFailure | ReturnDataOutOfBounds | NonceOverflow | BadCheatCode FunctionSelector+ | NonexistentFork Int deriving (Show, Eq, Ord) -- | Sometimes we can only partially execute a given program@@ -531,27 +548,27 @@ deriving (Show, Eq, Ord) -- | Effect types used by the vm implementation for side effects & control flow-data Effect s- = Query (Query s)- | Choose (Choose s)-deriving instance Show (Effect s)+data Effect t s where+ Query :: Query t s -> Effect t s+ Choose :: Choose s -> Effect Symbolic s+deriving instance Show (Effect t s) -- | Queries halt execution until resolved through RPC calls or SMT queries-data Query s where- PleaseFetchContract :: Addr -> BaseState -> (Contract -> EVM s ()) -> Query s- PleaseFetchSlot :: Addr -> W256 -> (W256 -> EVM s ()) -> Query s- PleaseAskSMT :: Expr EWord -> [Prop] -> (BranchCondition -> EVM s ()) -> Query s- PleaseDoFFI :: [String] -> (ByteString -> EVM s ()) -> Query s+data Query t s where+ PleaseFetchContract :: Addr -> BaseState -> (Contract -> EVM t s ()) -> Query t s+ PleaseFetchSlot :: Addr -> W256 -> (W256 -> EVM t s ()) -> Query t s+ PleaseAskSMT :: Expr EWord -> [Prop] -> (BranchCondition -> EVM Symbolic s ()) -> Query Symbolic s+ PleaseDoFFI :: [String] -> (ByteString -> EVM t s ()) -> Query t s -- | Execution could proceed down one of two branches data Choose s where- PleaseChoosePath :: Expr EWord -> (Bool -> EVM s ()) -> Choose s+ PleaseChoosePath :: Expr EWord -> (Bool -> EVM Symbolic s ()) -> Choose s -- | The possible return values of a SMT query data BranchCondition = Case Bool | Unknown deriving Show -instance Show (Query s) where+instance Show (Query t s) where showsPrec _ = \case PleaseFetchContract addr base _ -> (("<EVM.Query: fetch contract " ++ show addr ++ show base ++ ">") ++)@@ -572,40 +589,57 @@ (("<EVM.Choice: waiting for user to select path (0,1)") ++) -- | The possible result states of a VM-data VMResult s- = VMFailure EvmError -- ^ An operation failed- | VMSuccess (Expr Buf) -- ^ Reached STOP, RETURN, or end-of-code- | HandleEffect (Effect s) -- ^ An effect must be handled for execution to continue- | Unfinished PartialExec -- ^ Execution could not continue further+data VMResult (t :: VMType) s where+ Unfinished :: PartialExec -> VMResult Symbolic s -- ^ Execution could not continue further+ VMFailure :: EvmError -> VMResult t s -- ^ An operation failed+ VMSuccess :: (Expr Buf) -> VMResult t s -- ^ Reached STOP, RETURN, or end-of-code+ HandleEffect :: (Effect t s) -> VMResult t s -- ^ An effect must be handled for execution to continue -deriving instance Show (VMResult s)+deriving instance Show (VMResult t s) -- VM State ---------------------------------------------------------------------------------------- +data VMType = Symbolic | Concrete +type family Gas (t :: VMType) = r | r -> t where+ Gas Symbolic = ()+ Gas Concrete = Word64+ -- | The state of a stepwise EVM execution-data VM s = VM- { result :: Maybe (VMResult s)- , state :: FrameState s- , frames :: [Frame s]+data VM (t :: VMType) s = VM+ { result :: Maybe (VMResult t s)+ , state :: FrameState t s+ , frames :: [Frame t s] , env :: Env , block :: Block , tx :: TxState , logs :: [Expr Log] , traces :: Zipper.TreePos Zipper.Empty Trace , cache :: Cache- , burned :: {-# UNPACK #-} !Word64+ , burned :: !(Gas t) , iterations :: Map CodeLocation (Int, [Expr EWord]) -- ^ how many times we've visited a loc, and what the contents of the stack were when we were there last , constraints :: [Prop]- , keccakEqs :: [Prop] , config :: RuntimeConfig+ , forks :: Seq ForkState+ , currentFork :: Int }+ deriving (Generic)++data ForkState = ForkState+ { env :: Env+ , block :: Block+ , cache :: Cache+ , urlOrAlias :: String+ } deriving (Show, Generic) +deriving instance Show (VM Symbolic s)+deriving instance Show (VM Concrete s)+ -- | Alias for the type of e.g. @exec1@.-type EVM s a = StateT (VM s) (ST s) a+type EVM (t :: VMType) s a = StateT (VM t s) (ST s) a -- | The VM base state (i.e. should new contracts be created with abstract balance / storage?) data BaseState@@ -621,22 +655,15 @@ } deriving (Show) -abstRefineDefault :: AbstRefineConfig-abstRefineDefault = AbstRefineConfig False False--data AbstRefineConfig = AbstRefineConfig- { arith :: Bool- , mem :: Bool- }- deriving (Show, Eq)- -- | An entry in the VM's "call/create stack"-data Frame s = Frame+data Frame (t :: VMType) s = Frame { context :: FrameContext- , state :: FrameState s+ , state :: FrameState t s }- deriving (Show) +deriving instance Show (Frame Symbolic s)+deriving instance Show (Frame Concrete s)+ -- | Call/create info data FrameContext = CreationContext@@ -648,8 +675,8 @@ | CallContext { target :: Expr EAddr , context :: Expr EAddr- , offset :: W256- , size :: W256+ , offset :: Expr EWord+ , size :: Expr EWord , codehash :: Expr EWord , abi :: Maybe W256 , calldata :: Expr Buf@@ -670,7 +697,7 @@ deriving (Eq, Ord, Show) -- | The "registers" of the VM along with memory and data stack-data FrameState s = FrameState+data FrameState (t :: VMType) s = FrameState { contract :: Expr EAddr , codeContract :: Expr EAddr , code :: ContractCode@@ -681,12 +708,15 @@ , calldata :: Expr Buf , callvalue :: Expr EWord , caller :: Expr EAddr- , gas :: {-# UNPACK #-} !Word64+ , gas :: !(Gas t) , returndata :: Expr Buf , static :: Bool }- deriving (Show, Generic)+ deriving (Generic) +deriving instance Show (FrameState Symbolic s)+deriving instance Show (FrameState Concrete s)+ data Memory s = ConcreteMemory (MutableMemory s) | SymbolicMemory !(Expr Buf)@@ -716,6 +746,7 @@ { contracts :: Map (Expr EAddr) Contract , chainId :: W256 , freshAddresses :: Int+ , freshGasVals :: Int } deriving (Show, Generic) @@ -745,7 +776,41 @@ } deriving (Show, Eq, Ord) +class VMOps (t :: VMType) where+ burn' :: Gas t -> EVM t s () -> EVM t s ()+ -- TODO: change to EvmWord t+ burnExp :: Expr EWord -> EVM t s () -> EVM t s ()+ burnSha3 :: Expr EWord -> EVM t s () -> EVM t s ()+ burnCalldatacopy :: Expr EWord -> EVM t s () -> EVM t s ()+ burnCodecopy :: Expr EWord -> EVM t s () -> EVM t s ()+ burnExtcodecopy :: Expr EAddr -> Expr EWord -> EVM t s () -> EVM t s ()+ burnReturndatacopy :: Expr EWord -> EVM t s () -> EVM t s ()+ burnLog :: Expr EWord -> Word8 -> EVM t s () -> EVM t s () + initialGas :: Gas t+ ensureGas :: Word64 -> EVM t s () -> EVM t s ()+ -- TODO: change to EvmWord t+ gasTryFrom :: Expr EWord -> Either () (Gas t)++ -- Gas cost of create, including hash cost if needed+ costOfCreate :: FeeSchedule Word64 -> Gas t -> Expr EWord -> Bool -> (Gas t, Gas t)++ costOfCall+ :: FeeSchedule Word64 -> Bool -> Expr EWord -> Gas t -> Gas t -> Expr EAddr+ -> (Word64 -> Word64 -> EVM t s ()) -> EVM t s ()++ reclaimRemainingGasAllowance :: VM t s -> EVM t s ()+ payRefunds :: EVM t s ()+ pushGas :: EVM t s ()+ enoughGas :: Word64 -> Gas t -> Bool+ subGas :: Gas t -> Word64 -> Gas t+ toGas :: Word64 -> Gas t++ whenSymbolicElse :: EVM t s a -> EVM t s a -> EVM t s a++ partial :: PartialExec -> EVM t s ()+ branch :: Expr EWord -> (Bool -> EVM t s ()) -> EVM t s ()+ -- Bytecode Representations ------------------------------------------------------------------------ @@ -847,7 +912,7 @@ -- | A specification for an initial VM state-data VMOpts = VMOpts+data VMOpts (t :: VMType) = VMOpts { contract :: Contract , otherContracts :: [(Expr EAddr, Contract)] , calldata :: (Expr Buf, [Prop])@@ -857,7 +922,7 @@ , address :: Expr EAddr , caller :: Expr EAddr , origin :: Expr EAddr- , gas :: Word64+ , gas :: Gas t , gaslimit :: Word64 , number :: W256 , timestamp :: Expr EWord@@ -872,9 +937,12 @@ , create :: Bool , txAccessList :: Map (Expr EAddr) [W256] , allowFFI :: Bool- } deriving Show+ } +deriving instance Show (VMOpts Symbolic)+deriving instance Show (VMOpts Concrete) + -- Opcodes ----------------------------------------------------------------------------------------- @@ -1311,7 +1379,7 @@ Right s -> "\"" <> T.unpack s <> "\"" Left _ -> "❮utf8 decode failed❯: " <> (show $ ByteStringS bs) --- |'paddedShowHex' displays a number in hexidecimal and pads the number+-- |'paddedShowHex' displays a number in hexadecimal and pads the number -- with 0 so that it has a minimum length of @w@. paddedShowHex :: (Show a, Integral a) => Int -> a -> String paddedShowHex w n = pad ++ str@@ -1319,6 +1387,10 @@ str = showHex n "" pad = replicate (w - length str) '0' +untilFixpoint :: Eq a => (a -> a) -> a -> a+untilFixpoint f a = if f a == a+ then a+ else untilFixpoint f (f a) -- Optics ------------------------------------------------------------------------------------------
src/EVM/UnitTest.hs view
@@ -43,6 +43,7 @@ import GHC.Natural import System.IO (hFlush, stdout) import Witch (unsafeInto, into)+import EVM.Effects data UnitTestOptions s = UnitTestOptions { rpcInfo :: Fetch.RpcInfo@@ -50,7 +51,6 @@ , verbose :: Maybe Int , maxIter :: Maybe Integer , askSmtIters :: Integer- , smtDebug :: Bool , smtTimeout :: Maybe Natural , solver :: Maybe Text , match :: Text@@ -96,14 +96,13 @@ -- | Generate VeriOpts from UnitTestOptions makeVeriOpts :: UnitTestOptions s -> VeriOpts makeVeriOpts opts =- defaultVeriOpts { debug = opts.smtDebug- , maxIter = opts.maxIter+ defaultVeriOpts { maxIter = opts.maxIter , askSmtIters = opts.askSmtIters , rpcInfo = opts.rpcInfo } -- | Top level CLI endpoint for hevm test-unitTest :: UnitTestOptions RealWorld -> Contracts -> IO Bool+unitTest :: App m => UnitTestOptions RealWorld -> Contracts -> m Bool unitTest opts (Contracts cs) = do let unitTests = findUnitTests opts.match $ Map.elems cs results <- concatMapM (runUnitTestContract opts cs) unitTests@@ -111,7 +110,7 @@ -- | Assuming a constructor is loaded, this stepper will run the constructor -- to create the test contract, give it an initial balance, and run `setUp()'.-initializeUnitTest :: UnitTestOptions s -> SolcContract -> Stepper s ()+initializeUnitTest :: UnitTestOptions s -> SolcContract -> Stepper Concrete s () initializeUnitTest opts theContract = do let addr = opts.testParams.address@@ -143,15 +142,16 @@ _ -> popTrace runUnitTestContract- :: UnitTestOptions RealWorld+ :: App m+ => UnitTestOptions RealWorld -> Map Text SolcContract -> (Text, [Sig])- -> IO [Bool]+ -> m [Bool] runUnitTestContract opts@(UnitTestOptions {..}) contractMap (name, testSigs) = do -- Print a header- putStrLn $ "Running " ++ show (length testSigs) ++ " tests for " ++ unpack name+ liftIO $ putStrLn $ "Running " ++ show (length testSigs) ++ " tests for " ++ unpack name -- Look for the wanted contract by name from the Solidity info case Map.lookup name contractMap of@@ -161,12 +161,13 @@ Just theContract -> do -- Construct the initial VM and begin the contract's constructor- vm0 <- stToIO $ initialUnitTestVm opts theContract+ vm0 :: VM Concrete RealWorld <- liftIO $ stToIO $ initialUnitTestVm opts theContract vm1 <- Stepper.interpret (Fetch.oracle solvers rpcInfo) vm0 $ do Stepper.enter name initializeUnitTest opts theContract Stepper.evm get + writeTraceDapp dapp vm1 case vm1.result of Just (VMFailure _) -> liftIO $ do Text.putStrLn "\x1b[31m[BAIL]\x1b[0m setUp() "@@ -174,27 +175,25 @@ tick $ failOutput vm1 opts "setUp()" pure [False] Just (VMSuccess _) -> do- let- -- Run all the test cases and print their status details <- forM testSigs $ \s -> do (result, detail) <- symRun opts vm1 s- Text.putStrLn result+ liftIO $ Text.putStrLn result pure detail let running = rights details bailing = lefts details - tick "\n"- tick (Text.unlines (filter (not . Text.null) running))- tick (Text.unlines bailing)+ liftIO $ do+ tick "\n"+ tick (Text.unlines (filter (not . Text.null) running))+ tick (Text.unlines bailing) pure $ fmap isRight details _ -> internalError "setUp() did not end with a result" - -- | Define the thread spawner for symbolic tests-symRun :: UnitTestOptions RealWorld -> VM RealWorld -> Sig -> IO (Text, Either Text Text)+symRun :: App m => UnitTestOptions RealWorld -> VM Concrete RealWorld -> Sig -> m (Text, Either Text Text) symRun opts@UnitTestOptions{..} vm (Sig testName types) = do let cd = symCalldata testName types [] (AbstractBuf "txdata") shouldFail = "proveFail" `isPrefixOf` testName@@ -205,8 +204,7 @@ -- we need to read from slot 0 in the test contract and mask it with 0x10 to get the value of _failed -- we don't need to do this when reading the failed from the cheatcode address since we don't do any packing there let failed store = case Map.lookup cheatCode store of- Just cheatContract -> (And (readStorage' (Lit 0) (testContract store).storage) (Lit 0x10) .== Lit 0x10)- .|| (readStorage' (Lit 0x6661696c65640000000000000000000000000000000000000000000000000000) cheatContract.storage .== Lit 1)+ Just cheatContract -> readStorage' (Lit 0x6661696c65640000000000000000000000000000000000000000000000000000) cheatContract.storage .== Lit 1 Nothing -> And (readStorage' (Lit 0) (testContract store).storage) (Lit 2) .== Lit 2 postcondition = curry $ case shouldFail of True -> \(_, post) -> case post of@@ -226,9 +224,10 @@ pushTrace (EntryTrace testName) makeTxCall testParams cd get+ writeTraceDapp dapp vm' -- check postconditions against vm- (e, results) <- verify solvers (makeVeriOpts opts) vm' (Just postcondition)+ (e, results) <- verify solvers (makeVeriOpts opts) (symbolify vm') (Just postcondition) let allReverts = not . (any Expr.isSuccess) . flattenExpr $ e -- display results@@ -280,7 +279,7 @@ _ -> "" ] -execSymTest :: UnitTestOptions RealWorld -> ABIMethod -> (Expr Buf, [Prop]) -> Stepper RealWorld (Expr End)+execSymTest :: UnitTestOptions RealWorld -> ABIMethod -> (Expr Buf, [Prop]) -> Stepper Symbolic RealWorld (Expr End) execSymTest UnitTestOptions{ .. } method cd = do -- Set up the call to the test method Stepper.evm $ do@@ -289,7 +288,7 @@ -- Try running the test method runExpr -checkSymFailures :: UnitTestOptions RealWorld -> Stepper RealWorld (VM RealWorld)+checkSymFailures :: VMOps t => UnitTestOptions RealWorld -> Stepper t RealWorld (VM t RealWorld) checkSymFailures UnitTestOptions { .. } = do -- Ask whether any assertions failed Stepper.evm $ do@@ -302,7 +301,7 @@ let p = Text.replicate n " " in Text.unlines (map (p <>) (Text.lines s)) -passOutput :: VM s -> UnitTestOptions s -> Text -> Text+passOutput :: VM t s -> UnitTestOptions s -> Text -> Text passOutput vm UnitTestOptions { .. } testName = let ?context = DappContext { info = dapp, env = vm.env.contracts } in let v = fromMaybe 0 verbose@@ -317,7 +316,7 @@ ] else "" -failOutput :: VM s -> UnitTestOptions s -> Text -> Text+failOutput :: VM t s -> UnitTestOptions s -> Text -> Text failOutput vm UnitTestOptions { .. } testName = let ?context = DappContext { info = dapp, env = vm.env.contracts } in mconcat@@ -346,8 +345,8 @@ formatTestLog events (LogEntry _ args (topic:_)) = case maybeLitWord topic >>= \t1 -> (Map.lookup t1 events) of Nothing -> Nothing- Just (Event name _ types) ->- case (name <> parenthesise (abiTypeSolidity <$> (unindexed types))) of+ Just (Event name _ argInfos) ->+ case (name <> parenthesise (abiTypeSolidity <$> argTypes)) of "log(string)" -> Just $ unquote $ showValue AbiStringType args -- log_named_x(string, x)@@ -380,12 +379,12 @@ _ -> Nothing where- ts = unindexed types+ argTypes = [argType | (_, argType, NotIndexed) <- argInfos] unquote = Text.dropAround (\c -> c == '"' || c == '«' || c == '»') log_unnamed =- Just $ showValue (head ts) args+ Just $ showValue (head argTypes) args log_named =- let (key, val) = case take 2 (textValues ts args) of+ let (key, val) = case take 2 (textValues argTypes args) of [k, v] -> (k, v) _ -> internalError "shouldn't happen" in Just $ unquote key <> ": " <> val@@ -394,7 +393,7 @@ log_named_decimal = case args of (ConcreteBuf b) ->- case toList $ runGet (getAbiSeq (length ts) ts) (BSLazy.fromStrict b) of+ case toList $ runGet (getAbiSeq (length argTypes) argTypes) (BSLazy.fromStrict b) of [key, (AbiUInt 256 val), (AbiUInt 256 dec)] -> Just $ (unquote (showAbiValue key)) <> ": " <> showDecimal dec val [key, (AbiInt 256 val), (AbiUInt 256 dec)] ->@@ -402,14 +401,14 @@ _ -> Nothing _ -> Just "<symbolic decimal>" -abiCall :: TestVMParams -> Either (Text, AbiValue) ByteString -> EVM s ()+abiCall :: VMOps t => TestVMParams -> Either (Text, AbiValue) ByteString -> EVM t s () abiCall params args = let cd = case args of Left (sig, args') -> abiMethod sig args' Right b -> b in makeTxCall params (ConcreteBuf cd, []) -makeTxCall :: TestVMParams -> (Expr Buf, [Prop]) -> EVM s ()+makeTxCall :: VMOps t => TestVMParams -> (Expr Buf, [Prop]) -> EVM t s () makeTxCall params (cd, cdProps) = do resetState assign (#tx % #isCreate) False@@ -417,14 +416,14 @@ assign (#state % #calldata) cd #constraints %= (<> cdProps) assign (#state % #caller) params.caller- assign (#state % #gas) params.gasCall+ assign (#state % #gas) (toGas params.gasCall) origin <- fromMaybe (initialContract (RuntimeCode (ConcreteRuntimeCode ""))) <$> use (#env % #contracts % at params.origin) let insufficientBal = maybe False (\b -> b < params.gasprice * (into params.gasCall)) (maybeLitWord origin.balance) when insufficientBal $ internalError "insufficient balance for gas cost" vm <- get put $ initTx vm -initialUnitTestVm :: UnitTestOptions s -> SolcContract -> ST s (VM s)+initialUnitTestVm :: VMOps t => UnitTestOptions s -> SolcContract -> ST s (VM t s) initialUnitTestVm (UnitTestOptions {..}) theContract = do vm <- makeVm $ VMOpts { contract = initialContract (InitCode theContract.creationCode mempty)@@ -434,7 +433,7 @@ , address = testParams.address , caller = testParams.caller , origin = testParams.origin- , gas = testParams.gasCreate+ , gas = toGas testParams.gasCreate , gaslimit = testParams.gasCreate , coinbase = testParams.coinbase , number = testParams.number
test/BlockchainTests.hs view
@@ -1,9 +1,13 @@ module Main where -import EVM.Test.BlockchainTests+import EVM.Test.BlockchainTests qualified as BlockchainTests import Test.Tasty+import EVM.Effects +testEnv :: Env+testEnv = Env { config = defaultConfig }+ main :: IO () main = do- tests <- prepareTests+ tests <- runEnv testEnv BlockchainTests.prepareTests defaultMain tests
test/EVM/Test/BlockchainTests.hs view
@@ -7,7 +7,7 @@ import EVM.Format (hexText) import EVM.Stepper qualified import EVM.Transaction-import EVM.Types hiding (Block, Case)+import EVM.Types hiding (Block, Case, Env) import EVM.Test.Tracing (interpretWithTrace, VMTrace, compareTraces, EVMToolTraceOutput(..)) import Optics.Core@@ -15,6 +15,8 @@ import Control.Monad import Control.Monad.ST (RealWorld, stToIO) import Control.Monad.State.Strict+import Control.Monad.IO.Unlift+import EVM.Effects import Data.Aeson ((.:), (.:?), FromJSON (..)) import Data.Aeson qualified as JSON import Data.Aeson.Types qualified as JSON@@ -50,7 +52,7 @@ } deriving Show data Case = Case- { vmOpts :: VMOpts+ { vmOpts :: VMOpts Concrete , checkContracts :: Map Addr Contract , testExpectation :: Map Addr Contract } deriving Show@@ -62,38 +64,48 @@ , network :: String } deriving Show ++testEnv :: Env+testEnv = Env { config = defaultConfig }+ main :: IO () main = do- tests <- prepareTests+ tests <- runEnv testEnv prepareTests defaultMain tests -prepareTests :: IO TestTree+prepareTests :: App m => m TestTree prepareTests = do- repo <- getEnv "HEVM_ETHEREUM_TESTS_REPO"+ repo <- liftIO $ getEnv "HEVM_ETHEREUM_TESTS_REPO" let testsDir = "BlockchainTests/GeneralStateTests" let dir = repo </> testsDir- jsonFiles <- Find.find Find.always (Find.extension Find.==? ".json") dir- putStrLn "Loading and parsing json files from ethereum-tests..."- isCI <- isJust <$> lookupEnv "CI"+ jsonFiles <- liftIO $ Find.find Find.always (Find.extension Find.==? ".json") dir+ liftIO $ putStrLn "Loading and parsing json files from ethereum-tests..."+ isCI <- liftIO $ isJust <$> lookupEnv "CI" let problematicTests = if isCI then commonProblematicTests <> ciProblematicTests else commonProblematicTests let ignoredFiles = if isCI then ciIgnoredFiles else [] groups <- mapM (\f -> testGroup (makeRelative repo f) <$> (if any (`isInfixOf` f) ignoredFiles then pure [] else testsFromFile f problematicTests)) jsonFiles- putStrLn "Loaded."+ liftIO $ putStrLn "Loaded." pure $ testGroup "ethereum-tests" groups -testsFromFile :: String -> Map String (TestTree -> TestTree) -> IO [TestTree]+testsFromFile+ :: forall m . App m+ => String -> Map String (TestTree -> TestTree) -> m [TestTree] testsFromFile file problematicTests = do- parsed <- parseBCSuite <$> LazyByteString.readFile file+ parsed <- parseBCSuite <$> (liftIO $ LazyByteString.readFile file) case parsed of Left "No cases to check." -> pure [] -- error "no-cases ok" Left _err -> pure [] -- error _err- Right allTests -> pure $- (\(name, x) -> testCase' name $ runVMTest True (name, x)) <$> Map.toList allTests+ Right allTests -> mapM runTest (Map.toList allTests) where- testCase' name assertion =- case Map.lookup name problematicTests of- Just f -> f (testCase name assertion)- Nothing -> testCase name assertion+ runTest :: (String , Case) -> m TestTree+ runTest (name, x) = do+ exec <- toIO $ runVMTest True (name, x)+ pure $ testCase' name exec+ testCase' :: String -> Assertion -> TestTree+ testCase' name assertion =+ case Map.lookup name problematicTests of+ Just f -> f (testCase name (liftIO assertion))+ Nothing -> testCase name (liftIO assertion) -- CI has issues with some heaver tests, disable in bulk ciIgnoredFiles :: [String]@@ -126,24 +138,34 @@ , ("loopExp_d9g0v0_Shanghai", ignoreTest) ] -runVMTest :: Bool -> (String, Case) -> IO ()+runVMTest+ :: App m+ => Bool -> (String, Case) -> m () runVMTest diffmode (_name, x) = do- vm0 <- vmForCase x+ vm0 <- liftIO $ vmForCase x result <- EVM.Stepper.interpret (EVM.Fetch.zero 0 (Just 0)) vm0 EVM.Stepper.runFully+ writeTrace result maybeReason <- checkExpectation diffmode x result- forM_ maybeReason assertFailure+ liftIO $ forM_ maybeReason assertFailure + -- | Run a vm test and output a geth style per opcode trace-traceVMTest :: String -> String -> IO [VMTrace]+traceVMTest+ :: App m+ => String -> String -> m [VMTrace] traceVMTest file test = do- repo <- getEnv "HEVM_ETHEREUM_TESTS_REPO"- Right allTests <- parseBCSuite <$> LazyByteString.readFile (repo </> file)- let x = case filter (\(name, _) -> name == test) $ Map.toList allTests of+ repo <- liftIO $ getEnv "HEVM_ETHEREUM_TESTS_REPO"+ allTests <- parseBCSuite <$> (liftIO $ LazyByteString.readFile (repo </> file))+ let x = case filter (\(name, _) -> name == test) $ Map.toList (getRight allTests) of [(_, x')] -> x' _ -> internalError "test not found"- vm0 <- vmForCase x+ vm0 <- liftIO $ vmForCase x (_, (_, ts)) <- runStateT (interpretWithTrace (EVM.Fetch.zero 0 (Just 0)) EVM.Stepper.runFully) (vm0, []) pure ts+ where+ getRight :: Either a b -> b+ getRight (Right a) = a+ getRight (Left _) = error "Not allowed" -- | Read a geth trace from disk readTrace :: FilePath -> IO (Either String EVMToolTraceOutput)@@ -151,11 +173,14 @@ -- | given a path to a test file, a test case from within that file, and a trace from geth from running that test, compare the traces and show where we differ -- This would need a few tweaks to geth to make this really usable (i.e. evm statetest show allow running a single test from within the test file).-traceVsGeth :: String -> String -> FilePath -> IO ()+traceVsGeth+ :: App m+ => String -> String -> FilePath -> m () traceVsGeth file test gethTrace = do hevm <- traceVMTest file test- EVMToolTraceOutput ts _ <- fromJust <$> (JSON.decodeFileStrict gethTrace :: IO (Maybe EVMToolTraceOutput))- _ <- compareTraces hevm ts+ decodedContents <- liftIO (JSON.decodeFileStrict gethTrace :: IO (Maybe EVMToolTraceOutput))+ let EVMToolTraceOutput ts _ = fromJust decodedContents+ _ <- liftIO $ compareTraces hevm ts pure () splitEithers :: (Filterable f) => f (Either a b) -> (f a, f b)@@ -168,7 +193,7 @@ fromConcrete (ConcreteStore s) = s fromConcrete s = internalError $ "unexpected abstract store: " <> show s -checkStateFail :: Bool -> Case -> VM RealWorld -> (Bool, Bool, Bool, Bool) -> IO String+checkStateFail :: Bool -> Case -> VM Concrete RealWorld -> (Bool, Bool, Bool, Bool) -> IO String checkStateFail diff x vm (okMoney, okNonce, okData, okCode) = do let printContracts :: Map Addr Contract -> IO ()@@ -200,13 +225,15 @@ printContracts actual pure (unwords reason) -checkExpectation :: Bool -> Case -> VM RealWorld -> IO (Maybe String)+checkExpectation+ :: App m+ => Bool -> Case -> VM Concrete RealWorld -> m (Maybe String) checkExpectation diff x vm = do let expectation = x.testExpectation (okState, b2, b3, b4, b5) = checkExpectedContracts vm expectation if okState then pure Nothing- else+ else liftIO $ Just <$> checkStateFail diff x vm (b2, b3, b4, b5) -- quotient account state by nullness@@ -227,7 +254,7 @@ (RuntimeCode a', RuntimeCode b') -> a' == b' _ -> internalError "unexpected code" -checkExpectedContracts :: VM RealWorld -> Map Addr Contract -> (Bool, Bool, Bool, Bool, Bool)+checkExpectedContracts :: VM Concrete RealWorld -> Map Addr Contract -> (Bool, Bool, Bool, Bool, Bool) checkExpectedContracts vm expected = let cs = fmap (clearZeroStorage . clearOrigStorage) $ forceConcreteAddrs vm.env.contracts in ( (expected ~= cs)@@ -454,7 +481,7 @@ else pure prestate -vmForCase :: Case -> IO (VM RealWorld)+vmForCase :: Case -> IO (VM Concrete RealWorld) vmForCase x = do vm <- stToIO $ makeVm x.vmOpts <&> set (#env % #contracts) (Map.mapKeys LitAddr x.checkContracts)
test/EVM/Test/Tracing.hs view
@@ -1,11 +1,12 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-} {-| Module : Tracing Description : Tests to fuzz concrete tracing, and symbolic execution Functions here are used to generate traces for the concrete-execution of HEVM and check that against evmtool from go-ehereum. Re-using some+execution of HEVM and check that against evmtool from go-ethereum. Re-using some of this code, we also generate a symbolic expression then evaluate it concretely through Expr.simplify, then check that against evmtool's output. -}@@ -14,8 +15,9 @@ import Control.Monad (when) import Control.Monad.Operational qualified as Operational import Control.Monad.ST (RealWorld, ST, stToIO)-import Control.Monad.State.Strict (StateT(..), liftIO)+import Control.Monad.State.Strict (StateT(..)) import Control.Monad.State.Strict qualified as State+import Control.Monad.Reader (ReaderT) import Data.Aeson ((.:), (.:?)) import Data.Aeson qualified as JSON import Data.ByteString (ByteString)@@ -36,7 +38,7 @@ import Test.QuickCheck.Instances.Text() import Test.QuickCheck.Instances.Natural() import Test.QuickCheck.Instances.ByteString()-import Test.Tasty (testGroup, TestTree)+import Test.Tasty (testGroup, TestTree, TestName) import Test.Tasty.HUnit (assertEqual, testCase) import Test.Tasty.QuickCheck hiding (Failure, Success) import Witch (into, unsafeInto)@@ -44,7 +46,7 @@ import Optics.Core hiding (pre) import Optics.State -import EVM (makeVm, initialContract, exec1)+import EVM (makeVm, initialContract, exec1, symbolify) import EVM.Assembler (assemble) import EVM.Expr qualified as Expr import EVM.Concrete qualified as Concrete@@ -59,7 +61,9 @@ import EVM.SymExec import EVM.Traversals (mapExpr) import EVM.Transaction qualified-import EVM.Types+import EVM.Types hiding (Env)+import EVM.Effects+import Control.Monad.IO.Unlift data VMTrace = VMTrace@@ -295,7 +299,9 @@ fromAddress = fromJust $ deriveAddr sk toAddress = 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 -getHEVMRet :: OpContract -> ByteString -> Int -> IO (Either (EvmError, [VMTrace]) (Expr 'End, [VMTrace], VMTraceResult))+getHEVMRet+ :: App m+ => OpContract -> ByteString -> Int -> m (Either (EvmError, [VMTrace]) (Expr 'End, [VMTrace], VMTraceResult)) getHEVMRet contr txData gaslimitExec = do let (txn, evmEnv, contrAlloc, fromAddress, toAddress, _) = evmSetup contr txData gaslimitExec runCodeWithTrace Nothing evmEnv contrAlloc txn (LitAddr fromAddress) (LitAddr toAddress)@@ -371,7 +377,7 @@ pure False go [] [b] = do -- evmtool produces ONE more trace element of the error- -- hevm on the other hand stops and doens't produce one more+ -- hevm on the other hand stops and doesn't produce one more if isJust b.error then pure True else do putStrLn $ "Traces don't match. HEVM's trace is longer by:" <> (show b)@@ -407,19 +413,17 @@ System.Directory.removeFile traceFileName System.Directory.removeFile (traceFileName ++ ".json") --- Create symbolic VM from concrete VM-symbolify :: VM s -> VM s-symbolify vm = vm { state = vm.state { calldata = AbstractBuf "calldata" } }- -- | Takes a runtime code and calls it with the provided calldata -- Uses evmtool's alloc and transaction to set up the VM correctly-runCodeWithTrace :: Fetch.RpcInfo -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction -> Expr EAddr -> Expr EAddr -> IO (Either (EvmError, [VMTrace]) ((Expr 'End, [VMTrace], VMTraceResult)))+runCodeWithTrace+ :: App m+ => Fetch.RpcInfo -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction+ -> Expr EAddr -> Expr EAddr -> m (Either (EvmError, [VMTrace]) ((Expr 'End, [VMTrace], VMTraceResult))) runCodeWithTrace rpcinfo evmEnv alloc txn fromAddr toAddress = withSolvers Z3 0 Nothing $ \solvers -> do let calldata' = ConcreteBuf txn.txdata code' = alloc.code- buildExpr :: SolverGroup -> VM RealWorld -> IO (Expr End) buildExpr s vm = interpret (Fetch.oracle s Nothing) Nothing 1 Naive vm runExpr- origVM <- stToIO $ vmForRuntimeCode code' calldata' evmEnv alloc txn fromAddr toAddress+ origVM <- liftIO $ stToIO $ vmForRuntimeCode code' calldata' evmEnv alloc txn fromAddr toAddress expr <- buildExpr solvers $ symbolify origVM (res, (vm, trace)) <- runStateT (interpretWithTrace (Fetch.oracle solvers rpcinfo) Stepper.execFully) (origVM, [])@@ -427,7 +431,7 @@ Left x -> pure $ Left (x, trace) Right _ -> pure $ Right (expr, trace, vmres vm) -vmForRuntimeCode :: ByteString -> Expr Buf -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction -> Expr EAddr -> Expr EAddr -> ST s (VM s)+vmForRuntimeCode :: ByteString -> Expr Buf -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction -> Expr EAddr -> Expr EAddr -> ST s (VM Concrete s) vmForRuntimeCode runtimecode calldata' evmToolEnv alloc txn fromAddr toAddress = let contract = initialContract (RuntimeCode (ConcreteRuntimeCode runtimecode)) & set #balance (Lit alloc.balance)@@ -460,9 +464,9 @@ (Just (initialContract (RuntimeCode (ConcreteRuntimeCode BS.empty)))) <&> set (#state % #calldata) calldata' -runCode :: Fetch.RpcInfo -> ByteString -> Expr Buf -> IO (Maybe (Expr Buf))+runCode :: App m => Fetch.RpcInfo -> ByteString -> Expr Buf -> m (Maybe (Expr Buf)) runCode rpcinfo code' calldata' = withSolvers Z3 0 Nothing $ \solvers -> do- origVM <- stToIO $ vmForRuntimeCode+ origVM <- liftIO $ stToIO $ vmForRuntimeCode code' calldata' emptyEvmToolEnv@@ -475,7 +479,7 @@ Left _ -> Nothing Right b -> Just b -vmtrace :: VM s -> VMTrace+vmtrace :: VM Concrete s -> VMTrace vmtrace vm = let memsize = vm.state.memorySize@@ -490,7 +494,7 @@ , traceError = readoutError vm.result } where- readoutError :: Maybe (VMResult s) -> Maybe String+ readoutError :: Maybe (VMResult t s) -> Maybe String readoutError (Just (VMFailure e)) = case e of -- NOTE: error text made to closely match go-ethereum's errors.go file OutOfGas {} -> Just "out of gas"@@ -513,7 +517,7 @@ err -> Just $ "HEVM error: " <> show err readoutError _ = Nothing -vmres :: VM s -> VMTraceResult+vmres :: VM Concrete s -> VMTraceResult vmres vm = let gasUsed' = vm.tx.gaslimit - vm.state.gas@@ -528,14 +532,14 @@ , gasUsed = gasUsed' } -type TraceState s = (VM s, [VMTrace])+type TraceState s = (VM Concrete s, [VMTrace]) -execWithTrace :: StateT (TraceState RealWorld) IO (VMResult RealWorld)+execWithTrace :: App m => StateT (TraceState RealWorld) m (VMResult Concrete RealWorld) execWithTrace = do _ <- runWithTrace fromJust <$> use (_1 % #result) -runWithTrace :: StateT (TraceState RealWorld) IO (VM RealWorld)+runWithTrace :: App m => StateT (TraceState RealWorld) m (VM Concrete RealWorld) runWithTrace = do -- This is just like `exec` except for every instruction evaluated, -- we also increment a counter indexed by the current code location.@@ -556,35 +560,28 @@ Just _ -> pure vm0 interpretWithTrace- :: Fetch.Fetcher RealWorld- -> Stepper.Stepper RealWorld a- -> StateT (TraceState RealWorld) IO a+ :: forall m a . App m+ => Fetch.Fetcher Concrete m RealWorld+ -> Stepper.Stepper Concrete RealWorld a+ -> StateT (TraceState RealWorld) m a interpretWithTrace fetcher = eval . Operational.view- where eval- :: Operational.ProgramView (Stepper.Action RealWorld) a- -> StateT (TraceState RealWorld) IO a-- eval (Operational.Return x) =- pure x-+ :: App m+ => Operational.ProgramView (Stepper.Action Concrete RealWorld) a+ -> StateT (TraceState RealWorld) m a+ eval (Operational.Return x) = pure x eval (action Operational.:>>= k) = case action of Stepper.Exec -> execWithTrace >>= interpretWithTrace fetcher . k- Stepper.Wait q -> case q of- PleaseAskSMT (Lit x) _ continue ->- interpretWithTrace fetcher (Stepper.evm (continue (Case (x > 0))) >>= k)- _ -> do- m <- liftIO (fetcher q)- vm <- use _1- vm' <- liftIO $ stToIO $ State.execStateT m vm- assign _1 vm'- interpretWithTrace fetcher (k ())- Stepper.Ask _ ->- internalError "cannot make choice in this interpreter"+ Stepper.Wait q -> do+ m <- State.lift $ fetcher q+ vm <- use _1+ vm' <- liftIO $ stToIO $ State.execStateT m vm+ assign _1 vm'+ interpretWithTrace fetcher (k ()) Stepper.IOAct q -> liftIO q >>= interpretWithTrace fetcher . k Stepper.EVM m -> do@@ -608,7 +605,7 @@ where noStorageNoExtcalls :: Op -> Bool noStorageNoExtcalls o = case o of- -- Extrenal info functions+ -- External info functions OpExtcodecopy -> False OpExtcodehash -> False OpExtcodesize -> False@@ -808,7 +805,7 @@ randItem :: [a] -> IO a randItem = generate . Test.QuickCheck.elements -getOp :: VM s -> Word8+getOp :: VM t s -> Word8 getOp vm = let pcpos = vm ^. #state % #pc code' = vm ^. #state % #code@@ -820,29 +817,38 @@ in if xs == BS.empty then 0 else BS.head xs +testEnv :: Env+testEnv = Env { config = defaultConfig }++test :: TestName -> ReaderT Env IO () -> TestTree+test a b = testCase a $ runEnv testEnv b++prop:: Testable prop => ReaderT Env IO prop -> Property+prop a = ioProperty $ runEnv testEnv a+ tests :: TestTree tests = testGroup "contract-quickcheck-run"- [ testProperty "random-contract-concrete-call" $ \(contr :: OpContract, GasLimitInt gasLimit, TxDataRaw txDataRaw) -> ioProperty $ do+ [ testProperty "random-contract-concrete-call" $ \(contr :: OpContract, GasLimitInt gasLimit, TxDataRaw txDataRaw) -> prop $ do let txData = BS.pack $ toEnum <$> txDataRaw -- TODO: By removing external calls, we fuzz less -- It should work also when we external calls. Removing for now.- contrFixed <- fixContractJumps $ removeExtcalls contr+ contrFixed <- liftIO $ fixContractJumps $ removeExtcalls contr checkTraceAndOutputs contrFixed gasLimit txData- , testCase "calldata-wraparound" $ do+ , test "calldata-wraparound" $ do let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 31),OpMstore8] -- value, offs , [OpPush (Lit 0x3),OpPush (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff),OpPush (Lit 0x0),OpCalldatacopy] -- size, offs, destOffs , [OpPush (Lit 0x20),OpPush (Lit 0),OpReturn] -- datasize, offs ] checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5])- , testCase "calldata-wraparound2" $ do+ , test "calldata-wraparound2" $ do let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 0),OpMstore8] -- value, offs , [OpPush (Lit 0x10),OpPush (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff),OpPush (Lit 0x0),OpCalldatacopy] -- size, offs, destOffs , [OpPush (Lit 0x20),OpPush (Lit 0),OpReturn] -- datasize, offs ] checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5])- , testCase "calldata-overwrite-with-0-if-oversized" $ do+ , test "calldata-overwrite-with-0-if-oversized" $ do -- supposed to copy 1...6 and then 0s, overwriting the 0xff with 0 let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 1),OpMstore8] -- value, offs@@ -850,14 +856,14 @@ , [OpPush (Lit 10),OpPush (Lit 0x0),OpReturn] -- datasize, offset ] checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5, 6])- , testCase "calldata-overwrite-correct-size" $ do+ , test "calldata-overwrite-correct-size" $ do let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 8),OpMstore8] -- value, offs , [OpPush (Lit 10),OpPush (Lit 0),OpPush (Lit 0), OpCalldatacopy] -- size, offs, destOffs , [OpPush (Lit 10),OpPush (Lit 0x0),OpReturn] -- datasize, offset ] checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5, 6])- , testCase "calldata-offset-copy" $ do+ , test "calldata-offset-copy" $ do let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 8),OpMstore8] -- value, offs , [OpPush (Lit 0xff),OpPush (Lit 1),OpMstore8] -- value, offs@@ -867,13 +873,13 @@ checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5, 6]) ] -checkTraceAndOutputs :: OpContract -> Int -> ByteString -> IO ()+checkTraceAndOutputs :: App m => OpContract -> Int -> ByteString -> m () checkTraceAndOutputs contract gasLimit txData = do- evmtoolResult <- getEVMToolRet contract txData gasLimit+ evmtoolResult <- liftIO $ getEVMToolRet contract txData gasLimit hevmRun <- getHEVMRet contract txData gasLimit- (Just evmtoolTraceOutput) <- getTraceOutput evmtoolResult+ evmtoolTraceOutput <- fmap fromJust $ liftIO $ getTraceOutput evmtoolResult case hevmRun of- (Right (expr, hevmTrace, hevmTraceResult)) -> do+ (Right (expr, hevmTrace, hevmTraceResult)) -> liftIO $ do let concretize :: Expr a -> Expr Buf -> Expr a concretize a c = mapExpr go a@@ -893,7 +899,7 @@ -- putStrLn $ "evmtool trace: " <> show (evmtoolTraceOutput.trace) assertEqual "Traces and gas must match" traceOK True let resultOK = evmtoolTraceOutput.output.output == hevmTraceResult.out- if resultOK then do+ if resultOK then liftIO $ do putStrLn $ "HEVM & evmtool's outputs match: '" <> (bsToHex $ bssToBs evmtoolTraceOutput.output.output) <> "'" if isNothing simplConcrExprRetval || (fromJust simplConcrExprRetval) == (bssToBs hevmTraceResult.out) then do@@ -916,18 +922,19 @@ putStrLn $ "HEVM result len: " <> (show (BS.length $ bssToBs hevmTraceResult.out)) putStrLn $ "evm result len: " <> (show (BS.length $ bssToBs evmtoolTraceOutput.output.output)) assertEqual "Contract exec successful. HEVM & evmtool's outputs must match" resultOK True- Left (evmerr, hevmTrace) -> do+ Left (evmerr, hevmTrace) -> liftIO $ do putStrLn $ "HEVM contract exec issue: " <> (show evmerr) -- putStrLn $ "evmtool result was: " <> show (fromJust evmtoolResult) -- putStrLn $ "output by evmtool is: '" <> bsToHex evmtoolTraceOutput.toOutput.output <> "'" traceOK <- compareTraces hevmTrace (evmtoolTraceOutput.trace) assertEqual "Traces and gas must match" traceOK True- System.Directory.removeFile "txs.json"- System.Directory.removeFile "alloc-out.json"- System.Directory.removeFile "alloc.json"- System.Directory.removeFile "result.json"- System.Directory.removeFile "env.json"- deleteTraceOutputFiles evmtoolResult+ liftIO $ do+ System.Directory.removeFile "txs.json"+ System.Directory.removeFile "alloc-out.json"+ System.Directory.removeFile "alloc.json"+ System.Directory.removeFile "result.json"+ System.Directory.removeFile "env.json"+ deleteTraceOutputFiles evmtoolResult -- GasLimitInt newtype GasLimitInt = GasLimitInt (Int)
test/EVM/Test/Utils.hs view
@@ -20,18 +20,28 @@ import EVM.Solvers import EVM.UnitTest import Control.Monad.ST (RealWorld)+import Control.Monad.IO.Unlift+import Control.Monad.Catch (MonadMask)+import EVM.Effects+import Data.Maybe (fromMaybe)+import EVM.Types (internalError)+import System.Environment (lookupEnv) -runSolidityTestCustom :: FilePath -> Text -> Maybe Natural -> Maybe Integer -> Bool -> RpcInfo -> ProjectType -> IO Bool+runSolidityTestCustom+ :: (MonadMask m, App m)+ => FilePath -> Text -> Maybe Natural -> Maybe Integer -> Bool -> RpcInfo -> ProjectType -> m Bool runSolidityTestCustom testFile match timeout maxIter ffiAllowed rpcinfo projectType = do withSystemTempDirectory "dapp-test" $ \root -> do- compile projectType root testFile >>= \case+ (liftIO $ compile projectType root testFile) >>= \case Left e -> error e Right bo@(BuildOutput contracts _) -> do withSolvers Z3 1 timeout $ \solvers -> do- opts <- testOpts solvers root (Just bo) match maxIter ffiAllowed rpcinfo+ opts <- liftIO $ testOpts solvers root (Just bo) match maxIter ffiAllowed rpcinfo unitTest opts contracts -runSolidityTest :: FilePath -> Text -> IO Bool+runSolidityTest+ :: (MonadMask m, App m)+ => FilePath -> Text -> m Bool runSolidityTest testFile match = runSolidityTestCustom testFile match Nothing Nothing True Nothing Foundry testOpts :: SolverGroup -> FilePath -> Maybe BuildOutput -> Text -> Maybe Integer -> Bool -> RpcInfo -> IO (UnitTestOptions RealWorld)@@ -45,7 +55,6 @@ , rpcInfo = rpcinfo , maxIter = maxIter , askSmtIters = 1- , smtDebug = False , smtTimeout = Nothing , solver = Nothing , verbose = Just 1@@ -62,16 +71,26 @@ T.writeFile (root <> "/out/dapp.sol.json") json readBuildOutput root DappTools compile CombinedJSON _root _src = error "unsupported"-compile Foundry root src = do+compile foundryType root src = do createDirectory (root <> "/src") writeFile (root <> "/src/unit-tests.t.sol") =<< readFile =<< Paths.getDataFileName src initLib (root <> "/lib/ds-test") "test/contracts/lib/test.sol" "test.sol" initLib (root <> "/lib/tokens") "test/contracts/lib/erc20.sol" "erc20.sol"+ case foundryType of+ FoundryStdLib -> initStdForgeDir (root <> "/lib/forge-std")+ Foundry -> pure () r@(res,_,_) <- readProcessWithExitCode "forge" ["build", "--root", root] "" case res of ExitFailure _ -> pure . Left $ "compilation failed: " <> show r ExitSuccess -> readBuildOutput root Foundry where+ initStdForgeDir :: FilePath -> IO ()+ initStdForgeDir tld = do+ createDirectoryIfMissing True tld+ forgeStdRepo <- liftIO $ fromMaybe (internalError "cannot find forge-std repo") <$> (lookupEnv "HEVM_FORGE_STD_REPO")+ callProcess "mkdir" ["-p", tld]+ callProcess "cp" ["-r", forgeStdRepo <> "/src", tld <> "/src"]+ callProcess "cp" ["-r", forgeStdRepo <> "/lib", tld <> "/lib"] initLib :: FilePath -> FilePath -> FilePath -> IO () initLib tld srcFile dstFile = do createDirectoryIfMissing True (tld <> "/src")
test/rpc.hs view
@@ -11,7 +11,7 @@ import Data.Vector qualified as V import Optics.Core-import EVM (makeVm)+import EVM (makeVm, symbolify) import EVM.ABI import EVM.Fetch import EVM.SMT@@ -20,9 +20,18 @@ import EVM.SymExec import EVM.Test.Utils import EVM.Solidity (ProjectType(..))-import EVM.Types hiding (BlockNumber)+import EVM.Types hiding (BlockNumber, Env) import Control.Monad.ST (stToIO, RealWorld)+import Control.Monad.Reader (ReaderT)+import Control.Monad.IO.Unlift+import EVM.Effects +rpcEnv :: Env+rpcEnv = Env { config = defaultConfig }++test :: TestName -> ReaderT Env IO () -> TestTree+test a b = testCase a $ runEnv rpcEnv b+ main :: IO () main = defaultMain tests @@ -60,18 +69,19 @@ ] , testGroup "execution with remote state" -- execute against remote state from a ds-test harness- [ testCase "dapp-test" $ do+ [ test "dapp-test" $ do let testFile = "test/contracts/pass/rpc.sol"- runSolidityTestCustom testFile ".*" Nothing Nothing False testRpcInfo Foundry >>= assertEqual "test result" True+ res <- runSolidityTestCustom testFile ".*" Nothing Nothing False testRpcInfo Foundry+ liftIO $ assertEqual "test result" True res -- concretely exec "transfer" on WETH9 using remote rpc -- https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code- , testCase "weth-conc" $ do+ , test "weth-conc" $ do let blockNum = 16198552 wad = 0x999999999999999999 calldata' = ConcreteBuf $ abiMethod "transfer(address,uint256)" (AbiTuple (V.fromList [AbiAddress (Addr 0xdead), AbiUInt 256 wad]))- vm <- weth9VM blockNum (calldata', [])+ vm <- liftIO $ weth9VM blockNum (calldata', []) postVm <- withSolvers Z3 1 Nothing $ \solvers -> Stepper.interpret (oracle solvers (Just (BlockNumber blockNum, testRpc))) vm Stepper.runFully let@@ -83,26 +93,27 @@ msg = case postVm.result of Just (VMSuccess m) -> m _ -> internalError "VMSuccess expected"- assertEqual "should succeed" msg (ConcreteBuf $ word256Bytes 0x1)- assertEqual "should revert" receiverBal (W256 $ 2595433725034301 + wad)+ liftIO $ do+ assertEqual "should succeed" msg (ConcreteBuf $ word256Bytes 0x1)+ assertEqual "should revert" receiverBal (W256 $ 2595433725034301 + wad) -- symbolically exec "transfer" on WETH9 using remote rpc -- https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code- , testCase "weth-sym" $ do+ , test "weth-sym" $ do let blockNum = 16198552 calldata' = symCalldata "transfer(address,uint256)" [AbiAddressType, AbiUIntType 256] ["0xdead"] (AbstractBuf "txdata") postc _ (Failure _ _ (Revert _)) = PBool False postc _ _ = PBool True- vm <- weth9VM blockNum calldata'+ vm <- liftIO $ weth9VM blockNum calldata' (_, [Cex (_, model)]) <- withSolvers Z3 1 Nothing $ \solvers ->- verify solvers (rpcVeriOpts (BlockNumber blockNum, testRpc)) vm (Just postc)- assertBool "model should exceed caller balance" (getVar model "arg2" >= 695836005599316055372648)+ verify solvers (rpcVeriOpts (BlockNumber blockNum, testRpc)) (symbolify vm) (Just postc)+ liftIO $ assertBool "model should exceed caller balance" (getVar model "arg2" >= 695836005599316055372648) ] ] -- call into WETH9 from 0xf04a... (a large holder)-weth9VM :: W256 -> (Expr Buf, [Prop]) -> IO (VM RealWorld)+weth9VM :: W256 -> (Expr Buf, [Prop]) -> IO (VM Concrete RealWorld) weth9VM blockNum calldata' = do let caller' = LitAddr 0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e@@ -110,7 +121,7 @@ callvalue' = Lit 0 vmFromRpc blockNum calldata' callvalue' caller' weth9 -vmFromRpc :: W256 -> (Expr Buf, [Prop]) -> Expr EWord -> Expr EAddr -> Addr -> IO (VM RealWorld)+vmFromRpc :: W256 -> (Expr Buf, [Prop]) -> Expr EWord -> Expr EAddr -> Addr -> IO (VM Concrete RealWorld) vmFromRpc blockNum calldata callvalue caller address = do ctrct <- fetchContractFrom (BlockNumber blockNum) testRpc address >>= \case Nothing -> internalError $ "contract not found: " <> show address
test/test.hs view
@@ -9,3807 +9,4243 @@ import Data.Proxy import Control.Monad.ST (RealWorld, stToIO) import Control.Monad.State.Strict-import Data.Bits hiding (And, Xor)-import Data.ByteString (ByteString)-import Data.ByteString qualified as BS-import Data.ByteString.Base16 qualified as BS16-import Data.Binary.Put (runPut)-import Data.Binary.Get (runGetOrFail)-import Data.DoubleWord-import Data.Either-import Data.List qualified as List-import Data.Map.Strict qualified as Map-import Data.Maybe-import Data.String.Here-import Data.Text (Text)-import Data.Text qualified as T-import Data.Text.IO qualified as T-import Data.Time (diffUTCTime, getCurrentTime)-import Data.Typeable-import Data.Vector qualified as V-import Data.Word (Word8)-import GHC.Conc (getNumProcessors)-import System.Directory-import System.Environment-import Test.Tasty-import Test.Tasty.QuickCheck hiding (Failure, Success)-import Test.QuickCheck.Instances.Text()-import Test.QuickCheck.Instances.Natural()-import Test.QuickCheck.Instances.ByteString()-import Test.Tasty.HUnit-import Test.Tasty.Runners hiding (Failure, Success)-import Test.Tasty.ExpectedFailure-import Text.RE.TDFA.String-import Text.RE.Replace-import Witch (unsafeInto, into)--import Optics.Core hiding (pre, re, elements)-import Optics.State--import EVM hiding (choose)-import EVM.ABI-import EVM.Assembler-import EVM.Exec-import EVM.Expr qualified as Expr-import EVM.Fetch qualified as Fetch-import EVM.Format (hexText, formatExpr)-import EVM.Precompiled-import EVM.RLP-import EVM.SMT hiding (one)-import EVM.Solidity-import EVM.Solvers-import EVM.Stepper qualified as Stepper-import EVM.SymExec-import EVM.Test.Tracing qualified as Tracing-import EVM.Test.Utils-import EVM.Traversals-import EVM.Types--main :: IO ()-main = defaultMain tests---- | run a subset of tests in the repl. p is a tasty pattern:--- https://github.com/UnkindPartition/tasty/tree/ee6fe7136fbcc6312da51d7f1b396e1a2d16b98a#patterns-runSubSet :: String -> IO ()-runSubSet p = defaultMain . applyPattern p $ tests--tests :: TestTree-tests = testGroup "hevm"- [ Tracing.tests- , testGroup "simplify-storage"- [ testCase "simplify-storage-array-only-static" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- uint[] a;- function transfer(uint acct, uint val1, uint val2) public {- unchecked {- a[0] = val1 + 1;- a[1] = val2 + 2;- assert(a[0]+a[1] == val1 + val2 + 3);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts- assertEqual "Expression is not clean." (badStoresInExpr expr) False- -- This case is somewhat artificial. We can't simplify this using only- -- static rewrite rules, because acct is totally abstract and acct + 1- -- could overflow back to zero. we may be able to do better if we have some- -- smt assisted simplification that can take branch conditions into account.- , expectFail $ testCase "simplify-storage-array-symbolic-index" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- uint b;- uint[] a;- function transfer(uint acct, uint val1) public {- unchecked {- a[acct] = val1;- assert(a[acct] == val1);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts- -- T.writeFile "symbolic-index.expr" $ formatExpr expr- assertEqual "Expression is not clean." (badStoresInExpr expr) False- , expectFail $ testCase "simplify-storage-array-of-struct-symbolic-index" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- struct MyStruct {- uint a;- uint b;- }- MyStruct[] arr;- function transfer(uint acct, uint val1, uint val2) public {- unchecked {- arr[acct].a = val1+1;- arr[acct].b = val1+2;- assert(arr[acct].a + arr[acct].b == val1+val2+3);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts- assertEqual "Expression is not clean." (badStoresInExpr expr) False- , testCase "simplify-storage-array-loop-nonstruct" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- uint[] a;- function transfer(uint v) public {- for (uint i = 0; i < a.length; i++) {- a[i] = v;- assert(a[i] == v);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256)" [AbiUIntType 256])) [] (debugVeriOpts { maxIter = Just 5 })- assertEqual "Expression is not clean." (badStoresInExpr expr) False- , testCase "simplify-storage-array-loop-struct" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- struct MyStruct {- uint a;- uint b;- }- MyStruct[] arr;- function transfer(uint v1, uint v2) public {- for (uint i = 0; i < arr.length; i++) {- arr[i].a = v1+1;- arr[i].b = v2+2;- assert(arr[i].a + arr[i].b == v1 + v2 + 3);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] (debugVeriOpts { maxIter = Just 5 })- assertEqual "Expression is not clean." (badStoresInExpr expr) False- , testCase "simplify-storage-map-only-static" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- mapping(uint => uint) items1;- function transfer(uint acct, uint val1, uint val2) public {- unchecked {- items1[0] = val1+1;- items1[1] = val2+2;- assert(items1[0]+items1[1] == val1 + val2 + 3);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts- assertEqual "Expression is not clean." (badStoresInExpr expr) False- , testCase "simplify-storage-map-only-2" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- mapping(uint => uint) items1;- function transfer(uint acct, uint val1, uint val2) public {- unchecked {- items1[acct] = val1+1;- items1[acct+1] = val2+2;- assert(items1[acct]+items1[acct+1] == val1 + val2 + 3);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts- -- putStrLn $ T.unpack $ formatExpr expr- assertEqual "Expression is not clean." (badStoresInExpr expr) False- , testCase "simplify-storage-map-with-struct" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- struct MyStruct {- uint a;- uint b;- }- mapping(uint => MyStruct) items1;- function transfer(uint acct, uint val1, uint val2) public {- unchecked {- items1[acct].a = val1+1;- items1[acct].b = val2+2;- assert(items1[acct].a+items1[acct].b == val1 + val2 + 3);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts- assertEqual "Expression is not clean." (badStoresInExpr expr) False- , testCase "simplify-storage-map-and-array" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- uint[] a;- mapping(uint => uint) items1;- mapping(uint => uint) items2;- function transfer(uint acct, uint val1, uint val2) public {- uint beforeVal1 = items1[acct];- uint beforeVal2 = items2[acct];- unchecked {- items1[acct] = val1+1;- items2[acct] = val2+2;- a[0] = val1 + val2 + 1;- a[1] = val1 + val2 + 2;- assert(items1[acct]+items2[acct]+a[0]+a[1] > beforeVal1 + beforeVal2);- }- }- }- |]- expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts- -- putStrLn $ T.unpack $ formatExpr expr- assertEqual "Expression is not clean." (badStoresInExpr expr) False- ]- , testGroup "StorageTests"- [ testCase "read-from-sstore" $ assertEqual ""- (Lit 0xab)- (Expr.readStorage' (Lit 0x0) (SStore (Lit 0x0) (Lit 0xab) (AbstractStore (LitAddr 0x0))))- , testCase "read-from-concrete" $ assertEqual ""- (Lit 0xab)- (Expr.readStorage' (Lit 0x0) (ConcreteStore $ Map.fromList [(0x0, 0xab)]))- , testCase "read-past-write" $ assertEqual ""- (Lit 0xab)- (Expr.readStorage' (Lit 0x0) (SStore (Lit 0x1) (Var "b") (ConcreteStore $ Map.fromList [(0x0, 0xab)])))- , testCase "accessStorage uses fetchedStorage" $ do- let dummyContract =- (initialContract (RuntimeCode (ConcreteRuntimeCode mempty)))- { external = True }- vm <- stToIO $ vmForEthrunCreation ""- -- perform the initial access- vm1 <- stToIO $ execStateT (EVM.accessStorage (LitAddr 0) (Lit 0) (pure . pure ())) vm- -- it should fetch the contract first- vm2 <- case vm1.result of- Just (HandleEffect (Query (PleaseFetchContract _addr _ continue))) ->- stToIO $ execStateT (continue dummyContract) vm1- _ -> internalError "unexpected result"- -- then it should fetch the slow- vm3 <- case vm2.result of- Just (HandleEffect (Query (PleaseFetchSlot _addr _slot continue))) ->- stToIO $ execStateT (continue 1337) vm2- _ -> internalError "unexpected result"- -- perform the same access as for vm1- vm4 <- stToIO $ execStateT (EVM.accessStorage (LitAddr 0) (Lit 0) (pure . pure ())) vm3-- -- there won't be query now as accessStorage uses fetch cache- assertBool (show vm4.result) (isNothing vm4.result)- ]- , testGroup "SimplifierUnitTests"- -- common overflow cases that the simplifier was getting wrong- [ testCase "bufLength-simp" $ do- let- a = BufLength (ConcreteBuf "ab")- simp = Expr.simplify a- assertEqual "Must be simplified down to a Lit" simp (Lit 2)- , testCase "writeWord-overflow" $ do- let e = ReadByte (Lit 0x0) (WriteWord (Lit 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd) (Lit 0x0) (ConcreteBuf "\255\255\255\255"))- b <- checkEquiv e (Expr.simplify e)- assertBool "Simplifier failed" b- , testCase "CopySlice-overflow" $ do- let e = ReadWord (Lit 0x0) (CopySlice (Lit 0x0) (Lit 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc) (Lit 0x6) (ConcreteBuf "\255\255\255\255\255\255") (ConcreteBuf ""))- b <- checkEquiv e (Expr.simplify e)- assertBool "Simplifier failed" b- , testCase "stripWrites-overflow" $ do- -- below eventually boils down to- -- unsafeInto (0xf0000000000000000000000000000000000000000000000000000000000000+1) :: Int- -- which failed before- let- a = ReadByte (Lit 0xf0000000000000000000000000000000000000000000000000000000000000) (WriteByte (And (SHA256 (ConcreteBuf "")) (Lit 0x1)) (LitByte 0) (ConcreteBuf ""))- b = Expr.simplify a- ret <- checkEquiv a b- assertBool "must be equivalent" ret- ]- -- These tests fuzz the simplifier by generating a random expression,- -- applying some simplification rules, and then using the smt encoding to- -- check that the simplified version is semantically equivalent to the- -- unsimplified one- , adjustOption (\(Test.Tasty.QuickCheck.QuickCheckTests n) -> Test.Tasty.QuickCheck.QuickCheckTests (min n 50)) $ testGroup "SimplifierTests"- [ testProperty "buffer-simplification" $ \(expr :: Expr Buf) -> ioProperty $ do- let simplified = Expr.simplify expr- checkEquiv expr simplified- , testProperty "store-simplification" $ \(expr :: Expr Storage) -> ioProperty $ do- let simplified = Expr.simplify expr- checkEquiv expr simplified- , testProperty "byte-simplification" $ \(expr :: Expr Byte) -> ioProperty $ do- let simplified = Expr.simplify expr- checkEquiv expr simplified- , testProperty "word-simplification" $ \(ZeroDepthWord expr) -> ioProperty $ do- let simplified = Expr.simplify expr- checkEquiv expr simplified- , testProperty "readStorage-equivalance" $ \(store, slot) -> ioProperty $ do- let simplified = Expr.readStorage' slot store- full = SLoad slot store- checkEquiv simplified full- , testProperty "writeStorage-equivalance" $ \(val, GenWriteStorageExpr (slot, store)) -> ioProperty $ do- let simplified = Expr.writeStorage slot val store- full = SStore slot val store- checkEquiv simplified full- , testProperty "readWord-equivalance" $ \(buf, idx) -> ioProperty $ do- let simplified = Expr.readWord idx buf- full = ReadWord idx buf- checkEquiv simplified full- , testProperty "writeWord-equivalance" $ \(idx, val, WriteWordBuf buf) -> ioProperty $ do- let simplified = Expr.writeWord idx val buf- full = WriteWord idx val buf- checkEquiv simplified full- , testProperty "arith-simplification" $ \(_ :: Int) -> ioProperty $ do- expr <- generate . sized $ genWordArith 15- let simplified = Expr.simplify expr- checkEquiv expr simplified- , testProperty "readByte-equivalance" $ \(buf, idx) -> ioProperty $ do- let simplified = Expr.readByte idx buf- full = ReadByte idx buf- checkEquiv simplified full- -- we currently only simplify concrete writes over concrete buffers so that's what we test here- , testProperty "writeByte-equivalance" $ \(LitOnly val, LitOnly buf, GenWriteByteIdx idx) -> ioProperty $ do- let simplified = Expr.writeByte idx val buf- full = WriteByte idx val buf- checkEquiv simplified full- , testProperty "copySlice-equivalance" $ \(srcOff, GenCopySliceBuf src, GenCopySliceBuf dst, LitWord @300 size) -> ioProperty $ do- -- we bias buffers to be concrete more often than not- dstOff <- generate (maybeBoundedLit 100_000)- let simplified = Expr.copySlice srcOff dstOff size src dst- full = CopySlice srcOff dstOff size src dst- checkEquiv simplified full- , testProperty "indexWord-equivalence" $ \(src, LitWord @50 idx) -> ioProperty $ do- let simplified = Expr.indexWord idx src- full = IndexWord idx src- checkEquiv simplified full- , testProperty "indexWord-mask-equivalence" $ \(src :: Expr EWord, LitWord @35 idx) -> ioProperty $ do- mask <- generate $ do- pow <- arbitrary :: Gen Int- frequency- [ (1, pure $ Lit $ (shiftL 1 (pow `mod` 256)) - 1) -- potentially non byte aligned- , (1, pure $ Lit $ (shiftL 1 ((pow * 8) `mod` 256)) - 1) -- byte aligned- ]- let- input = And mask src- simplified = Expr.indexWord idx input- full = IndexWord idx input- checkEquiv simplified full- , testProperty "toList-equivalance" $ \buf -> ioProperty $ do- let- -- transforms the input buffer to give it a known length- fixLength :: Expr Buf -> Gen (Expr Buf)- fixLength = mapExprM go- where- go :: Expr a -> Gen (Expr a)- go = \case- WriteWord _ val b -> liftM3 WriteWord idx (pure val) (pure b)- WriteByte _ val b -> liftM3 WriteByte idx (pure val) (pure b)- CopySlice so _ sz src dst -> liftM5 CopySlice (pure so) idx (pure sz) (pure src) (pure dst)- AbstractBuf _ -> cbuf- e -> pure e- cbuf = do- bs <- arbitrary- pure $ ConcreteBuf bs- idx = do- w <- arbitrary- -- we use 100_000 as an upper bound for indices to keep tests reasonably fast here- pure $ Lit (w `mod` 100_000)-- input <- generate $ fixLength buf- case Expr.toList input of- Nothing -> do- putStrLn "skip"- pure True -- ignore cases where the buf cannot be represented as a list- Just asList -> do- let asBuf = Expr.fromList asList- checkEquiv asBuf input- , testProperty "simplifyProp-equivalence-lit" $ \(LitProp p) -> ioProperty $ do- let simplified = Expr.simplifyProps [p]- case simplified of- [] -> checkEquivProp (PBool True) p- [val@(PBool _)] -> checkEquivProp val p- _ -> assertFailure "must evaluate down to a literal bool"- , testProperty "simplifyProp-equivalence-sym" $ \(p) -> ioProperty $ do- let simplified = Expr.simplifyProp p- checkEquivProp simplified p- , testProperty "simpProp-equivalence-sym" $ \(ps :: [Prop]) -> ioProperty $ do- let simplified = pand (Expr.simplifyProps ps)- checkEquivProp simplified (pand ps)- , testProperty "simpProp-equivalence-sym" $ \(LitProp p) -> ioProperty $ do- let simplified = pand (Expr.simplifyProps [p])- checkEquivProp simplified p- -- This would need to be a fuzz test I think. The SMT encoding of Keccak is not precise- -- enough for this to succeed- , ignoreTest $ testProperty "storage-slot-simp-property" $ \(StorageExp s) -> ioProperty $ do- T.writeFile "unsimplified.expr" $ formatExpr s- let simplified = Expr.simplify s- T.writeFile "simplified.expr" $ formatExpr simplified- checkEquiv simplified s- ]- , testGroup "simpProp-concrete-tests" [- testCase "simpProp-concrete-trues" $ do- let- t = [PBool True, PBool True]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [] simplified- , testCase "simpProp-concrete-false1" $ do- let- t = [PBool True, PBool False]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [PBool False] simplified- , testCase "simpProp-concrete-false2" $ do- let- t = [PBool False, PBool False]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [PBool False] simplified- , testCase "simpProp-concrete-or-1" $ do- let- -- a = 5 && (a=4 || a=3) -> False- t = [PEq (Lit 5) (Var "a"), POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 3))]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [PBool False] simplified- , ignoreTest $ testCase "simpProp-concrete-or-2" $ do- let- -- Currently does not work, because we don't do simplification inside- -- POr/PAnd using canBeSat- -- a = 5 && (a=4 || a=5) -> a=5- t = [PEq (Lit 5) (Var "a"), POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 5))]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [] simplified- , testCase "simpProp-concrete-and-1" $ do- let- -- a = 5 && (a=4 && a=3) -> False- t = [PEq (Lit 5) (Var "a"), PAnd (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 3))]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [PBool False] simplified- , testCase "simpProp-concrete-or-of-or" $ do- let- -- a = 5 && ((a=4 || a=6) || a=3) -> False- t = [PEq (Lit 5) (Var "a"), POr (POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 6))) (PEq (Var "a") (Lit 3))]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [PBool False] simplified- , testCase "simpProp-concrete-or-eq-rem" $ do- let- -- a = 5 && ((a=4 || a=6) || a=3) -> False- t = [PEq (Lit 5) (Var "a"), POr (POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 6))) (PEq (Var "a") (Lit 3))]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [PBool False] simplified- , testCase "simpProp-inner-expr-simp" $ do- let- -- 5+1 = 6- t = [PEq (Add (Lit 5) (Lit 1)) (Var "a")]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [PEq (Lit 6) (Var "a")] simplified- , testCase "simpProp-inner-expr-simp-with-canBeSat" $ do- let- -- 5+1 = 6, 6 != 7- t = [PAnd (PEq (Add (Lit 5) (Lit 1)) (Var "a")) (PEq (Var "a") (Lit 7))]- simplified = Expr.simplifyProps t- assertEqual "Must be equal" [PBool False] simplified- ]- , testGroup "MemoryTests"- [ testCase "read-write-same-byte" $ assertEqual ""- (LitByte 0x12)- (Expr.readByte (Lit 0x20) (WriteByte (Lit 0x20) (LitByte 0x12) mempty))- , testCase "read-write-same-word" $ assertEqual ""- (Lit 0x12)- (Expr.readWord (Lit 0x20) (WriteWord (Lit 0x20) (Lit 0x12) mempty))- , testCase "read-byte-write-word" $ assertEqual ""- -- reading at byte 31 a word that's been written should return LSB- (LitByte 0x12)- (Expr.readByte (Lit 0x1f) (WriteWord (Lit 0x0) (Lit 0x12) mempty))- , testCase "read-byte-write-word2" $ assertEqual ""- -- Same as above, but offset not 0- (LitByte 0x12)- (Expr.readByte (Lit 0x20) (WriteWord (Lit 0x1) (Lit 0x12) mempty))- ,testCase "read-write-with-offset" $ assertEqual ""- -- 0x3F = 63 decimal, 0x20 = 32. 0x12 = 18- -- We write 128bits (32 Bytes), representing 18 at offset 32.- -- Hence, when reading out the 63rd byte, we should read out the LSB 8 bits- -- which is 0x12- (LitByte 0x12)- (Expr.readByte (Lit 0x3F) (WriteWord (Lit 0x20) (Lit 0x12) mempty))- ,testCase "read-write-with-offset2" $ assertEqual ""- -- 0x20 = 32, 0x3D = 61- -- we write 128 bits (32 Bytes) representing 0x10012, at offset 32.- -- we then read out a byte at offset 61.- -- So, at 63 we'd read 0x12, at 62 we'd read 0x00, at 61 we should read 0x1- (LitByte 0x1)- (Expr.readByte (Lit 0x3D) (WriteWord (Lit 0x20) (Lit 0x10012) mempty))- , testCase "read-write-with-extension-to-zero" $ assertEqual ""- -- write word and read it at the same place (i.e. 0 offset)- (Lit 0x12)- (Expr.readWord (Lit 0x0) (WriteWord (Lit 0x0) (Lit 0x12) mempty))- , testCase "read-write-with-extension-to-zero-with-offset" $ assertEqual ""- -- write word and read it at the same offset of 4- (Lit 0x12)- (Expr.readWord (Lit 0x4) (WriteWord (Lit 0x4) (Lit 0x12) mempty))- , testCase "read-write-with-extension-to-zero-with-offset2" $ assertEqual ""- -- write word and read it at the same offset of 16- (Lit 0x12)- (Expr.readWord (Lit 0x20) (WriteWord (Lit 0x20) (Lit 0x12) mempty))- , testCase "read-word-copySlice-overlap" $ assertEqual ""- -- we should not recurse into a copySlice if the read index + 32 overlaps the sliced region- (ReadWord (Lit 40) (CopySlice (Lit 0) (Lit 30) (Lit 12) (WriteWord (Lit 10) (Lit 0x64) (AbstractBuf "hi")) (AbstractBuf "hi")))- (Expr.readWord (Lit 40) (CopySlice (Lit 0) (Lit 30) (Lit 12) (WriteWord (Lit 10) (Lit 0x64) (AbstractBuf "hi")) (AbstractBuf "hi")))- , testCase "indexword-MSB" $ assertEqual ""- -- 31st is the LSB byte (of 32)- (LitByte 0x78)- (Expr.indexWord (Lit 31) (Lit 0x12345678))- , testCase "indexword-LSB" $ assertEqual ""- -- 0th is the MSB byte (of 32), Lit 0xff22bb... is exactly 32 Bytes.- (LitByte 0xff)- (Expr.indexWord (Lit 0) (Lit 0xff22bb4455667788990011223344556677889900112233445566778899001122))- , testCase "indexword-LSB2" $ assertEqual ""- -- same as above, but with offset 2- (LitByte 0xbb)- (Expr.indexWord (Lit 2) (Lit 0xff22bb4455667788990011223344556677889900112233445566778899001122))- , testCase "encodeConcreteStore-overwrite" $- assertEqual ""- "(store (store ((as const Storage) #x0000000000000000000000000000000000000000000000000000000000000000) (_ bv1 256) (_ bv2 256)) (_ bv3 256) (_ bv4 256))"- (EVM.SMT.encodeConcreteStore $- Map.fromList [(W256 1, W256 2), (W256 3, W256 4)])- , testCase "indexword-oob-sym" $ assertEqual ""- -- indexWord should return 0 for oob access- (LitByte 0x0)- (Expr.indexWord (Lit 100) (JoinBytes- (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0)- (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0)- (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0)- (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0)))- , testCase "stripbytes-concrete-bug" $ assertEqual ""- (Expr.simplifyReads (ReadByte (Lit 0) (ConcreteBuf "5")))- (LitByte 53)- ]- , testGroup "ABI"- [ testProperty "Put/get inverse" $ \x ->- case runGetOrFail (getAbi (abiValueType x)) (runPut (putAbi x)) of- Right ("", _, x') -> x' == x- _ -> False- ]- , testGroup "Solidity-Expressions"- [ testCase "Trivial" $- SolidityCall "x = 3;" []- ===> AbiUInt 256 3-- , testCase "Arithmetic" $ do- SolidityCall "x = a + 1;"- [AbiUInt 256 1] ===> AbiUInt 256 2- SolidityCall "unchecked { x = a - 1; }"- [AbiUInt 8 0] ===> AbiUInt 8 255-- , testCase "keccak256()" $- SolidityCall "x = uint(keccak256(abi.encodePacked(a)));"- [AbiString ""] ===> AbiUInt 256 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470-- , testProperty "abi encoding vs. solidity" $ withMaxSuccess 20 $ forAll (arbitrary >>= genAbiValue) $- \y -> ioProperty $ do- Just encoded <- runStatements [i| x = abi.encode(a);|]- [y] AbiBytesDynamicType- let solidityEncoded = case decodeAbiValue (AbiTupleType $ V.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of- AbiTuple (V.toList -> [e]) -> e- _ -> internalError "AbiTuple expected"- let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [y])- assertEqual "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded)-- , testProperty "abi encoding vs. solidity (2 args)" $ withMaxSuccess 20 $ forAll (arbitrary >>= bothM genAbiValue) $- \(x', y') -> ioProperty $ do- Just encoded <- runStatements [i| x = abi.encode(a, b);|]- [x', y'] AbiBytesDynamicType- let solidityEncoded = case decodeAbiValue (AbiTupleType $ V.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of- AbiTuple (V.toList -> [e]) -> e- _ -> internalError "AbiTuple expected"- let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [x',y'])- assertEqual "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded)-- -- we need a separate test for this because the type of a function is "function() external" in solidity but just "function" in the abi:- , testProperty "abi encoding vs. solidity (function pointer)" $ withMaxSuccess 20 $ forAll (genAbiValue AbiFunctionType) $- \y -> ioProperty $ do- Just encoded <- runFunction [i|- function foo(function() external a) public pure returns (bytes memory x) {- x = abi.encode(a);- }- |] (abiMethod "foo(function)" (AbiTuple (V.singleton y)))- let solidityEncoded = case decodeAbiValue (AbiTupleType $ V.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of- AbiTuple (V.toList -> [e]) -> e- _ -> internalError "AbiTuple expected"- let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [y])- assertEqual "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded)- ]-- , testGroup "Precompiled contracts"- [ testGroup "Example (reverse)"- [ testCase "success" $- assertEqual "example contract reverses"- (execute 0xdeadbeef "foobar" 6) (Just "raboof")- , testCase "failure" $- assertEqual "example contract fails on length mismatch"- (execute 0xdeadbeef "foobar" 5) Nothing- ]-- , testGroup "ECRECOVER"- [ testCase "success" $ do- let- r = hex "c84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4732"- s = hex "1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d"- v = hex "000000000000000000000000000000000000000000000000000000000000001c"- h = hex "513954cf30af6638cb8f626bd3f8c39183c26784ce826084d9d267868a18fb31"- a = hex "0000000000000000000000002d5e56d45c63150d937f2182538a0f18510cb11f"- assertEqual "successful recovery"- (Just a)- (execute 1 (h <> v <> r <> s) 32)- , testCase "fail on made up values" $ do- let- r = hex "c84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4731"- s = hex "1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d"- v = hex "000000000000000000000000000000000000000000000000000000000000001c"- h = hex "513954cf30af6638cb8f626bd3f8c39183c26784ce826084d9d267868a18fb31"- assertEqual "fail because bit flip"- Nothing- (execute 1 (h <> v <> r <> s) 32)- ]- ]- , testGroup "Byte/word manipulations"- [ testProperty "padLeft length" $ \n (Bytes bs) ->- BS.length (padLeft n bs) == max n (BS.length bs)- , testProperty "padLeft identity" $ \(Bytes bs) ->- padLeft (BS.length bs) bs == bs- , testProperty "padRight length" $ \n (Bytes bs) ->- BS.length (padLeft n bs) == max n (BS.length bs)- , testProperty "padRight identity" $ \(Bytes bs) ->- padLeft (BS.length bs) bs == bs- , testProperty "padLeft zeroing" $ \(NonNegative n) (Bytes bs) ->- let x = BS.take n (padLeft (BS.length bs + n) bs)- y = BS.replicate n 0- in x == y- ]-- , testGroup "Unresolved link detection"- [ testCase "holes detected" $ do- let code' = "608060405234801561001057600080fd5b5060405161040f38038061040f83398181016040528101906100329190610172565b73__$f3cbc3eb14e5bd0705af404abcf6f741ec$__63ab5c1ffe826040518263ffffffff1660e01b81526004016100699190610217565b60206040518083038186803b15801561008157600080fd5b505af4158015610095573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100b99190610145565b50506103c2565b60006100d36100ce84610271565b61024c565b9050828152602081018484840111156100ef576100ee610362565b5b6100fa8482856102ca565b509392505050565b600081519050610111816103ab565b92915050565b600082601f83011261012c5761012b61035d565b5b815161013c8482602086016100c0565b91505092915050565b60006020828403121561015b5761015a61036c565b5b600061016984828501610102565b91505092915050565b6000602082840312156101885761018761036c565b5b600082015167ffffffffffffffff8111156101a6576101a5610367565b5b6101b284828501610117565b91505092915050565b60006101c6826102a2565b6101d081856102ad565b93506101e08185602086016102ca565b6101e981610371565b840191505092915050565b60006102016003836102ad565b915061020c82610382565b602082019050919050565b6000604082019050818103600083015261023181846101bb565b90508181036020830152610244816101f4565b905092915050565b6000610256610267565b905061026282826102fd565b919050565b6000604051905090565b600067ffffffffffffffff82111561028c5761028b61032e565b5b61029582610371565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b60008115159050919050565b60005b838110156102e85780820151818401526020810190506102cd565b838111156102f7576000848401525b50505050565b61030682610371565b810181811067ffffffffffffffff821117156103255761032461032e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f6261720000000000000000000000000000000000000000000000000000000000600082015250565b6103b4816102be565b81146103bf57600080fd5b50565b603f806103d06000396000f3fe6080604052600080fdfea26469706673582212207d03b26e43dc3d116b0021ddc9817bde3762a3b14315351f11fc4be384fd14a664736f6c63430008060033"- assertBool "linker hole not detected" (containsLinkerHole code'),- testCase "no false positives" $ do- let code' = "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fea265627a7a723158205d775f914dcb471365a430b5f5b2cfe819e615cbbb5b2f1ccc7da1fd802e43c364736f6c634300050b0032"- assertBool "false positive" (not . containsLinkerHole $ code')- ]-- , testGroup "metadata stripper"- [ testCase "it strips the metadata for solc => 0.6" $ do- let code' = hexText "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fea265627a7a723158205d775f914dcb471365a430b5f5b2cfe819e615cbbb5b2f1ccc7da1fd802e43c364736f6c634300050b0032"- stripped = stripBytecodeMetadata code'- assertEqual "failed to strip metadata" (show (ByteStringS stripped)) "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fe"- ,- testCase "it strips the metadata and constructor args" $ do- let srccode =- [i|- contract A {- uint y;- constructor(uint x) public {- y = x;- }- }- |]-- Just initCode <- solidity "A" srccode- assertEqual "constructor args screwed up metadata stripping" (stripBytecodeMetadata (initCode <> encodeAbiValue (AbiUInt 256 1))) (stripBytecodeMetadata initCode)- ]-- , testGroup "RLP encodings"- [ testProperty "rlp decode is a retraction (bytes)" $ \(Bytes bs) ->- rlpdecode (rlpencode (BS bs)) == Just (BS bs)- , testProperty "rlp encode is a partial inverse (bytes)" $ \(Bytes bs) ->- case rlpdecode bs of- Just r -> rlpencode r == bs- Nothing -> True- , testProperty "rlp decode is a retraction (RLP)" $ \(RLPData r) ->- rlpdecode (rlpencode r) == Just r- ]- , testGroup "Panic code tests via symbolic execution"- [- testCase "assert-fail" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 a) external pure {- assert(a != 0);- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x01] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- assertEqual "Must be 0" 0 $ getVar ctr "arg1"- putStrLn $ "expected counterexample found, and it's correct: " <> (show $ getVar ctr "arg1")- ,- testCase "safeAdd-fail" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 a, uint256 b) external pure returns (uint256 c) {- c = a+b;- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x11] c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- let x = getVar ctr "arg1"- let y = getVar ctr "arg2"-- let maxUint = 2 ^ (256 :: Integer) :: Integer- assertBool "Overflow must occur" (toInteger x + toInteger y >= maxUint)- putStrLn "expected counterexample found"- ,- testCase "div-by-zero-fail" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 a, uint256 b) external pure returns (uint256 c) {- c = a/b;- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x12] c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- assertEqual "Division by 0 needs b=0" (getVar ctr "arg2") 0- putStrLn "expected counterexample found"- ,- testCase "unused-args-fail" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function fun(uint256 a) public pure {- assert(false);- }- }- |]- (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x1] c Nothing [] defaultVeriOpts- putStrLn "expected counterexample found"- ,- testCase "enum-conversion-fail" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- enum MyEnum { ONE, TWO }- function fun(uint256 a) external pure returns (MyEnum b) {- b = MyEnum(a);- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x21] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- assertBool "Enum is only defined for 0 and 1" $ (getVar ctr "arg1") > 1- putStrLn "expected counterexample found"- ,- -- TODO 0x22 is missing: "0x22: If you access a storage byte array that is incorrectly encoded."- -- TODO below should NOT fail- -- TODO this has a loop that depends on a symbolic value and currently causes interpret to loop- ignoreTest $ testCase "pop-empty-array" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- uint[] private arr;- function fun(uint8 a) external {- arr.push(1);- arr.push(2);- for (uint i = 0; i < a; i++) {- arr.pop();- }- }- }- |]- a <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x31] c (Just (Sig "fun(uint8)" [AbiUIntType 8])) [] defaultVeriOpts- print $ length a- print $ show a- putStrLn "expected counterexample found"- ,- testCase "access-out-of-bounds-array" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- uint[] private arr;- function fun(uint8 a) external returns (uint x){- arr.push(1);- arr.push(2);- x = arr[a];- }- }- |]- (_, [Cex (_, _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x32] c (Just (Sig "fun(uint8)" [AbiUIntType 8])) [] defaultVeriOpts- -- assertBool "Access must be beyond element 2" $ (getVar ctr "arg1") > 1- putStrLn "expected counterexample found"- ,- -- Note: we catch the assertion here, even though we are only able to explore partially- testCase "alloc-too-much" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 a) external {- uint[] memory arr = new uint[](a);- }- }- |]- (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s ->- checkAssert s [0x41] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "expected counterexample found"- ,- testCase "vm.deal unknown address" $ do- Just c <- solcRuntime "C"- [i|- interface Vm {- function deal(address,uint256) external;- }- contract C {- // this is not supported yet due to restrictions around symbolic address aliasing...- function f(address e, uint val) external {- Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);- vm.deal(e, val);- assert(e.balance == val);- }- }- |]- Right e <- reachableUserAsserts c (Just $ Sig "f(address,uint256)" [AbiAddressType, AbiUIntType 256])- assertBool "The expression is not partial" $ Expr.containsNode isPartial e- ,- testCase "vm.prank underflow" $ do- Just c <- solcRuntime "C"- [i|- interface Vm {- function prank(address) external;- }- contract Payable {- function hi() public payable {}- }- contract C {- function f() external {- Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);-- uint amt = 10;- address from = address(0xacab);- require(from.balance < amt);-- Payable target = new Payable();- vm.prank(from);- target.hi{value : amt}();- }- }- |]- r <- allBranchesFail c Nothing- assertBool "all branches must fail" (isRight r)- ,- testCase "call ffi when disabled" $ do- Just c <- solcRuntime "C"- [i|- interface Vm {- function ffi(string[] calldata) external;- }- contract C {- function f() external {- Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);-- string[] memory inputs = new string[](2);- inputs[0] = "echo";- inputs[1] = "acab";-- // should fail to explore this branch- vm.ffi(inputs);- }- }- |]- Right e <- reachableUserAsserts c Nothing- T.putStrLn $ formatExpr e- assertBool "The expression is not partial" $ Expr.containsNode isPartial e- ,- -- TODO: we can't deal with symbolic jump conditions- expectFail $ testCase "call-zero-inited-var-thats-a-function" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function (uint256) internal returns (uint) funvar;- function fun2(uint256 a) internal returns (uint){- return a;- }- function fun(uint256 a) external returns (uint) {- if (a != 44) {- funvar = fun2;- }- return funvar(a);- }- }- |]- (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $- \s -> checkAssert s [0x51] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- let a = fromJust $ Map.lookup (Var "arg1") cex.vars- assertEqual "unexpected cex value" a 44- putStrLn "expected counterexample found"- ]- , testGroup "Symbolic-Constructor-Args"- -- this produced some hard to debug failures. keeping it around since it seemed to exercise the contract creation code in interesting ways...- [ testCase "multiple-symbolic-constructor-calls" $ do- Just initCode <- solidity "C"- [i|- contract A {- uint public x;- constructor (uint z) {}- }-- contract B {- constructor (uint i) {}-- }-- contract C {- constructor(uint u) {- new A(u);- new B(u);- }- }- |]- withSolvers Z3 1 Nothing $ \s -> do- let calldata = (WriteWord (Lit 0x0) (Var "u") (ConcreteBuf ""), [])- initVM <- stToIO $ abstractVM calldata initCode Nothing True- expr <- Expr.simplify <$> interpret (Fetch.oracle s Nothing) Nothing 1 StackBased initVM runExpr- assertBool "unexptected partial execution" (not $ Expr.containsNode isPartial expr)- , testCase "mixed-concrete-symbolic-args" $ do- Just c <- solcRuntime "C"- [i|- contract B {- uint public x;- uint public y;- constructor (uint i, uint j) {- x = i;- y = j;- }-- }-- contract C {- function foo(uint i) public {- B b = new B(10, i);- assert(b.x() == 10);- assert(b.y() == i);- }- }- |]- Right expr <- reachableUserAsserts c (Just $ Sig "foo(uint256)" [AbiUIntType 256])- assertBool "unexptected partial execution" (not $ Expr.containsNode isPartial expr)- , testCase "jump-into-symbolic-region" $ do- let- -- our initCode just jumps directly to the end- code = BS.pack . mapMaybe maybeLitByte $ V.toList $ assemble- [ OpPush (Lit 0x85)- , OpJump- , OpPush (Lit 1)- , OpPush (Lit 1)- , OpPush (Lit 1)- , OpJumpdest- ]- -- we write a symbolic word to the middle, so the jump above should- -- fail since the target is not in the concrete region- initCode = (WriteWord (Lit 0x43) (Var "HI") (ConcreteBuf code), [])-- -- we pass in the above initCode buffer as calldata, and then copy- -- it into memory before calling Create- runtimecode = RuntimeCode (SymbolicRuntimeCode $ assemble- [ OpPush (Lit 0x85)- , OpPush (Lit 0x0)- , OpPush (Lit 0x0)- , OpCalldatacopy- , OpPush (Lit 0x85)- , OpPush (Lit 0x0)- , OpPush (Lit 0x0)- , OpCreate- ])- withSolvers Z3 1 Nothing $ \s -> do- vm <- stToIO $ loadSymVM runtimecode (Lit 0) initCode False- expr <- Expr.simplify <$> interpret (Fetch.oracle s Nothing) Nothing 1 StackBased vm runExpr- case expr of- Partial _ _ (JumpIntoSymbolicCode _ _) -> assertBool "" True- _ -> assertBool "did not encounter expected partial node" False- ]- , testGroup "Dapp-Tests"- [ testCase "Trivial-Pass" $ do- let testFile = "test/contracts/pass/trivial.sol"- runSolidityTest testFile ".*" >>= assertEqual "test result" True- , testCase "DappTools" $ do- -- quick smokecheck to make sure that we can parse dapptools style build outputs- let cases =- [ ("test/contracts/pass/trivial.sol", ".*", True)- , ("test/contracts/pass/dsProvePass.sol", "proveEasy", True)- , ("test/contracts/fail/trivial.sol", ".*", False)- , ("test/contracts/fail/dsProveFail.sol", "prove_add", False)- ]- results <- forM cases $ \(testFile, match, expected) -> do- actual <- runSolidityTestCustom testFile match Nothing Nothing False Nothing DappTools- pure (actual == expected)- assertBool "test result" (and results)- , testCase "Trivial-Fail" $ do- let testFile = "test/contracts/fail/trivial.sol"- runSolidityTest testFile "prove_false" >>= assertEqual "test result" False- , testCase "Abstract" $ do- let testFile = "test/contracts/pass/abstract.sol"- runSolidityTest testFile ".*" >>= assertEqual "test result" True- , testCase "Constantinople" $ do- let testFile = "test/contracts/pass/constantinople.sol"- runSolidityTest testFile ".*" >>= assertEqual "test result" True- , testCase "Prove-Tests-Pass" $ do- let testFile = "test/contracts/pass/dsProvePass.sol"- runSolidityTest testFile ".*" >>= assertEqual "test result" True- , testCase "prefix-check-for-dapp" $ do- let testFile = "test/contracts/fail/check-prefix.sol"- runSolidityTest testFile "check_trivial" >>= assertEqual "test result" False- , testCase "Prove-Tests-Fail" $ do- let testFile = "test/contracts/fail/dsProveFail.sol"- runSolidityTest testFile "prove_trivial" >>= assertEqual "test result" False- runSolidityTest testFile "prove_trivial_dstest" >>= assertEqual "test result" False- runSolidityTest testFile "prove_add" >>= assertEqual "test result" False- runSolidityTestCustom testFile "prove_smtTimeout" (Just 1) Nothing False Nothing Foundry >>= assertEqual "test result" False- runSolidityTest testFile "prove_multi" >>= assertEqual "test result" False- -- TODO: implement overflow checking optimizations and enable, currently this runs forever- --runSolidityTest testFile "prove_distributivity" >>= assertEqual "test result" False- , testCase "Loop-Tests" $ do- let testFile = "test/contracts/pass/loops.sol"- runSolidityTestCustom testFile "prove_loop" Nothing (Just 10) False Nothing Foundry >>= assertEqual "test result" True- runSolidityTestCustom testFile "prove_loop" Nothing (Just 100) False Nothing Foundry >>= assertEqual "test result" False- , testCase "Cheat-Codes-Pass" $ do- let testFile = "test/contracts/pass/cheatCodes.sol"- runSolidityTest testFile ".*" >>= assertEqual "test result" True- , testCase "Unwind" $ do- let testFile = "test/contracts/pass/unwind.sol"- runSolidityTest testFile ".*" >>= assertEqual "test result" True- ]- , testGroup "max-iterations"- [ testCase "concrete-loops-reached" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function fun() external payable returns (uint) {- uint count = 0;- for (uint i = 0; i < 5; i++) count++;- return count;- }- }- |]- let sig = Just $ Sig "fun()" []- opts = defaultVeriOpts{ maxIter = Just 3 }- (e, [Qed _]) <- withSolvers Z3 1 Nothing $- \s -> checkAssert s defaultPanicCodes c sig [] opts- assertBool "The expression is not partial" $ isPartial e- , testCase "concrete-loops-not-reached" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function fun() external payable returns (uint) {- uint count = 0;- for (uint i = 0; i < 5; i++) count++;- return count;- }- }- |]-- let sig = Just $ Sig "fun()" []- opts = defaultVeriOpts{ maxIter = Just 6 }- (e, [Qed _]) <- withSolvers Z3 1 Nothing $- \s -> checkAssert s defaultPanicCodes c sig [] opts- assertBool "The expression is partial" $ not $ isPartial e- , testCase "symbolic-loops-reached" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function fun(uint j) external payable returns (uint) {- uint count = 0;- for (uint i = 0; i < j; i++) count++;- return count;- }- }- |]- (e, [Qed _]) <- withSolvers Z3 1 Nothing $- \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] (defaultVeriOpts{ maxIter = Just 5 })- assertBool "The expression is not partial" $ Expr.containsNode isPartial e- , testCase "inconsistent-paths" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function fun(uint j) external payable returns (uint) {- require(j <= 3);- uint count = 0;- for (uint i = 0; i < j; i++) count++;- return count;- }- }- |]- let sig = Just $ Sig "fun(uint256)" [AbiUIntType 256]- -- we dont' ask the solver about the loop condition until we're- -- already in an inconsistent path (i == 5, j <= 3, i < j), so we- -- will continue looping here until we hit max iterations- opts = defaultVeriOpts{ maxIter = Just 10, askSmtIters = 5 }- (e, [Qed _]) <- withSolvers Z3 1 Nothing $- \s -> checkAssert s defaultPanicCodes c sig [] opts- assertBool "The expression is not partial" $ Expr.containsNode isPartial e- , testCase "symbolic-loops-not-reached" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function fun(uint j) external payable returns (uint) {- require(j <= 3);- uint count = 0;- for (uint i = 0; i < j; i++) count++;- return count;- }- }- |]- let sig = Just $ Sig "fun(uint256)" [AbiUIntType 256]- -- askSmtIters is low enough here to avoid the inconsistent path- -- conditions, so we never hit maxIters- opts = defaultVeriOpts{ maxIter = Just 5, askSmtIters = 1 }- (e, [Qed _]) <- withSolvers Z3 1 Nothing $- \s -> checkAssert s defaultPanicCodes c sig [] opts- assertBool "The expression is partial" $ not (Expr.containsNode isPartial e)- ]- , testGroup "Symbolic Addresses"- [ testCase "symbolic-address-create" $ do- let src = [i|- contract A {- constructor() payable {}- }- contract C {- function fun(uint256 a) external{- require(address(this).balance > a);- new A{value:a}();- }- }- |]- Just a <- solcRuntime "A" src- Just c <- solcRuntime "C" src- let sig = Sig "fun(uint256)" [AbiUIntType 256]- (expr, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->- verifyContract s c (Just sig) [] defaultVeriOpts Nothing Nothing- let isSuc (Success {}) = True- isSuc _ = False- case filter isSuc (flattenExpr expr) of- [Success _ _ _ store] -> do- let ca = fromJust (Map.lookup (SymAddr "freshSymAddr1") store)- let code = case ca.code of- RuntimeCode (ConcreteRuntimeCode c') -> c'- _ -> internalError "expected concrete code"- assertEqual "balance mismatch" (Var "arg1") ca.balance- assertEqual "code mismatch" (stripBytecodeMetadata a) (stripBytecodeMetadata code)- assertEqual "nonce mismatch" (Just 1) ca.nonce- _ -> assertBool "too many success nodes!" False- , testCase "symbolic-balance-call" $ do- let src = [i|- contract A {- function f() public payable returns (uint) {- return msg.value;- }- }- contract C {- function fun(uint256 x) external {- require(address(this).balance > x);- A a = new A();- uint res = a.f{value:x}();- assert(res == x);- }- }- |]- Just c <- solcRuntime "C" src- res <- reachableUserAsserts c Nothing- assertBool "unexpected cex" (isRight res)- -- TODO: implement missing aliasing rules- , expectFail $ testCase "deployed-contract-addresses-cannot-alias" $ do- Just c <- solcRuntime "C"- [i|- contract A {}- contract C {- function f() external {- A a = new A();- if (address(a) == address(this)) assert(false);- }- }- |]- res <- reachableUserAsserts c Nothing- assertBool "should not be able to alias" (isRight res)- , testCase "addresses-in-args-can-alias-anything" $ do- let addrs :: [Text]- addrs = ["address(this)", "tx.origin", "block.coinbase", "msg.sender"]- sig = Just $ Sig "f(address)" [AbiAddressType]- checkVs vs = [i|- contract C {- function f(address a) external {- if (${vs} == a) assert(false);- }- }- |]-- [self, origin, coinbase, caller] <- forM addrs $ \addr -> do- Just c <- solcRuntime "C" (checkVs addr)- Left [cex] <- reachableUserAsserts c sig- pure cex.addrs-- let check as a = (Map.lookup (SymAddr "arg1") as) @?= (Map.lookup a as)- check self (SymAddr "entrypoint")- check origin (SymAddr "origin")- check coinbase (SymAddr "coinbase")- check caller (SymAddr "caller")- , testCase "addresses-in-args-can-alias-themselves" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function f(address a, address b) external {- if (a == b) assert(false);- }- }- |]- let sig = Just $ Sig "f(address,address)" [AbiAddressType,AbiAddressType]- Left [cex] <- reachableUserAsserts c sig- let arg1 = fromJust $ Map.lookup (SymAddr "arg1") cex.addrs- arg2 = fromJust $ Map.lookup (SymAddr "arg1") cex.addrs- assertEqual "should match" arg1 arg2- -- TODO: fails due to missing aliasing rules- , expectFail $ testCase "tx.origin cannot alias deployed contracts" $ do- Just c <- solcRuntime "C"- [i|- contract A {}- contract C {- function f() external {- address a = address(new A());- if (tx.origin == a) assert(false);- }- }- |]- cexs <- reachableUserAsserts c Nothing- assertBool "unexpected cex" (isRight cexs)- , testCase "tx.origin can alias everything else" $ do- let addrs = ["address(this)", "block.coinbase", "msg.sender", "arg"] :: [Text]- sig = Just $ Sig "f(address)" [AbiAddressType]- checkVs vs = [i|- contract C {- function f(address arg) external {- if (${vs} == tx.origin) assert(false);- }- }- |]-- [self, coinbase, caller, arg] <- forM addrs $ \addr -> do- Just c <- solcRuntime "C" (checkVs addr)- Left [cex] <- reachableUserAsserts c sig- pure cex.addrs-- let check as a = (Map.lookup (SymAddr "origin") as) @?= (Map.lookup a as)- check self (SymAddr "entrypoint")- check coinbase (SymAddr "coinbase")- check caller (SymAddr "caller")- check arg (SymAddr "arg1")- , testCase "coinbase can alias anything" $ do- let addrs = ["address(this)", "tx.origin", "msg.sender", "a", "arg"] :: [Text]- sig = Just $ Sig "f(address)" [AbiAddressType]- checkVs vs = [i|- contract A {}- contract C {- function f(address arg) external {- address a = address(new A());- if (${vs} == block.coinbase) assert(false);- }- }- |]-- [self, origin, caller, a, arg] <- forM addrs $ \addr -> do- Just c <- solcRuntime "C" (checkVs addr)- Left [cex] <- reachableUserAsserts c sig- pure cex.addrs-- let check as a' = (Map.lookup (SymAddr "coinbase") as) @?= (Map.lookup a' as)- check self (SymAddr "entrypoint")- check origin (SymAddr "origin")- check caller (SymAddr "caller")- check a (SymAddr "freshSymAddr1")- check arg (SymAddr "arg1")- , testCase "caller can alias anything" $ do- let addrs = ["address(this)", "tx.origin", "block.coinbase", "a", "arg"] :: [Text]- sig = Just $ Sig "f(address)" [AbiAddressType]- checkVs vs = [i|- contract A {}- contract C {- function f(address arg) external {- address a = address(new A());- if (${vs} == msg.sender) assert(false);- }- }- |]-- [self, origin, coinbase, a, arg] <- forM addrs $ \addr -> do- Just c <- solcRuntime "C" (checkVs addr)- Left [cex] <- reachableUserAsserts c sig- pure cex.addrs-- let check as a' = (Map.lookup (SymAddr "caller") as) @?= (Map.lookup a' as)- check self (SymAddr "entrypoint")- check origin (SymAddr "origin")- check coinbase (SymAddr "coinbase")- check a (SymAddr "freshSymAddr1")- check arg (SymAddr "arg1")- , testCase "vm.load fails for a potentially aliased address" $ do- Just c <- solcRuntime "C"- [i|- interface Vm {- function load(address,bytes32) external;- }- contract C {- function f() external {- Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);- vm.load(msg.sender, 0x0);- }- }- |]- (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s ->- verifyContract s c Nothing [] defaultVeriOpts Nothing (Just $ checkBadCheatCode "load(address,bytes32)")- pure ()- , testCase "vm.store fails for a potentially aliased address" $ do- Just c <- solcRuntime "C"- [i|- interface Vm {- function store(address,bytes32,bytes32) external;- }- contract C {- function f() external {- Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);- vm.store(msg.sender, 0x0, 0x0);- }- }- |]- (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s ->- verifyContract s c Nothing [] defaultVeriOpts Nothing (Just $ checkBadCheatCode "store(address,bytes32,bytes32)")- pure ()- -- TODO: make this work properly- , testCase "transfering-eth-does-not-dealias" $ do- Just c <- solcRuntime "C"- [i|- // we can't do calls to unknown code yet so we use selfdestruct- contract Send {- constructor(address payable dst) payable {- selfdestruct(dst);- }- }- contract C {- function f() external {- uint preSender = msg.sender.balance;- uint preOrigin = tx.origin.balance;-- new Send{value:10}(payable(msg.sender));- new Send{value:5}(payable(tx.origin));-- if (msg.sender == tx.origin) {- assert(preSender == preOrigin- && msg.sender.balance == preOrigin + 15- && tx.origin.balance == preSender + 15);- } else {- assert(msg.sender.balance == preSender + 10- && tx.origin.balance == preOrigin + 5);- }- }- }- |]- Right e <- reachableUserAsserts c Nothing- -- TODO: this should work one day- assertBool "should be partial" (Expr.containsNode isPartial e)- , testCase "addresses-in-context-are-symbolic" $ do- Just a <- solcRuntime "A"- [i|- contract A {- function f() external {- assert(msg.sender != address(0x0));- }- }- |]- Just b <- solcRuntime "B"- [i|- contract B {- function f() external {- assert(block.coinbase != address(0x1));- }- }- |]- Just c <- solcRuntime "C"- [i|- contract C {- function f() external {- assert(tx.origin != address(0x2));- }- }- |]- Just d <- solcRuntime "D"- [i|- contract D {- function f() external {- assert(address(this) != address(0x3));- }- }- |]- [acex,bcex,ccex,dcex] <- forM [a,b,c,d] $ \con -> do- Left [cex] <- reachableUserAsserts con Nothing- assertEqual "wrong number of addresses" 1 (length (Map.keys cex.addrs))- pure cex-- assertEqual "wrong model for a" (Addr 0) (fromJust $ Map.lookup (SymAddr "caller") acex.addrs)- assertEqual "wrong model for b" (Addr 1) (fromJust $ Map.lookup (SymAddr "coinbase") bcex.addrs)- assertEqual "wrong model for c" (Addr 2) (fromJust $ Map.lookup (SymAddr "origin") ccex.addrs)- assertEqual "wrong model for d" (Addr 3) (fromJust $ Map.lookup (SymAddr "entrypoint") dcex.addrs)- ]- , testGroup "Symbolic execution"- [- testCase "require-test" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(int256 a) external pure {- require(a <= 0);- assert (a <= 0);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256)" [AbiIntType 256])) [] defaultVeriOpts- putStrLn "Require works as expected"- ,- testCase "ITE-with-bitwise-AND" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function f(uint256 x) public pure {- require(x > 0);- uint256 a = (x & 8);- bool w;- // assembly is needed here, because solidity doesn't allow uint->bool conversion- assembly {- w:=a- }- if (!w) assert(false); //we should get a CEX: when x has a 0 at bit 3- }- }- |]- -- should find a counterexample- (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "expected counterexample found"- ,- testCase "ITE-with-bitwise-OR" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function f(uint256 x) public pure {- uint256 a = (x | 8);- bool w;- // assembly is needed here, because solidity doesn't allow uint->bool conversion- assembly {- w:=a- }- assert(w); // due to bitwise OR with positive value, this must always be true- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "this should always be true, due to bitwise OR with positive value"- ,- -- CopySlice check- -- uses identity precompiled contract (0x4) to copy memory- -- checks 9af114613075a2cd350633940475f8b6699064de (readByte + CopySlice had src/dest mixed up)- -- without 9af114613 it dies with: `Exception: UnexpectedSymbolicArg 296 "MSTORE index"`- -- TODO: check 9e734b9da90e3e0765128b1f20ce1371f3a66085 (bufLength + copySlice was off by 1)- testCase "copyslice-check" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function checkval(uint8 a) public {- bytes memory data = new bytes(5);- for(uint i = 0; i < 5; i++) data[i] = bytes1(a);- bytes memory ret = new bytes(data.length);- assembly {- let len := mload(data)- if iszero(call(0xff, 0x04, 0, add(data, 0x20), len, add(ret,0x20), len)) {- invalid()- }- }- for(uint i = 0; i < 5; i++) assert(ret[i] == data[i]);- }- }- |]- let sig = Just (Sig "checkval(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->- checkAssert s defaultPanicCodes c sig [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- -- TODO look at tests here for SAR: https://github.com/dapphub/dapptools/blob/01ef8ea418c3fe49089a44d56013d8fcc34a1ec2/src/dapp-tests/pass/constantinople.sol#L250- testCase "opcode-sar-neg" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(int256 shift_by, int256 val) external pure returns (int256 out) {- require(shift_by >= 0);- require(val <= 0);- assembly {- out := sar(shift_by,val)- }- assert (out <= 0);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts- putStrLn "SAR works as expected"- ,- testCase "opcode-sar-pos" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(int256 shift_by, int256 val) external pure returns (int256 out) {- require(shift_by >= 0);- require(val >= 0);- assembly {- out := sar(shift_by,val)- }- assert (out >= 0);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts- putStrLn "SAR works as expected"- ,- testCase "opcode-sar-fixedval-pos" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(int256 shift_by, int256 val) external pure returns (int256 out) {- require(shift_by == 1);- require(val == 64);- assembly {- out := sar(shift_by,val)- }- assert (out == 32);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts- putStrLn "SAR works as expected"- ,- testCase "opcode-sar-fixedval-neg" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(int256 shift_by, int256 val) external pure returns (int256 out) {- require(shift_by == 1);- require(val == -64);- assembly {- out := sar(shift_by,val)- }- assert (out == -32);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts- putStrLn "SAR works as expected"- ,- testCase "opcode-div-zero-1" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 val) external pure {- uint out;- assembly {- out := div(val, 0)- }- assert(out == 0);-- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "sdiv works as expected"- ,- testCase "opcode-sdiv-zero-1" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 val) external pure {- uint out;- assembly {- out := sdiv(val, 0)- }- assert(out == 0);-- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "sdiv works as expected"- ,- testCase "opcode-sdiv-zero-2" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 val) external pure {- uint out;- assembly {- out := sdiv(0, val)- }- assert(out == 0);-- }- }- |]- (_, [Qed _]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "sdiv works as expected"- ,- testCase "signed-overflow-checks" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function fun(int256 a) external returns (int256) {- return a + a;- }- }- |]- (_, [Cex (_, _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x11] c (Just (Sig "fun(int256)" [AbiIntType 256])) [] defaultVeriOpts- putStrLn "expected cex discovered"- ,- testCase "opcode-signextend-neg" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 val, uint8 b) external pure {- require(b <= 31);- require(b >= 0);- require(val < (1 <<(b*8)));- require(val & (1 <<(b*8-1)) != 0); // MSbit set, i.e. negative- uint256 out;- assembly {- out := signextend(b, val)- }- if (b == 31) assert(out == val);- else assert(out > val);- assert(out & (1<<254) != 0); // MSbit set, i.e. negative- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "signextend works as expected"- ,- testCase "opcode-signextend-pos-nochop" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 val, uint8 b) external pure {- require(val < (1 <<(b*8)));- require(val & (1 <<(b*8-1)) == 0); // MSbit not set, i.e. positive- uint256 out;- assembly {- out := signextend(b, val)- }- assert (out == val);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts- putStrLn "signextend works as expected"- ,- testCase "opcode-signextend-pos-chopped" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 val, uint8 b) external pure {- require(b == 0); // 1-byte- require(val == 514); // but we set higher bits- uint256 out;- assembly {- out := signextend(b, val)- }- assert (out == 2); // chopped- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts- putStrLn "signextend works as expected"- ,- -- when b is too large, value is unchanged- testCase "opcode-signextend-pos-b-toolarge" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 val, uint8 b) external pure {- require(b >= 31);- uint256 out;- assembly {- out := signextend(b, val)- }- assert (out == val);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts- putStrLn "signextend works as expected"- ,- testCase "opcode-shl" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 shift_by, uint256 val) external pure {- require(val < (1<<16));- require(shift_by < 16);- uint256 out;- assembly {- out := shl(shift_by,val)- }- assert (out >= val);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- putStrLn "SAR works as expected"- ,- testCase "opcode-xor-cancel" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 a, uint256 b) external pure {- require(a == b);- uint256 c;- assembly {- c := xor(a,b)- }- assert (c == 0);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- putStrLn "XOR works as expected"- ,- testCase "opcode-xor-reimplement" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 a, uint256 b) external pure {- uint256 c;- assembly {- c := xor(a,b)- }- assert (c == (~(a & b)) & (a | b));- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- putStrLn "XOR works as expected"- ,- testCase "opcode-add-commutative" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint256 a, uint256 b) external pure {- uint256 res1;- uint256 res2;- assembly {- res1 := add(a,b)- res2 := add(b,a)- }- assert (res1 == res2);- }- }- |]- a <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- case a of- (_, [Cex (_, ctr)]) -> do- let x = getVar ctr "arg1"- let y = getVar ctr "arg2"- putStrLn $ "y:" <> show y- putStrLn $ "x:" <> show x- assertEqual "Addition is not commutative... that's wrong" False True- (_, [Qed _]) -> do- putStrLn "adding is commutative"- _ -> internalError "Unexpected"- ,- testCase "opcode-div-res-zero-on-div-by-zero" $ do- Just c <- solcRuntime "MyContract"- [i|- contract MyContract {- function fun(uint16 a) external pure {- uint16 b = 0;- uint16 res;- assembly {- res := div(a,b)- }- assert (res == 0);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint16)" [AbiUIntType 16])) [] defaultVeriOpts- putStrLn "DIV by zero is zero"- ,- -- Somewhat tautological since we are asserting the precondition- -- on the same form as the actual "requires" clause.- testCase "SafeAdd success case" $ do- Just safeAdd <- solcRuntime "SafeAdd"- [i|- contract SafeAdd {- function add(uint x, uint y) public pure returns (uint z) {- require((z = x + y) >= x);- }- }- |]- let pre preVM = let (x, y) = case getStaticAbiArgs 2 preVM of- [x', y'] -> (x', y')- _ -> internalError "expected 2 args"- in (x .<= Expr.add x y)- -- TODO check if it's needed- .&& preVM.state.callvalue .== Lit 0- post prestate leaf =- let (x, y) = case getStaticAbiArgs 2 prestate of- [x', y'] -> (x', y')- _ -> internalError "expected 2 args"- in case leaf of- Success _ _ b _ -> (ReadWord (Lit 0) b) .== (Add x y)- _ -> PBool True- sig = Just (Sig "add(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->- verifyContract s safeAdd sig [] defaultVeriOpts (Just pre) (Just post)- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,-- testCase "x == y => x + y == 2 * y" $ do- Just safeAdd <- solcRuntime "SafeAdd"- [i|- contract SafeAdd {- function add(uint x, uint y) public pure returns (uint z) {- require((z = x + y) >= x);- }- }- |]- let pre preVM = let (x, y) = case getStaticAbiArgs 2 preVM of- [x', y'] -> (x', y')- _ -> internalError "expected 2 args"- in (x .<= Expr.add x y)- .&& (x .== y)- .&& preVM.state.callvalue .== Lit 0- post prestate leaf =- let (_, y) = case getStaticAbiArgs 2 prestate of- [x', y'] -> (x', y')- _ -> internalError "expected 2 args"- in case leaf of- Success _ _ b _ -> (ReadWord (Lit 0) b) .== (Mul (Lit 2) y)- _ -> PBool True- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->- verifyContract s safeAdd (Just (Sig "add(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts (Just pre) (Just post)- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "summary storage writes" $ do- Just c <- solcRuntime "A"- [i|- contract A {- uint x;- function f(uint256 y) public {- unchecked {- x += y;- x += y;- }- }- }- |]- let pre vm = Lit 0 .== vm.state.callvalue- post prestate leaf =- let y = case getStaticAbiArgs 1 prestate of- [y'] -> y'- _ -> error "expected 1 arg"- this = prestate.state.codeContract- prestore = (fromJust (Map.lookup this prestate.env.contracts)).storage- prex = Expr.readStorage' (Lit 0) prestore- in case leaf of- Success _ _ _ postState -> let- poststore = (fromJust (Map.lookup this postState)).storage- in Expr.add prex (Expr.mul (Lit 2) y) .== (Expr.readStorage' (Lit 0) poststore)- _ -> PBool True- sig = Just (Sig "f(uint256)" [AbiUIntType 256])- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->- verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post)- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- -- tests how whiffValue handles Neg via application of the triple IsZero simplification rule- -- regression test for: https://github.com/dapphub/dapptools/pull/698- testCase "Neg" $ do- let src =- [i|- object "Neg" {- code {- // Deploy the contract- datacopy(0, dataoffset("runtime"), datasize("runtime"))- return(0, datasize("runtime"))- }- object "runtime" {- code {- let v := calldataload(4)- if iszero(iszero(and(v, not(0xffffffffffffffffffffffffffffffffffffffff)))) {- invalid()- }- }- }- }- |]- Just c <- yulRuntime "Neg" src- (res, [Qed _]) <- withSolvers Z3 4 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "hello(address)" [AbiAddressType])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "catch-storage-collisions-noproblem" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint x, uint y) public {- if (x != y) {- assembly {- let newx := sub(sload(x), 1)- let newy := add(sload(y), 1)- sstore(x,newx)- sstore(y,newy)- }- }- }- }- |]- let pre vm = (Lit 0) .== vm.state.callvalue- post prestate poststate =- let (x,y) = case getStaticAbiArgs 2 prestate of- [x',y'] -> (x',y')- _ -> error "expected 2 args"- this = prestate.state.codeContract- prestore = (fromJust (Map.lookup this prestate.env.contracts)).storage- prex = Expr.readStorage' x prestore- prey = Expr.readStorage' y prestore- in case poststate of- Success _ _ _ postcs -> let- poststore = (fromJust (Map.lookup this postcs)).storage- postx = Expr.readStorage' x poststore- posty = Expr.readStorage' y poststore- in Expr.add prex prey .== Expr.add postx posty- _ -> PBool True- sig = Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->- verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post)- putStrLn "Correct, this can never fail"- ,- -- Inspired by these `msg.sender == to` token bugs- -- which break linearity of totalSupply.- testCase "catch-storage-collisions-good" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint x, uint y) public {- assembly {- let newx := sub(sload(x), 1)- let newy := add(sload(y), 1)- sstore(x,newx)- sstore(y,newy)- }- }- }- |]- let pre vm = (Lit 0) .== vm.state.callvalue- post prestate leaf =- let (x,y) = case getStaticAbiArgs 2 prestate of- [x',y'] -> (x',y')- _ -> error "expected 2 args"- this = prestate.state.codeContract- prestore = (fromJust (Map.lookup this prestate.env.contracts)).storage- prex = Expr.readStorage' x prestore- prey = Expr.readStorage' y prestore- in case leaf of- Success _ _ _ poststate -> let- poststore = (fromJust (Map.lookup this poststate)).storage- postx = Expr.readStorage' x poststore- posty = Expr.readStorage' y poststore- in Expr.add prex prey .== Expr.add postx posty- _ -> PBool True- sig = Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s ->- verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post)- let x = getVar ctr "arg1"- let y = getVar ctr "arg2"- putStrLn $ "y:" <> show y- putStrLn $ "x:" <> show x- assertEqual "Catch storage collisions" x y- putStrLn "expected counterexample found"- ,- testCase "simple-assert" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo() external pure {- assert(false);- }- }- |]- (_, [Cex (Failure _ _ (Revert msg), _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo()" [])) [] defaultVeriOpts- assertEqual "incorrect revert msg" msg (ConcreteBuf $ panicMsg 0x01)- ,- testCase "simple-assert-2" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 x) external pure {- assert(x != 10);- }- }- |]- (_, [(Cex (_, ctr))]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- assertEqual "Must be 10" 10 $ getVar ctr "arg1"- putStrLn "Got 10 Cex, as expected"- ,- testCase "assert-fail-equal" $ do- Just c <- solcRuntime "AssertFailEqual"- [i|- contract AssertFailEqual {- function fun(uint256 deposit_count) external pure {- assert(deposit_count == 0);- assert(deposit_count == 11);- }- }- |]- (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- let ints = map (flip getVar "arg1") [a,b]- assertBool "0 must be one of the Cex-es" $ isJust $ List.elemIndex 0 ints- putStrLn "expected 2 counterexamples found, one Cex is the 0 value"- ,- testCase "assert-fail-notequal" $ do- Just c <- solcRuntime "AssertFailNotEqual"- [i|- contract AssertFailNotEqual {- function fun(uint256 deposit_count) external pure {- assert(deposit_count != 0);- assert(deposit_count != 11);- }- }- |]- (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- let x = getVar a "arg1"- let y = getVar b "arg1"- assertBool "At least one has to be 0, to go through the first assert" (x == 0 || y == 0)- putStrLn "expected 2 counterexamples found."- ,- testCase "assert-fail-twoargs" $ do- Just c <- solcRuntime "AssertFailTwoParams"- [i|- contract AssertFailTwoParams {- function fun(uint256 deposit_count1, uint256 deposit_count2) external pure {- assert(deposit_count1 != 0);- assert(deposit_count2 != 11);- }- }- |]- (_, [Cex _, Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- putStrLn "expected 2 counterexamples found"- ,- testCase "assert-2nd-arg" $ do- Just c <- solcRuntime "AssertFailTwoParams"- [i|- contract AssertFailTwoParams {- function fun(uint256 deposit_count1, uint256 deposit_count2) external pure {- assert(deposit_count2 != 666);- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- assertEqual "Must be 666" 666 $ getVar ctr "arg2"- putStrLn "Found arg2 Ctx to be 666"- ,- -- LSB is zeroed out, byte(31,x) takes LSB, so y==0 always holds- testCase "check-lsb-msb1" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 x) external pure {- x &= 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00;- uint8 y;- assembly { y := byte(31,x) }- assert(y == 0);- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- -- We zero out everything but the LSB byte. However, byte(31,x) takes the LSB byte- -- so there is a counterexamle, where LSB of x is not zero- testCase "check-lsb-msb2" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 x) external pure {- x &= 0x00000000000000000000000000000000000000000000000000000000000000ff;- uint8 y;- assembly { y := byte(31,x) }- assert(y == 0);- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- assertBool "last byte must be non-zero" $ ((Data.Bits..&.) (getVar ctr "arg1") 0xff) > 0- putStrLn "Expected counterexample found"- ,- -- We zero out everything but the 2nd LSB byte. However, byte(31,x) takes the 2nd LSB byte- -- so there is a counterexamle, where 2nd LSB of x is not zero- testCase "check-lsb-msb3 -- 2nd byte" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 x) external pure {- x &= 0x000000000000000000000000000000000000000000000000000000000000ff00;- uint8 y;- assembly { y := byte(30,x) }- assert(y == 0);- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- assertBool "second to last byte must be non-zero" $ ((Data.Bits..&.) (getVar ctr "arg1") 0xff00) > 0- putStrLn "Expected counterexample found"- ,- -- Reverse of thest above- testCase "check-lsb-msb4 2nd byte rev" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 x) external pure {- x &= 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff;- uint8 y;- assembly {- y := byte(30,x)- }- assert(y == 0);- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- -- Bitwise OR operation test- testCase "opcode-bitwise-or-full-1s" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 x) external pure {- uint256 y;- uint256 z = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;- assembly { y := or(x, z) }- assert(y == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "When OR-ing with full 1's we should get back full 1's"- ,- -- Bitwise OR operation test- testCase "opcode-bitwise-or-byte-of-1s" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 x) external pure {- uint256 y;- uint256 z = 0x000000000000000000000000000000000000000000000000000000000000ff00;- assembly { y := or(x, z) }- assert((y & 0x000000000000000000000000000000000000000000000000000000000000ff00) ==- 0x000000000000000000000000000000000000000000000000000000000000ff00);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "When OR-ing with a byte of 1's, we should get 1's back there"- ,- testCase "Deposit contract loop (z3)" $ do- Just c <- solcRuntime "Deposit"- [i|- contract Deposit {- function deposit(uint256 deposit_count) external pure {- require(deposit_count < 2**32 - 1);- ++deposit_count;- bool found = false;- for (uint height = 0; height < 32; height++) {- if ((deposit_count & 1) == 1) {- found = true;- break;- }- deposit_count = deposit_count >> 1;- }- assert(found);- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "deposit(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "Deposit-contract-loop-error-version" $ do- Just c <- solcRuntime "Deposit"- [i|- contract Deposit {- function deposit(uint8 deposit_count) external pure {- require(deposit_count < 2**32 - 1);- ++deposit_count;- bool found = false;- for (uint height = 0; height < 32; height++) {- if ((deposit_count & 1) == 1) {- found = true;- break;- }- deposit_count = deposit_count >> 1;- }- assert(found);- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s allPanicCodes c (Just (Sig "deposit(uint8)" [AbiUIntType 8])) [] defaultVeriOpts- assertEqual "Must be 255" 255 $ getVar ctr "arg1"- putStrLn $ "expected counterexample found, and it's correct: " <> (show $ getVar ctr "arg1")- ,- testCase "explore function dispatch" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint x) public pure returns (uint) {- return x;- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "check-asm-byte-in-bounds" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 idx, uint256 val) external pure {- uint256 actual;- uint256 expected;- require(idx < 32);- assembly {- actual := byte(idx,val)- expected := shr(248, shl(mul(idx, 8), val))- }- assert(actual == expected);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts- putStrLn "in bounds byte reads return the expected value"- ,- testCase "check-div-mod-sdiv-smod-by-zero-constant-prop" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 e) external pure {- uint x = 0;- uint y = 55;- uint z;- assembly { z := div(y,x) }- assert(z == 0);- assembly { z := div(x,y) }- assert(z == 0);- assembly { z := sdiv(y,x) }- assert(z == 0);- assembly { z := sdiv(x,y) }- assert(z == 0);- assembly { z := mod(y,x) }- assert(z == 0);- assembly { z := mod(x,y) }- assert(z == 0);- assembly { z := smod(y,x) }- assert(z == 0);- assembly { z := smod(x,y) }- assert(z == 0);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "div/mod/sdiv/smod by zero works as expected during constant propagation"- ,- testCase "check-asm-byte-oob" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function foo(uint256 x, uint256 y) external pure {- uint256 z;- require(x >= 32);- assembly { z := byte(x,y) }- assert(z == 0);- }- }- |]- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts- putStrLn "oob byte reads always return 0"- ,- testCase "injectivity of keccak (diff sizes)" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint128 x, uint256 y) external pure {- assert(- keccak256(abi.encodePacked(x)) !=- keccak256(abi.encodePacked(y))- );- }- }- |]- Right _ <- reachableUserAsserts c (Just $ Sig "f(uint128,uint256)" [AbiUIntType 128, AbiUIntType 256])- pure ()- ,- testCase "injectivity of keccak (32 bytes)" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint x, uint y) public pure {- if (keccak256(abi.encodePacked(x)) == keccak256(abi.encodePacked(y))) assert(x == y);- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "injectivity of keccak contrapositive (32 bytes)" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint x, uint y) public pure {- require (x != y);- assert (keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y)));- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "injectivity of keccak (64 bytes)" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint x, uint y, uint w, uint z) public pure {- assert (keccak256(abi.encodePacked(x,y)) != keccak256(abi.encodePacked(w,z)));- }- }- |]- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256,uint256,uint256,uint256)" (replicate 4 (AbiUIntType 256)))) [] defaultVeriOpts- let x = getVar ctr "arg1"- let y = getVar ctr "arg2"- let w = getVar ctr "arg3"- let z = getVar ctr "arg4"- assertEqual "x==y for hash collision" x y- assertEqual "w==z for hash collision" w z- putStrLn "expected counterexample found"- ,- testCase "calldata beyond calldatasize is 0 (symbolic calldata)" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f() public pure {- uint y;- assembly {- let x := calldatasize()- y := calldataload(x)- }- assert(y == 0);- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "calldata beyond calldatasize is 0 (concrete dalldata prefix)" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint256 z) public pure {- uint y;- assembly {- let x := calldatasize()- y := calldataload(x)- }- assert(y == 0);- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "calldata symbolic access" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint256 z) public pure {- uint x; uint y;- assembly {- y := calldatasize()- }- require(z >= y);- assembly {- x := calldataload(z)- }- assert(x == 0);- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "multiple-contracts" $ do- let code =- [i|- contract C {- uint x;- A constant a = A(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B);-- function call_A() public view {- // should fail since x can be anything- assert(a.x() == x);- }- }- contract A {- uint public x;- }- |]- aAddr = LitAddr (Addr 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B)- cAddr = SymAddr "entrypoint"- Just c <- solcRuntime "C" code- Just a <- solcRuntime "A" code- (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $ \s -> do- vm <- stToIO $ abstractVM (mkCalldata (Just (Sig "call_A()" [])) []) c Nothing False- <&> set (#state % #callvalue) (Lit 0)- <&> over (#env % #contracts)- (Map.insert aAddr (initialContract (RuntimeCode (ConcreteRuntimeCode a))))- verify s defaultVeriOpts vm (Just $ checkAssertions defaultPanicCodes)-- let storeCex = cex.store- testCex = case (Map.lookup cAddr storeCex, Map.lookup aAddr storeCex) of- (Just sC, Just sA) -> case (Map.lookup 0 sC, Map.lookup 0 sA) of- (Just x, Just y) -> x /= y- (Just x, Nothing) -> x /= 0- _ -> False- _ -> False- assertBool "Did not find expected storage cex" testCex- putStrLn "expected counterexample found"- ,- expectFail $ testCase "calling unique contracts (read from storage)" $ do- Just c <- solcRuntime "C"- [i|- contract C {- uint x;- A a;-- function call_A() public {- a = new A();- // should fail since x can be anything- assert(a.x() == x);- }- }- contract A {- uint public x;- }- |]- (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "call_A()" [])) [] defaultVeriOpts- putStrLn "expected counterexample found"- ,- testCase "keccak concrete and sym agree" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function kecc(uint x) public pure {- if (x == 0) {- assert(keccak256(abi.encode(x)) == keccak256(abi.encode(0)));- }- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "kecc(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "keccak concrete and sym injectivity" $ do- Just c <- solcRuntime "A"- [i|- contract A {- function f(uint x) public pure {- if (x !=3) assert(keccak256(abi.encode(x)) != keccak256(abi.encode(3)));- }- }- |]- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"- ,- testCase "safemath-distributivity-yul" $ do- let yulsafeDistributivity = hex "6355a79a6260003560e01c14156016576015601f565b5b60006000fd60a1565b603d602d604435600435607c565b6039602435600435607c565b605d565b6052604b604435602435605d565b600435607c565b141515605a57fe5b5b565b6000828201821115151560705760006000fd5b82820190505b92915050565b6000818384048302146000841417151560955760006000fd5b82820290505b92915050565b"- vm <- stToIO $ abstractVM (mkCalldata (Just (Sig "distributivity(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) []) yulsafeDistributivity Nothing False- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verify s defaultVeriOpts vm (Just $ checkAssertions defaultPanicCodes)- putStrLn "Proven"- ,- testCase "safemath-distributivity-sol" $ do- Just c <- solcRuntime "C"- [i|- contract C {- function distributivity(uint x, uint y, uint z) public {- assert(mul(x, add(y, z)) == add(mul(x, y), mul(x, z)));- }-- function add(uint x, uint y) internal pure returns (uint z) {- unchecked {- require((z = x + y) >= x, "ds-math-add-overflow");- }- }-- function mul(uint x, uint y) internal pure returns (uint z) {- unchecked {- require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");- }- }- }- |]-- (_, [Qed _]) <- withSolvers CVC5 1 (Just 99999999) $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "distributivity(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts- putStrLn "Proven"- ,- testCase "storage-cex-1" $ do- Just c <- solcRuntime "C"- [i|- contract C {- uint x;- uint y;- function fun(uint256 a) external{- assert (x == y);- }- }- |]- (_, [(Cex (_, cex))]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x01] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- let addr = SymAddr "entrypoint"- testCex = Map.size cex.store == 1 &&- case Map.lookup addr cex.store of- Just s -> Map.size s == 2 &&- case (Map.lookup 0 s, Map.lookup 1 s) of- (Just x, Just y) -> x /= y- _ -> False- _ -> False- assertBool "Did not find expected storage cex" testCex- putStrLn "Expected counterexample found"- ,- testCase "storage-cex-2" $ do- Just c <- solcRuntime "C"- [i|- contract C {- uint[10] arr1;- uint[10] arr2;- function fun(uint256 a) external{- assert (arr1[0] < arr2[a]);- }- }- |]- (_, [(Cex (_, cex))]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x01] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- let addr = SymAddr "entrypoint"- a = getVar cex "arg1"- testCex = Map.size cex.store == 1 &&- case Map.lookup addr cex.store of- Just s -> Map.size s == 2 &&- case (Map.lookup 0 s, Map.lookup (10 + a) s) of- (Just x, Just y) -> x >= y- _ -> False- _ -> False- assertBool "Did not find expected storage cex" testCex- putStrLn "Expected counterexample found"- ,- testCase "storage-cex-concrete" $ do- Just c <- solcRuntime "C"- [i|- contract C {- uint x;- uint y;- function fun(uint256 a) external{- assert (x != y);- }- }- |]- let sig = Just (Sig "fun(uint256)" [AbiUIntType 256])- (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $- \s -> verifyContract s c sig [] defaultVeriOpts Nothing (Just $ checkAssertions [0x01])- let addr = SymAddr "entrypoint"- testCex = Map.size cex.store == 1 &&- case Map.lookup addr cex.store of- Just s -> Map.size s == 2 &&- case (Map.lookup 0 s, Map.lookup 1 s) of- (Just x, Just y) -> x == y- _ -> False- _ -> False- assertBool "Did not find expected storage cex" testCex- putStrLn "Expected counterexample found"- ]- , testGroup "simplification-working"- [- testCase "prop-simp-bool1" $ do- let- a = successGen [PAnd (PBool True) (PBool False)]- b = Expr.simplify a- assertEqual "Must simplify down" (successGen [PBool False]) b- , testCase "prop-simp-bool2" $ do- let- a = successGen [POr (PBool True) (PBool False)]- b = Expr.simplify a- assertEqual "Must simplify down" (successGen []) b- , testCase "prop-simp-LT" $ do- let- a = successGen [PLT (Lit 1) (Lit 2)]- b = Expr.simplify a- assertEqual "Must simplify down" (successGen []) b- , testCase "prop-simp-GEq" $ do- let- a = successGen [PGEq (Lit 1) (Lit 2)]- b = Expr.simplify a- assertEqual "Must simplify down" (successGen [PBool False]) b- , testCase "prop-simp-multiple" $ do- let- a = successGen [PBool False, PBool True]- b = Expr.simplify a- assertEqual "Must simplify down" (successGen [PBool False]) b- , testCase "prop-simp-expr" $ do- let- a = successGen [PEq (Add (Lit 1) (Lit 2)) (Sub (Lit 4) (Lit 1))]- b = Expr.simplify a- assertEqual "Must simplify down" (successGen []) b- , testCase "prop-simp-impl" $ do- let- a = successGen [PImpl (PBool False) (PEq (Var "abc") (Var "bcd"))]- b = Expr.simplify a- assertEqual "Must simplify down" (successGen []) b- ]- , testGroup "equivalence-checking"- [- testCase "eq-yul-simple-cex" $ do- Just aPrgm <- yul ""- [i|- {- calldatacopy(0, 0, 32)- switch mload(0)- case 0 { }- case 1 { }- default { invalid() }- }- |]- Just bPrgm <- yul ""- [i|- {- calldatacopy(0, 0, 32)- switch mload(0)- case 0 { }- case 2 { }- default { invalid() }- }- |]- withSolvers Z3 3 Nothing $ \s -> do- a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])- assertBool "Must have a difference" (any isCex a)- ,- testCase "eq-sol-exp-qed" $ do- Just aPrgm <- solcRuntime "C"- [i|- contract C {- function a(uint8 x) public returns (uint8 b) {- unchecked {- b = x*2;- }- }- }- |]- Just bPrgm <- solcRuntime "C"- [i|- contract C {- function a(uint8 x) public returns (uint8 b) {- unchecked {- b = x<<1;- }- }- }- |]- withSolvers Z3 3 Nothing $ \s -> do- a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])- assertEqual "Must have no difference" [Qed ()] a- ,- testCase "eq-balance-differs" $ do- Just aPrgm <- solcRuntime "C"- [i|- contract Send {- constructor(address payable dst) payable {- selfdestruct(dst);- }- }- contract C {- function f() public {- new Send{value:2}(payable(address(0x0)));- }- }- |]- Just bPrgm <- solcRuntime "C"- [i|- contract Send {- constructor(address payable dst) payable {- selfdestruct(dst);- }- }- contract C {- function f() public {- new Send{value:1}(payable(address(0x0)));- }- }- |]- withSolvers Z3 3 Nothing $ \s -> do- a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])- assertBool "Must differ" (all isCex a)- ,- -- TODO: this fails because we don't check equivalence of deployed contracts- expectFail $ testCase "eq-handles-contract-deployment" $ do- Just aPrgm <- solcRuntime "B"- [i|- contract Send {- constructor(address payable dst) payable {- selfdestruct(dst);- }- }-- contract A {- address parent;- constructor(address p) {- parent = p;- }- function evil() public {- parent.call(abi.encode(B.drain.selector));- }- }-- contract B {- address child;- function a() public {- child = address(new A(address(this)));- }- function drain() public {- require(msg.sender == child);- new Send{value: address(this).balance}(payable(address(0x0)));- }- }- |]- Just bPrgm <- solcRuntime "D"- [i|- contract Send {- constructor(address payable dst) payable {- selfdestruct(dst);- }- }-- contract C {- address parent;- constructor(address p) {- parent = p;- }- }-- contract D {- address child;- function a() public {- child = address(new C(address(this)));- }- function drain() public {- require(msg.sender == child);- new Send{value: address(this).balance}(payable(address(0x0)));- }- }- |]- withSolvers Z3 3 Nothing $ \s -> do- a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])- assertBool "Must differ" (all isCex a)- ,- testCase "eq-unknown-addr" $ do- Just aPrgm <- solcRuntime "C"- [i|- contract C {- address addr;- function a(address a, address b) public {- addr = a;- }- }- |]- Just bPrgm <- solcRuntime "C"- [i|- contract C {- address addr;- function a(address a, address b) public {- addr = b;- }- }- |]- withSolvers Z3 3 Nothing $ \s -> do- let cd = mkCalldata (Just (Sig "a(address,address)" [AbiAddressType, AbiAddressType])) []- a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts cd- assertEqual "Must be different" (any isCex a) True- ,- testCase "eq-sol-exp-cex" $ do- Just aPrgm <- solcRuntime "C"- [i|- contract C {- function a(uint8 x) public returns (uint8 b) {- unchecked {- b = x*2+1;- }- }- }- |]- Just bPrgm <- solcRuntime "C"- [i|- contract C {- function a(uint8 x) public returns (uint8 b) {- unchecked {- b = x<<1;- }- }- }- |]- withSolvers Z3 3 Nothing $ \s -> do- a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])- assertEqual "Must be different" (any isCex a) True- , testCase "eq-all-yul-optimization-tests" $ do- let opts = defaultVeriOpts{ maxIter = Just 5, askSmtIters = 20, loopHeuristic = Naive }- ignoredTests =- -- unbounded loop --- [ "commonSubexpressionEliminator/branches_for.yul"- , "conditionalSimplifier/no_opt_if_break_is_not_last.yul"- , "conditionalUnsimplifier/no_opt_if_break_is_not_last.yul"- , "expressionSimplifier/inside_for.yul"- , "forLoopConditionIntoBody/cond_types.yul"- , "forLoopConditionIntoBody/simple.yul"- , "fullSimplify/inside_for.yul"- , "fullSuite/no_move_loop_orig.yul"- , "loopInvariantCodeMotion/multi.yul"- , "redundantAssignEliminator/for_deep_simple.yul"- , "unusedAssignEliminator/for_deep_noremove.yul"- , "unusedAssignEliminator/for_deep_simple.yul"- , "ssaTransform/for_def_in_init.yul"- , "loopInvariantCodeMotion/simple_state.yul"- , "loopInvariantCodeMotion/simple.yul"- , "loopInvariantCodeMotion/recursive.yul"- , "loopInvariantCodeMotion/no_move_staticall_returndatasize.yul"- , "loopInvariantCodeMotion/no_move_state_loop.yul"- , "loopInvariantCodeMotion/no_move_state.yul" -- not infinite, but rollaround on a large int- , "loopInvariantCodeMotion/no_move_loop.yul"-- -- unexpected symbolic arg ---- -- OpCreate2- , "expressionSimplifier/create2_and_mask.yul"-- -- OpCreate- , "expressionSimplifier/create_and_mask.yul"- , "expressionSimplifier/large_byte_access.yul"-- -- OpMload- , "yulOptimizerTests/expressionSplitter/inside_function.yul"- , "fullInliner/double_inline.yul"- , "fullInliner/inside_condition.yul"- , "fullInliner/large_function_multi_use.yul"- , "fullInliner/large_function_single_use.yul"- , "fullInliner/no_inline_into_big_global_context.yul"- , "fullSimplify/invariant.yul"- , "fullSuite/abi_example1.yul"- , "ssaAndBack/for_loop.yul"- , "ssaAndBack/multi_assign_multi_var_if.yul"- , "ssaAndBack/multi_assign_multi_var_switch.yul"- , "ssaAndBack/two_vars.yul"- , "ssaTransform/multi_assign.yul"- , "ssaTransform/multi_decl.yul"- , "expressionSplitter/inside_function.yul"- , "fullSuite/ssaReverseComplex.yul"-- -- OpMstore- , "commonSubexpressionEliminator/function_scopes.yul"- , "commonSubexpressionEliminator/variable_for_variable.yul"- , "expressionSplitter/trivial.yul"- , "fullInliner/multi_return.yul"- , "fullSimplify/constant_propagation.yul"- , "fullSimplify/identity_rules_complex.yul"- , "fullSuite/medium.yul"- , "loadResolver/memory_with_msize.yul"- , "loadResolver/merge_known_write.yul"- , "loadResolver/merge_known_write_with_distance.yul"- , "loadResolver/merge_unknown_write.yul"- , "loadResolver/reassign_value_expression.yul"- , "loadResolver/second_mstore_with_delta.yul"- , "loadResolver/second_store_with_delta.yul"- , "loadResolver/simple.yul"- , "loadResolver/simple_memory.yul"- , "fullSuite/ssaReverse.yul"- , "rematerialiser/cheap_caller.yul"- , "rematerialiser/non_movable_instruction.yul"- , "rematerialiser/for_break.yul"- , "rematerialiser/for_continue.yul"- , "rematerialiser/for_continue_2.yul"- , "ssaAndBack/multi_assign.yul"- , "ssaAndBack/multi_assign_if.yul"- , "ssaAndBack/multi_assign_switch.yul"- , "ssaAndBack/simple.yul"- , "ssaReverser/simple.yul"- , "loopInvariantCodeMotion/simple_storage.yul"-- -- OpMstore8- , "loadResolver/memory_with_different_kinds_of_invalidation.yul"-- -- OpRevert- , "ssaAndBack/ssaReverse.yul"- , "redundantAssignEliminator/for_continue_3.yul"- , "controlFlowSimplifier/terminating_for_revert.yul"-- -- invalid test --- -- https://github.com/ethereum/solidity/issues/9500- , "commonSubexpressionEliminator/object_access.yul"- , "expressionSplitter/object_access.yul"- , "fullSuite/stack_compressor_msize.yul"-- -- stack too deep --- , "fullSuite/abi2.yul"- , "fullSuite/aztec.yul"- , "stackCompressor/inlineInBlock.yul"- , "stackCompressor/inlineInFunction.yul"- , "stackCompressor/unusedPrunerWithMSize.yul"- , "wordSizeTransform/function_call.yul"- , "fullInliner/no_inline_into_big_function.yul"- , "controlFlowSimplifier/switch_only_default.yul"- , "stackLimitEvader" -- all that are in this subdirectory-- -- wrong number of args --- , "wordSizeTransform/functional_instruction.yul"- , "wordSizeTransform/if.yul"- , "wordSizeTransform/or_bool_renamed.yul"- , "wordSizeTransform/switch_1.yul"- , "wordSizeTransform/switch_2.yul"- , "wordSizeTransform/switch_3.yul"- , "wordSizeTransform/switch_4.yul"- , "wordSizeTransform/switch_5.yul"- , "unusedFunctionParameterPruner/too_many_arguments.yul"-- -- typed yul --- , "conditionalSimplifier/add_correct_type_wasm.yul"- , "conditionalSimplifier/add_correct_type.yul"- , "disambiguator/for_statement.yul"- , "disambiguator/funtion_call.yul"- , "disambiguator/if_statement.yul"- , "disambiguator/long_names.yul"- , "disambiguator/switch_statement.yul"- , "disambiguator/variables_clash.yul"- , "disambiguator/variables_inside_functions.yul"- , "disambiguator/variables.yul"- , "expressionInliner/simple.yul"- , "expressionInliner/with_args.yul"- , "expressionSplitter/typed.yul"- , "fullInliner/multi_return_typed.yul"- , "functionGrouper/empty_block.yul"- , "functionGrouper/multi_fun_mixed.yul"- , "functionGrouper/nested_fun.yul"- , "functionGrouper/single_fun.yul"- , "functionHoister/empty_block.yul"- , "functionHoister/multi_mixed.yul"- , "functionHoister/nested.yul"- , "functionHoister/single.yul"- , "mainFunction/empty_block.yul"- , "mainFunction/multi_fun_mixed.yul"- , "mainFunction/nested_fun.yul"- , "mainFunction/single_fun.yul"- , "ssaTransform/typed_for.yul"- , "ssaTransform/typed_switch.yul"- , "ssaTransform/typed.yul"- , "varDeclInitializer/typed.yul"-- -- New: symbolic index on MSTORE/MLOAD/CopySlice/CallDataCopy/ExtCodeCopy/Revert,- -- or exponent is symbolic (requires symbolic gas)- -- or SHA3 offset symbolic- , "blockFlattener/basic.yul"- , "commonSubexpressionEliminator/case2.yul"- , "equalStoreEliminator/indirect_inferrence.yul"- , "expressionJoiner/reassignment.yul"- , "expressionSimplifier/exp_simplifications.yul"- , "expressionSimplifier/zero_length_read.yul"- , "expressionSimplifier/side_effects_in_for_condition.yul"- , "fullSuite/create_and_mask.yul"- , "fullSuite/unusedFunctionParameterPruner_return.yul"- , "fullSuite/unusedFunctionParameterPruner_simple.yul"- , "fullSuite/unusedFunctionParameterPruner.yul"- , "loadResolver/double_mload_with_other_reassignment.yul"- , "loadResolver/double_mload_with_reassignment.yul"- , "loadResolver/double_mload.yul"- , "loadResolver/keccak_reuse_basic.yul"- , "loadResolver/keccak_reuse_expr_mstore.yul"- , "loadResolver/keccak_reuse_msize.yul"- , "loadResolver/keccak_reuse_mstore.yul"- , "loadResolver/keccak_reuse_reassigned_branch.yul"- , "loadResolver/keccak_reuse_reassigned_value.yul"- , "loadResolver/keccak_symbolic_memory.yul"- , "loadResolver/merge_mload_with_known_distance.yul"- , "loadResolver/mload_self.yul"- , "loadResolver/keccak_reuse_in_expression.yul"- , "loopInvariantCodeMotion/complex_move.yul"- , "loopInvariantCodeMotion/move_memory_function.yul"- , "loopInvariantCodeMotion/move_state_function.yul"- , "loopInvariantCodeMotion/no_move_memory.yul"- , "loopInvariantCodeMotion/no_move_storage.yul"- , "loopInvariantCodeMotion/not_first.yul"- , "ssaAndBack/single_assign_if.yul"- , "ssaAndBack/single_assign_switch.yul"- , "structuralSimplifier/switch_inline_no_match.yul"- , "unusedFunctionParameterPruner/simple.yul"- , "unusedStoreEliminator/covering_calldatacopy.yul"- , "unusedStoreEliminator/remove_before_revert.yul"- , "unusedStoreEliminator/unknown_length2.yul"- , "unusedStoreEliminator/unrelated_relative.yul"- , "fullSuite/extcodelength.yul"- , "unusedStoreEliminator/create_inside_function.yul"-- "trying to reset symbolic storage with writes in create"-- -- Takes too long, would timeout on most test setups.- -- We could probably fix these by "bunching together" queries- , "reasoningBasedSimplifier/mulmod.yul"- , "loadResolver/multi_sload_loop.yul"- , "reasoningBasedSimplifier/mulcheck.yul"- , "reasoningBasedSimplifier/smod.yul"-- -- TODO check what's wrong with these!- , "loadResolver/keccak_short.yul" -- ACTUAL bug -- keccak- , "reasoningBasedSimplifier/signed_division.yul" -- ACTUAL bug, SDIV- ]-- solcRepo <- fromMaybe (internalError "cannot find solidity repo") <$> (lookupEnv "HEVM_SOLIDITY_REPO")- let testDir = solcRepo <> "/test/libyul/yulOptimizerTests"- dircontents <- System.Directory.listDirectory testDir- let- fullpaths = map ((testDir ++ "/") ++) dircontents- recursiveList :: [FilePath] -> [FilePath] -> IO [FilePath]- recursiveList (a:ax) b = do- isdir <- doesDirectoryExist a- case isdir of- True -> do- fs <- System.Directory.listDirectory a- let fs2 = map ((a ++ "/") ++) fs- recursiveList (ax++fs2) b- False -> recursiveList ax (a:b)- recursiveList [] b = pure b- files <- recursiveList fullpaths []- let filesFiltered = filter (\file -> not $ any (`List.isSubsequenceOf` file) ignoredTests) files-- -- Takes one file which follows the Solidity Yul optimizer unit tests format,- -- extracts both the nonoptimized and the optimized versions, and checks equivalence.- forM_ filesFiltered (\f-> do- origcont <- readFile f- let- onlyAfter pattern (a:ax) = if a =~ pattern then (a:ax) else onlyAfter pattern ax- onlyAfter _ [] = []- replaceOnce pat repl inp = go inp [] where- go (a:ax) b = if a =~ pat then let a2 = replaceAll repl $ a *=~ pat in b ++ a2:ax- else go ax (b ++ [a])- go [] b = b-- -- takes a yul program and ensures memory is symbolic by prepending- -- `calldatacopy(0,0,1024)`. (calldata is symbolic, but memory starts empty).- -- This forces the exploration of more branches, and makes the test vectors a- -- little more thorough.- symbolicMem (a:ax) = if a =~ [re|"^ *object"|] then- let a2 = replaceAll "a calldatacopy(0,0,1024)" $ a *=~ [re|code {|]- in (a2:ax)- else replaceOnce [re|^ *{|] "{\ncalldatacopy(0,0,1024)" $ onlyAfter [re|^ *{|] (a:ax)- symbolicMem _ = internalError "Program too short"-- unfiltered = lines origcont- filteredASym = symbolicMem [ x | x <- unfiltered, (not $ x =~ [re|^//|]) && (not $ x =~ [re|^$|]) ]- filteredBSym = symbolicMem [ replaceAll "" $ x *=~[re|^//|] | x <- onlyAfter [re|^// step:|] unfiltered, not $ x =~ [re|^$|] ]- start <- getCurrentTime- putStrLn $ "Checking file: " <> f- when opts.debug $ do- putStrLn "-------------Original Below-----------------"- mapM_ putStrLn unfiltered- putStrLn "------------- Filtered A + Symb below-----------------"- mapM_ putStrLn filteredASym- putStrLn "------------- Filtered B + Symb below-----------------"- mapM_ putStrLn filteredBSym- putStrLn "------------- END -----------------"- Just aPrgm <- yul "" $ T.pack $ unlines filteredASym- Just bPrgm <- yul "" $ T.pack $ unlines filteredBSym- procs <- getNumProcessors- withSolvers CVC5 (unsafeInto procs) (Just 100) $ \s -> do- res <- equivalenceCheck s aPrgm bPrgm opts (mkCalldata Nothing [])- end <- getCurrentTime- case any isCex res of- False -> do- print $ "OK. Took " <> (show $ diffUTCTime end start) <> " seconds"- let timeouts = filter isTimeout res- unless (null timeouts) $ do- putStrLn $ "But " <> (show $ length timeouts) <> " timeout(s) occurred"- internalError "Encountered timeouts"- True -> do- putStrLn $ "Not OK: " <> show f <> " Got: " <> show res- internalError "Was NOT equivalent"- )- ]- ]- where- (===>) = assertSolidityComputation--checkEquivProp :: Prop -> Prop -> IO Bool-checkEquivProp = checkEquivBase (\l r -> PNeg (PImpl l r .&& PImpl r l))--checkEquiv :: (Typeable a) => Expr a -> Expr a -> IO Bool-checkEquiv = checkEquivBase (./=)--checkEquivBase :: Eq a => (a -> a -> Prop) -> a -> a -> IO Bool-checkEquivBase mkprop l r = withSolvers Z3 1 (Just 1) $ \solvers -> do- if l == r- then do- putStrLn "skip"- pure True- else do- let smt = assertPropsNoSimp abstRefineDefault [mkprop l r]- res <- checkSat solvers smt- print res- pure $ case res of- Unsat -> True- EVM.Solvers.Unknown -> True- Sat _ -> False- Error _ -> False---- | Takes a runtime code and calls it with the provided calldata---- | Takes a creation code and some calldata, runs the creation code, and calls the resulting contract with the provided calldata-runSimpleVM :: ByteString -> ByteString -> IO (Maybe ByteString)-runSimpleVM x ins = do- loadVM x >>= \case- Nothing -> pure Nothing- Just vm -> do- let calldata = (ConcreteBuf ins)- vm' = set (#state % #calldata) calldata vm- res <- Stepper.interpret (Fetch.zero 0 Nothing) vm' Stepper.execFully- case res of- (Right (ConcreteBuf bs)) -> pure $ Just bs- s -> internalError $ show s---- | Takes a creation code and returns a vm with the result of executing the creation code-loadVM :: ByteString -> IO (Maybe (VM RealWorld))-loadVM x = do- vm <- stToIO $ vmForEthrunCreation x- vm1 <- Stepper.interpret (Fetch.zero 0 Nothing) vm Stepper.runFully- case vm1.result of- Just (VMSuccess (ConcreteBuf targetCode)) -> do- let target = vm1.state.contract- vm2 <- Stepper.interpret (Fetch.zero 0 Nothing) vm1 (prepVm target targetCode >> Stepper.run)- pure $ Just vm2- _ -> pure Nothing- where- prepVm target targetCode = Stepper.evm $ do- replaceCodeOfSelf (RuntimeCode $ ConcreteRuntimeCode targetCode)- resetState- assign (#state % #gas) 0xffffffffffffffff -- kludge- execState (loadContract target) <$> get >>= put--hex :: ByteString -> ByteString-hex s =- case BS16.decodeBase16 s of- Right x -> x- Left e -> internalError $ T.unpack e--singleContract :: Text -> Text -> IO (Maybe ByteString)-singleContract x s =- solidity x [i|- pragma experimental ABIEncoderV2;- contract ${x} { ${s} }- |]--defaultDataLocation :: AbiType -> Text-defaultDataLocation t =- if (case t of- AbiBytesDynamicType -> True- AbiStringType -> True- AbiArrayDynamicType _ -> True- AbiArrayType _ _ -> True- _ -> False)- then "memory"- else ""--runFunction :: Text -> ByteString -> IO (Maybe ByteString)-runFunction c input = do- Just x <- singleContract "X" c- runSimpleVM x input--runStatements- :: Text -> [AbiValue] -> AbiType- -> IO (Maybe ByteString)-runStatements stmts args t = do- let params =- T.intercalate ", "- (map (\(x, c) -> abiTypeSolidity (abiValueType x)- <> " " <> defaultDataLocation (abiValueType x)- <> " " <> T.pack [c])- (zip args "abcdefg"))- s =- "foo(" <> T.intercalate ","- (map (abiTypeSolidity . abiValueType) args) <> ")"-- runFunction [i|- function foo(${params}) public pure returns (${abiTypeSolidity t} ${defaultDataLocation t} x) {- ${stmts}- }- |] (abiMethod s (AbiTuple $ V.fromList args))--getStaticAbiArgs :: Int -> VM s -> [Expr EWord]-getStaticAbiArgs n vm =- let cd = vm.state.calldata- in decodeStaticArgs 4 n cd---- includes shaving off 4 byte function sig-decodeAbiValues :: [AbiType] -> ByteString -> [AbiValue]-decodeAbiValues types bs =- let xy = case decodeAbiValue (AbiTupleType $ V.fromList types) (BS.fromStrict (BS.drop 4 bs)) of- AbiTuple xy' -> xy'- _ -> internalError "AbiTuple expected"- in V.toList xy--newtype Bytes = Bytes ByteString- deriving Eq--instance Show Bytes where- showsPrec _ (Bytes x) _ = show (BS.unpack x)--instance Arbitrary Bytes where- arbitrary = fmap (Bytes . BS.pack) arbitrary--newtype RLPData = RLPData RLP- deriving (Eq, Show)---- bias towards bytestring to try to avoid infinite recursion-instance Arbitrary RLPData where- arbitrary = frequency- [(5, do- Bytes bytes <- arbitrary- return $ RLPData $ BS bytes)- , (1, do- k <- choose (0,10)- ls <- vectorOf k arbitrary- return $ RLPData $ List [r | RLPData r <- ls])- ]--instance Arbitrary Word128 where- arbitrary = liftM2 fromHiAndLo arbitrary arbitrary--instance Arbitrary Word160 where- arbitrary = liftM2 fromHiAndLo arbitrary arbitrary--instance Arbitrary Word256 where- arbitrary = liftM2 fromHiAndLo arbitrary arbitrary--instance Arbitrary W64 where- arbitrary = fmap W64 arbitrary--instance Arbitrary W256 where- arbitrary = fmap W256 arbitrary--instance Arbitrary Addr where- arbitrary = fmap Addr arbitrary--instance Arbitrary (Expr EAddr) where- arbitrary = oneof- [ fmap LitAddr arbitrary- , fmap SymAddr (genName "addr")- ]--instance Arbitrary (Expr Storage) where- arbitrary = sized genStorage--instance Arbitrary (Expr EWord) where- arbitrary = sized defaultWord--instance Arbitrary (Expr Byte) where- arbitrary = sized genByte--instance Arbitrary (Expr Buf) where- arbitrary = sized defaultBuf--instance Arbitrary (Expr End) where- arbitrary = sized genEnd--instance Arbitrary (ContractCode) where- arbitrary = oneof- [ fmap UnknownCode arbitrary- , liftM2 InitCode arbitrary arbitrary- , fmap RuntimeCode arbitrary- ]--instance Arbitrary (RuntimeCode) where- arbitrary = oneof- [ fmap ConcreteRuntimeCode arbitrary- , fmap SymbolicRuntimeCode arbitrary- ]--instance Arbitrary (V.Vector (Expr Byte)) where- arbitrary = fmap V.fromList (listOf1 arbitrary)--instance Arbitrary (Expr EContract) where- arbitrary = sized genEContract---- LitOnly-newtype LitOnly a = LitOnly a- deriving (Show, Eq)--newtype LitWord (sz :: Nat) = LitWord (Expr EWord)- deriving (Show)--instance (KnownNat sz) => Arbitrary (LitWord sz) where- arbitrary = LitWord <$> genLit (fromInteger v)- where- v = natVal (Proxy @sz)--instance Arbitrary (LitOnly (Expr Byte)) where- arbitrary = LitOnly . LitByte <$> arbitrary--instance Arbitrary (LitOnly (Expr EWord)) where- arbitrary = LitOnly . Lit <$> arbitrary--instance Arbitrary (LitOnly (Expr Buf)) where- arbitrary = LitOnly . ConcreteBuf <$> arbitrary--genEContract :: Int -> Gen (Expr EContract)-genEContract sz = do- c <- arbitrary- b <- defaultWord sz- n <- arbitrary- s <- genStorage sz- pure $ C c s b n---- ZeroDepthWord-newtype ZeroDepthWord = ZeroDepthWord (Expr EWord)- deriving (Show, Eq)--instance Arbitrary ZeroDepthWord where- arbitrary = do- fmap ZeroDepthWord . sized $ genWord 0---- WriteWordBuf-newtype WriteWordBuf = WriteWordBuf (Expr Buf)- deriving (Show, Eq)--instance Arbitrary WriteWordBuf where- arbitrary = do- let mkBuf = oneof- [ pure $ ConcreteBuf "" -- empty- , fmap ConcreteBuf arbitrary -- concrete- , sized (genBuf 100) -- overlapping writes- , arbitrary -- sparse writes- ]- fmap WriteWordBuf mkBuf---- GenCopySliceBuf-newtype GenCopySliceBuf = GenCopySliceBuf (Expr Buf)- deriving (Show, Eq)--instance Arbitrary GenCopySliceBuf where- arbitrary = do- let mkBuf = oneof- [ pure $ ConcreteBuf ""- , fmap ConcreteBuf arbitrary- , arbitrary- ]- fmap GenCopySliceBuf mkBuf---- GenWriteStorageExpr-newtype GenWriteStorageExpr = GenWriteStorageExpr (Expr EWord, Expr Storage)- deriving (Show, Eq)--instance Arbitrary GenWriteStorageExpr where- arbitrary = do- slot <- arbitrary- let mkStore = oneof- [ pure $ ConcreteStore mempty- , fmap ConcreteStore arbitrary- , do- -- generate some write chains where we know that at least one- -- write matches either the input addr, or both the input- -- addr and slot- let addWrites :: Expr Storage -> Int -> Gen (Expr Storage)- addWrites b 0 = pure b- addWrites b n = liftM3 SStore arbitrary arbitrary (addWrites b (n - 1))- s <- arbitrary- addMatch <- fmap (SStore slot) arbitrary- let withMatch = addMatch s- newWrites <- oneof [ pure 0, pure 1, fmap (`mod` 5) arbitrary ]- addWrites withMatch newWrites- , arbitrary- ]- store <- mkStore- pure $ GenWriteStorageExpr (slot, store)---- GenWriteByteIdx-newtype GenWriteByteIdx = GenWriteByteIdx (Expr EWord)- deriving (Show, Eq)--instance Arbitrary GenWriteByteIdx where- arbitrary = do- -- 1st: can never overflow an Int- -- 2nd: can overflow an Int- let mkIdx = frequency [ (10, genLit 1_000_000) , (1, fmap Lit arbitrary) ]- fmap GenWriteByteIdx mkIdx--newtype LitProp = LitProp Prop- deriving (Show, Eq)--instance Arbitrary LitProp where- arbitrary = LitProp <$> sized (genProp True)---newtype StorageExp = StorageExp (Expr EWord)- deriving (Show, Eq)--instance Arbitrary StorageExp where- arbitrary = StorageExp <$> (genStorageExp)--genStorageExp :: Gen (Expr EWord)-genStorageExp = do- fromPos <- genSlot- storage <- genStorageWrites- pure $ SLoad fromPos storage--genSlot :: Gen (Expr EWord)-genSlot = frequency [ (1, do- buf <- genConcreteBufSlot 64- case buf of- (ConcreteBuf b) -> do- key <- genLit 10- pure $ Expr.MappingSlot b key- _ -> internalError "impossible"- )- -- map element- ,(2, do- l <- genLit 10- buf <- genConcreteBufSlot 64- pure $ Add (Keccak buf) l)- -- Array element- ,(2, do- l <- genLit 10- buf <- genConcreteBufSlot 32- pure $ Add (Keccak buf) l)- -- member of the Contract- ,(2, pure $ Lit 20)- -- array element- ,(2, do- arrayNum :: Int <- arbitrary- offs :: W256 <- arbitrary- pure $ Lit $ fst (Expr.preImages !! (arrayNum `mod` 3)) + (offs `mod` 3))- -- random stuff- ,(1, pure $ Lit (maxBound :: W256))- ]---- Generates an N-long buffer, all with the same value, at most 8 different ones-genConcreteBufSlot :: Int -> Gen (Expr Buf)-genConcreteBufSlot len = do- b :: Word8 <- arbitrary- pure $ ConcreteBuf $ BS.pack ([ 0 | _ <- [0..(len-2)]] ++ [b])--genStorageWrites :: Gen (Expr Storage)-genStorageWrites = do- toSlot <- genSlot- val <- genLit (maxBound :: W256)- store <- frequency [ (3, pure $ AbstractStore (SymAddr ""))- , (2, genStorageWrites)- ]- pure $ SStore toSlot val store--instance Arbitrary Prop where- arbitrary = sized (genProp False)--genProps :: Bool -> Int -> Gen [Prop]-genProps onlyLits sz2 = listOf $ genProp onlyLits sz2--genProp :: Bool -> Int -> Gen (Prop)-genProp _ 0 = PBool <$> arbitrary-genProp onlyLits sz = oneof- [ liftM2 PEq subWord subWord- , liftM2 PLT subWord subWord- , liftM2 PGT subWord subWord- , liftM2 PLEq subWord subWord- , liftM2 PGEq subWord subWord- , fmap PNeg subProp- , liftM2 PAnd subProp subProp- , liftM2 POr subProp subProp- , liftM2 PImpl subProp subProp- ]- where- subWord = if onlyLits then frequency [(2, Lit <$> arbitrary)- ,(1, pure $ Lit 0)- ,(1, pure $ Lit Expr.maxLit)- ]- else genWord 1 (sz `div` 2)- subProp = genProp onlyLits (sz `div` 2)--genByte :: Int -> Gen (Expr Byte)-genByte 0 = fmap LitByte arbitrary-genByte sz = oneof- [ liftM2 IndexWord subWord subWord- , liftM2 ReadByte subWord subBuf- ]- where- subWord = defaultWord (sz `div` 10)- subBuf = defaultBuf (sz `div` 10)--genLit :: W256 -> Gen (Expr EWord)-genLit bound = do- w <- arbitrary- pure $ Lit (w `mod` bound)--genNat :: Gen Int-genNat = fmap unsafeInto (arbitrary :: Gen Natural)--genName :: String -> Gen Text--- In order not to generate SMT reserved words, we prepend with "esc_"-genName ty = fmap (T.pack . (("esc_" <> ty <> "_") <> )) $ listOf1 (oneof . (fmap pure) $ ['a'..'z'] <> ['A'..'Z'])--genEnd :: Int -> Gen (Expr End)-genEnd 0 = oneof- [ fmap (Failure mempty mempty . UnrecognizedOpcode) arbitrary- , pure $ Failure mempty mempty IllegalOverflow- , pure $ Failure mempty mempty SelfDestruction- ]-genEnd sz = oneof- [ liftM3 Failure subProp (pure mempty) (fmap Revert subBuf)- , liftM4 Success subProp (pure mempty) subBuf arbitrary- , liftM3 ITE subWord subEnd subEnd- -- TODO Partial- ]- where- subBuf = defaultBuf (sz `div` 2)- subWord = defaultWord (sz `div` 2)- subEnd = genEnd (sz `div` 2)- subProp = genProps False (sz `div` 2)--genWord :: Int -> Int -> Gen (Expr EWord)-genWord litFreq 0 = frequency- [ (litFreq, do- val <- frequency- [ (10, fmap (`mod` 100) arbitrary)- , (1, pure 0)- , (1, pure Expr.maxLit)- , (1, arbitrary)- ]- pure $ Lit val- )- , (1, oneof- [ pure Origin- , pure Coinbase- , pure Timestamp- , pure BlockNumber- , pure PrevRandao- , pure GasLimit- , pure ChainId- , pure BaseFee- --, liftM2 SelfBalance arbitrary arbitrary- --, liftM2 Gas arbitrary arbitrary- , fmap Lit arbitrary- , fmap Var (genName "word")- ]- )- ]-genWord litFreq sz = frequency- [ (litFreq, do- val <- frequency- [ (10, fmap (`mod` 100) arbitrary)- , (1, arbitrary)- ]- pure $ Lit val- )- , (1, oneof- [ liftM2 Add subWord subWord- , liftM2 Sub subWord subWord- , liftM2 Mul subWord subWord- , liftM2 Div subWord subWord- , liftM2 SDiv subWord subWord- , liftM2 Mod subWord subWord- , liftM2 SMod subWord subWord- --, liftM3 AddMod subWord subWord subWord- --, liftM3 MulMod subWord subWord subWord -- it works, but it's VERY SLOW- --, liftM2 Exp subWord litWord- , liftM2 SEx subWord subWord- , liftM2 Min subWord subWord- , liftM2 LT subWord subWord- , liftM2 GT subWord subWord- , liftM2 LEq subWord subWord- , liftM2 GEq subWord subWord- , liftM2 SLT subWord subWord- --, liftM2 SGT subWord subWord- , liftM2 Eq subWord subWord- , fmap IsZero subWord- , liftM2 And subWord subWord- , liftM2 Or subWord subWord- , liftM2 Xor subWord subWord- , fmap Not subWord- , liftM2 SHL subWord subWord- , liftM2 SHR subWord subWord- , liftM2 SAR subWord subWord- , fmap BlockHash subWord- --, liftM3 Balance arbitrary arbitrary subWord- --, fmap CodeSize subWord- --, fmap ExtCodeHash subWord- , fmap Keccak subBuf- , fmap SHA256 subBuf- , liftM2 SLoad subWord subStore- , liftM2 ReadWord subWord subBuf- , fmap BufLength subBuf- , do- one <- subByte- two <- subByte- three <- subByte- four <- subByte- five <- subByte- six <- subByte- seven <- subByte- eight <- subByte- nine <- subByte- ten <- subByte- eleven <- subByte- twelve <- subByte- thirteen <- subByte- fourteen <- subByte- fifteen <- subByte- sixteen <- subByte- seventeen <- subByte- eighteen <- subByte- nineteen <- subByte- twenty <- subByte- twentyone <- subByte- twentytwo <- subByte- twentythree <- subByte- twentyfour <- subByte- twentyfive <- subByte- twentysix <- subByte- twentyseven <- subByte- twentyeight <- subByte- twentynine <- subByte- thirty <- subByte- thirtyone <- subByte- thirtytwo <- subByte- pure $ JoinBytes- one two three four five six seven eight nine ten- eleven twelve thirteen fourteen fifteen sixteen- seventeen eighteen nineteen twenty twentyone- twentytwo twentythree twentyfour twentyfive- twentysix twentyseven twentyeight twentynine- thirty thirtyone thirtytwo- ])- ]- where- subWord = genWord litFreq (sz `div` 5)- subBuf = defaultBuf (sz `div` 10)- subStore = genStorage (sz `div` 10)- subByte = genByte (sz `div` 10)--genWordArith :: Int -> Int -> Gen (Expr EWord)-genWordArith litFreq 0 = frequency- [ (litFreq, fmap Lit arbitrary)- , (1, oneof [ fmap Lit arbitrary ])- ]-genWordArith litFreq sz = frequency- [ (litFreq, fmap Lit arbitrary)- , (20, frequency- [ (20, liftM2 Add subWord subWord)- , (20, liftM2 Sub subWord subWord)- , (20, liftM2 Mul subWord subWord)- , (20, liftM2 SEx subWord subWord)- , (20, liftM2 Xor subWord subWord)- -- these reduce variability- , (3 , liftM2 Min subWord subWord)- , (3 , liftM2 Div subWord subWord)- , (3 , liftM2 SDiv subWord subWord)- , (3 , liftM2 Mod subWord subWord)- , (3 , liftM2 SMod subWord subWord)- , (3 , liftM2 SHL subWord subWord)- , (3 , liftM2 SHR subWord subWord)- , (3 , liftM2 SAR subWord subWord)- , (3 , liftM2 Or subWord subWord)- -- comparisons, reducing variability greatly- , (1 , liftM2 LEq subWord subWord)- , (1 , liftM2 GEq subWord subWord)- , (1 , liftM2 SLT subWord subWord)- --(1, , liftM2 SGT subWord subWord- , (1 , liftM2 Eq subWord subWord)- , (1 , liftM2 And subWord subWord)- , (1 , fmap IsZero subWord )- -- Expensive below- --(1, liftM3 AddMod subWord subWord subWord- --(1, liftM3 MulMod subWord subWord subWord- --(1, liftM2 Exp subWord litWord- ])- ]- where- subWord = genWordArith (litFreq `div` 2) (sz `div` 2)---- Used to check for unsimplified expressions-newtype FoundBad = FoundBad { bad :: Bool } deriving (Show)-initFoundBad :: FoundBad-initFoundBad = FoundBad { bad = False }---- Finds SLoad -> SStore. This should not occur in most scenarios--- as we can simplify them away-badStoresInExpr :: Expr a -> Bool-badStoresInExpr expr = bad- where- FoundBad bad = execState (mapExprM findBadStore expr) initFoundBad- findBadStore :: Expr a-> State FoundBad (Expr a)- findBadStore e = case e of- (SLoad _ (SStore _ _ _)) -> do- put (FoundBad { bad = True })- pure e- _ -> pure e--defaultBuf :: Int -> Gen (Expr Buf)-defaultBuf = genBuf (4_000_000)--defaultWord :: Int -> Gen (Expr EWord)-defaultWord = genWord 10--maybeBoundedLit :: W256 -> Gen (Expr EWord)-maybeBoundedLit bound = do- o <- (arbitrary :: Gen (Expr EWord))- pure $ case o of- Lit w -> Lit $ w `mod` bound- _ -> o--genBuf :: W256 -> Int -> Gen (Expr Buf)-genBuf _ 0 = oneof- [ fmap AbstractBuf (genName "buf")- , fmap ConcreteBuf arbitrary- ]-genBuf bound sz = oneof- [ liftM3 WriteWord (maybeBoundedLit bound) subWord subBuf- , liftM3 WriteByte (maybeBoundedLit bound) subByte subBuf- -- we don't generate copyslice instances where:- -- - size is abstract- -- - size > 100 (due to unrolling in SMT.hs)- -- - literal dstOffsets are > 4,000,000 (due to unrolling in SMT.hs)- -- n.b. that 4,000,000 is the theoretical maximum memory size given a 30,000,000 block gas limit- , liftM5 CopySlice subWord (maybeBoundedLit bound) smolLitWord subBuf subBuf- ]- where- -- copySlice gets unrolled in the generated SMT so we can't go too crazy here- smolLitWord = do- w <- arbitrary- pure $ Lit (w `mod` 100)- subWord = defaultWord (sz `div` 5)- subByte = genByte (sz `div` 10)- subBuf = genBuf bound (sz `div` 10)--genStorage :: Int -> Gen (Expr Storage)-genStorage 0 = oneof- [ fmap AbstractStore arbitrary- , fmap ConcreteStore arbitrary- ]-genStorage sz = liftM3 SStore subWord subWord subStore- where- subStore = genStorage (sz `div` 10)- subWord = defaultWord (sz `div` 5)--data Invocation- = SolidityCall Text [AbiValue]- deriving Show--assertSolidityComputation :: Invocation -> AbiValue -> IO ()-assertSolidityComputation (SolidityCall s args) x =- do y <- runStatements s args (abiValueType x)- assertEqual (T.unpack s)- (fmap Bytes (Just (encodeAbiValue x)))- (fmap Bytes y)--bothM :: (Monad m) => (a -> m b) -> (a, a) -> m (b, b)-bothM f (a, a') = do- b <- f a- b' <- f a'- return (b, b')--applyPattern :: String -> TestTree -> TestTree-applyPattern p = localOption (TestPattern (parseExpr p))--checkBadCheatCode :: Text -> Postcondition s-checkBadCheatCode sig _ = \case- (Failure _ _ (BadCheatCode s)) -> (ConcreteBuf $ into s.unFunctionSelector) ./= (ConcreteBuf $ selector sig)- _ -> PBool True--allBranchesFail :: ByteString -> Maybe Sig -> IO (Either [SMTCex] (Expr End))-allBranchesFail = checkPost (Just p)- where- p _ = \case- Success _ _ _ _ -> PBool False- _ -> PBool True--reachableUserAsserts :: ByteString -> Maybe Sig -> IO (Either [SMTCex] (Expr End))-reachableUserAsserts = checkPost (Just $ checkAssertions [0x01])--checkPost :: Maybe (Postcondition RealWorld) -> ByteString -> Maybe Sig -> IO (Either [SMTCex] (Expr End))+import Control.Monad.IO.Unlift+import Control.Monad.Reader (ReaderT)+import Data.Bits hiding (And, Xor)+import Data.ByteString (ByteString)+import Data.ByteString qualified as BS+import Data.ByteString.Base16 qualified as BS16+import Data.Binary.Put (runPut)+import Data.Binary.Get (runGetOrFail)+import Data.DoubleWord+import Data.Either+import Data.List qualified as List+import Data.List.Utils as DLU+import Data.Map.Strict qualified as Map+import Data.Maybe+import Data.String.Here+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.IO qualified as T+import Data.Time (diffUTCTime, getCurrentTime)+import Data.Typeable+import Data.Vector qualified as V+import Data.Word (Word8)+import GHC.Conc (getNumProcessors)+import System.Directory+import System.Environment+import Test.Tasty+import Test.Tasty.QuickCheck hiding (Failure, Success)+import Test.QuickCheck.Instances.Text()+import Test.QuickCheck.Instances.Natural()+import Test.QuickCheck.Instances.ByteString()+import Test.Tasty.HUnit+import Test.Tasty.Runners hiding (Failure, Success)+import Test.Tasty.ExpectedFailure+import Text.RE.TDFA.String+import Text.RE.Replace+import Witch (unsafeInto, into)+import Data.Containers.ListUtils (nubOrd)++import Optics.Core hiding (pre, re, elements)+import Optics.State++import EVM hiding (choose)+import EVM.ABI+import EVM.Assembler+import EVM.Exec+import EVM.Expr qualified as Expr+import EVM.Fetch qualified as Fetch+import EVM.Format (hexText, formatExpr)+import EVM.Precompiled+import EVM.RLP+import EVM.SMT hiding (one)+import EVM.Solidity+import EVM.Solvers+import EVM.Stepper qualified as Stepper+import EVM.SymExec+import EVM.Test.Tracing qualified as Tracing+import EVM.Test.Utils+import EVM.Traversals+import EVM.Types hiding (Env)+import EVM.Effects++testEnv :: Env+testEnv = Env { config = defaultConfig {+ dumpQueries = False+ , dumpExprs = False+ , dumpEndStates = False+ , debug = False+ , abstRefineArith = False+ , abstRefineMem = False+ , dumpTrace = False+ } }++putStrLnM :: (MonadUnliftIO m) => String -> m ()+putStrLnM a = liftIO $ putStrLn a++assertEqualM :: (App m, Eq a, Show a, HasCallStack) => String -> a -> a -> m ()+assertEqualM a b c = liftIO $ assertEqual a b c++assertBoolM+ :: (MonadUnliftIO m, HasCallStack)+ => String -> Bool -> m ()+assertBoolM a b = liftIO $ assertBool a b++test :: TestName -> ReaderT Env IO () -> TestTree+test a b = testCase a $ runEnv testEnv b++testFuzz :: TestName -> ReaderT Env IO () -> TestTree+testFuzz a b = testCase a $ runEnv (testEnv {config = testEnv.config {numCexFuzz = 100, onlyCexFuzz = True}}) b++prop :: Testable prop => ReaderT Env IO prop -> Property+prop a = ioProperty $ runEnv testEnv a++main :: IO ()+main = defaultMain tests++-- | run a subset of tests in the repl. p is a tasty pattern:+-- https://github.com/UnkindPartition/tasty/tree/ee6fe7136fbcc6312da51d7f1b396e1a2d16b98a#patterns+runSubSet :: String -> IO ()+runSubSet p = defaultMain . applyPattern p $ tests++tests :: TestTree+tests = testGroup "hevm"+ [ Tracing.tests+ , testGroup "simplify-storage"+ [ test "simplify-storage-array-only-static" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint[] a;+ function transfer(uint acct, uint val1, uint val2) public {+ unchecked {+ a[0] = val1 + 1;+ a[1] = val2 + 2;+ assert(a[0]+a[1] == val1 + val2 + 3);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ -- This case is somewhat artificial. We can't simplify this using only+ -- static rewrite rules, because acct is totally abstract and acct + 1+ -- could overflow back to zero. we may be able to do better if we have some+ -- smt assisted simplification that can take branch conditions into account.+ , expectFail $ test "simplify-storage-array-symbolic-index" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint b;+ uint[] a;+ function transfer(uint acct, uint val1) public {+ unchecked {+ a[acct] = val1;+ assert(a[acct] == val1);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ -- T.writeFile "symbolic-index.expr" $ formatExpr expr+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ , expectFail $ test "simplify-storage-array-of-struct-symbolic-index" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ struct MyStruct {+ uint a;+ uint b;+ }+ MyStruct[] arr;+ function transfer(uint acct, uint val1, uint val2) public {+ unchecked {+ arr[acct].a = val1+1;+ arr[acct].b = val1+2;+ assert(arr[acct].a + arr[acct].b == val1+val2+3);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ , test "simplify-storage-array-loop-nonstruct" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint[] a;+ function transfer(uint v) public {+ for (uint i = 0; i < a.length; i++) {+ a[i] = v;+ assert(a[i] == v);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256)" [AbiUIntType 256])) [] (defaultVeriOpts { maxIter = Just 5 })+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ , test "simplify-storage-array-loop-struct" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ struct MyStruct {+ uint a;+ uint b;+ }+ MyStruct[] arr;+ function transfer(uint v1, uint v2) public {+ for (uint i = 0; i < arr.length; i++) {+ arr[i].a = v1+1;+ arr[i].b = v2+2;+ assert(arr[i].a + arr[i].b == v1 + v2 + 3);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] (defaultVeriOpts { maxIter = Just 5 })+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ , test "decompose-1" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping (address => uint) balances;+ function prove_mapping_access(address x, address y) public {+ require(x != y);+ balances[x] = 1;+ balances[y] = 2;+ assert(balances[x] != balances[y]);+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "prove_mapping_access(address,address)" [AbiAddressType, AbiAddressType])) [] defaultVeriOpts+ let simpExpr = mapExprM Expr.decomposeStorage expr+ -- putStrLnM $ T.unpack $ formatExpr (fromJust simpExpr)+ assertEqualM "Decompose did not succeed." (isJust simpExpr) True+ , test "decompose-2" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping (address => uint) balances;+ function prove_mixed_symoblic_concrete_writes(address x, uint v) public {+ balances[x] = v;+ balances[address(0)] = balances[x];+ assert(balances[address(0)] == v);+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "prove_mixed_symoblic_concrete_writes(address,uint256)" [AbiAddressType, AbiUIntType 256])) [] defaultVeriOpts+ let simpExpr = mapExprM Expr.decomposeStorage expr+ -- putStrLnM $ T.unpack $ formatExpr (fromJust simpExpr)+ assertEqualM "Decompose did not succeed." (isJust simpExpr) True+ -- NOTE: we can't do the rewrite below, because x could be very large,+ -- which would allow us to overwrite *anything in the storage*, and that throws+ -- everything off.+ , expectFail $ test "decompose-3" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint[] a;+ function prove_array(uint x, uint v1, uint y, uint v2) public {+ require(v1 != v2);+ a[x] = v1;+ a[y] = v2;+ assert(a[x] == a[y]);+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "prove_array(uint256,uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ let simpExpr = mapExprM Expr.decomposeStorage expr+ -- putStrLnM $ T.unpack $ formatExpr (fromJust simpExpr)+ assertEqualM "Decompose did not succeed." (isJust simpExpr) True+ , test "decompose-4-mixed" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint[] a;+ mapping( uint => uint) balances;+ function prove_array(uint x, uint v1, uint y, uint v2) public {+ require(v1 != v2);+ balances[x] = v1+1;+ balances[y] = v1+2;+ a[x] = v1;+ assert(balances[x] != balances[y]);+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "prove_array(uint256,uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ let simpExpr = mapExprM Expr.decomposeStorage expr+ -- putStrLnM $ T.unpack $ formatExpr (fromJust simpExpr)+ assertEqualM "Decompose did not succeed." (isJust simpExpr) True+ , test "decompose-5-mixed" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping (address => uint) balances;+ mapping (uint => bool) auth;+ uint[] arr;+ uint a;+ uint b;+ function prove_mixed(address x, address y, uint val) public {+ b = val+1;+ require(x != y);+ balances[x] = val;+ a = val;+ arr[val] = 5;+ auth[val+1] = true;+ balances[y] = val+2;+ if (balances[y] == balances[y]) {+ assert(balances[y] == val);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "prove_mixed(address,address,uint256)" [AbiAddressType, AbiAddressType, AbiUIntType 256])) [] defaultVeriOpts+ let simpExpr = mapExprM Expr.decomposeStorage expr+ -- putStrLnM $ T.unpack $ formatExpr (fromJust simpExpr)+ assertEqualM "Decompose did not succeed." (isJust simpExpr) True+ -- TODO check what's going on here. Likely the "arbitrary write through array" is the reason why we fail+ , expectFail $ test "decompose-6-fail" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint[] arr;+ function prove_mixed(uint val) public {+ arr[val] = 5;+ arr[val+1] = val+5;+ assert(arr[val] == arr[val+1]);+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "prove_mixed(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ let simpExpr = mapExprM Expr.decomposeStorage expr+ -- putStrLnM $ T.unpack $ formatExpr (fromJust simpExpr)+ assertEqualM "Decompose did not succeed." (isJust simpExpr) True+ , test "simplify-storage-map-only-static" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping(uint => uint) items1;+ function transfer(uint acct, uint val1, uint val2) public {+ unchecked {+ items1[0] = val1+1;+ items1[1] = val2+2;+ assert(items1[0]+items1[1] == val1 + val2 + 3);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ , test "simplify-storage-map-only-2" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping(uint => uint) items1;+ function transfer(uint acct, uint val1, uint val2) public {+ unchecked {+ items1[acct] = val1+1;+ items1[acct+1] = val2+2;+ assert(items1[acct]+items1[acct+1] == val1 + val2 + 3);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ -- putStrLnM $ T.unpack $ formatExpr expr+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ , test "simplify-storage-map-with-struct" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ struct MyStruct {+ uint a;+ uint b;+ }+ mapping(uint => MyStruct) items1;+ function transfer(uint acct, uint val1, uint val2) public {+ unchecked {+ items1[acct].a = val1+1;+ items1[acct].b = val2+2;+ assert(items1[acct].a+items1[acct].b == val1 + val2 + 3);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ , test "simplify-storage-map-and-array" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint[] a;+ mapping(uint => uint) items1;+ mapping(uint => uint) items2;+ function transfer(uint acct, uint val1, uint val2) public {+ uint beforeVal1 = items1[acct];+ uint beforeVal2 = items2[acct];+ unchecked {+ items1[acct] = val1+1;+ items2[acct] = val2+2;+ a[0] = val1 + val2 + 1;+ a[1] = val1 + val2 + 2;+ assert(items1[acct]+items2[acct]+a[0]+a[1] > beforeVal1 + beforeVal2);+ }+ }+ }+ |]+ expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ -- putStrLnM $ T.unpack $ formatExpr expr+ assertEqualM "Expression is not clean." (badStoresInExpr expr) False+ ]+ , testGroup "StorageTests"+ [ test "read-from-sstore" $ assertEqualM ""+ (Lit 0xab)+ (Expr.readStorage' (Lit 0x0) (SStore (Lit 0x0) (Lit 0xab) (AbstractStore (LitAddr 0x0) Nothing)))+ , test "read-from-concrete" $ assertEqualM ""+ (Lit 0xab)+ (Expr.readStorage' (Lit 0x0) (ConcreteStore $ Map.fromList [(0x0, 0xab)]))+ , test "read-past-write" $ assertEqualM ""+ (Lit 0xab)+ (Expr.readStorage' (Lit 0x0) (SStore (Lit 0x1) (Var "b") (ConcreteStore $ Map.fromList [(0x0, 0xab)])))+ , test "accessStorage uses fetchedStorage" $ do+ let dummyContract =+ (initialContract (RuntimeCode (ConcreteRuntimeCode mempty)))+ { external = True }+ vm :: VM Concrete RealWorld <- liftIO $ stToIO $ vmForEthrunCreation ""+ -- perform the initial access+ vm1 <- liftIO $ stToIO $ execStateT (EVM.accessStorage (LitAddr 0) (Lit 0) (pure . pure ())) vm+ -- it should fetch the contract first+ vm2 <- case vm1.result of+ Just (HandleEffect (Query (PleaseFetchContract _addr _ continue))) ->+ liftIO $ stToIO $ execStateT (continue dummyContract) vm1+ _ -> internalError "unexpected result"+ -- then it should fetch the slow+ vm3 <- case vm2.result of+ Just (HandleEffect (Query (PleaseFetchSlot _addr _slot continue))) ->+ liftIO $ stToIO $ execStateT (continue 1337) vm2+ _ -> internalError "unexpected result"+ -- perform the same access as for vm1+ vm4 <- liftIO $ stToIO $ execStateT (EVM.accessStorage (LitAddr 0) (Lit 0) (pure . pure ())) vm3++ -- there won't be query now as accessStorage uses fetch cache+ assertBoolM (show vm4.result) (isNothing vm4.result)+ ]+ , testGroup "SimplifierUnitTests"+ -- common overflow cases that the simplifier was getting wrong+ [ test "writeWord-overflow" $ do+ let e = ReadByte (Lit 0x0) (WriteWord (Lit 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd) (Lit 0x0) (ConcreteBuf "\255\255\255\255"))+ b <- checkEquiv e (Expr.simplify e)+ assertBoolM "Simplifier failed" b+ , test "simp-max-buflength" $ do+ let simp = Expr.simplify $ Max (Lit 0) (BufLength (AbstractBuf "txdata"))+ assertEqualM "max-buflength rules" simp $ BufLength (AbstractBuf "txdata")+ , test "simp-PLT-max" $ do+ let simp = Expr.simplifyProp $ PLT (Max (Lit 5) (BufLength (AbstractBuf "txdata"))) (Lit 99)+ assertEqualM "max-buflength rules" simp $ PLT (BufLength (AbstractBuf "txdata")) (Lit 99)+ , test "simp-assoc-add1" $ do+ let simp = Expr.simplify $ Add (Var "a") (Add (Var "b") (Var "c"))+ assertEqualM "assoc rules" simp $ Add (Add (Var "a") (Var "b")) (Var "c")+ , test "simp-assoc-add2" $ do+ let simp = Expr.simplify $ Add (Lit 1) (Add (Var "b") (Var "c"))+ assertEqualM "assoc rules" simp $ Add (Add (Lit 1) (Var "b")) (Var "c")+ , test "simp-assoc-add3" $ do+ let simp = Expr.simplify $ Add (Lit 1) (Add (Lit 2) (Var "c"))+ assertEqualM "assoc rules" simp $ Add (Lit 3) (Var "c")+ , test "simp-assoc-add4" $ do+ let simp = Expr.simplify $ Add (Lit 1) (Add (Var "b") (Lit 2))+ assertEqualM "assoc rules" simp $ Add (Lit 3) (Var "b")+ , test "simp-assoc-add5" $ do+ let simp = Expr.simplify $ Add (Var "a") (Add (Lit 1) (Lit 2))+ assertEqualM "assoc rules" simp $ Add (Lit 3) (Var "a")+ , test "simp-assoc-add6" $ do+ let simp = Expr.simplify $ Add (Lit 7) (Add (Lit 1) (Lit 2))+ assertEqualM "assoc rules" simp $ Lit 10+ , test "simp-assoc-add-7" $ do+ let simp = Expr.simplify $ Add (Var "a") (Add (Var "b") (Lit 2))+ assertEqualM "assoc rules" simp $ Add (Add (Lit 2) (Var "a")) (Var "b")+ , test "simp-assoc-add8" $ do+ let simp = Expr.simplify $ Add (Add (Var "a") (Add (Lit 0x2) (Var "b"))) (Add (Var "c") (Add (Lit 0x2) (Var "d")))+ assertEqualM "assoc rules" simp $ Add (Add (Add (Add (Lit 0x4) (Var "a")) (Var "b")) (Var "c")) (Var "d")+ , test "simp-assoc-mul1" $ do+ let simp = Expr.simplify $ Mul (Var "a") (Mul (Var "b") (Var "c"))+ assertEqualM "assoc rules" simp $ Mul (Mul (Var "a") (Var "b")) (Var "c")+ , test "simp-assoc-mul2" $ do+ let simp = Expr.simplify $ Mul (Lit 2) (Mul (Var "a") (Lit 3))+ assertEqualM "assoc rules" simp $ Mul (Lit 6) (Var "a")+ , test "bufLength-simp" $ do+ let+ a = BufLength (ConcreteBuf "ab")+ simp = Expr.simplify a+ assertEqualM "Must be simplified down to a Lit" simp (Lit 2)+ , test "CopySlice-overflow" $ do+ let e = ReadWord (Lit 0x0) (CopySlice (Lit 0x0) (Lit 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc) (Lit 0x6) (ConcreteBuf "\255\255\255\255\255\255") (ConcreteBuf ""))+ b <- checkEquiv e (Expr.simplify e)+ assertBoolM "Simplifier failed" b+ , test "stripWrites-overflow" $ do+ -- below eventually boils down to+ -- unsafeInto (0xf0000000000000000000000000000000000000000000000000000000000000+1) :: Int+ -- which failed before+ let+ a = ReadByte (Lit 0xf0000000000000000000000000000000000000000000000000000000000000) (WriteByte (And (SHA256 (ConcreteBuf "")) (Lit 0x1)) (LitByte 0) (ConcreteBuf ""))+ b = Expr.simplify a+ ret <- checkEquiv a b+ assertBoolM "must be equivalent" ret+ ]+ -- These tests fuzz the simplifier by generating a random expression,+ -- applying some simplification rules, and then using the smt encoding to+ -- check that the simplified version is semantically equivalent to the+ -- unsimplified one+ , adjustOption (\(Test.Tasty.QuickCheck.QuickCheckTests n) -> Test.Tasty.QuickCheck.QuickCheckTests (min n 50)) $ testGroup "SimplifierTests"+ [ testProperty "buffer-simplification" $ \(expr :: Expr Buf) -> prop $ do+ let simplified = Expr.simplify expr+ checkEquiv expr simplified+ , testProperty "store-simplification" $ \(expr :: Expr Storage) -> prop $ do+ let simplified = Expr.simplify expr+ checkEquiv expr simplified+ , testProperty "load-simplification" $ \(GenWriteStorageLoad expr) -> prop $ do+ let simplified = Expr.simplify expr+ checkEquiv expr simplified+ , ignoreTest $ testProperty "load-decompose" $ \(GenWriteStorageLoad expr) -> prop $ do+ putStrLnM $ T.unpack $ formatExpr expr+ let simp = Expr.simplify expr+ let decomposed = fromMaybe simp $ mapExprM Expr.decomposeStorage simp+ -- putStrLnM $ "-----------------------------------------"+ -- putStrLnM $ T.unpack $ formatExpr decomposed+ -- putStrLnM $ "\n\n\n\n"+ checkEquiv expr decomposed+ , testProperty "byte-simplification" $ \(expr :: Expr Byte) -> prop $ do+ let simplified = Expr.simplify expr+ checkEquiv expr simplified+ , testProperty "word-simplification" $ \(ZeroDepthWord expr) -> prop $ do+ let simplified = Expr.simplify expr+ checkEquiv expr simplified+ , testProperty "readStorage-equivalance" $ \(store, slot) -> prop $ do+ let simplified = Expr.readStorage' slot store+ full = SLoad slot store+ checkEquiv simplified full+ , testProperty "writeStorage-equivalance" $ \(val, GenWriteStorageExpr (slot, store)) -> prop $ do+ let simplified = Expr.writeStorage slot val store+ full = SStore slot val store+ checkEquiv simplified full+ , testProperty "readWord-equivalance" $ \(buf, idx) -> prop $ do+ let simplified = Expr.readWord idx buf+ full = ReadWord idx buf+ checkEquiv simplified full+ , testProperty "writeWord-equivalance" $ \(idx, val, WriteWordBuf buf) -> prop $ do+ let simplified = Expr.writeWord idx val buf+ full = WriteWord idx val buf+ checkEquiv simplified full+ , testProperty "arith-simplification" $ \(_ :: Int) -> prop $ do+ expr <- liftIO $ generate . sized $ genWordArith 15+ let simplified = Expr.simplify expr+ checkEquiv expr simplified+ , testProperty "readByte-equivalance" $ \(buf, idx) -> prop $ do+ let simplified = Expr.readByte idx buf+ full = ReadByte idx buf+ checkEquiv simplified full+ -- we currently only simplify concrete writes over concrete buffers so that's what we test here+ , testProperty "writeByte-equivalance" $ \(LitOnly val, LitOnly buf, GenWriteByteIdx idx) -> prop $ do+ let simplified = Expr.writeByte idx val buf+ full = WriteByte idx val buf+ checkEquiv simplified full+ , testProperty "copySlice-equivalance" $ \(srcOff, GenCopySliceBuf src, GenCopySliceBuf dst, LitWord @300 size) -> prop $ do+ -- we bias buffers to be concrete more often than not+ dstOff <- liftIO $ generate (maybeBoundedLit 100_000)+ let simplified = Expr.copySlice srcOff dstOff size src dst+ full = CopySlice srcOff dstOff size src dst+ checkEquiv simplified full+ , testProperty "indexWord-equivalence" $ \(src, LitWord @50 idx) -> prop $ do+ let simplified = Expr.indexWord idx src+ full = IndexWord idx src+ checkEquiv simplified full+ , testProperty "indexWord-mask-equivalence" $ \(src :: Expr EWord, LitWord @35 idx) -> prop $ do+ mask <- liftIO $ generate $ do+ pow <- arbitrary :: Gen Int+ frequency+ [ (1, pure $ Lit $ (shiftL 1 (pow `mod` 256)) - 1) -- potentially non byte aligned+ , (1, pure $ Lit $ (shiftL 1 ((pow * 8) `mod` 256)) - 1) -- byte aligned+ ]+ let+ input = And mask src+ simplified = Expr.indexWord idx input+ full = IndexWord idx input+ checkEquiv simplified full+ , testProperty "toList-equivalance" $ \buf -> prop $ do+ let+ -- transforms the input buffer to give it a known length+ fixLength :: Expr Buf -> Gen (Expr Buf)+ fixLength = mapExprM go+ where+ go :: Expr a -> Gen (Expr a)+ go = \case+ WriteWord _ val b -> liftM3 WriteWord idx (pure val) (pure b)+ WriteByte _ val b -> liftM3 WriteByte idx (pure val) (pure b)+ CopySlice so _ sz src dst -> liftM5 CopySlice (pure so) idx (pure sz) (pure src) (pure dst)+ AbstractBuf _ -> cbuf+ e -> pure e+ cbuf = do+ bs <- arbitrary+ pure $ ConcreteBuf bs+ idx = do+ w <- arbitrary+ -- we use 100_000 as an upper bound for indices to keep tests reasonably fast here+ pure $ Lit (w `mod` 100_000)++ input <- liftIO $ generate $ fixLength buf+ case Expr.toList input of+ Nothing -> do+ putStrLnM "skip"+ pure True -- ignore cases where the buf cannot be represented as a list+ Just asList -> do+ let asBuf = Expr.fromList asList+ checkEquiv asBuf input+ , testProperty "simplifyProp-equivalence-lit" $ \(LitProp p) -> prop $ do+ let simplified = Expr.simplifyProps [p]+ case simplified of+ [] -> checkEquivProp (PBool True) p+ [val@(PBool _)] -> checkEquivProp val p+ _ -> liftIO $ assertFailure "must evaluate down to a literal bool"+ , testProperty "simplifyProp-equivalence-sym" $ \(p) -> prop $ do+ let simplified = Expr.simplifyProp p+ checkEquivProp simplified p+ , testProperty "simpProp-equivalence-sym-Prop" $ \(ps :: [Prop]) -> prop $ do+ let simplified = pand (Expr.simplifyProps ps)+ checkEquivProp simplified (pand ps)+ , testProperty "simpProp-equivalence-sym-LitProp" $ \(LitProp p) -> prop $ do+ let simplified = pand (Expr.simplifyProps [p])+ checkEquivProp simplified p+ , testProperty "storage-slot-simp-property" $ \(StorageExp s) -> prop $ do+ -- we have to run `Expr.structureArraySlots` on the unsimplified system, or+ -- we'd need some form of minimal simplifier for things to work out. As long as+ -- we trust the structureArraySlots, this is fine, as that function is standalone,+ -- and quite minimal+ let s2 = Expr.structureArraySlots s+ let simplified = Expr.simplify s2+ checkEquiv simplified s2+ ]+ , testGroup "simpProp-concrete-tests" [+ test "simpProp-concrete-trues" $ do+ let+ t = [PBool True, PBool True]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [] simplified+ , test "simpProp-concrete-false1" $ do+ let+ t = [PBool True, PBool False]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [PBool False] simplified+ , test "simpProp-concrete-false2" $ do+ let+ t = [PBool False, PBool False]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [PBool False] simplified+ , test "simpProp-concrete-or-1" $ do+ let+ -- a = 5 && (a=4 || a=3) -> False+ t = [PEq (Lit 5) (Var "a"), POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 3))]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [PBool False] simplified+ , ignoreTest $ test "simpProp-concrete-or-2" $ do+ let+ -- Currently does not work, because we don't do simplification inside+ -- POr/PAnd using canBeSat+ -- a = 5 && (a=4 || a=5) -> a=5+ t = [PEq (Lit 5) (Var "a"), POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 5))]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [] simplified+ , test "simpProp-concrete-and-1" $ do+ let+ -- a = 5 && (a=4 && a=3) -> False+ t = [PEq (Lit 5) (Var "a"), PAnd (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 3))]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [PBool False] simplified+ , test "simpProp-concrete-or-of-or" $ do+ let+ -- a = 5 && ((a=4 || a=6) || a=3) -> False+ t = [PEq (Lit 5) (Var "a"), POr (POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 6))) (PEq (Var "a") (Lit 3))]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [PBool False] simplified+ , test "simpProp-concrete-or-eq-rem" $ do+ let+ -- a = 5 && ((a=4 || a=6) || a=3) -> False+ t = [PEq (Lit 5) (Var "a"), POr (POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 6))) (PEq (Var "a") (Lit 3))]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [PBool False] simplified+ , test "simpProp-inner-expr-simp" $ do+ let+ -- 5+1 = 6+ t = [PEq (Add (Lit 5) (Lit 1)) (Var "a")]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [PEq (Lit 6) (Var "a")] simplified+ , test "simpProp-inner-expr-simp-with-canBeSat" $ do+ let+ -- 5+1 = 6, 6 != 7+ t = [PAnd (PEq (Add (Lit 5) (Lit 1)) (Var "a")) (PEq (Var "a") (Lit 7))]+ simplified = Expr.simplifyProps t+ assertEqualM "Must be equal" [PBool False] simplified+ ]+ , testGroup "MemoryTests"+ [ test "read-write-same-byte" $ assertEqualM ""+ (LitByte 0x12)+ (Expr.readByte (Lit 0x20) (WriteByte (Lit 0x20) (LitByte 0x12) mempty))+ , test "read-write-same-word" $ assertEqualM ""+ (Lit 0x12)+ (Expr.readWord (Lit 0x20) (WriteWord (Lit 0x20) (Lit 0x12) mempty))+ , test "read-byte-write-word" $ assertEqualM ""+ -- reading at byte 31 a word that's been written should return LSB+ (LitByte 0x12)+ (Expr.readByte (Lit 0x1f) (WriteWord (Lit 0x0) (Lit 0x12) mempty))+ , test "read-byte-write-word2" $ assertEqualM ""+ -- Same as above, but offset not 0+ (LitByte 0x12)+ (Expr.readByte (Lit 0x20) (WriteWord (Lit 0x1) (Lit 0x12) mempty))+ ,test "read-write-with-offset" $ assertEqualM ""+ -- 0x3F = 63 decimal, 0x20 = 32. 0x12 = 18+ -- We write 128bits (32 Bytes), representing 18 at offset 32.+ -- Hence, when reading out the 63rd byte, we should read out the LSB 8 bits+ -- which is 0x12+ (LitByte 0x12)+ (Expr.readByte (Lit 0x3F) (WriteWord (Lit 0x20) (Lit 0x12) mempty))+ ,test "read-write-with-offset2" $ assertEqualM ""+ -- 0x20 = 32, 0x3D = 61+ -- we write 128 bits (32 Bytes) representing 0x10012, at offset 32.+ -- we then read out a byte at offset 61.+ -- So, at 63 we'd read 0x12, at 62 we'd read 0x00, at 61 we should read 0x1+ (LitByte 0x1)+ (Expr.readByte (Lit 0x3D) (WriteWord (Lit 0x20) (Lit 0x10012) mempty))+ , test "read-write-with-extension-to-zero" $ assertEqualM ""+ -- write word and read it at the same place (i.e. 0 offset)+ (Lit 0x12)+ (Expr.readWord (Lit 0x0) (WriteWord (Lit 0x0) (Lit 0x12) mempty))+ , test "read-write-with-extension-to-zero-with-offset" $ assertEqualM ""+ -- write word and read it at the same offset of 4+ (Lit 0x12)+ (Expr.readWord (Lit 0x4) (WriteWord (Lit 0x4) (Lit 0x12) mempty))+ , test "read-write-with-extension-to-zero-with-offset2" $ assertEqualM ""+ -- write word and read it at the same offset of 16+ (Lit 0x12)+ (Expr.readWord (Lit 0x20) (WriteWord (Lit 0x20) (Lit 0x12) mempty))+ , test "read-word-copySlice-overlap" $ assertEqualM ""+ -- we should not recurse into a copySlice if the read index + 32 overlaps the sliced region+ (ReadWord (Lit 40) (CopySlice (Lit 0) (Lit 30) (Lit 12) (WriteWord (Lit 10) (Lit 0x64) (AbstractBuf "hi")) (AbstractBuf "hi")))+ (Expr.readWord (Lit 40) (CopySlice (Lit 0) (Lit 30) (Lit 12) (WriteWord (Lit 10) (Lit 0x64) (AbstractBuf "hi")) (AbstractBuf "hi")))+ , test "indexword-MSB" $ assertEqualM ""+ -- 31st is the LSB byte (of 32)+ (LitByte 0x78)+ (Expr.indexWord (Lit 31) (Lit 0x12345678))+ , test "indexword-LSB" $ assertEqualM ""+ -- 0th is the MSB byte (of 32), Lit 0xff22bb... is exactly 32 Bytes.+ (LitByte 0xff)+ (Expr.indexWord (Lit 0) (Lit 0xff22bb4455667788990011223344556677889900112233445566778899001122))+ , test "indexword-LSB2" $ assertEqualM ""+ -- same as above, but with offset 2+ (LitByte 0xbb)+ (Expr.indexWord (Lit 2) (Lit 0xff22bb4455667788990011223344556677889900112233445566778899001122))+ , test "encodeConcreteStore-overwrite" $+ assertEqualM ""+ "(store (store ((as const Storage) #x0000000000000000000000000000000000000000000000000000000000000000) (_ bv1 256) (_ bv2 256)) (_ bv3 256) (_ bv4 256))"+ (EVM.SMT.encodeConcreteStore $+ Map.fromList [(W256 1, W256 2), (W256 3, W256 4)])+ , test "indexword-oob-sym" $ assertEqualM ""+ -- indexWord should return 0 for oob access+ (LitByte 0x0)+ (Expr.indexWord (Lit 100) (JoinBytes+ (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0)+ (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0)+ (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0)+ (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0)))+ , test "stripbytes-concrete-bug" $ assertEqualM ""+ (Expr.simplifyReads (ReadByte (Lit 0) (ConcreteBuf "5")))+ (LitByte 53)+ ]+ , testGroup "ABI"+ [ testProperty "Put/get inverse" $ \x ->+ case runGetOrFail (getAbi (abiValueType x)) (runPut (putAbi x)) of+ Right ("", _, x') -> x' == x+ _ -> False+ ]+ , testGroup "Solidity-Expressions"+ [ test "Trivial" $+ SolidityCall "x = 3;" []+ ===> AbiUInt 256 3+ , test "Arithmetic" $ do+ SolidityCall "x = a + 1;"+ [AbiUInt 256 1] ===> AbiUInt 256 2+ SolidityCall "unchecked { x = a - 1; }"+ [AbiUInt 8 0] ===> AbiUInt 8 255++ , test "keccak256()" $+ SolidityCall "x = uint(keccak256(abi.encodePacked(a)));"+ [AbiString ""] ===> AbiUInt 256 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470++ , testProperty "abi encoding vs. solidity" $ withMaxSuccess 20 $ forAll (arbitrary >>= genAbiValue) $+ \y -> prop $ do+ Just encoded <- runStatements [i| x = abi.encode(a);|]+ [y] AbiBytesDynamicType+ let solidityEncoded = case decodeAbiValue (AbiTupleType $ V.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of+ AbiTuple (V.toList -> [e]) -> e+ _ -> internalError "AbiTuple expected"+ let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [y])+ assertEqualM "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded)++ , testProperty "abi encoding vs. solidity (2 args)" $ withMaxSuccess 20 $ forAll (arbitrary >>= bothM genAbiValue) $+ \(x', y') -> prop $ do+ Just encoded <- runStatements [i| x = abi.encode(a, b);|]+ [x', y'] AbiBytesDynamicType+ let solidityEncoded = case decodeAbiValue (AbiTupleType $ V.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of+ AbiTuple (V.toList -> [e]) -> e+ _ -> internalError "AbiTuple expected"+ let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [x',y'])+ assertEqualM "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded)++ -- we need a separate test for this because the type of a function is "function() external" in solidity but just "function" in the abi:+ , testProperty "abi encoding vs. solidity (function pointer)" $ withMaxSuccess 20 $ forAll (genAbiValue AbiFunctionType) $+ \y -> prop $ do+ Just encoded <- runFunction [i|+ function foo(function() external a) public pure returns (bytes memory x) {+ x = abi.encode(a);+ }+ |] (abiMethod "foo(function)" (AbiTuple (V.singleton y)))+ let solidityEncoded = case decodeAbiValue (AbiTupleType $ V.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of+ AbiTuple (V.toList -> [e]) -> e+ _ -> internalError "AbiTuple expected"+ let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [y])+ assertEqualM "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded)+ ]++ , testGroup "Precompiled contracts"+ [ testGroup "Example (reverse)"+ [ test "success" $+ assertEqualM "example contract reverses"+ (execute 0xdeadbeef "foobar" 6) (Just "raboof")+ , test "failure" $+ assertEqualM "example contract fails on length mismatch"+ (execute 0xdeadbeef "foobar" 5) Nothing+ ]++ , testGroup "ECRECOVER"+ [ test "success" $ do+ let+ r = hex "c84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4732"+ s = hex "1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d"+ v = hex "000000000000000000000000000000000000000000000000000000000000001c"+ h = hex "513954cf30af6638cb8f626bd3f8c39183c26784ce826084d9d267868a18fb31"+ a = hex "0000000000000000000000002d5e56d45c63150d937f2182538a0f18510cb11f"+ assertEqualM "successful recovery"+ (Just a)+ (execute 1 (h <> v <> r <> s) 32)+ , test "fail on made up values" $ do+ let+ r = hex "c84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4731"+ s = hex "1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d"+ v = hex "000000000000000000000000000000000000000000000000000000000000001c"+ h = hex "513954cf30af6638cb8f626bd3f8c39183c26784ce826084d9d267868a18fb31"+ assertEqualM "fail because bit flip"+ Nothing+ (execute 1 (h <> v <> r <> s) 32)+ ]+ ]+ , testGroup "Byte/word manipulations"+ [ testProperty "padLeft length" $ \n (Bytes bs) ->+ BS.length (padLeft n bs) == max n (BS.length bs)+ , testProperty "padLeft identity" $ \(Bytes bs) ->+ padLeft (BS.length bs) bs == bs+ , testProperty "padRight length" $ \n (Bytes bs) ->+ BS.length (padLeft n bs) == max n (BS.length bs)+ , testProperty "padRight identity" $ \(Bytes bs) ->+ padLeft (BS.length bs) bs == bs+ , testProperty "padLeft zeroing" $ \(NonNegative n) (Bytes bs) ->+ let x = BS.take n (padLeft (BS.length bs + n) bs)+ y = BS.replicate n 0+ in x == y+ ]++ , testGroup "Unresolved link detection"+ [ test "holes detected" $ do+ let code' = "608060405234801561001057600080fd5b5060405161040f38038061040f83398181016040528101906100329190610172565b73__$f3cbc3eb14e5bd0705af404abcf6f741ec$__63ab5c1ffe826040518263ffffffff1660e01b81526004016100699190610217565b60206040518083038186803b15801561008157600080fd5b505af4158015610095573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100b99190610145565b50506103c2565b60006100d36100ce84610271565b61024c565b9050828152602081018484840111156100ef576100ee610362565b5b6100fa8482856102ca565b509392505050565b600081519050610111816103ab565b92915050565b600082601f83011261012c5761012b61035d565b5b815161013c8482602086016100c0565b91505092915050565b60006020828403121561015b5761015a61036c565b5b600061016984828501610102565b91505092915050565b6000602082840312156101885761018761036c565b5b600082015167ffffffffffffffff8111156101a6576101a5610367565b5b6101b284828501610117565b91505092915050565b60006101c6826102a2565b6101d081856102ad565b93506101e08185602086016102ca565b6101e981610371565b840191505092915050565b60006102016003836102ad565b915061020c82610382565b602082019050919050565b6000604082019050818103600083015261023181846101bb565b90508181036020830152610244816101f4565b905092915050565b6000610256610267565b905061026282826102fd565b919050565b6000604051905090565b600067ffffffffffffffff82111561028c5761028b61032e565b5b61029582610371565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b60008115159050919050565b60005b838110156102e85780820151818401526020810190506102cd565b838111156102f7576000848401525b50505050565b61030682610371565b810181811067ffffffffffffffff821117156103255761032461032e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f6261720000000000000000000000000000000000000000000000000000000000600082015250565b6103b4816102be565b81146103bf57600080fd5b50565b603f806103d06000396000f3fe6080604052600080fdfea26469706673582212207d03b26e43dc3d116b0021ddc9817bde3762a3b14315351f11fc4be384fd14a664736f6c63430008060033"+ assertBoolM "linker hole not detected" (containsLinkerHole code'),+ test "no false positives" $ do+ let code' = "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fea265627a7a723158205d775f914dcb471365a430b5f5b2cfe819e615cbbb5b2f1ccc7da1fd802e43c364736f6c634300050b0032"+ assertBoolM "false positive" (not . containsLinkerHole $ code')+ ]++ , testGroup "metadata stripper"+ [ test "it strips the metadata for solc => 0.6" $ do+ let code' = hexText "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fea265627a7a723158205d775f914dcb471365a430b5f5b2cfe819e615cbbb5b2f1ccc7da1fd802e43c364736f6c634300050b0032"+ stripped = stripBytecodeMetadata code'+ assertEqualM "failed to strip metadata" (show (ByteStringS stripped)) "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fe"+ ,+ testCase "it strips the metadata and constructor args" $ do+ let srccode =+ [i|+ contract A {+ uint y;+ constructor(uint x) public {+ y = x;+ }+ }+ |]++ Just initCode <- solidity "A" srccode+ assertEqual "constructor args screwed up metadata stripping" (stripBytecodeMetadata (initCode <> encodeAbiValue (AbiUInt 256 1))) (stripBytecodeMetadata initCode)+ ]++ , testGroup "RLP encodings"+ [ testProperty "rlp decode is a retraction (bytes)" $ \(Bytes bs) ->+ rlpdecode (rlpencode (BS bs)) == Just (BS bs)+ , testProperty "rlp encode is a partial inverse (bytes)" $ \(Bytes bs) ->+ case rlpdecode bs of+ Just r -> rlpencode r == bs+ Nothing -> True+ , testProperty "rlp decode is a retraction (RLP)" $ \(RLPData r) ->+ rlpdecode (rlpencode r) == Just r+ ]+ , testGroup "Panic code tests via symbolic execution"+ [+ test "assert-fail" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 a) external pure {+ assert(a != 0);+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x01] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ assertEqualM "Must be 0" 0 $ getVar ctr "arg1"+ putStrLnM $ "expected counterexample found, and it's correct: " <> (show $ getVar ctr "arg1")+ ,+ test "safeAdd-fail" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 a, uint256 b) external pure returns (uint256 c) {+ c = a+b;+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x11] c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ let x = getVar ctr "arg1"+ let y = getVar ctr "arg2"++ let maxUint = 2 ^ (256 :: Integer) :: Integer+ assertBoolM "Overflow must occur" (toInteger x + toInteger y >= maxUint)+ putStrLnM "expected counterexample found"+ ,+ test "div-by-zero-fail" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 a, uint256 b) external pure returns (uint256 c) {+ c = a/b;+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Bitwuzla 1 Nothing $ \s -> checkAssert s [0x12] c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ assertEqualM "Division by 0 needs b=0" (getVar ctr "arg2") 0+ putStrLnM "expected counterexample found"+ ,+ test "unused-args-fail" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function fun(uint256 a) public pure {+ assert(false);+ }+ }+ |]+ (_, [Cex _]) <- withSolvers Bitwuzla 1 Nothing $ \s -> checkAssert s [0x1] c Nothing [] defaultVeriOpts+ putStrLnM "expected counterexample found"+ ,+ test "enum-conversion-fail" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ enum MyEnum { ONE, TWO }+ function fun(uint256 a) external pure returns (MyEnum b) {+ b = MyEnum(a);+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Bitwuzla 1 Nothing $ \s -> checkAssert s [0x21] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ assertBoolM "Enum is only defined for 0 and 1" $ (getVar ctr "arg1") > 1+ putStrLnM "expected counterexample found"+ ,+ -- TODO 0x22 is missing: "0x22: If you access a storage byte array that is incorrectly encoded."+ -- TODO below should NOT fail+ -- TODO this has a loop that depends on a symbolic value and currently causes interpret to loop+ ignoreTest $ test "pop-empty-array" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint[] private arr;+ function fun(uint8 a) external {+ arr.push(1);+ arr.push(2);+ for (uint i = 0; i < a; i++) {+ arr.pop();+ }+ }+ }+ |]+ a <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x31] c (Just (Sig "fun(uint8)" [AbiUIntType 8])) [] defaultVeriOpts+ liftIO $ do+ print $ length a+ print $ show a+ putStrLnM "expected counterexample found"+ ,+ test "access-out-of-bounds-array" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ uint[] private arr;+ function fun(uint8 a) external returns (uint x){+ arr.push(1);+ arr.push(2);+ x = arr[a];+ }+ }+ |]+ (_, [Cex (_, _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x32] c (Just (Sig "fun(uint8)" [AbiUIntType 8])) [] defaultVeriOpts+ -- assertBoolM "Access must be beyond element 2" $ (getVar ctr "arg1") > 1+ putStrLnM "expected counterexample found"+ ,+ -- Note: we catch the assertion here, even though we are only able to explore partially+ test "alloc-too-much" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 a) external {+ uint[] memory arr = new uint[](a);+ }+ }+ |]+ (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s ->+ checkAssert s [0x41] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "expected counterexample found"+ ,+ test "vm.deal unknown address" $ do+ Just c <- solcRuntime "C"+ [i|+ interface Vm {+ function deal(address,uint256) external;+ }+ contract C {+ // this is not supported yet due to restrictions around symbolic address aliasing...+ function f(address e, uint val) external {+ Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);+ vm.deal(e, val);+ assert(e.balance == val);+ }+ }+ |]+ Right e <- reachableUserAsserts c (Just $ Sig "f(address,uint256)" [AbiAddressType, AbiUIntType 256])+ assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e+ ,+ test "vm.prank underflow" $ do+ Just c <- solcRuntime "C"+ [i|+ interface Vm {+ function prank(address) external;+ }+ contract Payable {+ function hi() public payable {}+ }+ contract C {+ function f() external {+ Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);++ uint amt = 10;+ address from = address(0xacab);+ require(from.balance < amt);++ Payable target = new Payable();+ vm.prank(from);+ target.hi{value : amt}();+ }+ }+ |]+ r <- allBranchesFail c Nothing+ assertBoolM "all branches must fail" (isRight r)+ ,+ test "call ffi when disabled" $ do+ Just c <- solcRuntime "C"+ [i|+ interface Vm {+ function ffi(string[] calldata) external;+ }+ contract C {+ function f() external {+ Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);++ string[] memory inputs = new string[](2);+ inputs[0] = "echo";+ inputs[1] = "acab";++ // should fail to explore this branch+ vm.ffi(inputs);+ }+ }+ |]+ Right e <- reachableUserAsserts c Nothing+ liftIO $ T.putStrLn $ formatExpr e+ assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e+ ,+ -- TODO: we can't deal with symbolic jump conditions+ expectFail $ test "call-zero-inited-var-thats-a-function" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function (uint256) internal returns (uint) funvar;+ function fun2(uint256 a) internal returns (uint){+ return a;+ }+ function fun(uint256 a) external returns (uint) {+ if (a != 44) {+ funvar = fun2;+ }+ return funvar(a);+ }+ }+ |]+ (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $+ \s -> checkAssert s [0x51] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ let a = fromJust $ Map.lookup (Var "arg1") cex.vars+ assertEqualM "unexpected cex value" a 44+ putStrLnM "expected counterexample found"+ ]+ , testGroup "Symbolic-Constructor-Args"+ -- this produced some hard to debug failures. keeping it around since it seemed to exercise the contract creation code in interesting ways...+ [ test "multiple-symbolic-constructor-calls" $ do+ Just initCode <- solidity "C"+ [i|+ contract A {+ uint public x;+ constructor (uint z) {}+ }++ contract B {+ constructor (uint i) {}++ }++ contract C {+ constructor(uint u) {+ new A(u);+ new B(u);+ }+ }+ |]+ withSolvers Bitwuzla 1 Nothing $ \s -> do+ let calldata = (WriteWord (Lit 0x0) (Var "u") (ConcreteBuf ""), [])+ initVM <- liftIO $ stToIO $ abstractVM calldata initCode Nothing True+ expr <- Expr.simplify <$> interpret (Fetch.oracle s Nothing) Nothing 1 StackBased initVM runExpr+ assertBoolM "unexptected partial execution" (not $ Expr.containsNode isPartial expr)+ , test "mixed-concrete-symbolic-args" $ do+ Just c <- solcRuntime "C"+ [i|+ contract B {+ uint public x;+ uint public y;+ constructor (uint i, uint j) {+ x = i;+ y = j;+ }++ }++ contract C {+ function foo(uint i) public {+ B b = new B(10, i);+ assert(b.x() == 10);+ assert(b.y() == i);+ }+ }+ |]+ Right expr <- reachableUserAsserts c (Just $ Sig "foo(uint256)" [AbiUIntType 256])+ assertBoolM "unexptected partial execution" (not $ Expr.containsNode isPartial expr)+ , test "jump-into-symbolic-region" $ do+ let+ -- our initCode just jumps directly to the end+ code = BS.pack . mapMaybe maybeLitByte $ V.toList $ assemble+ [ OpPush (Lit 0x85)+ , OpJump+ , OpPush (Lit 1)+ , OpPush (Lit 1)+ , OpPush (Lit 1)+ , OpJumpdest+ ]+ -- we write a symbolic word to the middle, so the jump above should+ -- fail since the target is not in the concrete region+ initCode = (WriteWord (Lit 0x43) (Var "HI") (ConcreteBuf code), [])++ -- we pass in the above initCode buffer as calldata, and then copy+ -- it into memory before calling Create+ runtimecode = RuntimeCode (SymbolicRuntimeCode $ assemble+ [ OpPush (Lit 0x85)+ , OpPush (Lit 0x0)+ , OpPush (Lit 0x0)+ , OpCalldatacopy+ , OpPush (Lit 0x85)+ , OpPush (Lit 0x0)+ , OpPush (Lit 0x0)+ , OpCreate+ ])+ withSolvers Z3 1 Nothing $ \s -> do+ vm <- liftIO $ stToIO $ loadSymVM runtimecode (Lit 0) initCode False+ expr <- Expr.simplify <$> interpret (Fetch.oracle s Nothing) Nothing 1 StackBased vm runExpr+ case expr of+ Partial _ _ (JumpIntoSymbolicCode _ _) -> assertBoolM "" True+ _ -> assertBoolM "did not encounter expected partial node" False+ ]+ , testGroup "Dapp-Tests"+ [ test "Trivial-Pass" $ do+ let testFile = "test/contracts/pass/trivial.sol"+ runSolidityTest testFile ".*" >>= assertEqualM "test result" True+ , test "DappTools" $ do+ -- quick smokecheck to make sure that we can parse dapptools style build outputs+ let cases =+ [ ("test/contracts/pass/trivial.sol", ".*", True)+ , ("test/contracts/pass/dsProvePass.sol", "proveEasy", True)+ , ("test/contracts/fail/trivial.sol", ".*", False)+ , ("test/contracts/fail/dsProveFail.sol", "prove_add", False)+ ]+ results <- forM cases $ \(testFile, match, expected) -> do+ actual <- runSolidityTestCustom testFile match Nothing Nothing False Nothing DappTools+ pure (actual == expected)+ assertBoolM "test result" (and results)+ , test "Trivial-Fail" $ do+ let testFile = "test/contracts/fail/trivial.sol"+ runSolidityTest testFile "prove_false" >>= assertEqualM "test result" False+ , test "Abstract" $ do+ let testFile = "test/contracts/pass/abstract.sol"+ runSolidityTest testFile ".*" >>= assertEqualM "test result" True+ , test "Constantinople" $ do+ let testFile = "test/contracts/pass/constantinople.sol"+ runSolidityTest testFile ".*" >>= assertEqualM "test result" True+ , test "ConstantinopleMin" $ do+ let testFile = "test/contracts/pass/constantinople_min.sol"+ runSolidityTest testFile ".*" >>= assertEqualM "test result" True+ , test "Prove-Tests-Pass" $ do+ let testFile = "test/contracts/pass/dsProvePass.sol"+ runSolidityTest testFile ".*" >>= assertEqualM "test result" True+ , test "prefix-check-for-dapp" $ do+ let testFile = "test/contracts/fail/check-prefix.sol"+ runSolidityTest testFile "check_trivial" >>= assertEqualM "test result" False+ , test "transfer-dapp" $ do+ let testFile = "test/contracts/pass/transfer.sol"+ runSolidityTest testFile "prove_transfer" >>= assertEqualM "should prove transfer" True+ , test "badvault-sym-branch" $ do+ let testFile = "test/contracts/fail/10_BadVault.sol"+ runSolidityTestCustom testFile "prove_BadVault_usingExploitLaunchPad" Nothing Nothing True Nothing FoundryStdLib >>= assertEqualM "Must find counterexample" False+ , test "Prove-Tests-Fail" $ do+ let testFile = "test/contracts/fail/dsProveFail.sol"+ runSolidityTest testFile "prove_trivial" >>= assertEqualM "test result" False+ runSolidityTest testFile "prove_trivial_dstest" >>= assertEqualM "test result" False+ runSolidityTest testFile "prove_add" >>= assertEqualM "test result" False+ runSolidityTestCustom testFile "prove_smtTimeout" (Just 1) Nothing False Nothing Foundry >>= assertEqualM "test result" False+ runSolidityTest testFile "prove_multi" >>= assertEqualM "test result" False+ -- TODO: implement overflow checking optimizations and enable, currently this runs forever+ --runSolidityTest testFile "prove_distributivity" >>= assertEqualM "test result" False+ , test "Loop-Tests" $ do+ let testFile = "test/contracts/pass/loops.sol"+ runSolidityTestCustom testFile "prove_loop" Nothing (Just 10) False Nothing Foundry >>= assertEqualM "test result" True+ runSolidityTestCustom testFile "prove_loop" Nothing (Just 100) False Nothing Foundry >>= assertEqualM "test result" False+ , test "Cheat-Codes-Pass" $ do+ let testFile = "test/contracts/pass/cheatCodes.sol"+ runSolidityTest testFile ".*" >>= assertEqualM "test result" True+ , test "Cheat-Codes-Fork-Pass" $ do+ let testFile = "test/contracts/pass/cheatCodesFork.sol"+ runSolidityTest testFile ".*" >>= assertEqualM "test result" True+ , test "Unwind" $ do+ let testFile = "test/contracts/pass/unwind.sol"+ runSolidityTest testFile ".*" >>= assertEqualM "test result" True+ ]+ , testGroup "max-iterations"+ [ test "concrete-loops-reached" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function fun() external payable returns (uint) {+ uint count = 0;+ for (uint i = 0; i < 5; i++) count++;+ return count;+ }+ }+ |]+ let sig = Just $ Sig "fun()" []+ opts = defaultVeriOpts{ maxIter = Just 3 }+ (e, [Qed _]) <- withSolvers Z3 1 Nothing $+ \s -> checkAssert s defaultPanicCodes c sig [] opts+ assertBoolM "The expression is not partial" $ isPartial e+ , test "concrete-loops-not-reached" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function fun() external payable returns (uint) {+ uint count = 0;+ for (uint i = 0; i < 5; i++) count++;+ return count;+ }+ }+ |]++ let sig = Just $ Sig "fun()" []+ opts = defaultVeriOpts{ maxIter = Just 6 }+ (e, [Qed _]) <- withSolvers Z3 1 Nothing $+ \s -> checkAssert s defaultPanicCodes c sig [] opts+ assertBoolM "The expression is partial" $ not $ isPartial e+ , test "symbolic-loops-reached" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function fun(uint j) external payable returns (uint) {+ uint count = 0;+ for (uint i = 0; i < j; i++) count++;+ return count;+ }+ }+ |]+ (e, [Qed _]) <- withSolvers Z3 1 Nothing $+ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] (defaultVeriOpts{ maxIter = Just 5 })+ assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e+ , test "inconsistent-paths" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function fun(uint j) external payable returns (uint) {+ require(j <= 3);+ uint count = 0;+ for (uint i = 0; i < j; i++) count++;+ return count;+ }+ }+ |]+ let sig = Just $ Sig "fun(uint256)" [AbiUIntType 256]+ -- we don't ask the solver about the loop condition until we're+ -- already in an inconsistent path (i == 5, j <= 3, i < j), so we+ -- will continue looping here until we hit max iterations+ opts = defaultVeriOpts{ maxIter = Just 10, askSmtIters = 5 }+ (e, [Qed _]) <- withSolvers Z3 1 Nothing $+ \s -> checkAssert s defaultPanicCodes c sig [] opts+ assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e+ , test "symbolic-loops-not-reached" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function fun(uint j) external payable returns (uint) {+ require(j <= 3);+ uint count = 0;+ for (uint i = 0; i < j; i++) count++;+ return count;+ }+ }+ |]+ let sig = Just $ Sig "fun(uint256)" [AbiUIntType 256]+ -- askSmtIters is low enough here to avoid the inconsistent path+ -- conditions, so we never hit maxIters+ opts = defaultVeriOpts{ maxIter = Just 5, askSmtIters = 1 }+ (e, [Qed _]) <- withSolvers Z3 1 Nothing $+ \s -> checkAssert s defaultPanicCodes c sig [] opts+ assertBoolM "The expression is partial" $ not (Expr.containsNode isPartial e)+ ]+ , testGroup "Symbolic Addresses"+ [ test "symbolic-address-create" $ do+ let src = [i|+ contract A {+ constructor() payable {}+ }+ contract C {+ function fun(uint256 a) external{+ require(address(this).balance > a);+ new A{value:a}();+ }+ }+ |]+ Just a <- solcRuntime "A" src+ Just c <- solcRuntime "C" src+ let sig = Sig "fun(uint256)" [AbiUIntType 256]+ (expr, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s c (Just sig) [] defaultVeriOpts Nothing Nothing+ let isSuc (Success {}) = True+ isSuc _ = False+ case filter isSuc (flattenExpr expr) of+ [Success _ _ _ store] -> do+ let ca = fromJust (Map.lookup (SymAddr "freshSymAddr1") store)+ let code = case ca.code of+ RuntimeCode (ConcreteRuntimeCode c') -> c'+ _ -> internalError "expected concrete code"+ assertEqualM "balance mismatch" (Var "arg1") ca.balance+ assertEqualM "code mismatch" (stripBytecodeMetadata a) (stripBytecodeMetadata code)+ assertEqualM "nonce mismatch" (Just 1) ca.nonce+ _ -> assertBoolM "too many success nodes!" False+ , test "symbolic-balance-call" $ do+ let src = [i|+ contract A {+ function f() public payable returns (uint) {+ return msg.value;+ }+ }+ contract C {+ function fun(uint256 x) external {+ require(address(this).balance > x);+ A a = new A();+ uint res = a.f{value:x}();+ assert(res == x);+ }+ }+ |]+ Just c <- solcRuntime "C" src+ res <- reachableUserAsserts c Nothing+ assertBoolM "unexpected cex" (isRight res)+ -- TODO: implement missing aliasing rules+ , expectFail $ test "deployed-contract-addresses-cannot-alias" $ do+ Just c <- solcRuntime "C"+ [i|+ contract A {}+ contract C {+ function f() external {+ A a = new A();+ if (address(a) == address(this)) assert(false);+ }+ }+ |]+ res <- reachableUserAsserts c Nothing+ assertBoolM "should not be able to alias" (isRight res)+ , test "addresses-in-args-can-alias-anything" $ do+ let addrs :: [Text]+ addrs = ["address(this)", "tx.origin", "block.coinbase", "msg.sender"]+ sig = Just $ Sig "f(address)" [AbiAddressType]+ checkVs vs = [i|+ contract C {+ function f(address a) external {+ if (${vs} == a) assert(false);+ }+ }+ |]++ [self, origin, coinbase, caller] <- forM addrs $ \addr -> do+ Just c <- solcRuntime "C" (checkVs addr)+ Left [cex] <- reachableUserAsserts c sig+ pure cex.addrs++ liftIO $ do+ let check as a = (Map.lookup (SymAddr "arg1") as) @?= (Map.lookup a as)+ check self (SymAddr "entrypoint")+ check origin (SymAddr "origin")+ check coinbase (SymAddr "coinbase")+ check caller (SymAddr "caller")+ , test "addresses-in-args-can-alias-themselves" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function f(address a, address b) external {+ if (a == b) assert(false);+ }+ }+ |]+ let sig = Just $ Sig "f(address,address)" [AbiAddressType,AbiAddressType]+ Left [cex] <- reachableUserAsserts c sig+ let arg1 = fromJust $ Map.lookup (SymAddr "arg1") cex.addrs+ arg2 = fromJust $ Map.lookup (SymAddr "arg1") cex.addrs+ assertEqualM "should match" arg1 arg2+ -- TODO: fails due to missing aliasing rules+ , expectFail $ test "tx.origin cannot alias deployed contracts" $ do+ Just c <- solcRuntime "C"+ [i|+ contract A {}+ contract C {+ function f() external {+ address a = address(new A());+ if (tx.origin == a) assert(false);+ }+ }+ |]+ cexs <- reachableUserAsserts c Nothing+ assertBoolM "unexpected cex" (isRight cexs)+ , test "tx.origin can alias everything else" $ do+ let addrs = ["address(this)", "block.coinbase", "msg.sender", "arg"] :: [Text]+ sig = Just $ Sig "f(address)" [AbiAddressType]+ checkVs vs = [i|+ contract C {+ function f(address arg) external {+ if (${vs} == tx.origin) assert(false);+ }+ }+ |]++ [self, coinbase, caller, arg] <- forM addrs $ \addr -> do+ Just c <- solcRuntime "C" (checkVs addr)+ Left [cex] <- reachableUserAsserts c sig+ pure cex.addrs++ liftIO $ do+ let check as a = (Map.lookup (SymAddr "origin") as) @?= (Map.lookup a as)+ check self (SymAddr "entrypoint")+ check coinbase (SymAddr "coinbase")+ check caller (SymAddr "caller")+ check arg (SymAddr "arg1")+ , test "coinbase can alias anything" $ do+ let addrs = ["address(this)", "tx.origin", "msg.sender", "a", "arg"] :: [Text]+ sig = Just $ Sig "f(address)" [AbiAddressType]+ checkVs vs = [i|+ contract A {}+ contract C {+ function f(address arg) external {+ address a = address(new A());+ if (${vs} == block.coinbase) assert(false);+ }+ }+ |]++ [self, origin, caller, a, arg] <- forM addrs $ \addr -> do+ Just c <- solcRuntime "C" (checkVs addr)+ Left [cex] <- reachableUserAsserts c sig+ pure cex.addrs++ liftIO $ do+ let check as a' = (Map.lookup (SymAddr "coinbase") as) @?= (Map.lookup a' as)+ check self (SymAddr "entrypoint")+ check origin (SymAddr "origin")+ check caller (SymAddr "caller")+ check a (SymAddr "freshSymAddr1")+ check arg (SymAddr "arg1")+ , test "caller can alias anything" $ do+ let addrs = ["address(this)", "tx.origin", "block.coinbase", "a", "arg"] :: [Text]+ sig = Just $ Sig "f(address)" [AbiAddressType]+ checkVs vs = [i|+ contract A {}+ contract C {+ function f(address arg) external {+ address a = address(new A());+ if (${vs} == msg.sender) assert(false);+ }+ }+ |]++ [self, origin, coinbase, a, arg] <- forM addrs $ \addr -> do+ Just c <- solcRuntime "C" (checkVs addr)+ Left [cex] <- reachableUserAsserts c sig+ pure cex.addrs++ liftIO $ do+ let check as a' = (Map.lookup (SymAddr "caller") as) @?= (Map.lookup a' as)+ check self (SymAddr "entrypoint")+ check origin (SymAddr "origin")+ check coinbase (SymAddr "coinbase")+ check a (SymAddr "freshSymAddr1")+ check arg (SymAddr "arg1")+ , test "vm.load fails for a potentially aliased address" $ do+ Just c <- solcRuntime "C"+ [i|+ interface Vm {+ function load(address,bytes32) external;+ }+ contract C {+ function f() external {+ Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);+ vm.load(msg.sender, 0x0);+ }+ }+ |]+ (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s c Nothing [] defaultVeriOpts Nothing (Just $ checkBadCheatCode "load(address,bytes32)")+ pure ()+ , test "vm.store fails for a potentially aliased address" $ do+ Just c <- solcRuntime "C"+ [i|+ interface Vm {+ function store(address,bytes32,bytes32) external;+ }+ contract C {+ function f() external {+ Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);+ vm.store(msg.sender, 0x0, 0x0);+ }+ }+ |]+ (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s c Nothing [] defaultVeriOpts Nothing (Just $ checkBadCheatCode "store(address,bytes32,bytes32)")+ pure ()+ -- TODO: make this work properly+ , test "transfering-eth-does-not-dealias" $ do+ Just c <- solcRuntime "C"+ [i|+ // we can't do calls to unknown code yet so we use selfdestruct+ contract Send {+ constructor(address payable dst) payable {+ selfdestruct(dst);+ }+ }+ contract C {+ function f() external {+ uint preSender = msg.sender.balance;+ uint preOrigin = tx.origin.balance;++ new Send{value:10}(payable(msg.sender));+ new Send{value:5}(payable(tx.origin));++ if (msg.sender == tx.origin) {+ assert(preSender == preOrigin+ && msg.sender.balance == preOrigin + 15+ && tx.origin.balance == preSender + 15);+ } else {+ assert(msg.sender.balance == preSender + 10+ && tx.origin.balance == preOrigin + 5);+ }+ }+ }+ |]+ Right e <- reachableUserAsserts c Nothing+ -- TODO: this should work one day+ assertBoolM "should be partial" (Expr.containsNode isPartial e)+ , test "addresses-in-context-are-symbolic" $ do+ Just a <- solcRuntime "A"+ [i|+ contract A {+ function f() external {+ assert(msg.sender != address(0x0));+ }+ }+ |]+ Just b <- solcRuntime "B"+ [i|+ contract B {+ function f() external {+ assert(block.coinbase != address(0x1));+ }+ }+ |]+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function f() external {+ assert(tx.origin != address(0x2));+ }+ }+ |]+ Just d <- solcRuntime "D"+ [i|+ contract D {+ function f() external {+ assert(address(this) != address(0x3));+ }+ }+ |]+ [acex,bcex,ccex,dcex] <- forM [a,b,c,d] $ \con -> do+ Left [cex] <- reachableUserAsserts con Nothing+ assertEqualM "wrong number of addresses" 1 (length (Map.keys cex.addrs))+ pure cex++ assertEqualM "wrong model for a" (Addr 0) (fromJust $ Map.lookup (SymAddr "caller") acex.addrs)+ assertEqualM "wrong model for b" (Addr 1) (fromJust $ Map.lookup (SymAddr "coinbase") bcex.addrs)+ assertEqualM "wrong model for c" (Addr 2) (fromJust $ Map.lookup (SymAddr "origin") ccex.addrs)+ assertEqualM "wrong model for d" (Addr 3) (fromJust $ Map.lookup (SymAddr "entrypoint") dcex.addrs)+ ]+ , testGroup "Symbolic execution"+ [+ test "require-test" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(int256 a) external pure {+ require(a <= 0);+ assert (a <= 0);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256)" [AbiIntType 256])) [] defaultVeriOpts+ putStrLnM "Require works as expected"+ ,+ -- here test+ test "ITE-with-bitwise-AND" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function f(uint256 x) public pure {+ require(x > 0);+ uint256 a = (x & 8);+ bool w;+ // assembly is needed here, because solidity doesn't allow uint->bool conversion+ assembly {+ w:=a+ }+ if (!w) assert(false); //we should get a CEX: when x has a 0 at bit 3+ }+ }+ |]+ -- should find a counterexample+ (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "expected counterexample found"+ ,+ test "ITE-with-bitwise-OR" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function f(uint256 x) public pure {+ uint256 a = (x | 8);+ bool w;+ // assembly is needed here, because solidity doesn't allow uint->bool conversion+ assembly {+ w:=a+ }+ assert(w); // due to bitwise OR with positive value, this must always be true+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "this should always be true, due to bitwise OR with positive value"+ ,+ -- CopySlice check+ -- uses identity precompiled contract (0x4) to copy memory+ -- checks 9af114613075a2cd350633940475f8b6699064de (readByte + CopySlice had src/dest mixed up)+ -- without 9af114613 it dies with: `Exception: UnexpectedSymbolicArg 296 "MSTORE index"`+ -- TODO: check 9e734b9da90e3e0765128b1f20ce1371f3a66085 (bufLength + copySlice was off by 1)+ test "copyslice-check" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function checkval(uint8 a) public {+ bytes memory data = new bytes(5);+ for(uint i = 0; i < 5; i++) data[i] = bytes1(a);+ bytes memory ret = new bytes(data.length);+ assembly {+ let len := mload(data)+ if iszero(call(0xff, 0x04, 0, add(data, 0x20), len, add(ret,0x20), len)) {+ invalid()+ }+ }+ for(uint i = 0; i < 5; i++) assert(ret[i] == data[i]);+ }+ }+ |]+ let sig = Just (Sig "checkval(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->+ checkAssert s defaultPanicCodes c sig [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "opcode-mul-assoc" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(int256 a, int256 b, int256 c) external pure {+ int256 tmp1;+ int256 out1;+ int256 tmp2;+ int256 out2;+ assembly {+ tmp1 := mul(a, b)+ out1 := mul(tmp1,c)+ tmp2 := mul(b, c)+ out2 := mul(a, tmp2)+ }+ assert (out1 == out2);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256,int256)" [AbiIntType 256, AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts+ putStrLnM "MUL is associative"+ ,+ -- TODO look at tests here for SAR: https://github.com/dapphub/dapptools/blob/01ef8ea418c3fe49089a44d56013d8fcc34a1ec2/src/dapp-tests/pass/constantinople.sol#L250+ test "opcode-sar-neg" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(int256 shift_by, int256 val) external pure returns (int256 out) {+ require(shift_by >= 0);+ require(val <= 0);+ assembly {+ out := sar(shift_by,val)+ }+ assert (out <= 0);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts+ putStrLnM "SAR works as expected"+ ,+ test "opcode-sar-pos" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(int256 shift_by, int256 val) external pure returns (int256 out) {+ require(shift_by >= 0);+ require(val >= 0);+ assembly {+ out := sar(shift_by,val)+ }+ assert (out >= 0);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts+ putStrLnM "SAR works as expected"+ ,+ test "opcode-sar-fixedval-pos" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(int256 shift_by, int256 val) external pure returns (int256 out) {+ require(shift_by == 1);+ require(val == 64);+ assembly {+ out := sar(shift_by,val)+ }+ assert (out == 32);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts+ putStrLnM "SAR works as expected"+ ,+ test "opcode-sar-fixedval-neg" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(int256 shift_by, int256 val) external pure returns (int256 out) {+ require(shift_by == 1);+ require(val == -64);+ assembly {+ out := sar(shift_by,val)+ }+ assert (out == -32);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts+ putStrLnM "SAR works as expected"+ ,+ test "opcode-div-zero-1" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 val) external pure {+ uint out;+ assembly {+ out := div(val, 0)+ }+ assert(out == 0);++ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "sdiv works as expected"+ ,+ test "opcode-sdiv-zero-1" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 val) external pure {+ uint out;+ assembly {+ out := sdiv(val, 0)+ }+ assert(out == 0);++ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "sdiv works as expected"+ ,+ test "opcode-sdiv-zero-2" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 val) external pure {+ uint out;+ assembly {+ out := sdiv(0, val)+ }+ assert(out == 0);++ }+ }+ |]+ (_, [Qed _]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "sdiv works as expected"+ ,+ test "signed-overflow-checks" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function fun(int256 a) external returns (int256) {+ return a + a;+ }+ }+ |]+ (_, [Cex (_, _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x11] c (Just (Sig "fun(int256)" [AbiIntType 256])) [] defaultVeriOpts+ putStrLnM "expected cex discovered"+ ,+ test "opcode-signextend-neg" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 val, uint8 b) external pure {+ require(b <= 31);+ require(b >= 0);+ require(val < (1 <<(b*8)));+ require(val & (1 <<(b*8-1)) != 0); // MSbit set, i.e. negative+ uint256 out;+ assembly {+ out := signextend(b, val)+ }+ if (b == 31) assert(out == val);+ else assert(out > val);+ assert(out & (1<<254) != 0); // MSbit set, i.e. negative+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "signextend works as expected"+ ,+ test "opcode-signextend-pos-nochop" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 val, uint8 b) external pure {+ require(val < (1 <<(b*8)));+ require(val & (1 <<(b*8-1)) == 0); // MSbit not set, i.e. positive+ uint256 out;+ assembly {+ out := signextend(b, val)+ }+ assert (out == val);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts+ putStrLnM "signextend works as expected"+ ,+ test "opcode-signextend-pos-chopped" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 val, uint8 b) external pure {+ require(b == 0); // 1-byte+ require(val == 514); // but we set higher bits+ uint256 out;+ assembly {+ out := signextend(b, val)+ }+ assert (out == 2); // chopped+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts+ putStrLnM "signextend works as expected"+ ,+ -- when b is too large, value is unchanged+ test "opcode-signextend-pos-b-toolarge" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 val, uint8 b) external pure {+ require(b >= 31);+ uint256 out;+ assembly {+ out := signextend(b, val)+ }+ assert (out == val);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts+ putStrLnM "signextend works as expected"+ ,+ test "opcode-shl" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 shift_by, uint256 val) external pure {+ require(val < (1<<16));+ require(shift_by < 16);+ uint256 out;+ assembly {+ out := shl(shift_by,val)+ }+ assert (out >= val);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "SAR works as expected"+ ,+ test "opcode-xor-cancel" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 a, uint256 b) external pure {+ require(a == b);+ uint256 c;+ assembly {+ c := xor(a,b)+ }+ assert (c == 0);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "XOR works as expected"+ ,+ test "opcode-xor-reimplement" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 a, uint256 b) external pure {+ uint256 c;+ assembly {+ c := xor(a,b)+ }+ assert (c == (~(a & b)) & (a | b));+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "XOR works as expected"+ ,+ test "opcode-add-commutative" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint256 a, uint256 b) external pure {+ uint256 res1;+ uint256 res2;+ assembly {+ res1 := add(a,b)+ res2 := add(b,a)+ }+ assert (res1 == res2);+ }+ }+ |]+ a <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ case a of+ (_, [Cex (_, ctr)]) -> do+ let x = getVar ctr "arg1"+ let y = getVar ctr "arg2"+ putStrLnM $ "y:" <> show y+ putStrLnM $ "x:" <> show x+ assertEqualM "Addition is not commutative... that's wrong" False True+ (_, [Qed _]) -> do+ putStrLnM "adding is commutative"+ _ -> internalError "Unexpected"+ ,+ test "opcode-div-res-zero-on-div-by-zero" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function fun(uint16 a) external pure {+ uint16 b = 0;+ uint16 res;+ assembly {+ res := div(a,b)+ }+ assert (res == 0);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint16)" [AbiUIntType 16])) [] defaultVeriOpts+ putStrLnM "DIV by zero is zero"+ ,+ -- Somewhat tautological since we are asserting the precondition+ -- on the same form as the actual "requires" clause.+ test "SafeAdd success case" $ do+ Just safeAdd <- solcRuntime "SafeAdd"+ [i|+ contract SafeAdd {+ function add(uint x, uint y) public pure returns (uint z) {+ require((z = x + y) >= x);+ }+ }+ |]+ let pre preVM = let (x, y) = case getStaticAbiArgs 2 preVM of+ [x', y'] -> (x', y')+ _ -> internalError "expected 2 args"+ in (x .<= Expr.add x y)+ -- TODO check if it's needed+ .&& preVM.state.callvalue .== Lit 0+ post prestate leaf =+ let (x, y) = case getStaticAbiArgs 2 prestate of+ [x', y'] -> (x', y')+ _ -> internalError "expected 2 args"+ in case leaf of+ Success _ _ b _ -> (ReadWord (Lit 0) b) .== (Add x y)+ _ -> PBool True+ sig = Just (Sig "add(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s safeAdd sig [] defaultVeriOpts (Just pre) (Just post)+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,++ test "x == y => x + y == 2 * y" $ do+ Just safeAdd <- solcRuntime "SafeAdd"+ [i|+ contract SafeAdd {+ function add(uint x, uint y) public pure returns (uint z) {+ require((z = x + y) >= x);+ }+ }+ |]+ let pre preVM = let (x, y) = case getStaticAbiArgs 2 preVM of+ [x', y'] -> (x', y')+ _ -> internalError "expected 2 args"+ in (x .<= Expr.add x y)+ .&& (x .== y)+ .&& preVM.state.callvalue .== Lit 0+ post prestate leaf =+ let (_, y) = case getStaticAbiArgs 2 prestate of+ [x', y'] -> (x', y')+ _ -> internalError "expected 2 args"+ in case leaf of+ Success _ _ b _ -> (ReadWord (Lit 0) b) .== (Mul (Lit 2) y)+ _ -> PBool True+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s safeAdd (Just (Sig "add(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts (Just pre) (Just post)+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "summary storage writes" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ uint x;+ function f(uint256 y) public {+ unchecked {+ x += y;+ x += y;+ }+ }+ }+ |]+ let pre vm = Lit 0 .== vm.state.callvalue+ post prestate leaf =+ let y = case getStaticAbiArgs 1 prestate of+ [y'] -> y'+ _ -> error "expected 1 arg"+ this = prestate.state.codeContract+ prestore = (fromJust (Map.lookup this prestate.env.contracts)).storage+ prex = Expr.readStorage' (Lit 0) prestore+ in case leaf of+ Success _ _ _ postState -> let+ poststore = (fromJust (Map.lookup this postState)).storage+ in Expr.add prex (Expr.mul (Lit 2) y) .== (Expr.readStorage' (Lit 0) poststore)+ _ -> PBool True+ sig = Just (Sig "f(uint256)" [AbiUIntType 256])+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post)+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ -- tests how whiffValue handles Neg via application of the triple IsZero simplification rule+ -- regression test for: https://github.com/dapphub/dapptools/pull/698+ test "Neg" $ do+ let src =+ [i|+ object "Neg" {+ code {+ // Deploy the contract+ datacopy(0, dataoffset("runtime"), datasize("runtime"))+ return(0, datasize("runtime"))+ }+ object "runtime" {+ code {+ let v := calldataload(4)+ if iszero(iszero(and(v, not(0xffffffffffffffffffffffffffffffffffffffff)))) {+ invalid()+ }+ }+ }+ }+ |]+ Just c <- liftIO $ yulRuntime "Neg" src+ (res, [Qed _]) <- withSolvers Z3 4 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "hello(address)" [AbiAddressType])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "catch-storage-collisions-noproblem" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y) public {+ if (x != y) {+ assembly {+ let newx := sub(sload(x), 1)+ let newy := add(sload(y), 1)+ sstore(x,newx)+ sstore(y,newy)+ }+ }+ }+ }+ |]+ let pre vm = (Lit 0) .== vm.state.callvalue+ post prestate poststate =+ let (x,y) = case getStaticAbiArgs 2 prestate of+ [x',y'] -> (x',y')+ _ -> error "expected 2 args"+ this = prestate.state.codeContract+ prestore = (fromJust (Map.lookup this prestate.env.contracts)).storage+ prex = Expr.readStorage' x prestore+ prey = Expr.readStorage' y prestore+ in case poststate of+ Success _ _ _ postcs -> let+ poststore = (fromJust (Map.lookup this postcs)).storage+ postx = Expr.readStorage' x poststore+ posty = Expr.readStorage' y poststore+ in Expr.add prex prey .== Expr.add postx posty+ _ -> PBool True+ sig = Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post)+ putStrLnM "Correct, this can never fail"+ ,+ -- Inspired by these `msg.sender == to` token bugs+ -- which break linearity of totalSupply.+ test "catch-storage-collisions-good" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y) public {+ assembly {+ let newx := sub(sload(x), 1)+ let newy := add(sload(y), 1)+ sstore(x,newx)+ sstore(y,newy)+ }+ }+ }+ |]+ let pre vm = (Lit 0) .== vm.state.callvalue+ post prestate leaf =+ let (x,y) = case getStaticAbiArgs 2 prestate of+ [x',y'] -> (x',y')+ _ -> error "expected 2 args"+ this = prestate.state.codeContract+ prestore = (fromJust (Map.lookup this prestate.env.contracts)).storage+ prex = Expr.readStorage' x prestore+ prey = Expr.readStorage' y prestore+ in case leaf of+ Success _ _ _ poststate -> let+ poststore = (fromJust (Map.lookup this poststate)).storage+ postx = Expr.readStorage' x poststore+ posty = Expr.readStorage' y poststore+ in Expr.add prex prey .== Expr.add postx posty+ _ -> PBool True+ sig = Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])+ (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post)+ let x = getVar ctr "arg1"+ let y = getVar ctr "arg2"+ putStrLnM $ "y:" <> show y+ putStrLnM $ "x:" <> show x+ assertEqualM "Catch storage collisions" x y+ putStrLnM "expected counterexample found"+ ,+ test "simple-assert" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo() external pure {+ assert(false);+ }+ }+ |]+ (_, [Cex (Failure _ _ (Revert msg), _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo()" [])) [] defaultVeriOpts+ assertEqualM "incorrect revert msg" msg (ConcreteBuf $ panicMsg 0x01)+ ,+ test "simple-assert-2" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 x) external pure {+ assert(x != 10);+ }+ }+ |]+ (_, [(Cex (_, ctr))]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ assertEqualM "Must be 10" 10 $ getVar ctr "arg1"+ putStrLnM "Got 10 Cex, as expected"+ ,+ test "assert-fail-equal" $ do+ Just c <- solcRuntime "AssertFailEqual"+ [i|+ contract AssertFailEqual {+ function fun(uint256 deposit_count) external pure {+ assert(deposit_count == 0);+ assert(deposit_count == 11);+ }+ }+ |]+ (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ let ints = map (flip getVar "arg1") [a,b]+ assertBoolM "0 must be one of the Cex-es" $ isJust $ List.elemIndex 0 ints+ putStrLnM "expected 2 counterexamples found, one Cex is the 0 value"+ ,+ test "assert-fail-notequal" $ do+ Just c <- solcRuntime "AssertFailNotEqual"+ [i|+ contract AssertFailNotEqual {+ function fun(uint256 deposit_count) external pure {+ assert(deposit_count != 0);+ assert(deposit_count != 11);+ }+ }+ |]+ (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ let x = getVar a "arg1"+ let y = getVar b "arg1"+ assertBoolM "At least one has to be 0, to go through the first assert" (x == 0 || y == 0)+ putStrLnM "expected 2 counterexamples found."+ ,+ test "assert-fail-twoargs" $ do+ Just c <- solcRuntime "AssertFailTwoParams"+ [i|+ contract AssertFailTwoParams {+ function fun(uint256 deposit_count1, uint256 deposit_count2) external pure {+ assert(deposit_count1 != 0);+ assert(deposit_count2 != 11);+ }+ }+ |]+ (_, [Cex _, Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "expected 2 counterexamples found"+ ,+ test "assert-2nd-arg" $ do+ Just c <- solcRuntime "AssertFailTwoParams"+ [i|+ contract AssertFailTwoParams {+ function fun(uint256 deposit_count1, uint256 deposit_count2) external pure {+ assert(deposit_count2 != 666);+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ assertEqualM "Must be 666" 666 $ getVar ctr "arg2"+ putStrLnM "Found arg2 Ctx to be 666"+ ,+ -- LSB is zeroed out, byte(31,x) takes LSB, so y==0 always holds+ test "check-lsb-msb1" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 x) external pure {+ x &= 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00;+ uint8 y;+ assembly { y := byte(31,x) }+ assert(y == 0);+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ -- We zero out everything but the LSB byte. However, byte(31,x) takes the LSB byte+ -- so there is a counterexamle, where LSB of x is not zero+ test "check-lsb-msb2" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 x) external pure {+ x &= 0x00000000000000000000000000000000000000000000000000000000000000ff;+ uint8 y;+ assembly { y := byte(31,x) }+ assert(y == 0);+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ assertBoolM "last byte must be non-zero" $ ((Data.Bits..&.) (getVar ctr "arg1") 0xff) > 0+ putStrLnM "Expected counterexample found"+ ,+ -- We zero out everything but the 2nd LSB byte. However, byte(31,x) takes the 2nd LSB byte+ -- so there is a counterexamle, where 2nd LSB of x is not zero+ test "check-lsb-msb3 -- 2nd byte" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 x) external pure {+ x &= 0x000000000000000000000000000000000000000000000000000000000000ff00;+ uint8 y;+ assembly { y := byte(30,x) }+ assert(y == 0);+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ assertBoolM "second to last byte must be non-zero" $ ((Data.Bits..&.) (getVar ctr "arg1") 0xff00) > 0+ putStrLnM "Expected counterexample found"+ ,+ -- Reverse of test above+ test "check-lsb-msb4 2nd byte rev" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 x) external pure {+ x &= 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff;+ uint8 y;+ assembly {+ y := byte(30,x)+ }+ assert(y == 0);+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ -- Bitwise OR operation test+ test "opcode-bitwise-or-full-1s" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 x) external pure {+ uint256 y;+ uint256 z = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;+ assembly { y := or(x, z) }+ assert(y == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "When OR-ing with full 1's we should get back full 1's"+ ,+ -- Bitwise OR operation test+ test "opcode-bitwise-or-byte-of-1s" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 x) external pure {+ uint256 y;+ uint256 z = 0x000000000000000000000000000000000000000000000000000000000000ff00;+ assembly { y := or(x, z) }+ assert((y & 0x000000000000000000000000000000000000000000000000000000000000ff00) ==+ 0x000000000000000000000000000000000000000000000000000000000000ff00);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "When OR-ing with a byte of 1's, we should get 1's back there"+ ,+ test "Deposit contract loop (z3)" $ do+ Just c <- solcRuntime "Deposit"+ [i|+ contract Deposit {+ function deposit(uint256 deposit_count) external pure {+ require(deposit_count < 2**32 - 1);+ ++deposit_count;+ bool found = false;+ for (uint height = 0; height < 32; height++) {+ if ((deposit_count & 1) == 1) {+ found = true;+ break;+ }+ deposit_count = deposit_count >> 1;+ }+ assert(found);+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "deposit(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "Deposit-contract-loop-error-version" $ do+ Just c <- solcRuntime "Deposit"+ [i|+ contract Deposit {+ function deposit(uint8 deposit_count) external pure {+ require(deposit_count < 2**32 - 1);+ ++deposit_count;+ bool found = false;+ for (uint height = 0; height < 32; height++) {+ if ((deposit_count & 1) == 1) {+ found = true;+ break;+ }+ deposit_count = deposit_count >> 1;+ }+ assert(found);+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s allPanicCodes c (Just (Sig "deposit(uint8)" [AbiUIntType 8])) [] defaultVeriOpts+ assertEqualM "Must be 255" 255 $ getVar ctr "arg1"+ putStrLnM $ "expected counterexample found, and it's correct: " <> (show $ getVar ctr "arg1")+ ,+ test "explore function dispatch" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x) public pure returns (uint) {+ return x;+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "check-asm-byte-in-bounds" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 idx, uint256 val) external pure {+ uint256 actual;+ uint256 expected;+ require(idx < 32);+ assembly {+ actual := byte(idx,val)+ expected := shr(248, shl(mul(idx, 8), val))+ }+ assert(actual == expected);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts+ putStrLnM "in bounds byte reads return the expected value"+ ,+ test "check-div-mod-sdiv-smod-by-zero-constant-prop" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 e) external pure {+ uint x = 0;+ uint y = 55;+ uint z;+ assembly { z := div(y,x) }+ assert(z == 0);+ assembly { z := div(x,y) }+ assert(z == 0);+ assembly { z := sdiv(y,x) }+ assert(z == 0);+ assembly { z := sdiv(x,y) }+ assert(z == 0);+ assembly { z := mod(y,x) }+ assert(z == 0);+ assembly { z := mod(x,y) }+ assert(z == 0);+ assembly { z := smod(y,x) }+ assert(z == 0);+ assembly { z := smod(x,y) }+ assert(z == 0);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "div/mod/sdiv/smod by zero works as expected during constant propagation"+ ,+ test "check-asm-byte-oob" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function foo(uint256 x, uint256 y) external pure {+ uint256 z;+ require(x >= 32);+ assembly { z := byte(x,y) }+ assert(z == 0);+ }+ }+ |]+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts+ putStrLnM "oob byte reads always return 0"+ ,+ test "injectivity of keccak (diff sizes)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint128 x, uint256 y) external pure {+ assert(+ keccak256(abi.encodePacked(x)) !=+ keccak256(abi.encodePacked(y))+ );+ }+ }+ |]+ Right _ <- reachableUserAsserts c (Just $ Sig "f(uint128,uint256)" [AbiUIntType 128, AbiUIntType 256])+ pure ()+ ,+ test "injectivity of keccak (32 bytes)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y) public pure {+ if (keccak256(abi.encodePacked(x)) == keccak256(abi.encodePacked(y))) assert(x == y);+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "injectivity of keccak contrapositive (32 bytes)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y) public pure {+ require (x != y);+ assert (keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y)));+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "injectivity of keccak (64 bytes)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y, uint w, uint z) public pure {+ assert (keccak256(abi.encodePacked(x,y)) != keccak256(abi.encodePacked(w,z)));+ }+ }+ |]+ (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256,uint256,uint256,uint256)" (replicate 4 (AbiUIntType 256)))) [] defaultVeriOpts+ let x = getVar ctr "arg1"+ let y = getVar ctr "arg2"+ let w = getVar ctr "arg3"+ let z = getVar ctr "arg4"+ assertEqualM "x==y for hash collision" x y+ assertEqualM "w==z for hash collision" w z+ putStrLnM "expected counterexample found"+ ,+ test "calldata beyond calldatasize is 0 (symbolic calldata)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f() public pure {+ uint y;+ assembly {+ let x := calldatasize()+ y := calldataload(x)+ }+ assert(y == 0);+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "calldata beyond calldatasize is 0 (concrete dalldata prefix)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint256 z) public pure {+ uint y;+ assembly {+ let x := calldatasize()+ y := calldataload(x)+ }+ assert(y == 0);+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "calldata symbolic access" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint256 z) public pure {+ uint x; uint y;+ assembly {+ y := calldatasize()+ }+ require(z >= y);+ assembly {+ x := calldataload(z)+ }+ assert(x == 0);+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "multiple-contracts" $ do+ let code =+ [i|+ contract C {+ uint x;+ A constant a = A(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B);++ function call_A() public view {+ // should fail since x can be anything+ assert(a.x() == x);+ }+ }+ contract A {+ uint public x;+ }+ |]+ aAddr = LitAddr (Addr 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B)+ cAddr = SymAddr "entrypoint"+ Just c <- solcRuntime "C" code+ Just a <- solcRuntime "A" code+ (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $ \s -> do+ vm <- liftIO $ stToIO $ abstractVM (mkCalldata (Just (Sig "call_A()" [])) []) c Nothing False+ <&> set (#state % #callvalue) (Lit 0)+ <&> over (#env % #contracts)+ (Map.insert aAddr (initialContract (RuntimeCode (ConcreteRuntimeCode a))))+ verify s defaultVeriOpts vm (Just $ checkAssertions defaultPanicCodes)++ let storeCex = cex.store+ testCex = case (Map.lookup cAddr storeCex, Map.lookup aAddr storeCex) of+ (Just sC, Just sA) -> case (Map.lookup 0 sC, Map.lookup 0 sA) of+ (Just x, Just y) -> x /= y+ (Just x, Nothing) -> x /= 0+ _ -> False+ _ -> False+ assertBoolM "Did not find expected storage cex" testCex+ putStrLnM "expected counterexample found"+ ,+ expectFail $ test "calling unique contracts (read from storage)" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ uint x;+ A a;++ function call_A() public {+ a = new A();+ // should fail since x can be anything+ assert(a.x() == x);+ }+ }+ contract A {+ uint public x;+ }+ |]+ (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "call_A()" [])) [] defaultVeriOpts+ putStrLnM "expected counterexample found"+ ,+ test "keccak-concrete-and-sym-agree" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function kecc(uint x) public pure {+ if (x == 0) {+ assert(keccak256(abi.encode(x)) == keccak256(abi.encode(0)));+ }+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "kecc(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "keccak-concrete-and-sym-agree-nonzero" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function kecc(uint x) public pure {+ if (x == 55) {+ // Note: 3014... is the encode & keccak & uint256 conversion of 55+ assert(uint256(keccak256(abi.encode(x))) == 30148980456718914367279254941528755963179627010946392082519497346671089299886);+ }+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "kecc(uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "keccak concrete and sym injectivity" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x) public pure {+ if (x !=3) assert(keccak256(abi.encode(x)) != keccak256(abi.encode(3)));+ }+ }+ |]+ (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths"+ ,+ test "safemath-distributivity-yul" $ do+ let yulsafeDistributivity = hex "6355a79a6260003560e01c14156016576015601f565b5b60006000fd60a1565b603d602d604435600435607c565b6039602435600435607c565b605d565b6052604b604435602435605d565b600435607c565b141515605a57fe5b5b565b6000828201821115151560705760006000fd5b82820190505b92915050565b6000818384048302146000841417151560955760006000fd5b82820290505b92915050565b"+ vm <- liftIO $ stToIO $ abstractVM (mkCalldata (Just (Sig "distributivity(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) []) yulsafeDistributivity Nothing False+ (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verify s defaultVeriOpts vm (Just $ checkAssertions defaultPanicCodes)+ putStrLnM "Proven"+ ,+ test "safemath-distributivity-sol" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ function distributivity(uint x, uint y, uint z) public {+ assert(mul(x, add(y, z)) == add(mul(x, y), mul(x, z)));+ }++ function add(uint x, uint y) internal pure returns (uint z) {+ unchecked {+ require((z = x + y) >= x, "ds-math-add-overflow");+ }+ }++ function mul(uint x, uint y) internal pure returns (uint z) {+ unchecked {+ require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");+ }+ }+ }+ |]++ (_, [Qed _]) <- withSolvers Bitwuzla 1 (Just 99999999) $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "distributivity(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts+ putStrLnM "Proven"+ ,+ test "storage-cex-1" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ uint x;+ uint y;+ function fun(uint256 a) external{+ require(x != 0);+ require(y != 0);+ assert (x == y);+ }+ }+ |]+ (_, [(Cex (_, cex))]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x01] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ let addr = SymAddr "entrypoint"+ testCex = Map.size cex.store == 1 &&+ case Map.lookup addr cex.store of+ Just s -> Map.size s == 2 &&+ case (Map.lookup 0 s, Map.lookup 1 s) of+ (Just x, Just y) -> x /= y+ _ -> False+ _ -> False+ assertBoolM "Did not find expected storage cex" testCex+ putStrLnM "Expected counterexample found"+ ,+ test "storage-cex-2" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ uint[10] arr1;+ uint[10] arr2;+ function fun(uint256 a) external{+ assert (arr1[0] < arr2[a]);+ }+ }+ |]+ (_, [(Cex (_, cex))]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x01] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts+ let addr = SymAddr "entrypoint"+ a = getVar cex "arg1"+ testCex = Map.size cex.store == 1 &&+ case Map.lookup addr cex.store of+ Just s -> case (Map.lookup 0 s, Map.lookup (10 + a) s) of+ (Just x, Just y) -> x >= y+ (Just x, Nothing) -> x > 0 -- arr1 can be Nothing, it'll then be zero+ _ -> False+ Nothing -> False -- arr2 must contain an element, or it'll be 0+ assertBoolM "Did not find expected storage cex" testCex+ putStrLnM "Expected counterexample found"+ ,+ test "storage-cex-concrete" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ uint x;+ uint y;+ function fun(uint256 a) external{+ require (x != 0);+ require (y != 0);+ assert (x != y);+ }+ }+ |]+ let sig = Just (Sig "fun(uint256)" [AbiUIntType 256])+ (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $+ \s -> verifyContract s c sig [] defaultVeriOpts Nothing (Just $ checkAssertions [0x01])+ let addr = SymAddr "entrypoint"+ testCex = Map.size cex.store == 1 &&+ case Map.lookup addr cex.store of+ Just s -> Map.size s == 2 &&+ case (Map.lookup 0 s, Map.lookup 1 s) of+ (Just x, Just y) -> x == y+ _ -> False+ _ -> False+ assertBoolM "Did not find expected storage cex" testCex+ putStrLnM "Expected counterexample found"+ ]+ , testGroup "concr-fuzz"+ [ testFuzz "fuzz-complicated-mul" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function complicated(uint x, uint y, uint z) public {+ uint a;+ uint b;+ unchecked {+ a = x * x * x * y * y * y * z;+ b = x * x * x * x * y * y * z * z;+ }+ assert(a == b);+ }+ }+ |]+ let sig = (Sig "complicated(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])+ (_, [Cex (_, ctr)]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just sig) [] defaultVeriOpts+ let+ x = getVar ctr "arg1"+ y = getVar ctr "arg2"+ z = getVar ctr "arg3"+ a = x * x * x * y * y * y * z;+ b = x * x * x * x * y * y * z * z;+ val = a == b+ assertBoolM "Must fail" (not val)+ putStrLnM $ "expected counterexample found, x: " <> (show x) <> " y: " <> (show y) <> " z: " <> (show z)+ putStrLnM $ "cex a: " <> (show a) <> " b: " <> (show b)+ , testFuzz "fuzz-stores" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping(uint => uint) items;+ function func() public {+ assert(items[5] == 0);+ }+ }+ |]+ let sig = (Sig "func()" [])+ (_, [Cex (_, ctr)]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just sig) [] defaultVeriOpts+ putStrLnM $ "expected counterexample found. ctr: " <> (show ctr)+ , testFuzz "fuzz-simple-fixed-value" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping(uint => uint) items;+ function func(uint a) public {+ assert(a != 1337);+ }+ }+ |]+ let sig = (Sig "func(uint256)" [AbiUIntType 256])+ (_, [Cex (_, ctr)]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just sig) [] defaultVeriOpts+ putStrLnM $ "expected counterexample found. ctr: " <> (show ctr)+ , testFuzz "fuzz-simple-fixed-value2" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function func(uint a, uint b) public {+ assert(!((a == 1337) && (b == 99)));+ }+ }+ |]+ let sig = (Sig "func(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])+ (_, [Cex (_, ctr)]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just sig) [] defaultVeriOpts+ putStrLnM $ "expected counterexample found. ctr: " <> (show ctr)+ , testFuzz "fuzz-simple-fixed-value3" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ function func(uint a, uint b) public {+ assert(((a != 1337) && (b != 99)));+ }+ }+ |]+ let sig = (Sig "func(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])+ (_, [Cex (_, ctr1), Cex (_, ctr2)]) <- withSolvers Bitwuzla 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just sig) [] defaultVeriOpts+ putStrLnM $ "expected counterexamples found. ctr1: " <> (show ctr1) <> " ctr2: " <> (show ctr2)+ , testFuzz "fuzz-simple-fixed-value-store1" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping(uint => uint) items;+ function func(uint a) public {+ uint f = items[2];+ assert(a != f);+ }+ }+ |]+ let sig = (Sig "func(uint256)" [AbiUIntType 256, AbiUIntType 256])+ (_, [Cex _]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just sig) [] defaultVeriOpts+ putStrLnM $ "expected counterexamples found"+ , testFuzz "fuzz-simple-fixed-value-store2" $ do+ Just c <- solcRuntime "MyContract"+ [i|+ contract MyContract {+ mapping(uint => uint) items;+ function func(uint a) public {+ items[0] = 1337;+ assert(a != items[0]);+ }+ }+ |]+ let sig = (Sig "func(uint256)" [AbiUIntType 256, AbiUIntType 256])+ (_, [Cex (_, ctr1)]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just sig) [] defaultVeriOpts+ putStrLnM $ "expected counterexamples found: " <> show ctr1+ ]+ , testGroup "simplification-working"+ [+ test "PEq-and-PNot-PEq-1" $ do+ let a = [PEq (Lit 0x539) (Var "arg1"),PNeg (PEq (Lit 0x539) (Var "arg1"))]+ assertEqualM "Must simplify to PBool False" (Expr.simplifyProps a) ([PBool False])+ , test "PEq-and-PNot-PEq-2" $ do+ let a = [PEq (Var "arg1") (Lit 0x539),PNeg (PEq (Lit 0x539) (Var "arg1"))]+ assertEqualM "Must simplify to PBool False" (Expr.simplifyProps a) ([PBool False])+ , test "PEq-and-PNot-PEq-3" $ do+ let a = [PEq (Var "arg1") (Lit 0x539),PNeg (PEq (Var "arg1") (Lit 0x539))]+ assertEqualM "Must simplify to PBool False" (Expr.simplifyProps a) ([PBool False])+ , test "prop-simp-bool1" $ do+ let+ a = successGen [PAnd (PBool True) (PBool False)]+ b = Expr.simplify a+ assertEqualM "Must simplify down" (successGen [PBool False]) b+ , test "prop-simp-bool2" $ do+ let+ a = successGen [POr (PBool True) (PBool False)]+ b = Expr.simplify a+ assertEqualM "Must simplify down" (successGen []) b+ , test "prop-simp-LT" $ do+ let+ a = successGen [PLT (Lit 1) (Lit 2)]+ b = Expr.simplify a+ assertEqualM "Must simplify down" (successGen []) b+ , test "prop-simp-GEq" $ do+ let+ a = successGen [PGEq (Lit 1) (Lit 2)]+ b = Expr.simplify a+ assertEqualM "Must simplify down" (successGen [PBool False]) b+ , test "prop-simp-multiple" $ do+ let+ a = successGen [PBool False, PBool True]+ b = Expr.simplify a+ assertEqualM "Must simplify down" (successGen [PBool False]) b+ , test "prop-simp-expr" $ do+ let+ a = successGen [PEq (Add (Lit 1) (Lit 2)) (Sub (Lit 4) (Lit 1))]+ b = Expr.simplify a+ assertEqualM "Must simplify down" (successGen []) b+ , test "prop-simp-impl" $ do+ let+ a = successGen [PImpl (PBool False) (PEq (Var "abc") (Var "bcd"))]+ b = Expr.simplify a+ assertEqualM "Must simplify down" (successGen []) b+ , test "propSimp-no-duplicate1" $ do+ let a = [PAnd (PGEq (Max (Lit 0x44) (BufLength (AbstractBuf "txdata"))) (Lit 0x44)) (PLT (Max (Lit 0x44) (BufLength (AbstractBuf "txdata"))) (Lit 0x10000000000000000)), PAnd (PGEq (Var "arg1") (Lit 0x0)) (PLEq (Var "arg1") (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)),PEq (Lit 0x63) (Var "arg2"),PEq (Lit 0x539) (Var "arg1"),PEq TxValue (Lit 0x0),PEq (IsZero (Eq (Lit 0x63) (Var "arg2"))) (Lit 0x0)]+ let simp = Expr.simplifyProps a+ assertEqualM "must not duplicate" simp (nubOrd simp)+ assertEqualM "We must be able to remove all duplicates" (length $ nubOrd simp) (length $ DLU.uniq simp)+ , test "propSimp-no-duplicate2" $ do+ let a = [PNeg (PBool False),PAnd (PGEq (Max (Lit 0x44) (BufLength (AbstractBuf "txdata"))) (Lit 0x44)) (PLT (Max (Lit 0x44) (BufLength (AbstractBuf "txdata"))) (Lit 0x10000000000000000)),PAnd (PGEq (Var "arg2") (Lit 0x0)) (PLEq (Var "arg2") (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)),PAnd (PGEq (Var "arg1") (Lit 0x0)) (PLEq (Var "arg1") (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)),PEq (Lit 0x539) (Var "arg1"),PNeg (PEq (Lit 0x539) (Var "arg1")),PEq TxValue (Lit 0x0),PLT (BufLength (AbstractBuf "txdata")) (Lit 0x10000000000000000),PEq (IsZero (Eq (Lit 0x539) (Var "arg1"))) (Lit 0x0),PNeg (PEq (IsZero (Eq (Lit 0x539) (Var "arg1"))) (Lit 0x0)),PNeg (PEq (IsZero TxValue) (Lit 0x0))]+ let simp = Expr.simplifyProps a+ assertEqualM "must not duplicate" simp (nubOrd simp)+ assertEqualM "must not duplicate" (length simp) (length $ DLU.uniq simp)+ , test "full-order-prop1" $ do+ let a = [PNeg (PBool False),PAnd (PGEq (Max (Lit 0x44) (BufLength (AbstractBuf "txdata"))) (Lit 0x44)) (PLT (Max (Lit 0x44) (BufLength (AbstractBuf "txdata"))) (Lit 0x10000000000000000)),PAnd (PGEq (Var "arg2") (Lit 0x0)) (PLEq (Var "arg2") (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)),PAnd (PGEq (Var "arg1") (Lit 0x0)) (PLEq (Var "arg1") (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)),PEq (Lit 0x539) (Var "arg1"),PNeg (PEq (Lit 0x539) (Var "arg1")),PEq TxValue (Lit 0x0),PLT (BufLength (AbstractBuf "txdata")) (Lit 0x10000000000000000),PEq (IsZero (Eq (Lit 0x539) (Var "arg1"))) (Lit 0x0),PNeg (PEq (IsZero (Eq (Lit 0x539) (Var "arg1"))) (Lit 0x0)),PNeg (PEq (IsZero TxValue) (Lit 0x0))]+ let simp = Expr.simplifyProps a+ assertEqualM "We must be able to remove all duplicates" (length $ nubOrd simp) (length $ DLU.uniq simp)+ , test "full-order-prop2" $ do+ let a =[PNeg (PBool False),PAnd (PGEq (Max (Lit 0x44) (BufLength (AbstractBuf "txdata"))) (Lit 0x44)) (PLT (Max (Lit 0x44) (BufLength (AbstractBuf "txdata"))) (Lit 0x10000000000000000)),PAnd (PGEq (Var "arg2") (Lit 0x0)) (PLEq (Var "arg2") (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)),PAnd (PGEq (Var "arg1") (Lit 0x0)) (PLEq (Var "arg1") (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)),PEq (Lit 0x63) (Var "arg2"),PEq (Lit 0x539) (Var "arg1"),PEq TxValue (Lit 0x0),PLT (BufLength (AbstractBuf "txdata")) (Lit 0x10000000000000000),PEq (IsZero (Eq (Lit 0x63) (Var "arg2"))) (Lit 0x0),PEq (IsZero (Eq (Lit 0x539) (Var "arg1"))) (Lit 0x0),PNeg (PEq (IsZero TxValue) (Lit 0x0))]+ let simp = Expr.simplifyProps a+ assertEqualM "must not duplicate" simp (nubOrd simp)+ assertEqualM "We must be able to remove all duplicates" (length $ nubOrd simp) (length $ DLU.uniq simp)+ ]+ , testGroup "equivalence-checking"+ [+ test "eq-yul-simple-cex" $ do+ Just aPrgm <- liftIO $ yul ""+ [i|+ {+ calldatacopy(0, 0, 32)+ switch mload(0)+ case 0 { }+ case 1 { }+ default { invalid() }+ }+ |]+ Just bPrgm <- liftIO $ yul ""+ [i|+ {+ calldatacopy(0, 0, 32)+ switch mload(0)+ case 0 { }+ case 2 { }+ default { invalid() }+ }+ |]+ withSolvers Z3 3 Nothing $ \s -> do+ a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])+ assertBoolM "Must have a difference" (any isCex a)+ ,+ test "eq-sol-exp-qed" $ do+ Just aPrgm <- solcRuntime "C"+ [i|+ contract C {+ function a(uint8 x) public returns (uint8 b) {+ unchecked {+ b = x*2;+ }+ }+ }+ |]+ Just bPrgm <- solcRuntime "C"+ [i|+ contract C {+ function a(uint8 x) public returns (uint8 b) {+ unchecked {+ b = x<<1;+ }+ }+ }+ |]+ withSolvers Z3 3 Nothing $ \s -> do+ a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])+ assertEqualM "Must have no difference" [Qed ()] a+ ,+ test "eq-balance-differs" $ do+ Just aPrgm <- solcRuntime "C"+ [i|+ contract Send {+ constructor(address payable dst) payable {+ selfdestruct(dst);+ }+ }+ contract C {+ function f() public {+ new Send{value:2}(payable(address(0x0)));+ }+ }+ |]+ Just bPrgm <- solcRuntime "C"+ [i|+ contract Send {+ constructor(address payable dst) payable {+ selfdestruct(dst);+ }+ }+ contract C {+ function f() public {+ new Send{value:1}(payable(address(0x0)));+ }+ }+ |]+ withSolvers Z3 3 Nothing $ \s -> do+ a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])+ assertBoolM "Must differ" (all isCex a)+ ,+ -- TODO: this fails because we don't check equivalence of deployed contracts+ expectFail $ test "eq-handles-contract-deployment" $ do+ Just aPrgm <- solcRuntime "B"+ [i|+ contract Send {+ constructor(address payable dst) payable {+ selfdestruct(dst);+ }+ }++ contract A {+ address parent;+ constructor(address p) {+ parent = p;+ }+ function evil() public {+ parent.call(abi.encode(B.drain.selector));+ }+ }++ contract B {+ address child;+ function a() public {+ child = address(new A(address(this)));+ }+ function drain() public {+ require(msg.sender == child);+ new Send{value: address(this).balance}(payable(address(0x0)));+ }+ }+ |]+ Just bPrgm <- solcRuntime "D"+ [i|+ contract Send {+ constructor(address payable dst) payable {+ selfdestruct(dst);+ }+ }++ contract C {+ address parent;+ constructor(address p) {+ parent = p;+ }+ }++ contract D {+ address child;+ function a() public {+ child = address(new C(address(this)));+ }+ function drain() public {+ require(msg.sender == child);+ new Send{value: address(this).balance}(payable(address(0x0)));+ }+ }+ |]+ withSolvers Z3 3 Nothing $ \s -> do+ a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])+ assertBoolM "Must differ" (all isCex a)+ ,+ test "eq-unknown-addr" $ do+ Just aPrgm <- solcRuntime "C"+ [i|+ contract C {+ address addr;+ function a(address a, address b) public {+ addr = a;+ }+ }+ |]+ Just bPrgm <- solcRuntime "C"+ [i|+ contract C {+ address addr;+ function a(address a, address b) public {+ addr = b;+ }+ }+ |]+ withSolvers Z3 3 Nothing $ \s -> do+ let cd = mkCalldata (Just (Sig "a(address,address)" [AbiAddressType, AbiAddressType])) []+ a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts cd+ assertEqualM "Must be different" (any isCex a) True+ ,+ test "eq-sol-exp-cex" $ do+ Just aPrgm <- solcRuntime "C"+ [i|+ contract C {+ function a(uint8 x) public returns (uint8 b) {+ unchecked {+ b = x*2+1;+ }+ }+ }+ |]+ Just bPrgm <- solcRuntime "C"+ [i|+ contract C {+ function a(uint8 x) public returns (uint8 b) {+ unchecked {+ b = x<<1;+ }+ }+ }+ |]+ withSolvers Bitwuzla 3 Nothing $ \s -> do+ a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing [])+ assertEqualM "Must be different" (any isCex a) True+ , test "eq-all-yul-optimization-tests" $ do+ let opts = defaultVeriOpts{ maxIter = Just 5, askSmtIters = 20, loopHeuristic = Naive }+ ignoredTests =+ -- unbounded loop --+ [ "commonSubexpressionEliminator/branches_for.yul"+ , "conditionalSimplifier/no_opt_if_break_is_not_last.yul"+ , "conditionalUnsimplifier/no_opt_if_break_is_not_last.yul"+ , "expressionSimplifier/inside_for.yul"+ , "forLoopConditionIntoBody/cond_types.yul"+ , "forLoopConditionIntoBody/simple.yul"+ , "fullSimplify/inside_for.yul"+ , "fullSuite/no_move_loop_orig.yul"+ , "loopInvariantCodeMotion/multi.yul"+ , "redundantAssignEliminator/for_deep_simple.yul"+ , "unusedAssignEliminator/for_deep_noremove.yul"+ , "unusedAssignEliminator/for_deep_simple.yul"+ , "ssaTransform/for_def_in_init.yul"+ , "loopInvariantCodeMotion/simple_state.yul"+ , "loopInvariantCodeMotion/simple.yul"+ , "loopInvariantCodeMotion/recursive.yul"+ , "loopInvariantCodeMotion/no_move_staticall_returndatasize.yul"+ , "loopInvariantCodeMotion/no_move_state_loop.yul"+ , "loopInvariantCodeMotion/no_move_state.yul" -- not infinite, but rollaround on a large int+ , "loopInvariantCodeMotion/no_move_loop.yul"++ -- unexpected symbolic arg --++ -- OpCreate2+ , "expressionSimplifier/create2_and_mask.yul"++ -- OpCreate+ , "expressionSimplifier/create_and_mask.yul"+ , "expressionSimplifier/large_byte_access.yul"++ -- OpMload+ , "yulOptimizerTests/expressionSplitter/inside_function.yul"+ , "fullInliner/double_inline.yul"+ , "fullInliner/inside_condition.yul"+ , "fullInliner/large_function_multi_use.yul"+ , "fullInliner/large_function_single_use.yul"+ , "fullInliner/no_inline_into_big_global_context.yul"+ , "fullSimplify/invariant.yul"+ , "fullSuite/abi_example1.yul"+ , "ssaAndBack/for_loop.yul"+ , "ssaAndBack/multi_assign_multi_var_if.yul"+ , "ssaAndBack/multi_assign_multi_var_switch.yul"+ , "ssaAndBack/two_vars.yul"+ , "ssaTransform/multi_assign.yul"+ , "ssaTransform/multi_decl.yul"+ , "expressionSplitter/inside_function.yul"+ , "fullSuite/ssaReverseComplex.yul"++ -- OpMstore+ , "commonSubexpressionEliminator/function_scopes.yul"+ , "commonSubexpressionEliminator/variable_for_variable.yul"+ , "expressionSplitter/trivial.yul"+ , "fullInliner/multi_return.yul"+ , "fullSimplify/constant_propagation.yul"+ , "fullSimplify/identity_rules_complex.yul"+ , "fullSuite/medium.yul"+ , "loadResolver/memory_with_msize.yul"+ , "loadResolver/merge_known_write.yul"+ , "loadResolver/merge_known_write_with_distance.yul"+ , "loadResolver/merge_unknown_write.yul"+ , "loadResolver/reassign_value_expression.yul"+ , "loadResolver/second_mstore_with_delta.yul"+ , "loadResolver/second_store_with_delta.yul"+ , "loadResolver/simple.yul"+ , "loadResolver/simple_memory.yul"+ , "fullSuite/ssaReverse.yul"+ , "rematerialiser/cheap_caller.yul"+ , "rematerialiser/non_movable_instruction.yul"+ , "rematerialiser/for_break.yul"+ , "rematerialiser/for_continue.yul"+ , "rematerialiser/for_continue_2.yul"+ , "ssaAndBack/multi_assign.yul"+ , "ssaAndBack/multi_assign_if.yul"+ , "ssaAndBack/multi_assign_switch.yul"+ , "ssaAndBack/simple.yul"+ , "ssaReverser/simple.yul"+ , "loopInvariantCodeMotion/simple_storage.yul"++ -- OpMstore8+ , "loadResolver/memory_with_different_kinds_of_invalidation.yul"++ -- OpRevert+ , "ssaAndBack/ssaReverse.yul"+ , "redundantAssignEliminator/for_continue_3.yul"+ , "controlFlowSimplifier/terminating_for_revert.yul"++ -- invalid test --+ -- https://github.com/ethereum/solidity/issues/9500+ , "commonSubexpressionEliminator/object_access.yul"+ , "expressionSplitter/object_access.yul"+ , "fullSuite/stack_compressor_msize.yul"++ -- stack too deep --+ , "fullSuite/abi2.yul"+ , "fullSuite/aztec.yul"+ , "stackCompressor/inlineInBlock.yul"+ , "stackCompressor/inlineInFunction.yul"+ , "stackCompressor/unusedPrunerWithMSize.yul"+ , "wordSizeTransform/function_call.yul"+ , "fullInliner/no_inline_into_big_function.yul"+ , "controlFlowSimplifier/switch_only_default.yul"+ , "stackLimitEvader" -- all that are in this subdirectory++ -- wrong number of args --+ , "wordSizeTransform/functional_instruction.yul"+ , "wordSizeTransform/if.yul"+ , "wordSizeTransform/or_bool_renamed.yul"+ , "wordSizeTransform/switch_1.yul"+ , "wordSizeTransform/switch_2.yul"+ , "wordSizeTransform/switch_3.yul"+ , "wordSizeTransform/switch_4.yul"+ , "wordSizeTransform/switch_5.yul"+ , "unusedFunctionParameterPruner/too_many_arguments.yul"++ -- typed yul --+ , "conditionalSimplifier/add_correct_type_wasm.yul"+ , "conditionalSimplifier/add_correct_type.yul"+ , "disambiguator/for_statement.yul"+ , "disambiguator/funtion_call.yul"+ , "disambiguator/if_statement.yul"+ , "disambiguator/long_names.yul"+ , "disambiguator/switch_statement.yul"+ , "disambiguator/variables_clash.yul"+ , "disambiguator/variables_inside_functions.yul"+ , "disambiguator/variables.yul"+ , "expressionInliner/simple.yul"+ , "expressionInliner/with_args.yul"+ , "expressionSplitter/typed.yul"+ , "fullInliner/multi_return_typed.yul"+ , "functionGrouper/empty_block.yul"+ , "functionGrouper/multi_fun_mixed.yul"+ , "functionGrouper/nested_fun.yul"+ , "functionGrouper/single_fun.yul"+ , "functionHoister/empty_block.yul"+ , "functionHoister/multi_mixed.yul"+ , "functionHoister/nested.yul"+ , "functionHoister/single.yul"+ , "mainFunction/empty_block.yul"+ , "mainFunction/multi_fun_mixed.yul"+ , "mainFunction/nested_fun.yul"+ , "mainFunction/single_fun.yul"+ , "ssaTransform/typed_for.yul"+ , "ssaTransform/typed_switch.yul"+ , "ssaTransform/typed.yul"+ , "varDeclInitializer/typed.yul"++ -- New: symbolic index on MSTORE/MLOAD/CopySlice/CallDataCopy/ExtCodeCopy/Revert,+ -- or exponent is symbolic (requires symbolic gas)+ -- or SHA3 offset symbolic+ , "blockFlattener/basic.yul"+ , "commonSubexpressionEliminator/case2.yul"+ , "equalStoreEliminator/indirect_inferrence.yul"+ , "expressionJoiner/reassignment.yul"+ , "expressionSimplifier/exp_simplifications.yul"+ , "expressionSimplifier/zero_length_read.yul"+ , "expressionSimplifier/side_effects_in_for_condition.yul"+ , "fullSuite/create_and_mask.yul"+ , "fullSuite/unusedFunctionParameterPruner_return.yul"+ , "fullSuite/unusedFunctionParameterPruner_simple.yul"+ , "fullSuite/unusedFunctionParameterPruner.yul"+ , "loadResolver/double_mload_with_other_reassignment.yul"+ , "loadResolver/double_mload_with_reassignment.yul"+ , "loadResolver/double_mload.yul"+ , "loadResolver/keccak_reuse_basic.yul"+ , "loadResolver/keccak_reuse_expr_mstore.yul"+ , "loadResolver/keccak_reuse_msize.yul"+ , "loadResolver/keccak_reuse_mstore.yul"+ , "loadResolver/keccak_reuse_reassigned_branch.yul"+ , "loadResolver/keccak_reuse_reassigned_value.yul"+ , "loadResolver/keccak_symbolic_memory.yul"+ , "loadResolver/merge_mload_with_known_distance.yul"+ , "loadResolver/mload_self.yul"+ , "loadResolver/keccak_reuse_in_expression.yul"+ , "loopInvariantCodeMotion/complex_move.yul"+ , "loopInvariantCodeMotion/move_memory_function.yul"+ , "loopInvariantCodeMotion/move_state_function.yul"+ , "loopInvariantCodeMotion/no_move_memory.yul"+ , "loopInvariantCodeMotion/no_move_storage.yul"+ , "loopInvariantCodeMotion/not_first.yul"+ , "ssaAndBack/single_assign_if.yul"+ , "ssaAndBack/single_assign_switch.yul"+ , "structuralSimplifier/switch_inline_no_match.yul"+ , "unusedFunctionParameterPruner/simple.yul"+ , "unusedStoreEliminator/covering_calldatacopy.yul"+ , "unusedStoreEliminator/remove_before_revert.yul"+ , "unusedStoreEliminator/unknown_length2.yul"+ , "unusedStoreEliminator/unrelated_relative.yul"+ , "fullSuite/extcodelength.yul"+ , "unusedStoreEliminator/create_inside_function.yul"-- "trying to reset symbolic storage with writes in create"++ -- Takes too long, would timeout on most test setups.+ -- We could probably fix these by "bunching together" queries+ , "reasoningBasedSimplifier/mulmod.yul"+ , "loadResolver/multi_sload_loop.yul"+ , "reasoningBasedSimplifier/mulcheck.yul"+ , "reasoningBasedSimplifier/smod.yul"++ -- TODO check what's wrong with these!+ , "loadResolver/keccak_short.yul" -- ACTUAL bug -- keccak+ , "reasoningBasedSimplifier/signed_division.yul" -- ACTUAL bug, SDIV+ ]++ solcRepo <- liftIO $ fromMaybe (internalError "cannot find solidity repo") <$> (lookupEnv "HEVM_SOLIDITY_REPO")+ let testDir = solcRepo <> "/test/libyul/yulOptimizerTests"+ dircontents <- liftIO $ System.Directory.listDirectory testDir+ let+ fullpaths = map ((testDir ++ "/") ++) dircontents+ recursiveList :: [FilePath] -> [FilePath] -> IO [FilePath]+ recursiveList (a:ax) b = do+ isdir <- doesDirectoryExist a+ case isdir of+ True -> do+ fs <- System.Directory.listDirectory a+ let fs2 = map ((a ++ "/") ++) fs+ recursiveList (ax++fs2) b+ False -> recursiveList ax (a:b)+ recursiveList [] b = pure b+ files <- liftIO $ recursiveList fullpaths []+ let filesFiltered = filter (\file -> not $ any (`List.isSubsequenceOf` file) ignoredTests) files++ -- Takes one file which follows the Solidity Yul optimizer unit tests format,+ -- extracts both the nonoptimized and the optimized versions, and checks equivalence.+ forM_ filesFiltered (\f-> do+ origcont <- liftIO $ readFile f+ let+ onlyAfter pattern (a:ax) = if a =~ pattern then (a:ax) else onlyAfter pattern ax+ onlyAfter _ [] = []+ replaceOnce pat repl inp = go inp [] where+ go (a:ax) b = if a =~ pat then let a2 = replaceAll repl $ a *=~ pat in b ++ a2:ax+ else go ax (b ++ [a])+ go [] b = b++ -- takes a yul program and ensures memory is symbolic by prepending+ -- `calldatacopy(0,0,1024)`. (calldata is symbolic, but memory starts empty).+ -- This forces the exploration of more branches, and makes the test vectors a+ -- little more thorough.+ symbolicMem (a:ax) = if a =~ [re|"^ *object"|] then+ let a2 = replaceAll "a calldatacopy(0,0,1024)" $ a *=~ [re|code {|]+ in (a2:ax)+ else replaceOnce [re|^ *{|] "{\ncalldatacopy(0,0,1024)" $ onlyAfter [re|^ *{|] (a:ax)+ symbolicMem _ = internalError "Program too short"++ unfiltered = lines origcont+ filteredASym = symbolicMem [ x | x <- unfiltered, (not $ x =~ [re|^//|]) && (not $ x =~ [re|^$|]) ]+ filteredBSym = symbolicMem [ replaceAll "" $ x *=~[re|^//|] | x <- onlyAfter [re|^// step:|] unfiltered, not $ x =~ [re|^$|] ]+ start <- liftIO $ getCurrentTime+ putStrLnM $ "Checking file: " <> f+ conf <- readConfig+ when conf.debug $ liftIO $ do+ putStrLnM "-------------Original Below-----------------"+ mapM_ putStrLn unfiltered+ putStrLnM "------------- Filtered A + Symb below-----------------"+ mapM_ putStrLn filteredASym+ putStrLnM "------------- Filtered B + Symb below-----------------"+ mapM_ putStrLn filteredBSym+ putStrLnM "------------- END -----------------"+ Just aPrgm <- liftIO $ yul "" $ T.pack $ unlines filteredASym+ Just bPrgm <- liftIO $ yul "" $ T.pack $ unlines filteredBSym+ procs <- liftIO $ getNumProcessors+ withSolvers CVC5 (unsafeInto procs) (Just 100) $ \s -> do+ res <- equivalenceCheck s aPrgm bPrgm opts (mkCalldata Nothing [])+ end <- liftIO $ getCurrentTime+ case any isCex res of+ False -> liftIO $ do+ print $ "OK. Took " <> (show $ diffUTCTime end start) <> " seconds"+ let timeouts = filter isTimeout res+ unless (null timeouts) $ do+ putStrLnM $ "But " <> (show $ length timeouts) <> " timeout(s) occurred"+ internalError "Encountered timeouts"+ True -> liftIO $ do+ putStrLnM $ "Not OK: " <> show f <> " Got: " <> show res+ internalError "Was NOT equivalent"+ )+ ]+ ]+ where+ (===>) = assertSolidityComputation++checkEquivProp :: App m => Prop -> Prop -> m Bool+checkEquivProp = checkEquivBase (\l r -> PNeg (PImpl l r .&& PImpl r l))++checkEquiv :: (Typeable a, App m) => Expr a -> Expr a -> m Bool+checkEquiv = checkEquivBase (./=)++checkEquivBase :: (Eq a, App m) => (a -> a -> Prop) -> a -> a -> m Bool+checkEquivBase mkprop l r = do+ conf <- readConfig+ withSolvers Z3 1 (Just 1) $ \solvers -> liftIO $ do+ if l == r+ then do+ putStrLnM "skip"+ pure True+ else do+ let smt = assertPropsNoSimp conf [mkprop l r]+ res <- checkSat solvers smt+ print res+ pure $ case res of+ Unsat -> True+ EVM.Solvers.Unknown -> True+ Sat _ -> False+ Error _ -> False++-- | Takes a runtime code and calls it with the provided calldata++-- | Takes a creation code and some calldata, runs the creation code, and calls the resulting contract with the provided calldata+runSimpleVM :: App m => ByteString -> ByteString -> m (Maybe ByteString)+runSimpleVM x ins = do+ loadVM x >>= \case+ Nothing -> pure Nothing+ Just vm -> do+ let calldata = (ConcreteBuf ins)+ vm' = set (#state % #calldata) calldata vm+ res <- Stepper.interpret (Fetch.zero 0 Nothing) vm' Stepper.execFully+ case res of+ Right (ConcreteBuf bs) -> pure $ Just bs+ s -> internalError $ show s++-- | Takes a creation code and returns a vm with the result of executing the creation code+loadVM :: App m => ByteString -> m (Maybe (VM Concrete RealWorld))+loadVM x = do+ vm <- liftIO $ stToIO $ vmForEthrunCreation x+ vm1 <- Stepper.interpret (Fetch.zero 0 Nothing) vm Stepper.runFully+ case vm1.result of+ Just (VMSuccess (ConcreteBuf targetCode)) -> do+ let target = vm1.state.contract+ vm2 <- Stepper.interpret (Fetch.zero 0 Nothing) vm1 (prepVm target targetCode)+ writeTrace vm2+ pure $ Just vm2+ _ -> pure Nothing+ where+ prepVm target targetCode = Stepper.evm $ do+ replaceCodeOfSelf (RuntimeCode $ ConcreteRuntimeCode targetCode)+ resetState+ assign (#state % #gas) 0xffffffffffffffff -- kludge+ execState (loadContract target) <$> get >>= put+ get++hex :: ByteString -> ByteString+hex s =+ case BS16.decodeBase16 s of+ Right x -> x+ Left e -> internalError $ T.unpack e++singleContract :: Text -> Text -> IO (Maybe ByteString)+singleContract x s =+ solidity x [i|+ pragma experimental ABIEncoderV2;+ contract ${x} { ${s} }+ |]++defaultDataLocation :: AbiType -> Text+defaultDataLocation t =+ if (case t of+ AbiBytesDynamicType -> True+ AbiStringType -> True+ AbiArrayDynamicType _ -> True+ AbiArrayType _ _ -> True+ _ -> False)+ then "memory"+ else ""++runFunction :: App m => Text -> ByteString -> m (Maybe ByteString)+runFunction c input = do+ x <- liftIO $ singleContract "X" c+ runSimpleVM (fromJust x) input++runStatements :: App m => Text -> [AbiValue] -> AbiType -> m (Maybe ByteString)+runStatements stmts args t = do+ let params =+ T.intercalate ", "+ (map (\(x, c) -> abiTypeSolidity (abiValueType x)+ <> " " <> defaultDataLocation (abiValueType x)+ <> " " <> T.pack [c])+ (zip args "abcdefg"))+ s =+ "foo(" <> T.intercalate ","+ (map (abiTypeSolidity . abiValueType) args) <> ")"++ runFunction [i|+ function foo(${params}) public pure returns (${abiTypeSolidity t} ${defaultDataLocation t} x) {+ ${stmts}+ }+ |] (abiMethod s (AbiTuple $ V.fromList args))++getStaticAbiArgs :: Int -> VM Symbolic s -> [Expr EWord]+getStaticAbiArgs n vm =+ let cd = vm.state.calldata+ in decodeStaticArgs 4 n cd++-- includes shaving off 4 byte function sig+decodeAbiValues :: [AbiType] -> ByteString -> [AbiValue]+decodeAbiValues types bs =+ let xy = case decodeAbiValue (AbiTupleType $ V.fromList types) (BS.fromStrict (BS.drop 4 bs)) of+ AbiTuple xy' -> xy'+ _ -> internalError "AbiTuple expected"+ in V.toList xy++newtype Bytes = Bytes ByteString+ deriving Eq++instance Show Bytes where+ showsPrec _ (Bytes x) _ = show (BS.unpack x)++instance Arbitrary Bytes where+ arbitrary = fmap (Bytes . BS.pack) arbitrary++newtype RLPData = RLPData RLP+ deriving (Eq, Show)++-- bias towards bytestring to try to avoid infinite recursion+instance Arbitrary RLPData where+ arbitrary = frequency+ [(5, do+ Bytes bytes <- arbitrary+ return $ RLPData $ BS bytes)+ , (1, do+ k <- choose (0,10)+ ls <- vectorOf k arbitrary+ return $ RLPData $ List [r | RLPData r <- ls])+ ]++instance Arbitrary Word128 where+ arbitrary = liftM2 fromHiAndLo arbitrary arbitrary++instance Arbitrary Word160 where+ arbitrary = liftM2 fromHiAndLo arbitrary arbitrary++instance Arbitrary Word256 where+ arbitrary = liftM2 fromHiAndLo arbitrary arbitrary++instance Arbitrary W64 where+ arbitrary = fmap W64 arbitrary++instance Arbitrary W256 where+ arbitrary = fmap W256 arbitrary++instance Arbitrary Addr where+ arbitrary = fmap Addr arbitrary++instance Arbitrary (Expr EAddr) where+ arbitrary = oneof+ [ fmap LitAddr arbitrary+ , fmap SymAddr (genName "addr")+ ]++instance Arbitrary (Expr Storage) where+ arbitrary = sized genStorage++instance Arbitrary (Expr EWord) where+ arbitrary = sized defaultWord++instance Arbitrary (Expr Byte) where+ arbitrary = sized genByte++instance Arbitrary (Expr Buf) where+ arbitrary = sized defaultBuf++instance Arbitrary (Expr End) where+ arbitrary = sized genEnd++instance Arbitrary (ContractCode) where+ arbitrary = oneof+ [ fmap UnknownCode arbitrary+ , liftM2 InitCode arbitrary arbitrary+ , fmap RuntimeCode arbitrary+ ]++instance Arbitrary (RuntimeCode) where+ arbitrary = oneof+ [ fmap ConcreteRuntimeCode arbitrary+ , fmap SymbolicRuntimeCode arbitrary+ ]++instance Arbitrary (V.Vector (Expr Byte)) where+ arbitrary = fmap V.fromList (listOf1 arbitrary)++instance Arbitrary (Expr EContract) where+ arbitrary = sized genEContract++-- LitOnly+newtype LitOnly a = LitOnly a+ deriving (Show, Eq)++newtype LitWord (sz :: Nat) = LitWord (Expr EWord)+ deriving (Show)++instance (KnownNat sz) => Arbitrary (LitWord sz) where+ arbitrary = LitWord <$> genLit (fromInteger v)+ where+ v = natVal (Proxy @sz)++instance Arbitrary (LitOnly (Expr Byte)) where+ arbitrary = LitOnly . LitByte <$> arbitrary++instance Arbitrary (LitOnly (Expr EWord)) where+ arbitrary = LitOnly . Lit <$> arbitrary++instance Arbitrary (LitOnly (Expr Buf)) where+ arbitrary = LitOnly . ConcreteBuf <$> arbitrary++genEContract :: Int -> Gen (Expr EContract)+genEContract sz = do+ c <- arbitrary+ b <- defaultWord sz+ n <- arbitrary+ s <- genStorage sz+ pure $ C c s b n++-- ZeroDepthWord+newtype ZeroDepthWord = ZeroDepthWord (Expr EWord)+ deriving (Show, Eq)++instance Arbitrary ZeroDepthWord where+ arbitrary = do+ fmap ZeroDepthWord . sized $ genWord 0++-- WriteWordBuf+newtype WriteWordBuf = WriteWordBuf (Expr Buf)+ deriving (Show, Eq)++instance Arbitrary WriteWordBuf where+ arbitrary = do+ let mkBuf = oneof+ [ pure $ ConcreteBuf "" -- empty+ , fmap ConcreteBuf arbitrary -- concrete+ , sized (genBuf 100) -- overlapping writes+ , arbitrary -- sparse writes+ ]+ fmap WriteWordBuf mkBuf++-- GenCopySliceBuf+newtype GenCopySliceBuf = GenCopySliceBuf (Expr Buf)+ deriving (Show, Eq)++instance Arbitrary GenCopySliceBuf where+ arbitrary = do+ let mkBuf = oneof+ [ pure $ ConcreteBuf ""+ , fmap ConcreteBuf arbitrary+ , arbitrary+ ]+ fmap GenCopySliceBuf mkBuf++-- GenWriteStorageExpr+newtype GenWriteStorageExpr = GenWriteStorageExpr (Expr EWord, Expr Storage)+ deriving (Show, Eq)++instance Arbitrary GenWriteStorageExpr where+ arbitrary = do+ slot <- arbitrary+ let mkStore = oneof+ [ pure $ ConcreteStore mempty+ , fmap ConcreteStore arbitrary+ , do+ -- generate some write chains where we know that at least one+ -- write matches either the input addr, or both the input+ -- addr and slot+ let addWrites :: Expr Storage -> Int -> Gen (Expr Storage)+ addWrites b 0 = pure b+ addWrites b n = liftM3 SStore arbitrary arbitrary (addWrites b (n - 1))+ s <- arbitrary+ addMatch <- fmap (SStore slot) arbitrary+ let withMatch = addMatch s+ newWrites <- oneof [ pure 0, pure 1, fmap (`mod` 5) arbitrary ]+ addWrites withMatch newWrites+ , arbitrary+ ]+ store <- mkStore+ pure $ GenWriteStorageExpr (slot, store)++-- GenWriteByteIdx+newtype GenWriteByteIdx = GenWriteByteIdx (Expr EWord)+ deriving (Show, Eq)++instance Arbitrary GenWriteByteIdx where+ arbitrary = do+ -- 1st: can never overflow an Int+ -- 2nd: can overflow an Int+ let mkIdx = frequency [ (10, genLit 1_000_000) , (1, fmap Lit arbitrary) ]+ fmap GenWriteByteIdx mkIdx++newtype LitProp = LitProp Prop+ deriving (Show, Eq)++instance Arbitrary LitProp where+ arbitrary = LitProp <$> sized (genProp True)+++newtype StorageExp = StorageExp (Expr EWord)+ deriving (Show, Eq)++instance Arbitrary StorageExp where+ arbitrary = StorageExp <$> (genStorageExp)++genStorageExp :: Gen (Expr EWord)+genStorageExp = do+ fromPos <- genSlot+ storage <- genStorageWrites+ pure $ SLoad fromPos storage++genSlot :: Gen (Expr EWord)+genSlot = frequency [ (1, do+ buf <- genConcreteBufSlot 64+ case buf of+ (ConcreteBuf b) -> do+ key <- genLit 10+ pure $ Expr.MappingSlot b key+ _ -> internalError "impossible"+ )+ -- map element+ ,(2, do+ l <- genLit 10+ buf <- genConcreteBufSlot 64+ pure $ Add (Keccak buf) l)+ -- Array element+ ,(2, do+ l <- genLit 10+ buf <- genConcreteBufSlot 32+ pure $ Add (Keccak buf) l)+ -- member of the Contract+ ,(2, pure $ Lit 20)+ -- array element+ ,(2, do+ arrayNum :: Int <- arbitrary+ offs :: W256 <- arbitrary+ pure $ Lit $ fst (Expr.preImages !! (arrayNum `mod` 3)) + (offs `mod` 3))+ -- random stuff+ ,(1, pure $ Lit (maxBound :: W256))+ ]++-- Generates an N-long buffer, all with the same value, at most 8 different ones+genConcreteBufSlot :: Int -> Gen (Expr Buf)+genConcreteBufSlot len = do+ b :: Word8 <- arbitrary+ pure $ ConcreteBuf $ BS.pack ([ 0 | _ <- [0..(len-2)]] ++ [b])++genStorageWrites :: Gen (Expr Storage)+genStorageWrites = do+ toSlot <- genSlot+ val <- genLit (maxBound :: W256)+ store <- frequency [ (3, pure $ AbstractStore (SymAddr "") Nothing)+ , (2, genStorageWrites)+ ]+ pure $ SStore toSlot val store++instance Arbitrary Prop where+ arbitrary = sized (genProp False)++genProps :: Bool -> Int -> Gen [Prop]+genProps onlyLits sz2 = listOf $ genProp onlyLits sz2++genProp :: Bool -> Int -> Gen (Prop)+genProp _ 0 = PBool <$> arbitrary+genProp onlyLits sz = oneof+ [ liftM2 PEq subWord subWord+ , liftM2 PLT subWord subWord+ , liftM2 PGT subWord subWord+ , liftM2 PLEq subWord subWord+ , liftM2 PGEq subWord subWord+ , fmap PNeg subProp+ , liftM2 PAnd subProp subProp+ , liftM2 POr subProp subProp+ , liftM2 PImpl subProp subProp+ ]+ where+ subWord = if onlyLits then frequency [(2, Lit <$> arbitrary)+ ,(1, pure $ Lit 0)+ ,(1, pure $ Lit Expr.maxLit)+ ]+ else genWord 1 (sz `div` 2)+ subProp = genProp onlyLits (sz `div` 2)++genByte :: Int -> Gen (Expr Byte)+genByte 0 = fmap LitByte arbitrary+genByte sz = oneof+ [ liftM2 IndexWord subWord subWord+ , liftM2 ReadByte subWord subBuf+ ]+ where+ subWord = defaultWord (sz `div` 10)+ subBuf = defaultBuf (sz `div` 10)++genLit :: W256 -> Gen (Expr EWord)+genLit bound = do+ w <- arbitrary+ pure $ Lit (w `mod` bound)++genNat :: Gen Int+genNat = fmap unsafeInto (arbitrary :: Gen Natural)++genName :: String -> Gen Text+-- In order not to generate SMT reserved words, we prepend with "esc_"+genName ty = fmap (T.pack . (("esc_" <> ty <> "_") <> )) $ listOf1 (oneof . (fmap pure) $ ['a'..'z'] <> ['A'..'Z'])++genEnd :: Int -> Gen (Expr End)+genEnd 0 = oneof+ [ fmap (Failure mempty mempty . UnrecognizedOpcode) arbitrary+ , pure $ Failure mempty mempty IllegalOverflow+ , pure $ Failure mempty mempty SelfDestruction+ ]+genEnd sz = oneof+ [ liftM3 Failure subProp (pure mempty) (fmap Revert subBuf)+ , liftM4 Success subProp (pure mempty) subBuf arbitrary+ , liftM3 ITE subWord subEnd subEnd+ -- TODO Partial+ ]+ where+ subBuf = defaultBuf (sz `div` 2)+ subWord = defaultWord (sz `div` 2)+ subEnd = genEnd (sz `div` 2)+ subProp = genProps False (sz `div` 2)++genSmallLit :: W256 -> Gen (Expr EWord)+genSmallLit m = do+ val :: W256 <- arbitrary+ pure $ Lit (val `mod` m)++genWord :: Int -> Int -> Gen (Expr EWord)+genWord litFreq 0 = frequency+ [ (litFreq, do+ val <- frequency+ [ (10, fmap (`mod` 100) arbitrary)+ , (1, pure 0)+ , (1, pure Expr.maxLit)+ , (1, arbitrary)+ ]+ pure $ Lit val+ )+ , (1, oneof+ [ pure Origin+ , pure Coinbase+ , pure Timestamp+ , pure BlockNumber+ , pure PrevRandao+ , pure GasLimit+ , pure ChainId+ , pure BaseFee+ --, liftM2 SelfBalance arbitrary arbitrary+ --, liftM2 Gas arbitrary arbitrary+ , fmap Lit arbitrary+ , fmap Var (genName "word")+ ]+ )+ ]+genWord litFreq sz = frequency+ [ (litFreq, do+ val <- frequency+ [ (10, fmap (`mod` 100) arbitrary)+ , (1, arbitrary)+ ]+ pure $ Lit val+ )+ , (1, oneof+ [ liftM2 Add subWord subWord+ , liftM2 Sub subWord subWord+ , liftM2 Mul subWord subWord+ , liftM2 Div subWord subWord+ , liftM2 SDiv subWord subWord+ , liftM2 Mod subWord subWord+ , liftM2 SMod subWord subWord+ --, liftM3 AddMod subWord subWord subWord+ --, liftM3 MulMod subWord subWord subWord -- it works, but it's VERY SLOW+ --, liftM2 Exp subWord litWord+ , liftM2 SEx subWord subWord+ , liftM2 Min subWord subWord+ , liftM2 LT subWord subWord+ , liftM2 GT subWord subWord+ , liftM2 LEq subWord subWord+ , liftM2 GEq subWord subWord+ , liftM2 SLT subWord subWord+ --, liftM2 SGT subWord subWord+ , liftM2 Eq subWord subWord+ , fmap IsZero subWord+ , liftM2 And subWord subWord+ , liftM2 Or subWord subWord+ , liftM2 Xor subWord subWord+ , fmap Not subWord+ , liftM2 SHL subWord subWord+ , liftM2 SHR subWord subWord+ , liftM2 SAR subWord subWord+ , fmap BlockHash subWord+ --, liftM3 Balance arbitrary arbitrary subWord+ --, fmap CodeSize subWord+ --, fmap ExtCodeHash subWord+ , fmap Keccak subBuf+ , fmap SHA256 subBuf+ , liftM2 SLoad subWord subStore+ , liftM2 ReadWord subWord subBuf+ , fmap BufLength subBuf+ , do+ one <- subByte+ two <- subByte+ three <- subByte+ four <- subByte+ five <- subByte+ six <- subByte+ seven <- subByte+ eight <- subByte+ nine <- subByte+ ten <- subByte+ eleven <- subByte+ twelve <- subByte+ thirteen <- subByte+ fourteen <- subByte+ fifteen <- subByte+ sixteen <- subByte+ seventeen <- subByte+ eighteen <- subByte+ nineteen <- subByte+ twenty <- subByte+ twentyone <- subByte+ twentytwo <- subByte+ twentythree <- subByte+ twentyfour <- subByte+ twentyfive <- subByte+ twentysix <- subByte+ twentyseven <- subByte+ twentyeight <- subByte+ twentynine <- subByte+ thirty <- subByte+ thirtyone <- subByte+ thirtytwo <- subByte+ pure $ JoinBytes+ one two three four five six seven eight nine ten+ eleven twelve thirteen fourteen fifteen sixteen+ seventeen eighteen nineteen twenty twentyone+ twentytwo twentythree twentyfour twentyfive+ twentysix twentyseven twentyeight twentynine+ thirty thirtyone thirtytwo+ ])+ ]+ where+ subWord = genWord litFreq (sz `div` 5)+ subBuf = defaultBuf (sz `div` 10)+ subStore = genStorage (sz `div` 10)+ subByte = genByte (sz `div` 10)++genWordArith :: Int -> Int -> Gen (Expr EWord)+genWordArith litFreq 0 = frequency+ [ (litFreq, fmap Lit arbitrary)+ , (1, oneof [ fmap Lit arbitrary ])+ ]+genWordArith litFreq sz = frequency+ [ (litFreq, fmap Lit arbitrary)+ , (20, frequency+ [ (20, liftM2 Add subWord subWord)+ , (20, liftM2 Sub subWord subWord)+ , (20, liftM2 Mul subWord subWord)+ , (20, liftM2 SEx subWord subWord)+ , (20, liftM2 Xor subWord subWord)+ -- these reduce variability+ , (3 , liftM2 Min subWord subWord)+ , (3 , liftM2 Div subWord subWord)+ , (3 , liftM2 SDiv subWord subWord)+ , (3 , liftM2 Mod subWord subWord)+ , (3 , liftM2 SMod subWord subWord)+ , (3 , liftM2 SHL subWord subWord)+ , (3 , liftM2 SHR subWord subWord)+ , (3 , liftM2 SAR subWord subWord)+ , (3 , liftM2 Or subWord subWord)+ -- comparisons, reducing variability greatly+ , (1 , liftM2 LEq subWord subWord)+ , (1 , liftM2 GEq subWord subWord)+ , (1 , liftM2 SLT subWord subWord)+ --(1, , liftM2 SGT subWord subWord+ , (1 , liftM2 Eq subWord subWord)+ , (1 , liftM2 And subWord subWord)+ , (1 , fmap IsZero subWord )+ -- Expensive below+ --(1, liftM3 AddMod subWord subWord subWord+ --(1, liftM3 MulMod subWord subWord subWord+ --(1, liftM2 Exp subWord litWord+ ])+ ]+ where+ subWord = genWordArith (litFreq `div` 2) (sz `div` 2)++-- Used to check for unsimplified expressions+newtype FoundBad = FoundBad { bad :: Bool } deriving (Show)+initFoundBad :: FoundBad+initFoundBad = FoundBad { bad = False }++-- Finds SLoad -> SStore. This should not occur in most scenarios+-- as we can simplify them away+badStoresInExpr :: Expr a -> Bool+badStoresInExpr expr = bad+ where+ FoundBad bad = execState (mapExprM findBadStore expr) initFoundBad+ findBadStore :: Expr a-> State FoundBad (Expr a)+ findBadStore e = case e of+ (SLoad _ (SStore _ _ _)) -> do+ put (FoundBad { bad = True })+ pure e+ _ -> pure e++defaultBuf :: Int -> Gen (Expr Buf)+defaultBuf = genBuf (4_000_000)++defaultWord :: Int -> Gen (Expr EWord)+defaultWord = genWord 10++maybeBoundedLit :: W256 -> Gen (Expr EWord)+maybeBoundedLit bound = do+ o <- (arbitrary :: Gen (Expr EWord))+ pure $ case o of+ Lit w -> Lit $ w `mod` bound+ _ -> o++genBuf :: W256 -> Int -> Gen (Expr Buf)+genBuf _ 0 = oneof+ [ fmap AbstractBuf (genName "buf")+ , fmap ConcreteBuf arbitrary+ ]+genBuf bound sz = oneof+ [ liftM3 WriteWord (maybeBoundedLit bound) subWord subBuf+ , liftM3 WriteByte (maybeBoundedLit bound) subByte subBuf+ -- we don't generate copyslice instances where:+ -- - size is abstract+ -- - size > 100 (due to unrolling in SMT.hs)+ -- - literal dstOffsets are > 4,000,000 (due to unrolling in SMT.hs)+ -- n.b. that 4,000,000 is the theoretical maximum memory size given a 30,000,000 block gas limit+ , liftM5 CopySlice subWord (maybeBoundedLit bound) smolLitWord subBuf subBuf+ ]+ where+ -- copySlice gets unrolled in the generated SMT so we can't go too crazy here+ smolLitWord = do+ w <- arbitrary+ pure $ Lit (w `mod` 100)+ subWord = defaultWord (sz `div` 5)+ subByte = genByte (sz `div` 10)+ subBuf = genBuf bound (sz `div` 10)++genStorage :: Int -> Gen (Expr Storage)+genStorage 0 = oneof+ [ liftM2 AbstractStore arbitrary (pure Nothing)+ , fmap ConcreteStore arbitrary+ ]+genStorage sz = liftM3 SStore key val subStore+ where+ subStore = genStorage (sz `div` 10)+ val = defaultWord (sz `div` 5)+ key = genStorageKey++genStorageKey :: Gen (Expr EWord)+genStorageKey = frequency+ -- array slot+ [ (4, liftM2 Expr.ArraySlotWithOffset (genByteStringKey 32) (genSmallLit 5))+ , (4, fmap Expr.ArraySlotZero (genByteStringKey 32))+ -- mapping slot+ , (8, liftM2 Expr.MappingSlot (genByteStringKey 64) (genSmallLit 5))+ -- small slot+ , (4, genLit 20)+ -- unrecognized slot type+ , (1, genSmallLit 5)+ ]++genByteStringKey :: W256 -> Gen (ByteString)+genByteStringKey len = do+ b :: Word8 <- arbitrary+ pure $ BS.pack ([ 0 | _ <- [0..(len-2)]] ++ [b `mod` 5])++-- GenWriteStorageLoad+newtype GenWriteStorageLoad = GenWriteStorageLoad (Expr EWord)+ deriving (Show, Eq)++instance Arbitrary GenWriteStorageLoad where+ arbitrary = do+ load <- genStorageLoad 10+ pure $ GenWriteStorageLoad load++ where+ genStorageLoad :: Int -> Gen (Expr EWord)+ genStorageLoad sz = liftM2 SLoad key subStore+ where+ subStore = genStorage (sz `div` 10)+ key = genStorageKey++data Invocation+ = SolidityCall Text [AbiValue]+ deriving Show++assertSolidityComputation :: App m => Invocation -> AbiValue -> m ()+assertSolidityComputation (SolidityCall s args) x =+ do y <- runStatements s args (abiValueType x)+ liftIO $ assertEqual (T.unpack s)+ (fmap Bytes (Just (encodeAbiValue x)))+ (fmap Bytes y)++bothM :: (Monad m) => (a -> m b) -> (a, a) -> m (b, b)+bothM f (a, a') = do+ b <- f a+ b' <- f a'+ return (b, b')++applyPattern :: String -> TestTree -> TestTree+applyPattern p = localOption (TestPattern (parseExpr p))++checkBadCheatCode :: Text -> Postcondition s+checkBadCheatCode sig _ = \case+ (Failure _ _ (BadCheatCode s)) -> (ConcreteBuf $ into s.unFunctionSelector) ./= (ConcreteBuf $ selector sig)+ _ -> PBool True++allBranchesFail :: App m => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End))+allBranchesFail = checkPost (Just p)+ where+ p _ = \case+ Success _ _ _ _ -> PBool False+ _ -> PBool True++reachableUserAsserts :: App m => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End))+reachableUserAsserts = checkPost (Just $ checkAssertions [0x01])++checkPost :: App m => Maybe (Postcondition RealWorld) -> ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) checkPost post c sig = do (e, res) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c sig [] defaultVeriOpts Nothing post