hevm 0.51.3 → 0.52.0
raw patch · 55 files changed
+10119/−12270 lines, 55 filesdep +pretty-hexdep +system-cxx-std-libdep −ansi-wl-pprintdep −tuplePVP ok
version bump matches the API change (PVP)
Dependencies added: pretty-hex, system-cxx-std-lib
Dependencies removed: ansi-wl-pprint, tuple
API changes (from Hackage documentation)
- EVM.Concrete: (^) :: W256 -> W256 -> W256
- EVM.Concrete: readByteOrZero :: Int -> ByteString -> Word8
- EVM.Concrete: wordAt :: Int -> ByteString -> W256
- EVM.Dapp: ConcreteTest :: Text -> Test
- EVM.Dapp: InvariantTest :: Text -> Test
- EVM.Dapp: SymbolicTest :: Text -> Test
- EVM.Dapp: data Test
- EVM.Dapp: extractSig :: Test -> Text
- EVM.Dapp: instance GHC.Show.Show EVM.Dapp.Test
- EVM.Dapp: mkTest :: Text -> Maybe Test
- EVM.Debug: Debug :: Mode
- EVM.Debug: JsonTrace :: Mode
- EVM.Debug: Run :: Mode
- EVM.Debug: data Mode
- EVM.Debug: instance GHC.Classes.Eq EVM.Debug.Mode
- EVM.Debug: instance GHC.Show.Show EVM.Debug.Mode
- EVM.Debug: object :: [(Doc, Doc)] -> Doc
- EVM.Debug: prettyContract :: Contract -> Doc
- EVM.Debug: prettyContracts :: Map Addr Contract -> Doc
- EVM.Debug: srcMapCode :: SourceCache -> SrcMap -> Maybe ByteString
- EVM.Debug: srcMapCodePos :: SourceCache -> SrcMap -> Maybe (FilePath, Int)
- EVM.Dev: analyzeDai :: IO ()
- EVM.Dev: analyzeDeposit :: IO ()
- EVM.Dev: analyzeVat :: IO ()
- EVM.Dev: buildExpr :: SolverGroup -> ByteString -> IO (Expr End)
- EVM.Dev: checkEquiv :: Typeable a => Expr a -> Expr a -> IO ()
- EVM.Dev: dai :: IO ByteString
- EVM.Dev: daiExpr :: IO (Expr End)
- EVM.Dev: doTest :: IO ()
- EVM.Dev: initVm :: ByteString -> VM
- EVM.Dev: reachable' :: Bool -> ByteString -> IO ()
- EVM.Dev: runDappTest :: FilePath -> IO ()
- EVM.Dev: safeAdd :: IO ByteString
- EVM.Dev: showExpr :: ByteString -> IO ()
- EVM.Dev: summaryStore :: IO ByteString
- EVM.Dev: testContract :: IO ByteString
- EVM.Dev: testOpts :: SolverGroup -> FilePath -> FilePath -> IO UnitTestOptions
- EVM.Dev: vat :: IO ByteString
- EVM.Expr: concPrefix :: Expr Buf -> Maybe Integer
- EVM.Facts: BalanceFact :: Addr -> W256 -> Fact
- EVM.Facts: CodeFact :: Addr -> ByteString -> Fact
- EVM.Facts: Data :: ASCII -> Data
- EVM.Facts: File :: Path -> Data -> File
- EVM.Facts: NonceFact :: Addr -> W256 -> Fact
- EVM.Facts: Path :: [ASCII] -> ASCII -> Path
- EVM.Facts: StorageFact :: Addr -> W256 -> W256 -> Fact
- EVM.Facts: [$sel:addr:BalanceFact] :: Fact -> Addr
- EVM.Facts: [$sel:blob:BalanceFact] :: Fact -> ByteString
- EVM.Facts: [$sel:dataASCII:Data] :: Data -> ASCII
- EVM.Facts: [$sel:fileData:File] :: File -> Data
- EVM.Facts: [$sel:filePath:File] :: File -> Path
- EVM.Facts: [$sel:what:BalanceFact] :: Fact -> W256
- EVM.Facts: [$sel:which:BalanceFact] :: Fact -> W256
- EVM.Facts: apply :: VM -> Set Fact -> VM
- EVM.Facts: applyCache :: VM -> Set Fact -> VM
- EVM.Facts: cacheFacts :: Cache -> Set Fact
- EVM.Facts: contractFacts :: Addr -> Contract -> Map W256 (Map W256 W256) -> [Fact]
- EVM.Facts: data Fact
- EVM.Facts: data File
- EVM.Facts: data Path
- EVM.Facts: factToFile :: Fact -> File
- EVM.Facts: fileToFact :: File -> Maybe Fact
- EVM.Facts: instance EVM.Facts.AsASCII Data.ByteString.Internal.Type.ByteString
- EVM.Facts: instance EVM.Facts.AsASCII EVM.Types.Addr
- EVM.Facts: instance EVM.Facts.AsASCII EVM.Types.W256
- EVM.Facts: instance GHC.Classes.Eq EVM.Facts.Data
- EVM.Facts: instance GHC.Classes.Eq EVM.Facts.Fact
- EVM.Facts: instance GHC.Classes.Eq EVM.Facts.File
- EVM.Facts: instance GHC.Classes.Eq EVM.Facts.Path
- EVM.Facts: instance GHC.Classes.Ord EVM.Facts.Data
- EVM.Facts: instance GHC.Classes.Ord EVM.Facts.Fact
- EVM.Facts: instance GHC.Classes.Ord EVM.Facts.File
- EVM.Facts: instance GHC.Classes.Ord EVM.Facts.Path
- EVM.Facts: instance GHC.Show.Show EVM.Facts.Data
- EVM.Facts: instance GHC.Show.Show EVM.Facts.Fact
- EVM.Facts: instance GHC.Show.Show EVM.Facts.File
- EVM.Facts: instance GHC.Show.Show EVM.Facts.Path
- EVM.Facts: newtype Data
- EVM.Facts: vmFacts :: VM -> Set Fact
- EVM.Facts.Git: RepoAt :: String -> RepoAt
- EVM.Facts.Git: instance GHC.Classes.Eq EVM.Facts.Git.RepoAt
- EVM.Facts.Git: instance GHC.Classes.Ord EVM.Facts.Git.RepoAt
- EVM.Facts.Git: instance GHC.Show.Show EVM.Facts.Git.RepoAt
- EVM.Facts.Git: loadFacts :: RepoAt -> IO (Set Fact)
- EVM.Facts.Git: newtype RepoAt
- EVM.Facts.Git: saveFacts :: RepoAt -> Set Fact -> IO ()
- EVM.FeeSchedule: berlin :: Num n => FeeSchedule n
- EVM.FeeSchedule: eip1108 :: EIP n
- EVM.FeeSchedule: eip150 :: EIP n
- EVM.FeeSchedule: eip160 :: EIP n
- EVM.FeeSchedule: eip1884 :: EIP n
- EVM.FeeSchedule: eip2028 :: EIP n
- EVM.FeeSchedule: eip2200 :: EIP n
- EVM.FeeSchedule: eip2929 :: EIP n
- EVM.FeeSchedule: homestead :: Num n => FeeSchedule n
- EVM.FeeSchedule: istanbul :: Num n => FeeSchedule n
- EVM.FeeSchedule: metropolis :: Num n => FeeSchedule n
- EVM.FeeSchedule: type EIP n = Num n => FeeSchedule n -> FeeSchedule n
- EVM.Hexdump: paddedShowHex :: (Show a, Integral a) => Int -> a -> String
- EVM.Hexdump: prettyHex :: Int -> ByteString -> String
- EVM.Hexdump: simpleHex :: ByteString -> String
- EVM.Patricia: DB :: Free (KV k v) a -> DB k v a
- EVM.Patricia: Empty :: Node
- EVM.Patricia: Full :: Seq Ref -> ByteString -> Node
- EVM.Patricia: Get :: k -> (v -> a) -> KV k v a
- EVM.Patricia: Hash :: ByteString -> Ref
- EVM.Patricia: Literal :: Node -> Ref
- EVM.Patricia: Put :: k -> v -> a -> KV k v a
- EVM.Patricia: Shortcut :: Path -> Either Ref ByteString -> Node
- EVM.Patricia: addPrefix :: Path -> Node -> NodeDB Node
- EVM.Patricia: calcRoot :: [(ByteString, ByteString)] -> Maybe ByteString
- EVM.Patricia: data KV k v a
- EVM.Patricia: data Node
- EVM.Patricia: data Ref
- EVM.Patricia: delete :: Node -> Path -> NodeDB Node
- EVM.Patricia: emptyRef :: Ref
- EVM.Patricia: emptyRefs :: Seq Ref
- EVM.Patricia: encodePath :: Path -> Bool -> ByteString
- EVM.Patricia: getNode :: Ref -> NodeDB Node
- EVM.Patricia: getVal :: Path -> Node -> NodeDB ByteString
- EVM.Patricia: insert :: Ref -> ByteString -> ByteString -> NodeDB Ref
- EVM.Patricia: insertDB :: k -> v -> DB k v ()
- EVM.Patricia: insertRef :: Ref -> Path -> ByteString -> NodeDB Ref
- EVM.Patricia: insertValues :: [(ByteString, ByteString)] -> Maybe Ref
- EVM.Patricia: instance GHC.Base.Applicative (EVM.Patricia.DB k v)
- EVM.Patricia: instance GHC.Base.Functor (EVM.Patricia.DB k v)
- EVM.Patricia: instance GHC.Base.Functor (EVM.Patricia.KV k v)
- EVM.Patricia: instance GHC.Base.Monad (EVM.Patricia.DB k v)
- EVM.Patricia: instance GHC.Classes.Eq EVM.Patricia.Node
- EVM.Patricia: instance GHC.Classes.Eq EVM.Patricia.Ref
- EVM.Patricia: instance GHC.Show.Show (EVM.Patricia.NodeDB EVM.Patricia.Node)
- EVM.Patricia: instance GHC.Show.Show EVM.Patricia.Node
- EVM.Patricia: instance GHC.Show.Show EVM.Patricia.Ref
- EVM.Patricia: lookupDB :: k -> DB k v v
- EVM.Patricia: lookupIn :: Ref -> ByteString -> NodeDB ByteString
- EVM.Patricia: lookupPath :: Ref -> Path -> NodeDB ByteString
- EVM.Patricia: newtype DB k v a
- EVM.Patricia: putNode :: Node -> NodeDB Ref
- EVM.Patricia: rlpNode :: Node -> RLP
- EVM.Patricia: rlpRef :: Ref -> RLP
- EVM.Patricia: runDB :: Monad m => (k -> v -> m ()) -> (k -> m v) -> DB k v a -> m a
- EVM.Patricia: runMapDB :: Ord k => DB k v a -> MapDB k v a
- EVM.Patricia: runTrie :: DB ByteString ByteString a -> Trie a
- EVM.Patricia: type MapDB k v a = StateT (Map k v) Maybe a
- EVM.Patricia: type NodeDB = DB ByteString Node
- EVM.Patricia: type Path = [Nibble]
- EVM.Patricia: type Trie = StateT Ref NodeDB
- EVM.Patricia: update :: Node -> Path -> ByteString -> NodeDB Node
- EVM.SMT: interpret2DArray :: Map Symbol Term -> Term -> W256 -> W256 -> W256
- EVM.SMT: parseFrameCtx :: Text -> Expr EWord
- EVM.SMT: referencedBlockContext' :: Prop -> [(Builder, [Prop])]
- EVM.SMT: referencedBlockContextGo :: Expr a -> [(Builder, [Prop])]
- EVM.SMT: referencedBufs' :: Prop -> [Builder]
- EVM.SMT: referencedBufsGo :: Expr a -> [Builder]
- EVM.SMT: referencedFrameContext' :: Prop -> [(Builder, [Prop])]
- EVM.SMT: referencedFrameContextGo :: Expr a -> [(Builder, [Prop])]
- EVM.SMT: referencedVars' :: Prop -> [Builder]
- EVM.SMT: referencedVarsGo :: Expr a -> [Builder]
- EVM.Solidity: solidity' :: Text -> IO (Text, Text)
- EVM.Solidity: yul' :: Text -> IO (Text, Text)
- EVM.Stepper: [Run] :: Action VM
- EVM.Stepper: entering :: Text -> Stepper a -> Stepper a
- EVM.StorageLayout: findContractDefinition :: DappInfo -> SolcContract -> Maybe Value
- EVM.StorageLayout: grokDeclarationType :: Value -> SlotType
- EVM.StorageLayout: grokMappingType :: [Value] -> SlotType
- EVM.StorageLayout: grokValueType :: Value -> AbiType
- EVM.StorageLayout: isStorageVariableDeclaration :: Value -> Bool
- EVM.StorageLayout: nodeIs :: Text -> Value -> Bool
- EVM.StorageLayout: slotTypeForDeclaration :: Value -> SlotType
- EVM.StorageLayout: storageLayout :: DappInfo -> SolcContract -> [Text]
- EVM.StorageLayout: storageVariablesForContract :: Value -> Maybe [Text]
- EVM.SymExec: Sig :: Text -> [AbiType] -> Sig
- EVM.SymExec: data Sig
- EVM.SymExec: evalProp :: Prop -> Prop
- EVM.TTY: AbiPane :: Name
- EVM.TTY: BrowserPane :: Name
- EVM.TTY: BytecodePane :: Name
- EVM.TTY: Continue :: Stepper a -> Continuation a
- EVM.TTY: Pager :: Name
- EVM.TTY: SolidityPane :: Name
- EVM.TTY: StackPane :: Name
- EVM.TTY: Step :: !Int -> StepMode
- EVM.TTY: StepUntil :: Pred VM -> StepMode
- EVM.TTY: Stopped :: a -> Continuation a
- EVM.TTY: TestPickerPane :: Name
- EVM.TTY: TracePane :: Name
- EVM.TTY: UiBrowserState :: List Name (Addr, Contract) -> UiVmState -> UiBrowserState
- EVM.TTY: UiTestPickerState :: List Name (Text, Text) -> DappInfo -> UnitTestOptions -> UiTestPickerState
- EVM.TTY: UiVmState :: VM -> Int -> Map Int (VM, Stepper ()) -> Stepper () -> Bool -> UnitTestOptions -> UiVmState
- EVM.TTY: ViewContracts :: UiBrowserState -> UiState
- EVM.TTY: ViewHelp :: UiVmState -> UiState
- EVM.TTY: ViewPicker :: UiTestPickerState -> UiState
- EVM.TTY: ViewVm :: UiVmState -> UiState
- EVM.TTY: [$sel:contracts:UiBrowserState] :: UiBrowserState -> List Name (Addr, Contract)
- EVM.TTY: [$sel:dapp:UiTestPickerState] :: UiTestPickerState -> DappInfo
- EVM.TTY: [$sel:opts:UiTestPickerState] :: UiTestPickerState -> UnitTestOptions
- EVM.TTY: [$sel:showMemory:UiVmState] :: UiVmState -> Bool
- EVM.TTY: [$sel:snapshots:UiVmState] :: UiVmState -> Map Int (VM, Stepper ())
- EVM.TTY: [$sel:step:UiVmState] :: UiVmState -> Int
- EVM.TTY: [$sel:stepper:UiVmState] :: UiVmState -> Stepper ()
- EVM.TTY: [$sel:testOpts:UiVmState] :: UiVmState -> UnitTestOptions
- EVM.TTY: [$sel:tests:UiTestPickerState] :: UiTestPickerState -> List Name (Text, Text)
- EVM.TTY: [$sel:vm:UiBrowserState] :: UiBrowserState -> UiVmState
- EVM.TTY: [$sel:vm:UiVmState] :: UiVmState -> VM
- EVM.TTY: _ViewContracts :: Prism' UiState UiBrowserState
- EVM.TTY: _ViewHelp :: Prism' UiState UiVmState
- EVM.TTY: _ViewPicker :: Prism' UiState UiTestPickerState
- EVM.TTY: _ViewVm :: Prism' UiState UiVmState
- EVM.TTY: activeAttr :: AttrName
- EVM.TTY: app :: UnitTestOptions -> App UiState () Name
- EVM.TTY: appEvent :: (?fetcher :: Fetcher, ?maxIter :: Maybe Integer) => BrickEvent Name e -> EventM Name UiState ()
- EVM.TTY: backstep :: (?fetcher :: Fetcher, ?maxIter :: Maybe Integer) => UiVmState -> IO UiVmState
- EVM.TTY: backstepUntil :: (?fetcher :: Fetcher, ?maxIter :: Maybe Integer) => (UiVmState -> Pred VM) -> EventM n UiState ()
- EVM.TTY: boldAttr :: AttrName
- EVM.TTY: currentSrcMap :: DappInfo -> VM -> Maybe SrcMap
- EVM.TTY: data Continuation a
- EVM.TTY: data Name
- EVM.TTY: data StepMode
- EVM.TTY: data UiBrowserState
- EVM.TTY: data UiState
- EVM.TTY: data UiTestPickerState
- EVM.TTY: data UiVmState
- EVM.TTY: debuggableTests :: UnitTestOptions -> (Text, [(Test, [AbiType])]) -> [(Text, Text)]
- EVM.TTY: dim :: Widget n -> Widget n
- EVM.TTY: dimAttr :: AttrName
- EVM.TTY: drawBytecodePane :: UiVmState -> UiWidget
- EVM.TTY: drawHelpBar :: UiWidget
- EVM.TTY: drawHelpView :: [UiWidget]
- EVM.TTY: drawSolidityPane :: UiVmState -> UiWidget
- EVM.TTY: drawStackPane :: UiVmState -> UiWidget
- EVM.TTY: drawTestPicker :: UiTestPickerState -> [UiWidget]
- EVM.TTY: drawTracePane :: UiVmState -> UiWidget
- EVM.TTY: drawUi :: UiState -> [UiWidget]
- EVM.TTY: drawVm :: UiVmState -> [UiWidget]
- EVM.TTY: drawVmBrowser :: UiBrowserState -> [UiWidget]
- EVM.TTY: ifTallEnough :: Int -> Widget n -> Widget n -> Widget n
- EVM.TTY: initUiVmState :: VM -> UnitTestOptions -> Stepper () -> UiVmState
- EVM.TTY: initialUiVmStateForTest :: UnitTestOptions -> (Text, Text) -> UiVmState
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Brick.Widgets.List.List EVM.TTY.Name (Data.Text.Internal.Text, Data.Text.Internal.Text), b GHC.Types.~ Brick.Widgets.List.List EVM.TTY.Name (Data.Text.Internal.Text, Data.Text.Internal.Text)) => Optics.Label.LabelOptic "tests" k EVM.TTY.UiTestPickerState EVM.TTY.UiTestPickerState a b
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Brick.Widgets.List.List EVM.TTY.Name (EVM.Types.Addr, EVM.Types.Contract), b GHC.Types.~ Brick.Widgets.List.List EVM.TTY.Name (EVM.Types.Addr, EVM.Types.Contract)) => Optics.Label.LabelOptic "contracts" k EVM.TTY.UiBrowserState EVM.TTY.UiBrowserState a b
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map GHC.Types.Int (EVM.Types.VM, EVM.Stepper.Stepper ()), b GHC.Types.~ Data.Map.Internal.Map GHC.Types.Int (EVM.Types.VM, EVM.Stepper.Stepper ())) => Optics.Label.LabelOptic "snapshots" k EVM.TTY.UiVmState EVM.TTY.UiVmState a b
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Dapp.DappInfo, b GHC.Types.~ EVM.Dapp.DappInfo) => Optics.Label.LabelOptic "dapp" k EVM.TTY.UiTestPickerState EVM.TTY.UiTestPickerState a b
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Stepper.Stepper (), b GHC.Types.~ EVM.Stepper.Stepper ()) => Optics.Label.LabelOptic "stepper" k EVM.TTY.UiVmState EVM.TTY.UiVmState a b
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.TTY.UiVmState, b GHC.Types.~ EVM.TTY.UiVmState) => Optics.Label.LabelOptic "vm" k EVM.TTY.UiBrowserState EVM.TTY.UiBrowserState a b
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.VM, b GHC.Types.~ EVM.Types.VM) => Optics.Label.LabelOptic "vm" k EVM.TTY.UiVmState EVM.TTY.UiVmState a b
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.UnitTest.UnitTestOptions, b GHC.Types.~ EVM.UnitTest.UnitTestOptions) => Optics.Label.LabelOptic "opts" k EVM.TTY.UiTestPickerState EVM.TTY.UiTestPickerState a b
- EVM.TTY: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.UnitTest.UnitTestOptions, b GHC.Types.~ EVM.UnitTest.UnitTestOptions) => Optics.Label.LabelOptic "testOpts" k EVM.TTY.UiVmState EVM.TTY.UiVmState a b
- EVM.TTY: 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 "showMemory" k EVM.TTY.UiVmState EVM.TTY.UiVmState a b
- EVM.TTY: 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 "step" k EVM.TTY.UiVmState EVM.TTY.UiVmState a b
- EVM.TTY: instance GHC.Classes.Eq EVM.TTY.Name
- EVM.TTY: instance GHC.Classes.Ord EVM.TTY.Name
- EVM.TTY: instance GHC.Show.Show EVM.TTY.Name
- EVM.TTY: interpret :: (?fetcher :: Fetcher, ?maxIter :: Maybe Integer) => StepMode -> Stepper a -> StateT UiVmState IO (Continuation a)
- EVM.TTY: isExecutionHalted :: UiVmState -> Pred VM
- EVM.TTY: isFuzzTest :: (Test, [AbiType]) -> Bool
- EVM.TTY: isNewTraceAdded :: UiVmState -> Pred VM
- EVM.TTY: isNextSourcePosition :: UiVmState -> Pred VM
- EVM.TTY: isNextSourcePositionWithoutEntering :: UiVmState -> Pred VM
- EVM.TTY: isUnitTestContract :: Text -> DappInfo -> Bool
- EVM.TTY: keepExecuting :: (?fetcher :: Fetcher, ?maxIter :: Maybe Integer) => StepMode -> Stepper a -> StateT UiVmState IO (Continuation a)
- EVM.TTY: main :: UnitTestOptions -> FilePath -> Maybe BuildOutput -> IO ()
- EVM.TTY: message :: VM -> String
- EVM.TTY: mkVty :: IO Vty
- EVM.TTY: myTheme :: [(AttrName, Attr)]
- EVM.TTY: opWidget :: (Integral a, Show a) => (a, Op) -> Widget n
- EVM.TTY: ourWrap :: String -> Widget n
- EVM.TTY: prettyIfConcrete :: Expr Buf -> String
- EVM.TTY: runFromVM :: SolverGroup -> RpcInfo -> Maybe Integer -> DappInfo -> VM -> IO VM
- EVM.TTY: selectedAttr :: AttrName
- EVM.TTY: snapshotInterval :: Int
- EVM.TTY: solidityList :: VM -> DappInfo -> List Name (Int, ByteString)
- EVM.TTY: stepOneOpcode :: Stepper a -> StateT UiVmState IO ()
- EVM.TTY: takeStep :: (?fetcher :: Fetcher, ?maxIter :: Maybe Integer) => UiVmState -> StepMode -> EventM n UiState ()
- EVM.TTY: type Pred a = a -> Bool
- EVM.TTY: type UiWidget = Widget Name
- EVM.TTY: withHighlight :: Bool -> Widget n -> Widget n
- EVM.TTY: wordAttr :: AttrName
- EVM.TTYCenteredList: drawListElements :: (Ord n, Show n) => Bool -> List n e -> (Bool -> e -> Widget n) -> Widget n
- EVM.TTYCenteredList: renderList :: (Ord n, Show n) => (Bool -> e -> Widget n) -> Bool -> List n e -> Widget n
- EVM.Traversals: foldTrace :: forall b. Monoid b => (forall a. Expr a -> b) -> b -> Trace -> b
- EVM.Traversals: foldTraces :: forall b. Monoid b => (forall a. Expr a -> b) -> b -> Traces -> b
- EVM.Traversals: mapTrace :: (forall a. Expr a -> Expr a) -> Trace -> Trace
- EVM.Traversals: mapTraceM :: forall m. Monad m => (forall a. Expr a -> m (Expr a)) -> Trace -> m Trace
- EVM.Traversals: mapTracesM :: forall m. Monad m => (forall a. Expr a -> m (Expr a)) -> Traces -> m Traces
- EVM.Types: ConcreteS :: StorageModel
- EVM.Types: InitialS :: StorageModel
- EVM.Types: SymbolicS :: StorageModel
- EVM.Types: [$sel:allowFFI:VM] :: VM -> Bool
- EVM.Types: [$sel:contractcode:Contract] :: Contract -> ContractCode
- EVM.Types: [$sel:fetchedContracts:Cache] :: Cache -> Map Addr Contract
- EVM.Types: [$sel:fetchedStorage:Cache] :: Cache -> Map W256 (Map W256 W256)
- EVM.Types: [$sel:initialStorage:VMOpts] :: VMOpts -> Expr Storage
- EVM.Types: [$sel:origStorage:Env] :: Env -> Map W256 (Map W256 W256)
- EVM.Types: [$sel:overrideCaller:VM] :: VM -> Maybe Addr
- EVM.Types: [$sel:storage:Env] :: Env -> Expr Storage
- EVM.Types: [Address] :: Int -> Expr EWord
- EVM.Types: [CallCode] :: Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> [Expr Log] -> Expr Storage -> Expr EWord
- EVM.Types: [CallValue] :: Int -> Expr EWord
- EVM.Types: [Call] :: Expr EWord -> Maybe (Expr EWord) -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> [Expr Log] -> Expr Storage -> Expr EWord
- EVM.Types: [Caller] :: Int -> Expr EWord
- EVM.Types: [Create2] :: Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr Buf -> [Expr Log] -> Expr Storage -> Expr EWord
- EVM.Types: [Create] :: Expr EWord -> Expr EWord -> Expr EWord -> Expr Buf -> [Expr Log] -> Expr Storage -> Expr EWord
- EVM.Types: [DelegeateCall] :: Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord -> [Expr Log] -> Expr Storage -> Expr EWord
- EVM.Types: [EmptyStore] :: Expr Storage
- EVM.Types: [ExtCodeHash] :: Expr EWord -> Expr EWord
- EVM.Types: [SelfBalance] :: Int -> Int -> Expr EWord
- EVM.Types: data StorageModel
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract, b GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract) => Optics.Label.LabelOptic "contracts" k EVM.Types.Env EVM.Types.Env a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract, b GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract) => Optics.Label.LabelOptic "fetchedContracts" k EVM.Types.Cache EVM.Types.Cache a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract, b GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract) => Optics.Label.LabelOptic "txReversion" k EVM.Types.TxState EVM.Types.TxState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr [EVM.Types.W256], b GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr [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 EVM.Types.VM a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.W256 (Data.Map.Internal.Map EVM.Types.W256 EVM.Types.W256), b GHC.Types.~ Data.Map.Internal.Map EVM.Types.W256 (Data.Map.Internal.Map EVM.Types.W256 EVM.Types.W256)) => Optics.Label.LabelOptic "fetchedStorage" k EVM.Types.Cache EVM.Types.Cache a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.W256 (Data.Map.Internal.Map EVM.Types.W256 EVM.Types.W256), b GHC.Types.~ Data.Map.Internal.Map EVM.Types.W256 (Data.Map.Internal.Map EVM.Types.W256 EVM.Types.W256)) => Optics.Label.LabelOptic "origStorage" k EVM.Types.Env EVM.Types.Env a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Set.Internal.Set (EVM.Types.Addr, EVM.Types.W256), b GHC.Types.~ Data.Set.Internal.Set (EVM.Types.Addr, EVM.Types.W256)) => Optics.Label.LabelOptic "accessedStorageKeys" k EVM.Types.SubState EVM.Types.SubState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Set.Internal.Set EVM.Types.Addr, b GHC.Types.~ Data.Set.Internal.Set EVM.Types.Addr) => Optics.Label.LabelOptic "accessedAddresses" k EVM.Types.SubState EVM.Types.SubState 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 EVM.Types.VM a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Addr, b GHC.Types.~ EVM.Types.Addr) => 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.Addr, b GHC.Types.~ EVM.Types.Addr) => Optics.Label.LabelOptic "codeContract" k EVM.Types.FrameState EVM.Types.FrameState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Addr, b GHC.Types.~ EVM.Types.Addr) => Optics.Label.LabelOptic "coinbase" k EVM.Types.Block EVM.Types.Block a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Addr, b GHC.Types.~ EVM.Types.Addr) => 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.Addr, b GHC.Types.~ EVM.Types.Addr) => Optics.Label.LabelOptic "contract" k EVM.Types.FrameState EVM.Types.FrameState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Addr, b GHC.Types.~ EVM.Types.Addr) => Optics.Label.LabelOptic "origin" k EVM.Types.TxState EVM.Types.TxState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Addr, b GHC.Types.~ EVM.Types.Addr) => 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.Addr, b GHC.Types.~ EVM.Types.Addr) => Optics.Label.LabelOptic "toAddr" k EVM.Types.TxState EVM.Types.TxState 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 EVM.Types.VM 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 EVM.Types.VM 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 EVM.Types.FrameState 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 "contractcode" k EVM.Types.Contract EVM.Types.Contract 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 EVM.Types.VM 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 EVM.Types.FrameState 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 "memory" k EVM.Types.FrameState EVM.Types.FrameState 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 EVM.Types.FrameState 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 "caller" k EVM.Types.FrameState EVM.Types.FrameState 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 "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.EWord, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EWord) => Optics.Label.LabelOptic "callvalue" k EVM.Types.FrameState EVM.Types.FrameState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.Storage, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.Storage) => Optics.Label.LabelOptic "initialStorage" 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.Storage, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.Storage) => Optics.Label.LabelOptic "storage" k EVM.Types.Env EVM.Types.Env 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 EVM.Types.Frame a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.FrameState, b GHC.Types.~ EVM.Types.FrameState) => Optics.Label.LabelOptic "state" k EVM.Types.Frame EVM.Types.Frame a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.FrameState, b GHC.Types.~ EVM.Types.FrameState) => Optics.Label.LabelOptic "state" k EVM.Types.VM EVM.Types.VM 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 EVM.Types.VM 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 "balance" k EVM.Types.Contract EVM.Types.Contract 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 "nonce" k EVM.Types.Contract EVM.Types.Contract a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe EVM.Types.Addr, b GHC.Types.~ GHC.Maybe.Maybe EVM.Types.Addr) => Optics.Label.LabelOptic "overrideCaller" k EVM.Types.VM EVM.Types.VM a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe EVM.Types.VMResult, b GHC.Types.~ GHC.Maybe.Maybe EVM.Types.VMResult) => Optics.Label.LabelOptic "result" k EVM.Types.VM EVM.Types.VM 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.VM EVM.Types.VM 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 EVM.Types.FrameState 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 EVM.Types.FrameState 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 EVM.Types.VM 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 EVM.Types.FrameState 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 EVM.Types.FrameState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [(EVM.Types.Addr, GHC.Word.Word64)], b GHC.Types.~ [(EVM.Types.Addr, GHC.Word.Word64)]) => Optics.Label.LabelOptic "refunds" k EVM.Types.SubState EVM.Types.SubState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Addr], b GHC.Types.~ [EVM.Types.Addr]) => Optics.Label.LabelOptic "selfdestructs" k EVM.Types.SubState EVM.Types.SubState a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Addr], b GHC.Types.~ [EVM.Types.Addr]) => Optics.Label.LabelOptic "touchedAccounts" k EVM.Types.SubState EVM.Types.SubState 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 EVM.Types.FrameState 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 EVM.Types.VM a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [EVM.Types.Frame], b GHC.Types.~ [EVM.Types.Frame]) => Optics.Label.LabelOptic "frames" k EVM.Types.VM EVM.Types.VM 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 EVM.Types.VM 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 EVM.Types.VM a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.An_AffineTraversal, a GHC.Types.~ (Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract, EVM.Types.Expr 'EVM.Types.Storage), b GHC.Types.~ (Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract, EVM.Types.Expr 'EVM.Types.Storage)) => Optics.Label.LabelOptic "callreversion" k EVM.Types.FrameContext EVM.Types.FrameContext a b
- EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.An_AffineTraversal, a GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract, b GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract) => Optics.Label.LabelOptic "createreversion" 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.Addr, b GHC.Types.~ EVM.Types.Addr) => Optics.Label.LabelOptic "address" 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.Addr, b GHC.Types.~ EVM.Types.Addr) => Optics.Label.LabelOptic "context" 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.Addr, b GHC.Types.~ EVM.Types.Addr) => Optics.Label.LabelOptic "target" k EVM.Types.FrameContext EVM.Types.FrameContext a b
- EVM.Types: instance GHC.Generics.Generic EVM.Types.FrameState
- EVM.Types: instance GHC.Generics.Generic EVM.Types.VM
- EVM.Types: instance GHC.Read.Read EVM.Types.StorageModel
- EVM.Types: instance GHC.Show.Show EVM.Types.Choose
- EVM.Types: instance GHC.Show.Show EVM.Types.Effect
- EVM.Types: instance GHC.Show.Show EVM.Types.Frame
- EVM.Types: instance GHC.Show.Show EVM.Types.FrameState
- EVM.Types: instance GHC.Show.Show EVM.Types.Query
- EVM.Types: instance GHC.Show.Show EVM.Types.StorageModel
- EVM.Types: instance GHC.Show.Show EVM.Types.VM
- EVM.Types: instance GHC.Show.Show EVM.Types.VMResult
- EVM.Types: instance Options.Generic.ParseField EVM.Types.StorageModel
- EVM.Types: unifyCachedStorage :: Map W256 W256 -> Map W256 W256 -> Map W256 W256
- EVM.UnitTest: OpLocation :: Contract -> Int -> OpLocation
- EVM.UnitTest: [$sel:covMatch:UnitTestOptions] :: UnitTestOptions -> Maybe Text
- EVM.UnitTest: [$sel:fuzzRuns:UnitTestOptions] :: UnitTestOptions -> Int
- EVM.UnitTest: [$sel:maxDepth:UnitTestOptions] :: UnitTestOptions -> Maybe Int
- EVM.UnitTest: [$sel:replay:UnitTestOptions] :: UnitTestOptions -> Maybe (Text, ByteString)
- EVM.UnitTest: [$sel:srcContract:OpLocation] :: OpLocation -> Contract
- EVM.UnitTest: [$sel:srcOpIx:OpLocation] :: OpLocation -> Int
- EVM.UnitTest: [$sel:vmModifier:UnitTestOptions] :: UnitTestOptions -> VM -> VM
- EVM.UnitTest: checkFailures :: UnitTestOptions -> ABIMethod -> Bool -> Stepper Bool
- EVM.UnitTest: coverageForUnitTestContract :: UnitTestOptions -> Map Text SolcContract -> SourceCache -> (Text, [(Test, [AbiType])]) -> IO (MultiSet SrcMap)
- EVM.UnitTest: coverageReport :: DappInfo -> MultiSet SrcMap -> Map FilePath (Vector (Int, ByteString))
- EVM.UnitTest: currentOpLocation :: VM -> OpLocation
- EVM.UnitTest: data OpLocation
- EVM.UnitTest: decodeCalls :: ByteString -> [ExploreTx]
- EVM.UnitTest: execTest :: UnitTestOptions -> VM -> ABIMethod -> AbiValue -> IO (Bool, VM)
- EVM.UnitTest: execTestStepper :: UnitTestOptions -> ABIMethod -> AbiValue -> Stepper Bool
- EVM.UnitTest: execWithCoverage :: StateT CoverageState IO VMResult
- EVM.UnitTest: explorationStepper :: UnitTestOptions -> ABIMethod -> [ExploreTx] -> [Addr] -> RLP -> Int -> Stepper (Bool, RLP)
- EVM.UnitTest: exploreRun :: UnitTestOptions -> VM -> ABIMethod -> [ExploreTx] -> IO (Text, Either Text Text, VM)
- EVM.UnitTest: exploreStep :: UnitTestOptions -> ByteString -> Stepper Bool
- EVM.UnitTest: fuzzRun :: UnitTestOptions -> VM -> Text -> [AbiType] -> IO (Text, Either Text Text, VM)
- EVM.UnitTest: fuzzTest :: UnitTestOptions -> Text -> [AbiType] -> VM -> Property
- EVM.UnitTest: getParametersFromEnvironmentVariables :: Maybe Text -> IO TestVMParams
- EVM.UnitTest: getTargetContracts :: UnitTestOptions -> Stepper [Addr]
- EVM.UnitTest: initialExplorationStepper :: UnitTestOptions -> ABIMethod -> [ExploreTx] -> [Addr] -> Int -> Stepper (Bool, RLP)
- EVM.UnitTest: instance GHC.Classes.Eq EVM.UnitTest.OpLocation
- EVM.UnitTest: instance GHC.Classes.Ord EVM.UnitTest.OpLocation
- EVM.UnitTest: instance GHC.Show.Show EVM.UnitTest.OpLocation
- EVM.UnitTest: interpretWithCoverage :: UnitTestOptions -> Stepper a -> StateT CoverageState IO a
- EVM.UnitTest: prettyCalldata :: (?context :: DappContext) => SMTCex -> Expr Buf -> Text -> [AbiType] -> Text
- EVM.UnitTest: runOne :: UnitTestOptions -> VM -> ABIMethod -> AbiValue -> IO (Text, Either Text Text, VM)
- EVM.UnitTest: runTest :: UnitTestOptions -> VM -> (Test, [AbiType]) -> IO (Text, Either Text Text, VM)
- EVM.UnitTest: runUnitTest :: UnitTestOptions -> ABIMethod -> AbiValue -> Stepper Bool
- EVM.UnitTest: runWithCoverage :: StateT CoverageState IO VM
- EVM.UnitTest: showCalldata :: (?context :: DappContext) => SMTCex -> [AbiType] -> Expr Buf -> Text
- EVM.UnitTest: showVal :: AbiValue -> Text
- EVM.UnitTest: srcMapForOpLocation :: DappInfo -> OpLocation -> Maybe SrcMap
- EVM.UnitTest: type CoverageState = (VM, MultiSet OpLocation)
- EVM.UnitTest: type ExploreTx = (Addr, Addr, ByteString, W256)
+ EVM: abstractContract :: ContractCode -> Expr EAddr -> Contract
+ EVM: create2Address :: Expr EAddr -> W256 -> ByteString -> EVM s (Expr EAddr)
+ EVM: createAddress :: Expr EAddr -> Maybe W64 -> EVM s (Expr EAddr)
+ EVM: emptyContract :: Contract
+ EVM: forceAddr :: Expr EWord -> String -> (Expr EAddr -> EVM s ()) -> EVM s ()
+ EVM: forceConcreteAddr :: Expr EAddr -> String -> (Addr -> EVM s ()) -> EVM s ()
+ EVM: forceConcreteAddr2 :: (Expr EAddr, Expr EAddr) -> String -> ((Addr, Addr) -> EVM s ()) -> EVM s ()
+ EVM: freezeMemory :: MutableMemory s -> EVM s (Expr Buf)
+ EVM: freshSymAddr :: EVM s (Expr EAddr)
+ EVM: isCreation :: ContractCode -> Bool
+ EVM: isPrecompileAddr :: Expr EAddr -> Bool
+ EVM: noJumpIntoInitData :: Int -> EVM s () -> EVM s ()
+ EVM: parseInitCode :: Expr Buf -> Maybe ContractCode
+ EVM: pushAddr :: Expr EAddr -> EVM s ()
+ EVM: unknownContract :: Expr EAddr -> Contract
+ EVM: writeMemory :: MutableMemory s -> Int -> ByteString -> EVM s ()
+ EVM.ABI: Sig :: Text -> [AbiType] -> Sig
+ EVM.ABI: data Sig
+ EVM.ABI: instance GHC.Classes.Eq EVM.ABI.Sig
+ EVM.ABI: instance GHC.Show.Show EVM.ABI.Sig
+ EVM.Dapp: mkSig :: Method -> Maybe Sig
+ EVM.Dapp: srcMapCode :: SourceCache -> SrcMap -> Maybe ByteString
+ EVM.Dapp: srcMapCodePos :: SourceCache -> SrcMap -> Maybe (FilePath, Int)
+ EVM.Expr: ConstState :: Map (Expr EWord) W256 -> Bool -> ConstState
+ EVM.Expr: [$sel:canBeSat:ConstState] :: ConstState -> Bool
+ EVM.Expr: [$sel:values:ConstState] :: ConstState -> Map (Expr EWord) W256
+ EVM.Expr: concretePrefix :: Expr Buf -> Vector Word8
+ EVM.Expr: constFoldProp :: [Prop] -> Bool
+ EVM.Expr: data ConstState
+ EVM.Expr: flattenProps :: [Prop] -> [Prop]
+ EVM.Expr: getAddr :: Expr Storage -> Maybe (Expr EAddr)
+ EVM.Expr: idsDontMatch :: ByteString -> ByteString -> Bool
+ EVM.Expr: instance GHC.Show.Show EVM.Expr.ConstState
+ EVM.Expr: isFailure :: Expr End -> Bool
+ EVM.Expr: isPartial :: Expr End -> Bool
+ EVM.Expr: isSuccess :: Expr End -> Bool
+ EVM.Expr: litToArrayPreimage :: W256 -> Maybe (Word8, W256)
+ EVM.Expr: maxLit :: W256
+ EVM.Expr: pattern ArraySlotWithOffset :: ByteString -> Expr EWord -> Expr EWord
+ EVM.Expr: pattern ArraySlotZero :: ByteString -> Expr EWord
+ EVM.Expr: pattern Keccak64Bytes :: Expr EWord
+ EVM.Expr: pattern MappingSlot :: ByteString -> Expr EWord -> Expr EWord
+ EVM.Expr: preImages :: [(W256, Word8)]
+ EVM.Expr: remRedundantProps :: [Prop] -> [Prop]
+ EVM.Expr: simplifyProp :: Prop -> Prop
+ EVM.Expr: simplifyProps :: [Prop] -> [Prop]
+ EVM.Expr: slotPos :: Word8 -> ByteString
+ EVM.Expr: structureArraySlots :: Expr a -> Expr a
+ EVM.Expr: wordToAddr :: Expr EWord -> Maybe (Expr EAddr)
+ EVM.FeeSchedule: feeSchedule :: Num n => FeeSchedule n
+ EVM.Format: showVal :: AbiValue -> Text
+ EVM.Keccak: keccakCompute :: [Prop] -> [Expr Buf] -> [Expr Storage] -> [Prop]
+ EVM.SMT: AbstState :: Map (Expr EWord) Int -> Int -> AbstState
+ EVM.SMT: RefinementEqs :: [Builder] -> RefinementEqs
+ EVM.SMT: [$sel:addrs:CexVars] :: CexVars -> [Text]
+ EVM.SMT: [$sel:addrs:SMTCex] :: SMTCex -> Map (Expr EAddr) Addr
+ EVM.SMT: [$sel:count:AbstState] :: AbstState -> Int
+ EVM.SMT: [$sel:words:AbstState] :: AbstState -> Map (Expr EWord) Int
+ EVM.SMT: abstractAwayProps :: AbstRefineConfig -> [Prop] -> ([Prop], AbstState)
+ EVM.SMT: assertPropsNoSimp :: AbstRefineConfig -> [Prop] -> SMT2
+ EVM.SMT: data AbstState
+ EVM.SMT: declareAbstractStores :: [Builder] -> SMT2
+ EVM.SMT: declareAddrs :: [Builder] -> SMT2
+ EVM.SMT: formatEAddr :: Expr EAddr -> Builder
+ EVM.SMT: getAddrs :: (Text -> Expr EAddr) -> (Text -> IO Text) -> [Text] -> IO (Map (Expr EAddr) Addr)
+ EVM.SMT: getOne :: (SpecConstant -> a) -> (Text -> IO Text) -> Map Text a -> Text -> IO (Map Text a)
+ EVM.SMT: instance GHC.Base.Monoid EVM.SMT.RefinementEqs
+ EVM.SMT: instance GHC.Base.Semigroup EVM.SMT.RefinementEqs
+ EVM.SMT: instance GHC.Classes.Eq EVM.SMT.RefinementEqs
+ EVM.SMT: instance GHC.Show.Show EVM.SMT.AbstState
+ EVM.SMT: instance GHC.Show.Show EVM.SMT.RefinementEqs
+ EVM.SMT: newtype RefinementEqs
+ EVM.SMT: parseAddr :: SpecConstant -> Addr
+ EVM.SMT: parseEAddr :: Text -> Expr EAddr
+ EVM.SMT: parseTxCtx :: Text -> Expr EWord
+ EVM.SMT: referencedAbstractStores :: TraversableTerm a => a -> Set Builder
+ EVM.SMT: referencedWAddrs :: TraversableTerm a => a -> Set Builder
+ EVM.SMT: smt2Line :: Builder -> SMT2
+ EVM.SMT: storeName :: Expr EAddr -> Builder
+ EVM.Solvers: checkCommand :: SolverInstance -> Text -> IO ()
+ EVM.SymExec: [$sel:abstRefineConfig:VeriOpts] :: VeriOpts -> AbstRefineConfig
+ EVM.SymExec: debugAbstVeriOpts :: VeriOpts
+ EVM.SymExec: defaultSymbolicValues :: Expr a -> Expr a
+ EVM.SymExec: expandCex :: VM s -> SMTCex -> SMTCex
+ EVM.SymExec: getCex :: ProofResult a b c -> Maybe b
+ EVM.SymExec: getExpr :: SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> IO (Expr End)
+ EVM.SymExec: getTimeout :: ProofResult a b c -> Maybe c
+ EVM.SymExec: prettyCalldata :: SMTCex -> Expr Buf -> Text -> [AbiType] -> Text
+ EVM.SymExec: subAddrs :: Map (Expr EAddr) Addr -> Expr a -> Expr a
+ EVM.SymExec: subBufs :: Map (Expr Buf) ByteString -> Expr a -> Expr a
+ EVM.SymExec: subStores :: Map (Expr EAddr) (Map W256 W256) -> Expr a -> Expr a
+ EVM.SymExec: subVars :: Map (Expr EWord) W256 -> Expr a -> Expr a
+ EVM.SymExec: toEContract :: Contract -> Expr EContract
+ EVM.Traversals: foldCode :: forall b. Monoid b => (forall a. Expr a -> b) -> ContractCode -> b
+ EVM.Traversals: foldContract :: forall b. Monoid b => (forall a. Expr a -> b) -> b -> Contract -> b
+ EVM.Traversals: foldEContract :: forall b. Monoid b => (forall a. Expr a -> b) -> b -> Expr EContract -> b
+ EVM.Traversals: mapCodeM :: Monad m => (forall a. Expr a -> m (Expr a)) -> ContractCode -> m ContractCode
+ EVM.Traversals: mapContractM :: Monad m => (forall a. Expr a -> m (Expr a)) -> Contract -> m Contract
+ EVM.Traversals: mapEContractM :: Monad m => (forall a. Expr a -> m (Expr a)) -> Expr EContract -> m (Expr EContract)
+ EVM.Traversals: mapProp' :: (Prop -> Prop) -> Prop -> Prop
+ EVM.Types: AbstRefineConfig :: Bool -> Bool -> AbstRefineConfig
+ EVM.Types: AbstractBase :: BaseState
+ EVM.Types: ConcreteMemory :: MutableMemory s -> Memory s
+ EVM.Types: EAddr :: EType
+ EVM.Types: EContract :: EType
+ EVM.Types: EmptyBase :: BaseState
+ EVM.Types: JumpIntoSymbolicCode :: Int -> Int -> PartialExec
+ EVM.Types: RuntimeConfig :: Bool -> Maybe (Expr EAddr) -> BaseState -> RuntimeConfig
+ EVM.Types: SymbolicMemory :: !Expr Buf -> Memory s
+ EVM.Types: UnknownCode :: Expr EAddr -> ContractCode
+ EVM.Types: [$sel:allowFFI:RuntimeConfig] :: RuntimeConfig -> Bool
+ EVM.Types: [$sel:arith:AbstRefineConfig] :: AbstRefineConfig -> Bool
+ EVM.Types: [$sel:baseState:RuntimeConfig] :: RuntimeConfig -> BaseState
+ EVM.Types: [$sel:baseState:VMOpts] :: VMOpts -> BaseState
+ EVM.Types: [$sel:code:Contract] :: Contract -> ContractCode
+ EVM.Types: [$sel:config:VM] :: VM s -> RuntimeConfig
+ EVM.Types: [$sel:fetched:Cache] :: Cache -> Map Addr Contract
+ EVM.Types: [$sel:freshAddresses:Env] :: Env -> Int
+ EVM.Types: [$sel:jumpDst:UnexpectedSymbolicArg] :: PartialExec -> Int
+ EVM.Types: [$sel:mem:AbstRefineConfig] :: AbstRefineConfig -> Bool
+ EVM.Types: [$sel:origStorage:Contract] :: Contract -> Expr Storage
+ EVM.Types: [$sel:otherContracts:VMOpts] :: VMOpts -> [(Expr EAddr, Contract)]
+ EVM.Types: [$sel:overrideCaller:RuntimeConfig] :: RuntimeConfig -> Maybe (Expr EAddr)
+ EVM.Types: [$sel:storage:Contract] :: Contract -> Expr Storage
+ EVM.Types: [C] :: ContractCode -> Expr Storage -> Expr EWord -> Maybe W64 -> Expr EContract
+ EVM.Types: [CodeHash] :: Expr EAddr -> Expr EWord
+ EVM.Types: [LitAddr] :: Addr -> Expr EAddr
+ EVM.Types: [SymAddr] :: Text -> Expr EAddr
+ EVM.Types: [TxValue] :: Expr EWord
+ EVM.Types: [WAddr] :: Expr EAddr -> Expr EWord
+ EVM.Types: abstRefineDefault :: AbstRefineConfig
+ EVM.Types: data AbstRefineConfig
+ EVM.Types: data BaseState
+ EVM.Types: data Memory s
+ EVM.Types: data RuntimeConfig
+ 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.Contract, b GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) EVM.Types.Contract) => Optics.Label.LabelOptic "contracts" k EVM.Types.Env EVM.Types.Env 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.Contract, b GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) EVM.Types.Contract) => Optics.Label.LabelOptic "txReversion" k EVM.Types.TxState EVM.Types.TxState 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.Addr EVM.Types.Contract, b GHC.Types.~ Data.Map.Internal.Map EVM.Types.Addr EVM.Types.Contract) => Optics.Label.LabelOptic "fetched" k EVM.Types.Cache EVM.Types.Cache 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.Set.Internal.Set (EVM.Types.Expr 'EVM.Types.EAddr), b GHC.Types.~ Data.Set.Internal.Set (EVM.Types.Expr 'EVM.Types.EAddr)) => Optics.Label.LabelOptic "accessedAddresses" k EVM.Types.SubState EVM.Types.SubState a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Set.Internal.Set (EVM.Types.Expr 'EVM.Types.EAddr, EVM.Types.W256), b GHC.Types.~ Data.Set.Internal.Set (EVM.Types.Expr 'EVM.Types.EAddr, EVM.Types.W256)) => Optics.Label.LabelOptic "accessedStorageKeys" k EVM.Types.SubState EVM.Types.SubState 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.Types.BaseState, b GHC.Types.~ EVM.Types.BaseState) => Optics.Label.LabelOptic "baseState" k EVM.Types.RuntimeConfig EVM.Types.RuntimeConfig 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.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.ContractCode, b GHC.Types.~ EVM.Types.ContractCode) => Optics.Label.LabelOptic "code" k EVM.Types.Contract EVM.Types.Contract 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.Block EVM.Types.Block 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.TxState EVM.Types.TxState 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.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "toAddr" k EVM.Types.TxState EVM.Types.TxState 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 "balance" k EVM.Types.Contract EVM.Types.Contract 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.Storage, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.Storage) => Optics.Label.LabelOptic "origStorage" k EVM.Types.Contract EVM.Types.Contract a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ EVM.Types.Expr 'EVM.Types.Storage, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.Storage) => Optics.Label.LabelOptic "storage" k EVM.Types.Contract EVM.Types.Contract 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.~ GHC.Maybe.Maybe (EVM.Types.Expr 'EVM.Types.EAddr), b GHC.Types.~ GHC.Maybe.Maybe (EVM.Types.Expr 'EVM.Types.EAddr)) => Optics.Label.LabelOptic "overrideCaller" k EVM.Types.RuntimeConfig EVM.Types.RuntimeConfig 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.Maybe.Maybe EVM.Types.W64, b GHC.Types.~ GHC.Maybe.Maybe EVM.Types.W64) => Optics.Label.LabelOptic "nonce" k EVM.Types.Contract EVM.Types.Contract 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.RuntimeConfig EVM.Types.RuntimeConfig 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 "freshAddresses" 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 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 "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 "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.EAddr, GHC.Word.Word64)], b GHC.Types.~ [(EVM.Types.Expr 'EVM.Types.EAddr, GHC.Word.Word64)]) => Optics.Label.LabelOptic "refunds" k EVM.Types.SubState EVM.Types.SubState 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 "selfdestructs" k EVM.Types.SubState EVM.Types.SubState 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 "touchedAccounts" k EVM.Types.SubState EVM.Types.SubState 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.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) EVM.Types.Contract, b GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) EVM.Types.Contract) => Optics.Label.LabelOptic "callreversion" k EVM.Types.FrameContext EVM.Types.FrameContext a b
+ EVM.Types: instance (k GHC.Types.~ Optics.Internal.Optic.Types.An_AffineTraversal, a GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) EVM.Types.Contract, b GHC.Types.~ Data.Map.Internal.Map (EVM.Types.Expr 'EVM.Types.EAddr) EVM.Types.Contract) => Optics.Label.LabelOptic "createreversion" 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.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "address" 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.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "context" 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.EAddr, b GHC.Types.~ EVM.Types.Expr 'EVM.Types.EAddr) => Optics.Label.LabelOptic "target" 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.Choose 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.Memory 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.BaseState
+ EVM.Types: instance GHC.Show.Show EVM.Types.RuntimeConfig
+ EVM.Types: instance Witch.From.From EVM.Types.W64 EVM.Types.W256
+ EVM.Types: instance Witch.From.From GHC.Word.Word32 Data.ByteString.Internal.Type.ByteString
+ EVM.Types: instance Witch.TryFrom.TryFrom EVM.Types.W256 EVM.Types.W64
+ EVM.Types: instance Witch.TryFrom.TryFrom GHC.Num.Integer.Integer EVM.Types.Addr
+ EVM.Types: isPBool :: Prop -> Bool
+ EVM.Types: maybeConcreteStore :: Expr Storage -> Maybe (Map W256 W256)
+ EVM.Types: maybeLitAddr :: Expr EAddr -> Maybe Addr
+ EVM.Types: paddedShowHex :: (Show a, Integral a) => Int -> a -> String
+ EVM.Types: truncateToAddr :: W256 -> Addr
+ EVM.Types: type MutableMemory s = STVector s Word8
+ EVM.UnitTest: allBranchRev :: Text -> Text
+ EVM.UnitTest: paramsFromRpc :: RpcInfo -> IO TestVMParams
- EVM: accessAccountForGas :: Addr -> EVM Bool
+ EVM: accessAccountForGas :: Expr EAddr -> EVM s Bool
- EVM: accessAndBurn :: Addr -> EVM () -> EVM ()
+ EVM: accessAndBurn :: Expr EAddr -> EVM s () -> EVM s ()
- EVM: accessMemoryRange :: W256 -> W256 -> EVM () -> EVM ()
+ EVM: accessMemoryRange :: W256 -> W256 -> EVM s () -> EVM s ()
- EVM: accessMemoryWord :: W256 -> EVM () -> EVM ()
+ EVM: accessMemoryWord :: W256 -> EVM s () -> EVM s ()
- EVM: accessStorage :: Addr -> Expr EWord -> (Expr EWord -> EVM ()) -> EVM ()
+ EVM: accessStorage :: Expr EAddr -> Expr EWord -> (Expr EWord -> EVM s ()) -> EVM s ()
- EVM: accessStorageForGas :: Addr -> Expr EWord -> EVM Bool
+ EVM: accessStorageForGas :: Expr EAddr -> Expr EWord -> EVM s Bool
- EVM: accessUnboundedMemoryRange :: Word64 -> Word64 -> EVM () -> EVM ()
+ EVM: accessUnboundedMemoryRange :: Word64 -> Word64 -> EVM s () -> EVM s ()
- EVM: accountExists :: Addr -> VM -> Bool
+ EVM: accountExists :: Expr EAddr -> VM s -> Bool
- EVM: blankState :: FrameState
+ EVM: blankState :: ST s (FrameState s)
- EVM: branch :: CodeLocation -> Expr EWord -> (Bool -> EVM ()) -> EVM ()
+ EVM: branch :: forall s. Expr EWord -> (Bool -> EVM s ()) -> EVM s ()
- EVM: burn :: Word64 -> EVM () -> EVM ()
+ EVM: burn :: Word64 -> EVM s () -> EVM s ()
- EVM: bytecode :: Getter Contract (Expr Buf)
+ EVM: bytecode :: Getter Contract (Maybe (Expr Buf))
- EVM: callChecks :: (?op :: Word8) => Contract -> Word64 -> Addr -> Addr -> W256 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> (Word64 -> EVM ()) -> EVM ()
+ EVM: callChecks :: (?op :: Word8) => Contract -> Word64 -> Expr EAddr -> Expr EAddr -> Expr EWord -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> (Word64 -> EVM s ()) -> EVM s ()
- EVM: cheat :: (?op :: Word8) => (W256, W256) -> (W256, W256) -> EVM ()
+ EVM: cheat :: (?op :: Word8) => (W256, W256) -> (W256, W256) -> EVM s ()
- EVM: cheatActions :: Map FunctionSelector CheatAction
+ EVM: cheatActions :: Map FunctionSelector (CheatAction s)
- EVM: cheatCode :: Addr
+ EVM: cheatCode :: Expr EAddr
- EVM: checkJump :: Int -> [Expr EWord] -> EVM ()
+ EVM: checkJump :: Int -> [Expr EWord] -> EVM s ()
- EVM: choose :: Choose -> EVM ()
+ EVM: choose :: Choose s -> EVM s ()
- EVM: codeloc :: EVM CodeLocation
+ EVM: codeloc :: EVM s CodeLocation
- EVM: copyBytesToMemory :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM ()
+ EVM: copyBytesToMemory :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM s ()
- EVM: copyCallBytesToMemory :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM ()
+ EVM: copyCallBytesToMemory :: Expr Buf -> Expr EWord -> Expr EWord -> EVM s ()
- EVM: costOfCall :: FeeSchedule Word64 -> Bool -> W256 -> Word64 -> Word64 -> Addr -> EVM (Word64, Word64)
+ EVM: costOfCall :: FeeSchedule Word64 -> Bool -> Expr EWord -> Word64 -> Word64 -> Expr EAddr -> EVM s (Word64, Word64)
- EVM: create :: (?op :: Word8) => Addr -> Contract -> W256 -> Word64 -> W256 -> [Expr EWord] -> Addr -> Expr Buf -> EVM ()
+ EVM: create :: (?op :: Word8) => Expr EAddr -> Contract -> W256 -> Word64 -> Expr EWord -> [Expr EWord] -> Expr EAddr -> Expr Buf -> EVM s ()
- EVM: currentContract :: VM -> Maybe Contract
+ EVM: currentContract :: VM s -> Maybe Contract
- EVM: delegateCall :: (?op :: Word8) => Contract -> Word64 -> Expr EWord -> Expr EWord -> W256 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> (Addr -> EVM ()) -> EVM ()
+ 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: exec1 :: EVM ()
+ EVM: exec1 :: EVM s ()
- EVM: executePrecompile :: (?op :: Word8) => Addr -> Word64 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> EVM ()
+ EVM: executePrecompile :: (?op :: Word8) => Addr -> Word64 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> EVM s ()
- EVM: fetchAccount :: Addr -> (Contract -> EVM ()) -> EVM ()
+ EVM: fetchAccount :: Expr EAddr -> (Contract -> EVM s ()) -> EVM s ()
- EVM: finalize :: EVM ()
+ EVM: finalize :: EVM s ()
- EVM: finishFrame :: FrameResult -> EVM ()
+ EVM: finishFrame :: FrameResult -> EVM s ()
- EVM: forceConcrete :: Expr EWord -> String -> (W256 -> EVM ()) -> EVM ()
+ EVM: forceConcrete :: Expr EWord -> String -> (W256 -> EVM s ()) -> EVM s ()
- EVM: forceConcrete2 :: (Expr EWord, Expr EWord) -> String -> ((W256, W256) -> EVM ()) -> EVM ()
+ EVM: forceConcrete2 :: (Expr EWord, Expr EWord) -> String -> ((W256, W256) -> EVM s ()) -> EVM s ()
- EVM: forceConcrete3 :: (Expr EWord, Expr EWord, Expr EWord) -> String -> ((W256, W256, W256) -> EVM ()) -> EVM ()
+ 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 ()) -> EVM ()
+ 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 ()) -> EVM ()
+ 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 ()) -> EVM ()
+ 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: forceConcreteBuf :: Expr Buf -> String -> (ByteString -> EVM ()) -> EVM ()
+ EVM: forceConcreteBuf :: Expr Buf -> String -> (ByteString -> EVM s ()) -> EVM s ()
- EVM: getCodeLocation :: VM -> CodeLocation
+ EVM: getCodeLocation :: VM s -> CodeLocation
- EVM: insertTrace :: TraceData -> EVM ()
+ EVM: insertTrace :: TraceData -> EVM s ()
- EVM: isValidJumpDest :: VM -> Int -> Bool
+ EVM: isValidJumpDest :: VM s -> Int -> Bool
- EVM: limitStack :: Int -> EVM () -> EVM ()
+ EVM: limitStack :: Int -> EVM s () -> EVM s ()
- EVM: loadContract :: Addr -> EVM ()
+ EVM: loadContract :: Expr EAddr -> State (VM s) ()
- EVM: makeVm :: VMOpts -> VM
+ EVM: makeVm :: VMOpts -> ST s (VM s)
- EVM: next :: (?op :: Word8) => EVM ()
+ EVM: next :: (?op :: Word8) => EVM s ()
- EVM: notStatic :: EVM () -> EVM ()
+ EVM: notStatic :: EVM s () -> EVM s ()
- EVM: partial :: PartialExec -> EVM ()
+ EVM: partial :: PartialExec -> EVM s ()
- EVM: popTrace :: EVM ()
+ EVM: popTrace :: EVM s ()
- EVM: precompiledContract :: (?op :: Word8) => Contract -> Word64 -> Addr -> Addr -> W256 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> EVM ()
+ EVM: precompiledContract :: (?op :: Word8) => Contract -> Word64 -> Addr -> Addr -> Expr EWord -> W256 -> W256 -> W256 -> W256 -> [Expr EWord] -> EVM s ()
- EVM: push :: W256 -> EVM ()
+ EVM: push :: W256 -> EVM s ()
- EVM: pushSym :: Expr EWord -> EVM ()
+ EVM: pushSym :: Expr EWord -> EVM s ()
- EVM: pushTrace :: TraceData -> EVM ()
+ EVM: pushTrace :: TraceData -> EVM s ()
- EVM: query :: Query -> EVM ()
+ EVM: query :: Query s -> EVM s ()
- EVM: readMemory :: Expr EWord -> Expr EWord -> VM -> Expr Buf
+ EVM: readMemory :: Expr EWord -> Expr EWord -> EVM s (Expr Buf)
- EVM: refund :: Word64 -> EVM ()
+ EVM: refund :: Word64 -> EVM s ()
- EVM: replaceCode :: Addr -> ContractCode -> EVM ()
+ EVM: replaceCode :: Expr EAddr -> ContractCode -> EVM s ()
- EVM: replaceCodeOfSelf :: ContractCode -> EVM ()
+ EVM: replaceCodeOfSelf :: ContractCode -> EVM s ()
- EVM: resetState :: EVM ()
+ EVM: resetState :: EVM s ()
- EVM: selfdestruct :: Addr -> EVM ()
+ EVM: selfdestruct :: Expr EAddr -> EVM s ()
- EVM: stackOp1 :: (?op :: Word8) => Word64 -> (Expr EWord -> Expr EWord) -> EVM ()
+ EVM: stackOp1 :: (?op :: Word8) => Word64 -> (Expr EWord -> Expr EWord) -> EVM s ()
- EVM: stackOp2 :: (?op :: Word8) => Word64 -> ((Expr EWord, Expr EWord) -> Expr EWord) -> EVM ()
+ EVM: stackOp2 :: (?op :: Word8) => Word64 -> (Expr EWord -> Expr EWord -> Expr EWord) -> EVM s ()
- EVM: stackOp3 :: (?op :: Word8) => Word64 -> ((Expr EWord, Expr EWord, Expr EWord) -> Expr EWord) -> EVM ()
+ EVM: stackOp3 :: (?op :: Word8) => Word64 -> (Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord) -> EVM s ()
- EVM: toBuf :: ContractCode -> Expr Buf
+ EVM: toBuf :: ContractCode -> Maybe (Expr Buf)
- EVM: touchAccount :: Addr -> EVM ()
+ EVM: touchAccount :: Expr EAddr -> EVM s ()
- EVM: traceContext :: Expr End -> Map Addr Contract
+ EVM: traceContext :: Expr End -> Map (Expr EAddr) Contract
- EVM: traceForest :: VM -> Forest Trace
+ EVM: traceForest :: VM s -> Forest Trace
- EVM: traceTopLog :: [Expr Log] -> EVM ()
+ EVM: traceTopLog :: [Expr Log] -> EVM s ()
- EVM: transfer :: Addr -> Addr -> W256 -> EVM ()
+ EVM: transfer :: Expr EAddr -> Expr EAddr -> Expr EWord -> EVM s ()
- EVM: type CheatAction = Expr EWord -> Expr EWord -> Expr Buf -> EVM ()
+ EVM: type CheatAction s = Expr EWord -> Expr EWord -> Expr Buf -> EVM s ()
- EVM: unRefund :: Word64 -> EVM ()
+ EVM: unRefund :: Word64 -> EVM s ()
- EVM: underrun :: EVM ()
+ EVM: underrun :: EVM s ()
- EVM: use' :: (VM -> a) -> EVM a
+ EVM: use' :: (VM s -> a) -> EVM s a
- EVM: vmError :: EvmError -> EVM ()
+ EVM: vmError :: EvmError -> EVM s ()
- EVM: vmOp :: VM -> Maybe Op
+ EVM: vmOp :: VM s -> Maybe Op
- EVM: vmOpIx :: VM -> Maybe Int
+ EVM: vmOpIx :: VM s -> Maybe Int
- EVM: withTraceLocation :: TraceData -> EVM Trace
+ EVM: withTraceLocation :: TraceData -> EVM s Trace
- EVM.Concrete: create2Address :: Addr -> W256 -> ByteString -> Addr
+ EVM.Concrete: create2Address :: Addr -> W256 -> ByteString -> Expr EAddr
- EVM.Concrete: createAddress :: Addr -> W256 -> Addr
+ EVM.Concrete: createAddress :: Addr -> W64 -> Expr EAddr
- EVM.Dapp: DappContext :: DappInfo -> Map Addr Contract -> DappContext
+ EVM.Dapp: DappContext :: DappInfo -> Map (Expr EAddr) Contract -> DappContext
- EVM.Dapp: DappInfo :: FilePath -> Map Text SolcContract -> Map W256 (CodeType, SolcContract) -> [(Code, SolcContract)] -> SourceCache -> [(Text, [(Test, [AbiType])])] -> Map FunctionSelector Method -> Map W256 Event -> Map W256 SolError -> Map Int Value -> (SrcMap -> Maybe Value) -> DappInfo
+ EVM.Dapp: DappInfo :: FilePath -> Map Text SolcContract -> Map W256 (CodeType, SolcContract) -> [(Code, SolcContract)] -> SourceCache -> [(Text, [Sig])] -> Map FunctionSelector Method -> Map W256 Event -> Map W256 SolError -> Map Int Value -> (SrcMap -> Maybe Value) -> DappInfo
- EVM.Dapp: [$sel:env:DappContext] :: DappContext -> Map Addr Contract
+ EVM.Dapp: [$sel:env:DappContext] :: DappContext -> Map (Expr EAddr) Contract
- EVM.Dapp: [$sel:unitTests:DappInfo] :: DappInfo -> [(Text, [(Test, [AbiType])])]
+ EVM.Dapp: [$sel:unitTests:DappInfo] :: DappInfo -> [(Text, [Sig])]
- EVM.Dapp: findAllUnitTests :: [SolcContract] -> [(Text, [(Test, [AbiType])])]
+ EVM.Dapp: findAllUnitTests :: [SolcContract] -> [(Text, [Sig])]
- EVM.Dapp: findUnitTests :: Text -> [SolcContract] -> [(Text, [(Test, [AbiType])])]
+ EVM.Dapp: findUnitTests :: Text -> [SolcContract] -> [(Text, [Sig])]
- EVM.Dapp: unitTestMethods :: SolcContract -> [(Test, [AbiType])]
+ EVM.Dapp: unitTestMethods :: SolcContract -> [Sig]
- EVM.Dapp: unitTestMethodsFiltered :: (Text -> Bool) -> SolcContract -> [(Test, [AbiType])]
+ EVM.Dapp: unitTestMethodsFiltered :: (Text -> Bool) -> SolcContract -> [Sig]
- EVM.Exec: exec :: State VM VMResult
+ EVM.Exec: exec :: EVM s (VMResult s)
- EVM.Exec: execWhile :: (VM -> Bool) -> State VM Int
+ EVM.Exec: execWhile :: (VM s -> Bool) -> State (VM s) Int
- EVM.Exec: run :: State VM VM
+ EVM.Exec: run :: EVM s (VM s)
- EVM.Exec: vmForEthrunCreation :: ByteString -> VM
+ EVM.Exec: vmForEthrunCreation :: ByteString -> ST s (VM s)
- EVM.Expr: readStorage :: Expr EWord -> Expr EWord -> Expr Storage -> Maybe (Expr EWord)
+ EVM.Expr: readStorage :: Expr EWord -> Expr Storage -> Maybe (Expr EWord)
- EVM.Expr: readStorage' :: Expr EWord -> Expr EWord -> Expr Storage -> Expr EWord
+ EVM.Expr: readStorage' :: Expr EWord -> Expr Storage -> Expr EWord
- EVM.Expr: writeStorage :: Expr EWord -> Expr EWord -> Expr EWord -> Expr Storage -> Expr Storage
+ EVM.Expr: writeStorage :: Expr EWord -> Expr EWord -> Expr Storage -> Expr Storage
- EVM.Fetch: [QueryNonce] :: Addr -> RpcQuery W256
+ EVM.Fetch: [QueryNonce] :: Addr -> RpcQuery W64
- EVM.Fetch: http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher
+ EVM.Fetch: http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher s
- EVM.Fetch: oracle :: SolverGroup -> RpcInfo -> Fetcher
+ EVM.Fetch: oracle :: SolverGroup -> RpcInfo -> Fetcher s
- EVM.Fetch: type Fetcher = Query -> IO (EVM ())
+ EVM.Fetch: type Fetcher s = Query s -> IO (EVM s ())
- EVM.Fetch: zero :: Natural -> Maybe Natural -> Fetcher
+ EVM.Fetch: zero :: Natural -> Maybe Natural -> Fetcher s
- EVM.Format: showTraceTree :: DappInfo -> VM -> Text
+ EVM.Format: showTraceTree :: DappInfo -> VM s -> Text
- EVM.Format: showWordExact :: W256 -> Text
+ EVM.Format: showWordExact :: Integral i => i -> Text
- EVM.SMT: CexVars :: [Text] -> Map Text (Expr EWord) -> [(Expr EWord, Expr EWord)] -> [Text] -> [Text] -> CexVars
+ EVM.SMT: CexVars :: [Text] -> [Text] -> Map Text (Expr EWord) -> Map (Expr EAddr) (Set (Expr EWord)) -> [Text] -> [Text] -> CexVars
- EVM.SMT: SMT2 :: [Builder] -> CexVars -> SMT2
+ EVM.SMT: SMT2 :: [Builder] -> RefinementEqs -> CexVars -> SMT2
- EVM.SMT: SMTCex :: Map (Expr EWord) W256 -> Map (Expr Buf) BufModel -> Map W256 (Map W256 W256) -> Map (Expr EWord) W256 -> Map (Expr EWord) W256 -> SMTCex
+ EVM.SMT: SMTCex :: Map (Expr EWord) W256 -> Map (Expr EAddr) Addr -> Map (Expr Buf) BufModel -> Map (Expr EAddr) (Map W256 W256) -> Map (Expr EWord) W256 -> Map (Expr EWord) W256 -> SMTCex
- EVM.SMT: [$sel:store:SMTCex] :: SMTCex -> Map W256 (Map W256 W256)
+ EVM.SMT: [$sel:store:SMTCex] :: SMTCex -> Map (Expr EAddr) (Map W256 W256)
- EVM.SMT: [$sel:storeReads:CexVars] :: CexVars -> [(Expr EWord, Expr EWord)]
+ EVM.SMT: [$sel:storeReads:CexVars] :: CexVars -> Map (Expr EAddr) (Set (Expr EWord))
- EVM.SMT: assertProps :: [Prop] -> SMT2
+ EVM.SMT: assertProps :: AbstRefineConfig -> [Prop] -> SMT2
- EVM.SMT: encodeConcreteStore :: Map W256 (Map W256 W256) -> Builder
+ EVM.SMT: encodeConcreteStore :: Map W256 W256 -> Builder
- EVM.SMT: findStorageReads :: Prop -> [(Expr EWord, Expr EWord)]
+ EVM.SMT: findStorageReads :: Prop -> Map (Expr EAddr) (Set (Expr EWord))
- EVM.SMT: getStore :: (Text -> IO Text) -> [(Expr EWord, Expr EWord)] -> IO (Map W256 (Map W256 W256))
+ EVM.SMT: getStore :: (Text -> IO Text) -> Map (Expr EAddr) (Set (Expr EWord)) -> IO (Map (Expr EAddr) (Map W256 W256))
- EVM.SMT: referencedBlockContext :: Expr a -> [(Builder, [Prop])]
+ EVM.SMT: referencedBlockContext :: TraversableTerm a => a -> [(Builder, [Prop])]
- EVM.SMT: referencedBufs :: Expr a -> [Builder]
+ EVM.SMT: referencedBufs :: TraversableTerm a => a -> [Builder]
- EVM.SMT: referencedFrameContext :: Expr a -> [(Builder, [Prop])]
+ EVM.SMT: referencedFrameContext :: TraversableTerm a => a -> [(Builder, [Prop])]
- EVM.SMT: referencedVars :: Expr a -> [Builder]
+ EVM.SMT: referencedVars :: TraversableTerm a => a -> [Builder]
- EVM.Solvers: splitSExpr :: [Text] -> Text
+ EVM.Solvers: splitSExpr :: [Text] -> [Text]
- EVM.Stepper: [Ask] :: Choose -> Action ()
+ EVM.Stepper: [Ask] :: Choose s -> Action s ()
- EVM.Stepper: [EVM] :: EVM a -> Action a
+ EVM.Stepper: [EVM] :: EVM s a -> Action s a
- EVM.Stepper: [Exec] :: Action VMResult
+ EVM.Stepper: [Exec] :: Action s (VMResult s)
- EVM.Stepper: [IOAct] :: StateT VM IO a -> Action a
+ EVM.Stepper: [IOAct] :: IO a -> Action s a
- EVM.Stepper: [Wait] :: Query -> Action ()
+ EVM.Stepper: [Wait] :: Query s -> Action s ()
- EVM.Stepper: ask :: Choose -> Stepper ()
+ EVM.Stepper: ask :: Choose s -> Stepper s ()
- EVM.Stepper: data Action a
+ EVM.Stepper: data Action s a
- EVM.Stepper: enter :: Text -> Stepper ()
+ EVM.Stepper: enter :: Text -> Stepper s ()
- EVM.Stepper: evm :: EVM a -> Stepper a
+ EVM.Stepper: evm :: EVM s a -> Stepper s a
- EVM.Stepper: evmIO :: StateT VM IO a -> Stepper a
+ EVM.Stepper: evmIO :: IO a -> Stepper s a
- EVM.Stepper: exec :: Stepper VMResult
+ EVM.Stepper: exec :: Stepper s (VMResult s)
- EVM.Stepper: execFully :: Stepper (Either EvmError (Expr Buf))
+ EVM.Stepper: execFully :: Stepper s (Either EvmError (Expr Buf))
- EVM.Stepper: interpret :: Fetcher -> VM -> Stepper a -> IO a
+ EVM.Stepper: interpret :: Fetcher RealWorld -> VM RealWorld -> Stepper RealWorld a -> IO a
- EVM.Stepper: run :: Stepper VM
+ EVM.Stepper: run :: Stepper s (VM s)
- EVM.Stepper: runFully :: Stepper VM
+ EVM.Stepper: runFully :: Stepper s (VM s)
- EVM.Stepper: type Stepper a = Program Action a
+ EVM.Stepper: type Stepper s a = Program (Action s) a
- EVM.Stepper: wait :: Query -> Stepper ()
+ EVM.Stepper: wait :: Query s -> Stepper s ()
- EVM.SymExec: VeriOpts :: Bool -> Bool -> Maybe Integer -> Integer -> LoopHeuristic -> RpcInfo -> VeriOpts
+ EVM.SymExec: VeriOpts :: Bool -> Bool -> Maybe Integer -> Integer -> LoopHeuristic -> AbstRefineConfig -> RpcInfo -> VeriOpts
- EVM.SymExec: abstractVM :: (Expr Buf, [Prop]) -> ByteString -> Maybe Precondition -> Expr Storage -> VM
+ EVM.SymExec: abstractVM :: (Expr Buf, [Prop]) -> ByteString -> Maybe (Precondition s) -> Bool -> ST s (VM s)
- EVM.SymExec: askSmtItersReached :: VM -> Integer -> Bool
+ EVM.SymExec: askSmtItersReached :: VM s -> Integer -> Bool
- EVM.SymExec: checkAssertions :: [Word256] -> Postcondition
+ EVM.SymExec: checkAssertions :: [Word256] -> Postcondition s
- EVM.SymExec: formatCex :: Expr Buf -> SMTCex -> Text
+ EVM.SymExec: formatCex :: Expr Buf -> Maybe Sig -> SMTCex -> Text
- EVM.SymExec: interpret :: Fetcher -> Maybe Integer -> Integer -> LoopHeuristic -> VM -> Stepper (Expr End) -> IO (Expr End)
+ EVM.SymExec: interpret :: Fetcher RealWorld -> Maybe Integer -> Integer -> LoopHeuristic -> VM RealWorld -> Stepper RealWorld (Expr End) -> IO (Expr End)
- EVM.SymExec: isLoopHead :: LoopHeuristic -> VM -> Maybe Bool
+ EVM.SymExec: isLoopHead :: LoopHeuristic -> VM s -> Maybe Bool
- EVM.SymExec: loadSymVM :: ContractCode -> Expr Storage -> Expr EWord -> Expr EWord -> (Expr Buf, [Prop]) -> VM
+ EVM.SymExec: loadSymVM :: ContractCode -> Expr EWord -> (Expr Buf, [Prop]) -> Bool -> ST s (VM s)
- EVM.SymExec: maxIterationsReached :: VM -> Maybe Integer -> Maybe Bool
+ EVM.SymExec: maxIterationsReached :: VM s -> Maybe Integer -> Maybe Bool
- EVM.SymExec: runExpr :: Stepper (Expr End)
+ EVM.SymExec: runExpr :: Stepper RealWorld (Expr End)
- EVM.SymExec: type Postcondition = VM -> Expr End -> Prop
+ EVM.SymExec: type Postcondition s = VM s -> Expr End -> Prop
- EVM.SymExec: type Precondition = VM -> Prop
+ EVM.SymExec: type Precondition s = VM s -> Prop
- EVM.SymExec: verify :: SolverGroup -> VeriOpts -> VM -> Maybe Postcondition -> IO (Expr End, [VerifyResult])
+ EVM.SymExec: verify :: SolverGroup -> VeriOpts -> VM RealWorld -> Maybe (Postcondition RealWorld) -> IO (Expr End, [VerifyResult])
- EVM.SymExec: verifyContract :: SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> Expr Storage -> Maybe Precondition -> Maybe Postcondition -> IO (Expr End, [VerifyResult])
+ EVM.SymExec: verifyContract :: SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> Maybe (Precondition RealWorld) -> Maybe (Postcondition RealWorld) -> IO (Expr End, [VerifyResult])
- EVM.Transaction: accountAt :: Addr -> Getter (Map Addr Contract) Contract
+ EVM.Transaction: accountAt :: Expr EAddr -> Getter (Map (Expr EAddr) Contract) Contract
- EVM.Transaction: initTx :: VM -> VM
+ EVM.Transaction: initTx :: VM s -> VM s
- EVM.Transaction: setupTx :: Addr -> Addr -> W256 -> Word64 -> Map Addr Contract -> Map Addr Contract
+ EVM.Transaction: setupTx :: Expr EAddr -> Expr EAddr -> W256 -> Word64 -> Map (Expr EAddr) Contract -> Map (Expr EAddr) Contract
- EVM.Transaction: touchAccount :: Addr -> Map Addr Contract -> Map Addr Contract
+ EVM.Transaction: touchAccount :: Expr EAddr -> Map (Expr EAddr) Contract -> Map (Expr EAddr) Contract
- EVM.Types: BalanceTooLow :: W256 -> W256 -> EvmError
+ EVM.Types: BalanceTooLow :: Expr EWord -> Expr EWord -> EvmError
- EVM.Types: Block :: Addr -> Expr EWord -> W256 -> W256 -> Word64 -> W256 -> W256 -> FeeSchedule Word64 -> Block
+ EVM.Types: Block :: Expr EAddr -> Expr EWord -> W256 -> W256 -> Word64 -> W256 -> W256 -> FeeSchedule Word64 -> Block
- EVM.Types: Cache :: Map Addr Contract -> Map W256 (Map W256 W256) -> Map (CodeLocation, Int) Bool -> Cache
+ EVM.Types: Cache :: Map Addr Contract -> Map (CodeLocation, Int) Bool -> Cache
- EVM.Types: CallContext :: Addr -> Addr -> W256 -> W256 -> Expr EWord -> Maybe W256 -> Expr Buf -> (Map Addr Contract, Expr Storage) -> SubState -> FrameContext
+ EVM.Types: CallContext :: Expr EAddr -> Expr EAddr -> W256 -> W256 -> Expr EWord -> Maybe W256 -> Expr Buf -> Map (Expr EAddr) Contract -> SubState -> FrameContext
- EVM.Types: Choose :: Choose -> Effect
+ EVM.Types: Choose :: Choose s -> Effect s
- EVM.Types: Contract :: ContractCode -> W256 -> W256 -> Expr EWord -> Vector Int -> Vector (Int, Op) -> Bool -> Contract
+ EVM.Types: Contract :: ContractCode -> Expr Storage -> Expr Storage -> Expr EWord -> Maybe W64 -> Expr EWord -> Vector Int -> Vector (Int, Op) -> Bool -> Contract
- EVM.Types: CreationContext :: Addr -> Expr EWord -> Map Addr Contract -> SubState -> FrameContext
+ EVM.Types: CreationContext :: Expr EAddr -> Expr EWord -> Map (Expr EAddr) Contract -> SubState -> FrameContext
- EVM.Types: Env :: Map Addr Contract -> W256 -> Expr Storage -> Map W256 (Map W256 W256) -> Env
+ EVM.Types: Env :: Map (Expr EAddr) Contract -> W256 -> Int -> Env
- EVM.Types: Frame :: FrameContext -> FrameState -> Frame
+ EVM.Types: Frame :: FrameContext -> FrameState s -> Frame s
- EVM.Types: FrameState :: Addr -> Addr -> ContractCode -> {-# UNPACK #-} !Int -> [Expr EWord] -> Expr Buf -> Word64 -> Expr Buf -> Expr EWord -> Expr EWord -> {-# UNPACK #-} !Word64 -> Expr Buf -> Bool -> FrameState
+ 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: HandleEffect :: Effect -> VMResult
+ EVM.Types: HandleEffect :: Effect s -> VMResult s
- EVM.Types: MaxIterationsReached :: Int -> Addr -> PartialExec
+ EVM.Types: MaxIterationsReached :: Int -> Expr EAddr -> PartialExec
- EVM.Types: Query :: Query -> Effect
+ EVM.Types: Query :: Query s -> Effect s
- EVM.Types: SubState :: [Addr] -> [Addr] -> Set Addr -> Set (Addr, W256) -> [(Addr, Word64)] -> SubState
+ EVM.Types: SubState :: [Expr EAddr] -> [Expr EAddr] -> Set (Expr EAddr) -> Set (Expr EAddr, W256) -> [(Expr EAddr, Word64)] -> SubState
- EVM.Types: Traces :: Forest Trace -> Map Addr Contract -> Traces
+ EVM.Types: Traces :: Forest Trace -> Map (Expr EAddr) Contract -> Traces
- EVM.Types: TxState :: W256 -> Word64 -> W256 -> Addr -> Addr -> Expr EWord -> SubState -> Bool -> Map Addr Contract -> TxState
+ EVM.Types: TxState :: W256 -> Word64 -> W256 -> Expr EAddr -> Expr EAddr -> Expr EWord -> SubState -> Bool -> Map (Expr EAddr) Contract -> TxState
- EVM.Types: Unfinished :: PartialExec -> VMResult
+ EVM.Types: Unfinished :: PartialExec -> VMResult s
- EVM.Types: VM :: Maybe VMResult -> FrameState -> [Frame] -> Env -> Block -> TxState -> [Expr Log] -> TreePos Empty Trace -> Cache -> {-# UNPACK #-} !Word64 -> Map CodeLocation (Int, [Expr EWord]) -> [Prop] -> [Prop] -> Bool -> Maybe Addr -> VM
+ 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: VMFailure :: EvmError -> VMResult
+ EVM.Types: VMFailure :: EvmError -> VMResult s
- EVM.Types: VMOpts :: Contract -> (Expr Buf, [Prop]) -> Expr Storage -> Expr EWord -> W256 -> Addr -> Expr EWord -> Addr -> Word64 -> Word64 -> W256 -> Expr EWord -> Addr -> W256 -> W256 -> Word64 -> W256 -> W256 -> FeeSchedule Word64 -> W256 -> Bool -> Map Addr [W256] -> Bool -> VMOpts
+ 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: VMSuccess :: Expr Buf -> VMResult
+ EVM.Types: VMSuccess :: Expr Buf -> VMResult s
- EVM.Types: [$sel:accessedAddresses:SubState] :: SubState -> Set Addr
+ EVM.Types: [$sel:accessedAddresses:SubState] :: SubState -> Set (Expr EAddr)
- EVM.Types: [$sel:accessedStorageKeys:SubState] :: SubState -> Set (Addr, W256)
+ EVM.Types: [$sel:accessedStorageKeys:SubState] :: SubState -> Set (Expr EAddr, W256)
- EVM.Types: [$sel:addr:UnexpectedSymbolicArg] :: PartialExec -> Addr
+ EVM.Types: [$sel:addr:UnexpectedSymbolicArg] :: PartialExec -> Expr EAddr
- EVM.Types: [$sel:address:CreationContext] :: FrameContext -> Addr
+ EVM.Types: [$sel:address:CreationContext] :: FrameContext -> Expr EAddr
- EVM.Types: [$sel:address:VMOpts] :: VMOpts -> Addr
+ EVM.Types: [$sel:address:VMOpts] :: VMOpts -> Expr EAddr
- EVM.Types: [$sel:balance:Contract] :: Contract -> W256
+ EVM.Types: [$sel:balance:Contract] :: Contract -> Expr EWord
- EVM.Types: [$sel:block:VM] :: VM -> Block
+ EVM.Types: [$sel:block:VM] :: VM s -> Block
- EVM.Types: [$sel:burned:VM] :: VM -> {-# UNPACK #-} !Word64
+ EVM.Types: [$sel:burned:VM] :: VM s -> {-# UNPACK #-} !Word64
- EVM.Types: [$sel:cache:VM] :: VM -> Cache
+ EVM.Types: [$sel:cache:VM] :: VM s -> Cache
- EVM.Types: [$sel:calldata:FrameState] :: FrameState -> Expr Buf
+ EVM.Types: [$sel:calldata:FrameState] :: FrameState s -> Expr Buf
- EVM.Types: [$sel:caller:FrameState] :: FrameState -> Expr EWord
+ EVM.Types: [$sel:caller:FrameState] :: FrameState s -> Expr EAddr
- EVM.Types: [$sel:caller:VMOpts] :: VMOpts -> Expr EWord
+ EVM.Types: [$sel:caller:VMOpts] :: VMOpts -> Expr EAddr
- EVM.Types: [$sel:callreversion:CreationContext] :: FrameContext -> (Map Addr Contract, Expr Storage)
+ EVM.Types: [$sel:callreversion:CreationContext] :: FrameContext -> Map (Expr EAddr) Contract
- EVM.Types: [$sel:callvalue:FrameState] :: FrameState -> Expr EWord
+ EVM.Types: [$sel:callvalue:FrameState] :: FrameState s -> Expr EWord
- EVM.Types: [$sel:code:FrameState] :: FrameState -> ContractCode
+ EVM.Types: [$sel:code:FrameState] :: FrameState s -> ContractCode
- EVM.Types: [$sel:codeContract:FrameState] :: FrameState -> Addr
+ EVM.Types: [$sel:codeContract:FrameState] :: FrameState s -> Expr EAddr
- EVM.Types: [$sel:coinbase:Block] :: Block -> Addr
+ EVM.Types: [$sel:coinbase:Block] :: Block -> Expr EAddr
- EVM.Types: [$sel:coinbase:VMOpts] :: VMOpts -> Addr
+ EVM.Types: [$sel:coinbase:VMOpts] :: VMOpts -> Expr EAddr
- EVM.Types: [$sel:constraints:VM] :: VM -> [Prop]
+ EVM.Types: [$sel:constraints:VM] :: VM s -> [Prop]
- EVM.Types: [$sel:context:CreationContext] :: FrameContext -> Addr
+ EVM.Types: [$sel:context:CreationContext] :: FrameContext -> Expr EAddr
- EVM.Types: [$sel:context:Frame] :: Frame -> FrameContext
+ EVM.Types: [$sel:context:Frame] :: Frame s -> FrameContext
- EVM.Types: [$sel:contract:FrameState] :: FrameState -> Addr
+ EVM.Types: [$sel:contract:FrameState] :: FrameState s -> Expr EAddr
- EVM.Types: [$sel:contracts:Env] :: Env -> Map Addr Contract
+ EVM.Types: [$sel:contracts:Env] :: Env -> Map (Expr EAddr) Contract
- EVM.Types: [$sel:contracts:Traces] :: Traces -> Map Addr Contract
+ EVM.Types: [$sel:contracts:Traces] :: Traces -> Map (Expr EAddr) Contract
- EVM.Types: [$sel:createreversion:CreationContext] :: FrameContext -> Map Addr Contract
+ EVM.Types: [$sel:createreversion:CreationContext] :: FrameContext -> Map (Expr EAddr) Contract
- EVM.Types: [$sel:env:VM] :: VM -> Env
+ EVM.Types: [$sel:env:VM] :: VM s -> Env
- EVM.Types: [$sel:frames:VM] :: VM -> [Frame]
+ EVM.Types: [$sel:frames:VM] :: VM s -> [Frame s]
- EVM.Types: [$sel:gas:FrameState] :: FrameState -> {-# UNPACK #-} !Word64
+ EVM.Types: [$sel:gas:FrameState] :: FrameState s -> {-# UNPACK #-} !Word64
- EVM.Types: [$sel:iterations:VM] :: VM -> Map CodeLocation (Int, [Expr EWord])
+ EVM.Types: [$sel:iterations:VM] :: VM s -> Map CodeLocation (Int, [Expr EWord])
- EVM.Types: [$sel:keccakEqs:VM] :: VM -> [Prop]
+ EVM.Types: [$sel:keccakEqs:VM] :: VM s -> [Prop]
- EVM.Types: [$sel:logs:VM] :: VM -> [Expr Log]
+ EVM.Types: [$sel:logs:VM] :: VM s -> [Expr Log]
- EVM.Types: [$sel:memory:FrameState] :: FrameState -> Expr Buf
+ EVM.Types: [$sel:memory:FrameState] :: FrameState s -> Memory s
- EVM.Types: [$sel:memorySize:FrameState] :: FrameState -> Word64
+ EVM.Types: [$sel:memorySize:FrameState] :: FrameState s -> Word64
- EVM.Types: [$sel:nonce:Contract] :: Contract -> W256
+ EVM.Types: [$sel:nonce:Contract] :: Contract -> Maybe W64
- EVM.Types: [$sel:origin:TxState] :: TxState -> Addr
+ EVM.Types: [$sel:origin:TxState] :: TxState -> Expr EAddr
- EVM.Types: [$sel:origin:VMOpts] :: VMOpts -> Addr
+ EVM.Types: [$sel:origin:VMOpts] :: VMOpts -> Expr EAddr
- EVM.Types: [$sel:pc:FrameState] :: FrameState -> {-# UNPACK #-} !Int
+ EVM.Types: [$sel:pc:FrameState] :: FrameState s -> {-# UNPACK #-} !Int
- EVM.Types: [$sel:refunds:SubState] :: SubState -> [(Addr, Word64)]
+ EVM.Types: [$sel:refunds:SubState] :: SubState -> [(Expr EAddr, Word64)]
- EVM.Types: [$sel:result:VM] :: VM -> Maybe VMResult
+ EVM.Types: [$sel:result:VM] :: VM s -> Maybe (VMResult s)
- EVM.Types: [$sel:returndata:FrameState] :: FrameState -> Expr Buf
+ EVM.Types: [$sel:returndata:FrameState] :: FrameState s -> Expr Buf
- EVM.Types: [$sel:selfdestructs:SubState] :: SubState -> [Addr]
+ EVM.Types: [$sel:selfdestructs:SubState] :: SubState -> [Expr EAddr]
- EVM.Types: [$sel:stack:FrameState] :: FrameState -> [Expr EWord]
+ EVM.Types: [$sel:stack:FrameState] :: FrameState s -> [Expr EWord]
- EVM.Types: [$sel:state:Frame] :: Frame -> FrameState
+ EVM.Types: [$sel:state:Frame] :: Frame s -> FrameState s
- EVM.Types: [$sel:state:VM] :: VM -> FrameState
+ EVM.Types: [$sel:state:VM] :: VM s -> FrameState s
- EVM.Types: [$sel:static:FrameState] :: FrameState -> Bool
+ EVM.Types: [$sel:static:FrameState] :: FrameState s -> Bool
- EVM.Types: [$sel:target:CreationContext] :: FrameContext -> Addr
+ EVM.Types: [$sel:target:CreationContext] :: FrameContext -> Expr EAddr
- EVM.Types: [$sel:toAddr:TxState] :: TxState -> Addr
+ EVM.Types: [$sel:toAddr:TxState] :: TxState -> Expr EAddr
- EVM.Types: [$sel:touchedAccounts:SubState] :: SubState -> [Addr]
+ EVM.Types: [$sel:touchedAccounts:SubState] :: SubState -> [Expr EAddr]
- EVM.Types: [$sel:traces:VM] :: VM -> TreePos Empty Trace
+ EVM.Types: [$sel:traces:VM] :: VM s -> TreePos Empty Trace
- EVM.Types: [$sel:tx:VM] :: VM -> TxState
+ EVM.Types: [$sel:tx:VM] :: VM s -> TxState
- EVM.Types: [$sel:txAccessList:VMOpts] :: VMOpts -> Map Addr [W256]
+ EVM.Types: [$sel:txAccessList:VMOpts] :: VMOpts -> Map (Expr EAddr) [W256]
- EVM.Types: [$sel:txReversion:TxState] :: TxState -> Map Addr Contract
+ EVM.Types: [$sel:txReversion:TxState] :: TxState -> Map (Expr EAddr) Contract
- EVM.Types: [AbstractStore] :: Expr Storage
+ EVM.Types: [AbstractStore] :: Expr EAddr -> Expr Storage
- EVM.Types: [Balance] :: Int -> Int -> Expr EWord -> Expr EWord
+ EVM.Types: [Balance] :: Expr EAddr -> Expr EWord
- EVM.Types: [CodeSize] :: Expr EWord -> Expr EWord
+ EVM.Types: [CodeSize] :: Expr EAddr -> Expr EWord
- EVM.Types: [ConcreteStore] :: Map W256 (Map W256 W256) -> Expr Storage
+ EVM.Types: [ConcreteStore] :: Map W256 W256 -> Expr Storage
- EVM.Types: [PleaseAskSMT] :: Expr EWord -> [Prop] -> (BranchCondition -> EVM ()) -> Query
+ EVM.Types: [PleaseAskSMT] :: Expr EWord -> [Prop] -> (BranchCondition -> EVM s ()) -> Query s
- EVM.Types: [PleaseChoosePath] :: Expr EWord -> (Bool -> EVM ()) -> Choose
+ EVM.Types: [PleaseChoosePath] :: Expr EWord -> (Bool -> EVM s ()) -> Choose s
- EVM.Types: [PleaseDoFFI] :: [String] -> (ByteString -> EVM ()) -> Query
+ EVM.Types: [PleaseDoFFI] :: [String] -> (ByteString -> EVM s ()) -> Query s
- EVM.Types: [PleaseFetchContract] :: Addr -> (Contract -> EVM ()) -> Query
+ EVM.Types: [PleaseFetchContract] :: Addr -> BaseState -> (Contract -> EVM s ()) -> Query s
- EVM.Types: [PleaseFetchSlot] :: Addr -> W256 -> (W256 -> EVM ()) -> Query
+ EVM.Types: [PleaseFetchSlot] :: Addr -> W256 -> (W256 -> EVM s ()) -> Query s
- EVM.Types: [SLoad] :: Expr EWord -> Expr EWord -> Expr Storage -> Expr EWord
+ EVM.Types: [SLoad] :: Expr EWord -> Expr Storage -> Expr EWord
- EVM.Types: [SStore] :: Expr EWord -> Expr EWord -> Expr EWord -> Expr Storage -> Expr Storage
+ EVM.Types: [SStore] :: Expr EWord -> Expr EWord -> Expr Storage -> Expr Storage
- EVM.Types: [Success] :: [Prop] -> Traces -> Expr Buf -> Expr Storage -> Expr End
+ EVM.Types: [Success] :: [Prop] -> Traces -> Expr Buf -> Map (Expr EAddr) (Expr EContract) -> Expr End
- EVM.Types: data Choose
+ EVM.Types: data Choose s
- EVM.Types: data Effect
+ EVM.Types: data Effect s
- EVM.Types: data Frame
+ EVM.Types: data Frame s
- EVM.Types: data FrameState
+ EVM.Types: data FrameState s
- EVM.Types: data Query
+ EVM.Types: data Query s
- EVM.Types: data VM
+ EVM.Types: data VM s
- EVM.Types: data VMResult
+ EVM.Types: data VMResult s
- EVM.Types: type CodeLocation = (Addr, Int)
+ EVM.Types: type CodeLocation = (Expr EAddr, Int)
- EVM.Types: type EVM a = State VM a
+ EVM.Types: type EVM s a = StateT (VM s) (ST s) a
- EVM.UnitTest: TestVMParams :: Addr -> Addr -> Addr -> Word64 -> Word64 -> W256 -> W256 -> W256 -> Addr -> W256 -> W256 -> Word64 -> W256 -> W256 -> W256 -> W256 -> TestVMParams
+ EVM.UnitTest: TestVMParams :: Expr EAddr -> Expr EAddr -> Expr EAddr -> Word64 -> Word64 -> W256 -> W256 -> W256 -> Expr EAddr -> W256 -> W256 -> Word64 -> W256 -> W256 -> W256 -> W256 -> TestVMParams
- EVM.UnitTest: UnitTestOptions :: RpcInfo -> SolverGroup -> Maybe Int -> Maybe Integer -> Integer -> Bool -> Maybe Int -> Maybe Natural -> Maybe Text -> Maybe Text -> Text -> Int -> Maybe (Text, ByteString) -> (VM -> VM) -> DappInfo -> TestVMParams -> Bool -> UnitTestOptions
+ EVM.UnitTest: UnitTestOptions :: RpcInfo -> SolverGroup -> Maybe Int -> Maybe Integer -> Integer -> Bool -> Maybe Natural -> Maybe Text -> Text -> DappInfo -> TestVMParams -> Bool -> UnitTestOptions s
- EVM.UnitTest: [$sel:address:TestVMParams] :: TestVMParams -> Addr
+ EVM.UnitTest: [$sel:address:TestVMParams] :: TestVMParams -> Expr EAddr
- EVM.UnitTest: [$sel:askSmtIters:UnitTestOptions] :: UnitTestOptions -> Integer
+ EVM.UnitTest: [$sel:askSmtIters:UnitTestOptions] :: UnitTestOptions s -> Integer
- EVM.UnitTest: [$sel:caller:TestVMParams] :: TestVMParams -> Addr
+ EVM.UnitTest: [$sel:caller:TestVMParams] :: TestVMParams -> Expr EAddr
- EVM.UnitTest: [$sel:coinbase:TestVMParams] :: TestVMParams -> Addr
+ EVM.UnitTest: [$sel:coinbase:TestVMParams] :: TestVMParams -> Expr EAddr
- EVM.UnitTest: [$sel:dapp:UnitTestOptions] :: UnitTestOptions -> DappInfo
+ EVM.UnitTest: [$sel:dapp:UnitTestOptions] :: UnitTestOptions s -> DappInfo
- EVM.UnitTest: [$sel:ffiAllowed:UnitTestOptions] :: UnitTestOptions -> Bool
+ EVM.UnitTest: [$sel:ffiAllowed:UnitTestOptions] :: UnitTestOptions s -> Bool
- EVM.UnitTest: [$sel:match:UnitTestOptions] :: UnitTestOptions -> Text
+ EVM.UnitTest: [$sel:match:UnitTestOptions] :: UnitTestOptions s -> Text
- EVM.UnitTest: [$sel:maxIter:UnitTestOptions] :: UnitTestOptions -> Maybe Integer
+ EVM.UnitTest: [$sel:maxIter:UnitTestOptions] :: UnitTestOptions s -> Maybe Integer
- EVM.UnitTest: [$sel:origin:TestVMParams] :: TestVMParams -> Addr
+ EVM.UnitTest: [$sel:origin:TestVMParams] :: TestVMParams -> Expr EAddr
- EVM.UnitTest: [$sel:rpcInfo:UnitTestOptions] :: UnitTestOptions -> RpcInfo
+ EVM.UnitTest: [$sel:rpcInfo:UnitTestOptions] :: UnitTestOptions s -> RpcInfo
- EVM.UnitTest: [$sel:smtDebug:UnitTestOptions] :: UnitTestOptions -> Bool
+ EVM.UnitTest: [$sel:smtDebug:UnitTestOptions] :: UnitTestOptions s -> Bool
- EVM.UnitTest: [$sel:smtTimeout:UnitTestOptions] :: UnitTestOptions -> Maybe Natural
+ EVM.UnitTest: [$sel:smtTimeout:UnitTestOptions] :: UnitTestOptions s -> Maybe Natural
- EVM.UnitTest: [$sel:solver:UnitTestOptions] :: UnitTestOptions -> Maybe Text
+ EVM.UnitTest: [$sel:solver:UnitTestOptions] :: UnitTestOptions s -> Maybe Text
- EVM.UnitTest: [$sel:solvers:UnitTestOptions] :: UnitTestOptions -> SolverGroup
+ EVM.UnitTest: [$sel:solvers:UnitTestOptions] :: UnitTestOptions s -> SolverGroup
- EVM.UnitTest: [$sel:testParams:UnitTestOptions] :: UnitTestOptions -> TestVMParams
+ EVM.UnitTest: [$sel:testParams:UnitTestOptions] :: UnitTestOptions s -> TestVMParams
- EVM.UnitTest: [$sel:verbose:UnitTestOptions] :: UnitTestOptions -> Maybe Int
+ EVM.UnitTest: [$sel:verbose:UnitTestOptions] :: UnitTestOptions s -> Maybe Int
- EVM.UnitTest: abiCall :: TestVMParams -> Either (Text, AbiValue) ByteString -> EVM ()
+ EVM.UnitTest: abiCall :: TestVMParams -> Either (Text, AbiValue) ByteString -> EVM s ()
- EVM.UnitTest: checkSymFailures :: UnitTestOptions -> Stepper VM
+ EVM.UnitTest: checkSymFailures :: UnitTestOptions RealWorld -> Stepper RealWorld (VM RealWorld)
- EVM.UnitTest: data UnitTestOptions
+ EVM.UnitTest: data UnitTestOptions s
- EVM.UnitTest: execSymTest :: UnitTestOptions -> ABIMethod -> (Expr Buf, [Prop]) -> Stepper (Expr End)
+ EVM.UnitTest: execSymTest :: UnitTestOptions RealWorld -> ABIMethod -> (Expr Buf, [Prop]) -> Stepper RealWorld (Expr End)
- EVM.UnitTest: failOutput :: VM -> UnitTestOptions -> Text -> Text
+ EVM.UnitTest: failOutput :: VM s -> UnitTestOptions s -> Text -> Text
- EVM.UnitTest: initialUnitTestVm :: UnitTestOptions -> SolcContract -> VM
+ EVM.UnitTest: initialUnitTestVm :: UnitTestOptions s -> SolcContract -> ST s (VM s)
- EVM.UnitTest: initializeUnitTest :: UnitTestOptions -> SolcContract -> Stepper ()
+ EVM.UnitTest: initializeUnitTest :: UnitTestOptions s -> SolcContract -> Stepper s ()
- EVM.UnitTest: makeTxCall :: TestVMParams -> (Expr Buf, [Prop]) -> EVM ()
+ EVM.UnitTest: makeTxCall :: TestVMParams -> (Expr Buf, [Prop]) -> EVM s ()
- EVM.UnitTest: makeVeriOpts :: UnitTestOptions -> VeriOpts
+ EVM.UnitTest: makeVeriOpts :: UnitTestOptions s -> VeriOpts
- EVM.UnitTest: passOutput :: VM -> UnitTestOptions -> Text -> Text
+ EVM.UnitTest: passOutput :: VM s -> UnitTestOptions s -> Text -> Text
- EVM.UnitTest: runUnitTestContract :: UnitTestOptions -> Map Text SolcContract -> (Text, [(Test, [AbiType])]) -> IO [(Bool, VM)]
+ EVM.UnitTest: runUnitTestContract :: UnitTestOptions RealWorld -> Map Text SolcContract -> (Text, [Sig]) -> IO [Bool]
- EVM.UnitTest: symFailure :: UnitTestOptions -> Text -> Expr Buf -> [AbiType] -> [(Expr End, SMTCex)] -> Text
+ EVM.UnitTest: symFailure :: UnitTestOptions RealWorld -> Text -> Expr Buf -> [AbiType] -> [(Expr End, SMTCex)] -> Text
- EVM.UnitTest: symRun :: UnitTestOptions -> VM -> Text -> [AbiType] -> IO (Text, Either Text Text, VM)
+ EVM.UnitTest: symRun :: UnitTestOptions RealWorld -> VM RealWorld -> Sig -> IO (Text, Either Text Text)
- EVM.UnitTest: unitTest :: UnitTestOptions -> Contracts -> Maybe String -> IO Bool
+ EVM.UnitTest: unitTest :: UnitTestOptions RealWorld -> Contracts -> IO Bool
Files
- CHANGELOG.md +114/−0
- bench/bench.hs +15/−28
- bench/contracts/deposit.sol +182/−0
- bench/contracts/vat.sol +246/−0
- cli/cli.hs +599/−0
- hevm-cli/hevm-cli.hs +0/−716
- hevm.cabal +22/−57
- src/EVM.hs +2627/−2316
- src/EVM/ABI.hs +79/−76
- src/EVM/Concrete.hs +9/−35
- src/EVM/Dapp.hs +44/−44
- src/EVM/Debug.hs +0/−52
- src/EVM/Dev.hs +0/−498
- src/EVM/Exec.hs +16/−15
- src/EVM/Expr.hs +443/−107
- src/EVM/Facts.hs +0/−231
- src/EVM/Facts/Git.hs +0/−48
- src/EVM/FeeSchedule.hs +16/−95
- src/EVM/Fetch.hs +26/−23
- src/EVM/Format.hs +258/−54
- src/EVM/Hexdump.hs +0/−129
- src/EVM/Keccak.hs +26/−4
- src/EVM/Patricia.hs +0/−226
- src/EVM/SMT.hs +288/−202
- src/EVM/Solidity.hs +46/−112
- src/EVM/Solvers.hs +48/−19
- src/EVM/Stepper.hs +34/−45
- src/EVM/StorageLayout.hs +0/−152
- src/EVM/SymExec.hs +272/−175
- src/EVM/TTY.hs +0/−1047
- src/EVM/TTYCenteredList.hs +0/−70
- src/EVM/Transaction.hs +13/−23
- src/EVM/Traversals.hs +120/−222
- src/EVM/Types.hs +212/−199
- src/EVM/UnitTest.hs +127/−650
- test/EVM/Test/BlockchainTests.hs +140/−132
- test/EVM/Test/Tracing.hs +234/−160
- test/EVM/Test/Utils.hs +9/−23
- test/contracts/fail/cheatCodes.sol +0/−39
- test/contracts/fail/dsProveFail.sol +0/−66
- test/contracts/fail/invariantFail.sol +0/−50
- test/contracts/fail/trivial.sol +0/−9
- test/contracts/lib/erc20.sol +0/−201
- test/contracts/lib/test.sol +0/−469
- test/contracts/pass/abstract.sol +0/−15
- test/contracts/pass/cheatCodes.sol +0/−125
- test/contracts/pass/constantinople.sol +0/−331
- test/contracts/pass/dsProvePass.sol +0/−83
- test/contracts/pass/invariants.sol +0/−41
- test/contracts/pass/libraries.sol +0/−18
- test/contracts/pass/loops.sol +0/−12
- test/contracts/pass/rpc.sol +0/−16
- test/contracts/pass/trivial.sol +0/−9
- test/rpc.hs +41/−38
- test/test.hs +3813/−2763
CHANGELOG.md view
@@ -5,6 +5,119 @@ 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). +## [0.52.0] - 2023-10-26++This is a major breaking release that removes several user facing features and includes non trivial+breakage for library users. These changes mean the code is significantly simpler, more performant,+and allow support for new features like fully symbolic addresses.++In addition to the changes below, this release includes significant work on performance+optimization for symbolic execution.++## Added++The major new user facing feature in this release is support for fully symbolic addresses (including+fully symbolic addresses for deployed contracts). This allows tests to be writen that call+`vm.prank` with a symbolic value, making some tests (e.g. access control, token transfer logic) much+more comprehensive.++Some restrictions around reading balances from and transfering value between symbolic addresses are+currently in place. Currently, if the address is symbolic, then you will only be able to read it's+balance, or transfer value to/from it, if it is the address of a contract that is actually deployed.+This is required to ensure soundness in the face of aliasing between symbolic addresses. We intend+to lift this restriction in a future release.++### Other++- Support for `vm.deal`+- Support for `vm.assume` (this is semantically identical to using `require`, but does simplify the+ process of porting exisitng fuzz tests to be symbolic)+- the `check` prefix now recognized for symbolic tests+- `hevm test` now takes a `--number` argument to specify which block should be used when making rpc queries++## Changed++### Revert Semantics in Solidity Tests++solidity tests no longer consider reverts to be a failure, and check only for the ds-test failed bit+or user defined assertion failures (i.e. `Panic(0x01)`). This makes writing tests much easier as+users no longer have to consider trivial reverts (e.g. arithmetic overflow).++A positive (i.e. `prove`/`check`) test with no rechable assertion violations that does not have any+succesful branches will still be considered a failure.++## Removed++hevm has been around for a while, and over time has accumulated many features. We decided to remove+some of these features in the interest of focusing our attention, increasing our iteration speed and+simplifying maintainance. The following user facing features have been removed from this release:++- The visual debugger has been removed+- All concrete ds-test executors have been removed (i.e. plain, fuzzer, invariant)+- Rpc caching and state serialization has been removed (i.e. all `--cache` / `--state` flags)+- The various `DAPP_TEST` variables are no longer observed+- The `--initial-storage` flag no longer accepts a concrete prestore (valid values are now `Empty` or `Abstract`)++## Fixed++This release also includes many small bugfixes:++- CopySlice wraparound issue especially during CopyCallBytesToMemory+- Contracts deployed during symbolic execution are created with an empty storage (instead of abstract in previous versions)+- EVM.Solidity.toCode to include contractName in error string+- Better cex reconstruction in cases where branches do not refer to all input variables in calldata+- Correctly handle empty bytestring compiled contracts' JSON+- No more false positives when keccak is called with inputs of different sizes+- `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+- storage layout information will now be parsed from the output of `forge build` if it is available++## API Changes++### Reworked Storage Model / Symbolic Addresses++Adding symbolic addresses required some fairly significant changes to the way that we model storage.+We introduced a new address type to `Expr` (`Expr EAddr`), that allows us to model fully symbolic+addresses. Instead of modelling storage as a global symbolic 2D map (`address -> word -> word`) in+`vm.env`, each contract now has it's own symbolic 1D map (`word -> word`), that is stored in the+`vm.contracts` mapping. `vm.contracts` is now keyed on `Expr EAddr` instead of `Addr`. Addresses+that are keys to the `vm.contracts` mapping are asserted to be distinct in our smt encoding. This+allows us to support symbolic addresses in a fully static manner (i.e. we do not currently need to+make any extra smt queries when looking up storage for a symbolic address).++### Mutable Memory & ST++We now use a mutable representation of memory if it is currently completely concrete. This is a+significant performance improvement, and fixed a particulary egregious memory leak. It does entail+the use of the `ST` monad, and introduces a new type parameter to the `VM` type that tags a given+instance with it's thread local state. Library users will now need to either use the ST moand and+`runST` or `stToIO` to compose and sequence vm executions.++## GHC 9.4++Hevm is now built with ghc9.4. While earlier compiler versions may continue to work for now, they+are no longer explicitly tested or supported.++### Other++- Contract balances can now be fully symbolic+- Contract code can now be fully abstract. Calls into contracts with unknown code will fail with `UnexpectedSymbolicArg`.+- Run expression simplification on branch conditions+- SLoad/SStore simplifications based on assumptions regarding Keccak non-collision&preimage+- Improved Prop simplification+- 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+- `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.+- `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+ setting contracts that are deployed at runtime via a call to+ `CREATE`/`CREATE2` have zero initialized storage.+ ## [0.51.3] - 2023-07-14 ## Fixed@@ -16,6 +129,7 @@ ## Changed - Removed sha3Crack which has been deprecated for keccakEqs+- Abstraction-refinement for more complicated expressions such as MULMOD ## Added
bench/bench.hs view
@@ -3,30 +3,23 @@ import GHC.Natural import Control.Monad import Data.Maybe-import System.Environment (lookupEnv, getEnv)+import System.Environment (getEnv) import qualified Paths_hevm as Paths import Test.Tasty (localOption, withResource) import Test.Tasty.Bench-import Data.Functor-import Data.String.Here import Data.ByteString (ByteString) import System.FilePath.Posix-import Control.Monad.State.Strict import qualified Data.Map.Strict as Map import qualified Data.Text as T import qualified System.FilePath.Find as Find import qualified Data.ByteString.Lazy as LazyByteString -import EVM (StorageModel(..)) import EVM.SymExec import EVM.Solidity import EVM.Solvers-import EVM.ABI-import EVM.Dapp-import EVM.Types-import qualified EVM.TTY as TTY+import EVM.Format (hexByteString) import qualified EVM.Stepper as Stepper import qualified EVM.Fetch as Fetch @@ -34,10 +27,10 @@ main :: IO () main = defaultMain- [ mkbench erc20 "erc20" Nothing [1]- , mkbench (pure vat) "vat" Nothing [4]- , mkbench (pure deposit) "deposit" (Just 32) [4]- , mkbench (pure uniV2Pair) "uniV2" (Just 10) [4]+ [ mkbench erc20 "erc20" 0 [1]+ , mkbench (pure vat) "vat" 0 [4]+ , mkbench (pure deposit) "deposit" 32 [4]+ , mkbench (pure uniV2Pair) "uniV2" 10 [4] , withResource bcjsons (pure . const ()) blockchainTests ] @@ -59,7 +52,7 @@ parseSuite path = do contents <- LazyByteString.readFile path case BCTests.parseBCSuite contents of- Left e -> pure (path, mempty)+ Left _ -> pure (path, mempty) Right tests -> pure (path, tests) -- | executes all provided bc tests in sequence and accumulates a boolean value representing their success.@@ -67,7 +60,7 @@ blockchainTests :: IO (Map.Map FilePath (Map.Map String BCTests.Case)) -> Benchmark blockchainTests ts = bench "blockchain-tests" $ nfIO $ do tests <- ts- putStrLn "\n executing tests:"+ putStrLn " executing blockchain tests" let cases = concat . Map.elems . (fmap Map.toList) $ tests ignored = Map.keys BCTests.commonProblematicTests foldM (\acc (n, c) ->@@ -75,7 +68,6 @@ then pure True else do res <- runBCTest c- putStrLn $ " " <> n pure $ acc && res ) True cases @@ -83,8 +75,8 @@ runBCTest :: BCTests.Case -> IO Bool runBCTest x = do- let vm0 = BCTests.vmForCase x- result <- execStateT (Stepper.interpret (Fetch.zero 0 (Just 0)) . void $ Stepper.execFully) vm0+ vm0 <- BCTests.vmForCase x+ result <- Stepper.interpret (Fetch.zero 0 Nothing) vm0 Stepper.runFully maybeReason <- BCTests.checkExpectation False x result pure $ isNothing maybeReason @@ -92,17 +84,12 @@ --- Helpers ---------------------------------------------------------------------------------------- -debugContract :: ByteString -> IO ()-debugContract c = withSolvers CVC5 4 Nothing $ \solvers -> do- let prestate = abstractVM (mkCalldata Nothing []) c Nothing SymbolicS- void $ TTY.runFromVM solvers Nothing Nothing emptyDapp prestate--findPanics :: Solver -> Natural -> Maybe Integer -> ByteString -> IO ()+findPanics :: Solver -> Natural -> Integer -> ByteString -> IO () findPanics solver count iters c = do- (_, res) <- withSolvers solver count Nothing $ \s -> do+ _ <- withSolvers solver count Nothing $ \s -> do let opts = defaultVeriOpts- { maxIter = iters- , askSmtIters = (+ 1) <$> iters+ { maxIter = Just iters+ , askSmtIters = iters + 1 } checkAssert s allPanicCodes c Nothing [] opts putStrLn "done"@@ -112,7 +99,7 @@ -- assertion violations takes an iteration bound, as well as a list of solver -- counts to benchmark, allowing us to construct benchmarks that compare the -- performance impact of increasing solver parallelisation-mkbench :: IO ByteString -> String -> Maybe Integer -> [Natural] -> Benchmark+mkbench :: IO ByteString -> String -> Integer -> [Natural] -> Benchmark mkbench c name iters counts = localOption WallTime $ env c (bgroup name . bmarks) where bmarks c' = concat $ [
+ bench/contracts/deposit.sol view
@@ -0,0 +1,182 @@+/**+ *Submitted for verification at Etherscan.io on 2020-10-14+*/++// ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━+// ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓+// ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛+// ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━+// ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓+// ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛+// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━+// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━++// SPDX-License-Identifier: CC0-1.0++pragma solidity 0.6.11;++// This interface is designed to be compatible with the Vyper version.+/// @notice This is the Ethereum 2.0 deposit contract interface.+/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs+interface IDepositContract {+ /// @notice A processed deposit event.+ event DepositEvent(+ bytes pubkey,+ bytes withdrawal_credentials,+ bytes amount,+ bytes signature,+ bytes index+ );++ /// @notice Submit a Phase 0 DepositData object.+ /// @param pubkey A BLS12-381 public key.+ /// @param withdrawal_credentials Commitment to a public key for withdrawals.+ /// @param signature A BLS12-381 signature.+ /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.+ /// Used as a protection against malformed input.+ function deposit(+ bytes calldata pubkey,+ bytes calldata withdrawal_credentials,+ bytes calldata signature,+ bytes32 deposit_data_root+ ) external payable;++ /// @notice Query the current deposit root hash.+ /// @return The deposit root hash.+ function get_deposit_root() external view returns (bytes32);++ /// @notice Query the current deposit count.+ /// @return The deposit count encoded as a little endian 64-bit number.+ function get_deposit_count() external view returns (bytes memory);+}++// Based on official specification in https://eips.ethereum.org/EIPS/eip-165+interface ERC165 {+ /// @notice Query if a contract implements an interface+ /// @param interfaceId The interface identifier, as specified in ERC-165+ /// @dev Interface identification is specified in ERC-165. This function+ /// uses less than 30,000 gas.+ /// @return `true` if the contract implements `interfaceId` and+ /// `interfaceId` is not 0xffffffff, `false` otherwise+ function supportsInterface(bytes4 interfaceId) external pure returns (bool);+}++// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity.+// It tries to stay as close as possible to the original source code.+/// @notice This is the Ethereum 2.0 deposit contract interface.+/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs+contract DepositContract is IDepositContract, ERC165 {+ uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32;+ // NOTE: this also ensures `deposit_count` will fit into 64-bits+ uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1;++ bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch;+ uint256 deposit_count;++ bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes;++ constructor() public {+ // Compute hashes in empty sparse Merkle tree+ for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++)+ zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height]));+ }++ function get_deposit_root() override external view returns (bytes32) {+ bytes32 node;+ uint size = deposit_count;+ for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {+ if ((size & 1) == 1)+ node = sha256(abi.encodePacked(branch[height], node));+ else+ node = sha256(abi.encodePacked(node, zero_hashes[height]));+ size /= 2;+ }+ return sha256(abi.encodePacked(+ node,+ to_little_endian_64(uint64(deposit_count)),+ bytes24(0)+ ));+ }++ function get_deposit_count() override external view returns (bytes memory) {+ return to_little_endian_64(uint64(deposit_count));+ }++ function deposit(+ bytes calldata pubkey,+ bytes calldata withdrawal_credentials,+ bytes calldata signature,+ bytes32 deposit_data_root+ ) override external payable {+ // Extended ABI length checks since dynamic types are used.+ require(pubkey.length == 48, "DepositContract: invalid pubkey length");+ require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length");+ require(signature.length == 96, "DepositContract: invalid signature length");++ // Check deposit amount+ require(msg.value >= 1 ether, "DepositContract: deposit value too low");+ require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei");+ uint deposit_amount = msg.value / 1 gwei;+ require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high");++ // Emit `DepositEvent` log+ bytes memory amount = to_little_endian_64(uint64(deposit_amount));+ emit DepositEvent(+ pubkey,+ withdrawal_credentials,+ amount,+ signature,+ to_little_endian_64(uint64(deposit_count))+ );++ // Compute deposit data root (`DepositData` hash tree root)+ bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0)));+ bytes32 signature_root = sha256(abi.encodePacked(+ sha256(abi.encodePacked(signature[:64])),+ sha256(abi.encodePacked(signature[64:], bytes32(0)))+ ));+ bytes32 node = sha256(abi.encodePacked(+ sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),+ sha256(abi.encodePacked(amount, bytes24(0), signature_root))+ ));++ // Verify computed and expected deposit data roots match+ require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root");++ // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`)+ require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full");++ // Add deposit data root to Merkle tree (update a single `branch` node)+ deposit_count += 1;+ uint size = deposit_count;+ for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {+ if ((size & 1) == 1) {+ branch[height] = node;+ return;+ }+ node = sha256(abi.encodePacked(branch[height], node));+ size /= 2;+ }+ // As the loop should always end prematurely with the `return` statement,+ // this code should be unreachable. We assert `false` just to be safe.+ assert(false);+ }++ function supportsInterface(bytes4 interfaceId) override external pure returns (bool) {+ return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId;+ }++ function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) {+ ret = new bytes(8);+ bytes8 bytesValue = bytes8(value);+ // Byteswapping during copying to bytes.+ ret[0] = bytesValue[7];+ ret[1] = bytesValue[6];+ ret[2] = bytesValue[5];+ ret[3] = bytesValue[4];+ ret[4] = bytesValue[3];+ ret[5] = bytesValue[2];+ ret[6] = bytesValue[1];+ ret[7] = bytesValue[0];+ }+}
+ bench/contracts/vat.sol view
@@ -0,0 +1,246 @@+// SPDX-License-Identifier: AGPL-3.0-or-later++/// vat.sol -- Dai CDP database++// Copyright (C) 2018 Rain <rainbreak@riseup.net>+//+// This program is free software: you can redistribute it and/or modify+// it under the terms of the GNU Affero General Public License as published by+// the Free Software Foundation, either version 3 of the License, or+// (at your option) any later version.+//+// This program is distributed in the hope that it will be useful,+// but WITHOUT ANY WARRANTY; without even the implied warranty of+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+// GNU Affero General Public License for more details.+//+// You should have received a copy of the GNU Affero General Public License+// along with this program. If not, see <https://www.gnu.org/licenses/>.++pragma solidity ^0.6.12;++// FIXME: This contract was altered compared to the production version.+// It doesn't use LibNote anymore.+// New deployments of this contract will need to include custom events (TO DO).++contract Vat {+ // --- Auth ---+ mapping (address => uint) public wards;+ function rely(address usr) external auth { require(live == 1, "Vat/not-live"); wards[usr] = 1; }+ function deny(address usr) external auth { require(live == 1, "Vat/not-live"); wards[usr] = 0; }+ modifier auth {+ require(wards[msg.sender] == 1, "Vat/not-authorized");+ _;+ }++ mapping(address => mapping (address => uint)) public can;+ function hope(address usr) external { can[msg.sender][usr] = 1; }+ function nope(address usr) external { can[msg.sender][usr] = 0; }+ function wish(address bit, address usr) internal view returns (bool) {+ return either(bit == usr, can[bit][usr] == 1);+ }++ // --- Data ---+ struct Ilk {+ uint256 Art; // Total Normalised Debt [wad]+ uint256 rate; // Accumulated Rates [ray]+ uint256 spot; // Price with Safety Margin [ray]+ uint256 line; // Debt Ceiling [rad]+ uint256 dust; // Urn Debt Floor [rad]+ }+ struct Urn {+ uint256 ink; // Locked Collateral [wad]+ uint256 art; // Normalised Debt [wad]+ }++ mapping (bytes32 => Ilk) public ilks;+ mapping (bytes32 => mapping (address => Urn )) public urns;+ mapping (bytes32 => mapping (address => uint)) public gem; // [wad]+ mapping (address => uint256) public dai; // [rad]+ mapping (address => uint256) public sin; // [rad]++ uint256 public debt; // Total Dai Issued [rad]+ uint256 public vice; // Total Unbacked Dai [rad]+ uint256 public Line; // Total Debt Ceiling [rad]+ uint256 public live; // Active Flag++ // --- Init ---+ constructor() public {+ wards[msg.sender] = 1;+ live = 1;+ }++ // --- Math ---+ function _add(uint x, int y) internal pure returns (uint z) {+ z = x + uint(y);+ require(y >= 0 || z <= x);+ require(y <= 0 || z >= x);+ }+ function _sub(uint x, int y) internal pure returns (uint z) {+ z = x - uint(y);+ require(y <= 0 || z <= x);+ require(y >= 0 || z >= x);+ }+ function _mul(uint x, int y) internal pure returns (int z) {+ z = int(x) * y;+ require(int(x) >= 0);+ require(y == 0 || z / y == int(x));+ }+ function _add(uint x, uint y) internal pure returns (uint z) {+ require((z = x + y) >= x);+ }+ function _sub(uint x, uint y) internal pure returns (uint z) {+ require((z = x - y) <= x);+ }+ function _mul(uint x, uint y) internal pure returns (uint z) {+ require(y == 0 || (z = x * y) / y == x);+ }++ // --- Administration ---+ function init(bytes32 ilk) external auth {+ require(ilks[ilk].rate == 0, "Vat/ilk-already-init");+ ilks[ilk].rate = 10 ** 27;+ }+ function file(bytes32 what, uint data) external auth {+ require(live == 1, "Vat/not-live");+ if (what == "Line") Line = data;+ else revert("Vat/file-unrecognized-param");+ }+ function file(bytes32 ilk, bytes32 what, uint data) external auth {+ require(live == 1, "Vat/not-live");+ if (what == "spot") ilks[ilk].spot = data;+ else if (what == "line") ilks[ilk].line = data;+ else if (what == "dust") ilks[ilk].dust = data;+ else revert("Vat/file-unrecognized-param");+ }+ function cage() external auth {+ live = 0;+ }++ // --- Fungibility ---+ function slip(bytes32 ilk, address usr, int256 wad) external auth {+ gem[ilk][usr] = _add(gem[ilk][usr], wad);+ }+ function flux(bytes32 ilk, address src, address dst, uint256 wad) external {+ require(wish(src, msg.sender), "Vat/not-allowed");+ gem[ilk][src] = _sub(gem[ilk][src], wad);+ gem[ilk][dst] = _add(gem[ilk][dst], wad);+ }+ function move(address src, address dst, uint256 rad) external {+ require(wish(src, msg.sender), "Vat/not-allowed");+ dai[src] = _sub(dai[src], rad);+ dai[dst] = _add(dai[dst], rad);+ }++ function either(bool x, bool y) internal pure returns (bool z) {+ assembly{ z := or(x, y)}+ }+ function both(bool x, bool y) internal pure returns (bool z) {+ assembly{ z := and(x, y)}+ }++ // --- CDP Manipulation ---+ function frob(bytes32 i, address u, address v, address w, int dink, int dart) external {+ // system is live+ require(live == 1, "Vat/not-live");++ Urn memory urn = urns[i][u];+ Ilk memory ilk = ilks[i];+ // ilk has been initialised+ require(ilk.rate != 0, "Vat/ilk-not-init");++ urn.ink = _add(urn.ink, dink);+ urn.art = _add(urn.art, dart);+ ilk.Art = _add(ilk.Art, dart);++ int dtab = _mul(ilk.rate, dart);+ uint tab = _mul(ilk.rate, urn.art);+ debt = _add(debt, dtab);++ // either debt has decreased, or debt ceilings are not exceeded+ require(either(dart <= 0, both(_mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded");+ // urn is either less risky than before, or it is safe+ require(either(both(dart <= 0, dink >= 0), tab <= _mul(urn.ink, ilk.spot)), "Vat/not-safe");++ // urn is either more safe, or the owner consents+ require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u");+ // collateral src consents+ require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v");+ // debt dst consents+ require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w");++ // urn has no debt, or a non-dusty amount+ require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust");++ gem[i][v] = _sub(gem[i][v], dink);+ dai[w] = _add(dai[w], dtab);++ urns[i][u] = urn;+ ilks[i] = ilk;+ }+ // --- CDP Fungibility ---+ function fork(bytes32 ilk, address src, address dst, int dink, int dart) external {+ Urn storage u = urns[ilk][src];+ Urn storage v = urns[ilk][dst];+ Ilk storage i = ilks[ilk];++ u.ink = _sub(u.ink, dink);+ u.art = _sub(u.art, dart);+ v.ink = _add(v.ink, dink);+ v.art = _add(v.art, dart);++ uint utab = _mul(u.art, i.rate);+ uint vtab = _mul(v.art, i.rate);++ // both sides consent+ require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed");++ // both sides safe+ require(utab <= _mul(u.ink, i.spot), "Vat/not-safe-src");+ require(vtab <= _mul(v.ink, i.spot), "Vat/not-safe-dst");++ // both sides non-dusty+ require(either(utab >= i.dust, u.art == 0), "Vat/dust-src");+ require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst");+ }+ // --- CDP Confiscation ---+ function grab(bytes32 i, address u, address v, address w, int dink, int dart) external auth {+ Urn storage urn = urns[i][u];+ Ilk storage ilk = ilks[i];++ urn.ink = _add(urn.ink, dink);+ urn.art = _add(urn.art, dart);+ ilk.Art = _add(ilk.Art, dart);++ int dtab = _mul(ilk.rate, dart);++ gem[i][v] = _sub(gem[i][v], dink);+ sin[w] = _sub(sin[w], dtab);+ vice = _sub(vice, dtab);+ }++ // --- Settlement ---+ function heal(uint rad) external {+ address u = msg.sender;+ sin[u] = _sub(sin[u], rad);+ dai[u] = _sub(dai[u], rad);+ vice = _sub(vice, rad);+ debt = _sub(debt, rad);+ }+ function suck(address u, address v, uint rad) external auth {+ sin[u] = _add(sin[u], rad);+ dai[v] = _add(dai[v], rad);+ vice = _add(vice, rad);+ debt = _add(debt, rad);+ }++ // --- Rates ---+ function fold(bytes32 i, address u, int rate) external auth {+ require(live == 1, "Vat/not-live");+ Ilk storage ilk = ilks[i];+ ilk.rate = _add(ilk.rate, rate);+ int rad = _mul(ilk.Art, rate);+ dai[u] = _add(dai[u], rad);+ debt = _add(debt, rad);+ }+}
+ cli/cli.hs view
@@ -0,0 +1,599 @@+-- Main file of the hevm CLI program++{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE TemplateHaskell #-}++module Main where++import Control.Monad (when, forM_, unless)+import Control.Monad.ST (RealWorld, stToIO)+import Data.ByteString (ByteString)+import Data.DoubleWord (Word256)+import Data.List (intersperse)+import Data.Maybe (fromMaybe, mapMaybe, fromJust)+import Data.Text qualified as T+import Data.Text.IO qualified as T+import Data.Version (showVersion)+import Data.Word (Word64)+import GHC.Conc (getNumProcessors)+import Numeric.Natural (Natural)+import Optics.Core ((&), set)+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.FilePath ((</>))+import System.Exit (exitFailure, exitWith, ExitCode(..))++import EVM (initialContract, abstractContract, makeVm)+import EVM.ABI (Sig(..))+import EVM.Dapp (dappInfo, DappInfo, emptyDapp)+import EVM.Expr qualified as Expr+import EVM.Concrete qualified as Concrete+import GitHash+import EVM.FeeSchedule (feeSchedule)+import EVM.Fetch qualified as Fetch+import EVM.Format (hexByteString, strip0x, showTraceTree, 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.UnitTest++-- This record defines the program's command-line options+-- automatically via the `optparse-generic` package.+data Command w+ = Symbolic -- Symbolically explore an abstract program, or specialized with specified env & calldata+ -- vm opts+ { code :: w ::: Maybe ByteString <?> "Program bytecode"+ , calldata :: w ::: Maybe ByteString <?> "Tx: calldata"+ , address :: w ::: Maybe Addr <?> "Tx: address"+ , caller :: w ::: Maybe Addr <?> "Tx: caller"+ , origin :: w ::: Maybe Addr <?> "Tx: origin"+ , coinbase :: w ::: Maybe Addr <?> "Block: coinbase"+ , value :: w ::: Maybe W256 <?> "Tx: Eth amount"+ , nonce :: w ::: Maybe Word64 <?> "Nonce of origin"+ , gas :: w ::: Maybe Word64 <?> "Tx: gas amount"+ , number :: w ::: Maybe W256 <?> "Block: number"+ , timestamp :: w ::: Maybe W256 <?> "Block: timestamp"+ , basefee :: w ::: Maybe W256 <?> "Block: base fee"+ , priorityFee :: w ::: Maybe W256 <?> "Tx: priority fee"+ , gaslimit :: w ::: Maybe Word64 <?> "Tx: gas limit"+ , gasprice :: w ::: Maybe W256 <?> "Tx: gas price"+ , create :: w ::: Bool <?> "Tx: creation"+ , maxcodesize :: w ::: Maybe W256 <?> "Block: max code size"+ , prevRandao :: w ::: Maybe W256 <?> "Block: prevRandao"+ , chainid :: w ::: Maybe W256 <?> "Env: chainId"+ -- remote state opts+ , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"+ , block :: w ::: Maybe W256 <?> "Block state is be fetched from"++ -- symbolic execution opts+ , root :: w ::: Maybe String <?> "Path to project root directory (default: . )"+ , projectType :: w ::: Maybe ProjectType <?> "Is this a Foundry or DappTools project (default: Foundry)"+ , initialStorage :: w ::: Maybe (InitialStorage) <?> "Starting state for storage: Empty, Abstract (default Abstract)"+ , sig :: w ::: Maybe Text <?> "Signature of types to decode / encode"+ , arg :: w ::: [String] <?> "Values to encode"+ , getModels :: w ::: Bool <?> "Print example testcase for each execution path"+ , showTree :: w ::: Bool <?> "Print branches explored in tree view"+ , 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"+ , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"+ , 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)"+ , 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"+ , 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"+ }+ | Equivalence -- prove equivalence between two programs+ { codeA :: w ::: ByteString <?> "Bytecode of the first program"+ , codeB :: w ::: ByteString <?> "Bytecode of the second program"+ , sig :: w ::: Maybe Text <?> "Signature of types to decode / encode"+ , arg :: w ::: [String] <?> "Values to encode"+ , 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"+ , smtoutput :: w ::: Bool <?> "Print verbose smt output"+ , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"+ , 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)"+ , 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"+ }+ | Exec -- Execute a given program with specified env & calldata+ { code :: w ::: Maybe ByteString <?> "Program bytecode"+ , calldata :: w ::: Maybe ByteString <?> "Tx: calldata"+ , address :: w ::: Maybe Addr <?> "Tx: address"+ , caller :: w ::: Maybe Addr <?> "Tx: caller"+ , origin :: w ::: Maybe Addr <?> "Tx: origin"+ , coinbase :: w ::: Maybe Addr <?> "Block: coinbase"+ , value :: w ::: Maybe W256 <?> "Tx: Eth amount"+ , nonce :: w ::: Maybe Word64 <?> "Nonce of origin"+ , gas :: w ::: Maybe Word64 <?> "Tx: gas amount"+ , number :: w ::: Maybe W256 <?> "Block: number"+ , timestamp :: w ::: Maybe W256 <?> "Block: timestamp"+ , basefee :: w ::: Maybe W256 <?> "Block: base fee"+ , priorityFee :: w ::: Maybe W256 <?> "Tx: priority fee"+ , gaslimit :: w ::: Maybe Word64 <?> "Tx: gas limit"+ , gasprice :: w ::: Maybe W256 <?> "Tx: gas price"+ , create :: w ::: Bool <?> "Tx: creation"+ , maxcodesize :: w ::: Maybe W256 <?> "Block: max code size"+ , prevRandao :: w ::: Maybe W256 <?> "Block: prevRandao"+ , chainid :: w ::: Maybe W256 <?> "Env: chainId"+ , 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"+ , root :: w ::: Maybe String <?> "Path to project root directory (default: . )"+ , projectType :: w ::: Maybe ProjectType <?> "Is this a Foundry or DappTools project (default: Foundry)"+ }+ | Test -- Run DSTest unit tests+ { root :: w ::: Maybe String <?> "Path to project root directory (default: . )"+ , projectType :: w ::: Maybe ProjectType <?> "Is this a Foundry or DappTools project (default: Foundry)"+ , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"+ , number :: w ::: Maybe W256 <?> "Block: number"+ , 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"+ , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"+ , 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"+ , 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++ deriving (Options.Generic)++type URL = Text+++-- For some reason haskell can't derive a+-- parseField instance for (Text, ByteString)+instance Options.ParseField (Text, ByteString)++deriving instance Options.ParseField Word256+deriving instance Options.ParseField [Word256]++instance Options.ParseRecord (Command Options.Wrapped) where+ parseRecord =+ Options.parseRecordWithModifiers Options.lispCaseModifiers++data InitialStorage+ = Empty+ | Abstract+ deriving (Show, Read, Options.ParseField)++getFullVersion :: [Char]+getFullVersion = showVersion Paths.version <> " [" <> gitVersion <> "]"+ where+ gitInfo = $$tGitInfoCwdTry+ gitVersion = case gitInfo of+ Right val -> "git rev " <> giBranch val <> "@" <> giHash val+ Left _ -> "no git revision present"++main :: IO ()+main = do+ cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator"+ case cmd of+ Version {} ->putStrLn getFullVersion+ Symbolic {} -> do+ root <- getRoot cmd+ withCurrentDirectory root $ assert cmd+ Equivalence {} -> equivalence cmd+ Exec {} ->+ 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++equivalence :: Command Options.Unwrapped -> IO ()+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+ withSolvers solver 3 Nothing $ \s -> do+ res <- equivalenceCheck s bytecodeA bytecodeB veriOpts calldata+ case any isCex res of+ False -> do+ putStrLn "No discrepancies found"+ when (any isTimeout res) $ do+ putStrLn "But timeout(s) occurred"+ exitFailure+ True -> do+ let cexs = mapMaybe getCex res+ T.putStrLn . T.unlines $+ [ "Not equivalent. The following inputs result in differing behaviours:"+ , "" , "-----", ""+ ] <> (intersperse (T.unlines [ "", "-----" ]) $ fmap (formatCex (AbstractBuf "txdata") Nothing) cexs)+ exitFailure++getSolver :: Command Options.Unwrapped -> IO Solver+getSolver cmd = case cmd.solver of+ Nothing -> pure Z3+ Just s -> case T.unpack s of+ "z3" -> pure Z3+ "cvc5" -> pure CVC5+ input -> do+ putStrLn $ "unrecognised solver: " <> input+ exitFailure++getSrcInfo :: Command Options.Unwrapped -> IO DappInfo+getSrcInfo cmd = do+ root <- getRoot cmd+ withCurrentDirectory root $ do+ outExists <- doesDirectoryExist (root </> "out")+ if outExists+ then do+ buildOutput <- readBuildOutput root (getProjectType cmd)+ case buildOutput of+ Left _ -> pure emptyDapp+ Right o -> pure $ dappInfo root o+ else pure emptyDapp++getProjectType :: Command Options.Unwrapped -> ProjectType+getProjectType cmd = fromMaybe Foundry cmd.projectType++getRoot :: Command Options.Unwrapped -> IO FilePath+getRoot cmd = maybe getCurrentDirectory pure (cmd.root)+++-- | Builds a buffer representing calldata based on the given cli arguments+buildCalldata :: Command Options.Unwrapped -> IO (Expr Buf, [Prop])+buildCalldata cmd = case (cmd.calldata, cmd.sig) of+ -- fully abstract calldata+ (Nothing, Nothing) -> pure $ mkCalldata Nothing []+ -- fully concrete calldata+ (Just c, Nothing) -> pure (ConcreteBuf (hexByteString "bytes" . strip0x $ c), [])+ -- calldata according to given abi with possible specializations from the `arg` list+ (Nothing, Just sig') -> do+ method' <- functionAbi sig'+ pure $ mkCalldata (Just (Sig method'.methodSignature (snd <$> method'.inputs))) cmd.arg+ -- both args provided+ (_, _) -> do+ putStrLn "incompatible options provided: --calldata and --sig"+ exitFailure+++-- If function signatures are known, they should always be given for best results.+assert :: Command Options.Unwrapped -> IO ()+assert cmd = do+ let block' = maybe Fetch.Latest Fetch.BlockNumber cmd.block+ rpcinfo = (,) block' <$> cmd.rpc+ calldata <- buildCalldata cmd+ preState <- symvmFromCommand cmd calldata+ let errCodes = fromMaybe defaultPanicCodes cmd.assertions+ cores <- unsafeInto <$> getNumProcessors+ let solverCount = fromMaybe cores cmd.numSolvers+ solver <- 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"+ showExtras solvers cmd calldata expr+ _ -> do+ let cexs = snd <$> mapMaybe getCex res+ timeouts = mapMaybe getTimeout res+ counterexamples+ | null cexs = []+ | otherwise =+ [ ""+ , "Discovered the following counterexamples:"+ , ""+ ] <> fmap (formatCex (fst calldata) Nothing) cexs+ unknowns+ | null timeouts = []+ | otherwise =+ [ ""+ , "Could not determine reachability of the following end states:"+ , ""+ ] <> fmap (formatExpr) timeouts+ T.putStrLn $ T.unlines (counterexamples <> unknowns)+ showExtras solvers cmd calldata expr+ exitFailure++showExtras :: SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> IO ()+showExtras solvers cmd calldata expr = do+ when cmd.showTree $ 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 ""+ when cmd.getModels $ do+ putStrLn $ "=== Models for " <> show (Expr.numBranches expr) <> " branches ===\n"+ ms <- produceModels solvers expr+ forM_ ms (showModel (fst calldata))++isTestOrLib :: Text -> Bool+isTestOrLib file = T.isSuffixOf ".t.sol" file || areAnyPrefixOf ["src/test/", "src/tests/", "lib/"] file++areAnyPrefixOf :: [Text] -> Text -> Bool+areAnyPrefixOf prefixes t = any (flip T.isPrefixOf t) prefixes++launchExec :: Command Options.Unwrapped -> IO ()+launchExec cmd = do+ dapp <- getSrcInfo cmd+ vm <- vmFromCommand cmd+ let+ block = maybe Fetch.Latest Fetch.BlockNumber cmd.block+ rpcinfo = (,) block <$> cmd.rpc++ -- 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')+ case vm'.result of+ Just (VMFailure (Revert msg)) -> do+ let res = case msg of+ ConcreteBuf bs -> bs+ _ -> "<symbolic>"+ putStrLn $ "Revert: " <> (show $ ByteStringS res)+ exitWith (ExitFailure 2)+ Just (VMFailure err) -> 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+ let msg = case buf of+ ConcreteBuf msg' -> msg'+ _ -> "<symbolic>"+ print $ "Return: " <> (show $ ByteStringS msg)+ _ ->+ internalError "no EVM result"++-- | Creates a (concrete) VM from command line options+vmFromCommand :: Command Options.Unwrapped -> IO (VM RealWorld)+vmFromCommand cmd = do+ (miner,ts,baseFee,blockNum,prevRan) <- case cmd.rpc of+ Nothing -> pure (LitAddr 0,Lit 0,0,0,0)+ Just url -> Fetch.fetchBlockFrom block url >>= \case+ Nothing -> error "Error: Could not fetch block"+ Just Block{..} -> pure ( coinbase+ , timestamp+ , baseFee+ , number+ , prevRandao+ )++ contract <- case (cmd.rpc, cmd.address, cmd.code) of+ (Just url, Just addr', Just c) -> do+ Fetch.fetchContractFrom block url addr' >>= \case+ Nothing ->+ error $ "Error: contract not found: " <> show address+ Just contract ->+ -- if both code and url is given,+ -- fetch the contract and overwrite the code+ pure $+ initialContract (mkCode $ hexByteString "--code" $ strip0x c)+ & set #balance (contract.balance)+ & set #nonce (contract.nonce)+ & set #external (contract.external)++ (Just url, Just addr', Nothing) ->+ Fetch.fetchContractFrom block url addr' >>= \case+ Nothing ->+ error $ "Error: contract not found: " <> show address+ Just contract -> pure contract++ (_, _, Just c) ->+ pure $+ initialContract (mkCode $ hexByteString "--code" $ strip0x c)++ (_, _, Nothing) ->+ error "Error: must provide at least (rpc + address) or code"++ let ts' = case maybeLitWord ts of+ Just t -> t+ Nothing -> internalError "unexpected symbolic timestamp when executing vm test"++ vm <- stToIO $ vm0 baseFee miner ts' blockNum prevRan contract+ pure $ EVM.Transaction.initTx vm+ where+ block = maybe Fetch.Latest Fetch.BlockNumber cmd.block+ value = word (.value) 0+ caller = addr (.caller) (LitAddr 0)+ origin = addr (.origin) (LitAddr 0)+ calldata = ConcreteBuf $ bytes (.calldata) ""+ decipher = hexByteString "bytes" . strip0x+ mkCode bs = if cmd.create+ then InitCode bs mempty+ else RuntimeCode (ConcreteRuntimeCode bs)+ address = if cmd.create+ then addr (.address) (Concrete.createAddress (fromJust $ maybeLitAddr origin) (W64 $ word64 (.nonce) 0))+ else addr (.address) (LitAddr 0xacab)++ vm0 baseFee miner ts blockNum prevRan c = makeVm $ VMOpts+ { contract = c+ , otherContracts = []+ , calldata = (calldata, [])+ , value = Lit value+ , address = address+ , caller = caller+ , origin = origin+ , gas = word64 (.gas) 0xffffffffffffffff+ , baseFee = baseFee+ , priorityFee = word (.priorityFee) 0+ , gaslimit = word64 (.gaslimit) 0xffffffffffffffff+ , coinbase = addr (.coinbase) miner+ , number = word (.number) blockNum+ , timestamp = Lit $ word (.timestamp) ts+ , blockGaslimit = word64 (.gaslimit) 0xffffffffffffffff+ , gasprice = word (.gasprice) 0+ , maxCodeSize = word (.maxcodesize) 0xffffffff+ , prevRandao = word (.prevRandao) prevRan+ , schedule = feeSchedule+ , chainId = word (.chainid) 1+ , create = (.create) cmd+ , baseState = EmptyBase+ , txAccessList = mempty -- TODO: support me soon+ , allowFFI = False+ }+ word f def = fromMaybe def (f cmd)+ word64 f def = fromMaybe def (f cmd)+ 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 cmd calldata = do+ (miner,blockNum,baseFee,prevRan) <- case cmd.rpc of+ Nothing -> pure (SymAddr "miner",0,0,0)+ Just url -> Fetch.fetchBlockFrom block url >>= \case+ Nothing -> error "Error: Could not fetch block"+ Just Block{..} -> pure ( coinbase+ , number+ , baseFee+ , prevRandao+ )++ let+ caller = SymAddr "caller"+ ts = maybe Timestamp Lit cmd.timestamp+ callvalue = maybe TxValue Lit cmd.value++ contract <- case (cmd.rpc, cmd.address, cmd.code) of+ (Just url, Just addr', _) ->+ Fetch.fetchContractFrom block url addr' >>= \case+ Nothing ->+ error "Error: contract not found."+ Just contract' -> pure contract''+ where+ contract'' = case cmd.code of+ Nothing -> contract'+ -- if both code and url is given,+ -- fetch the contract and overwrite the code+ Just c -> initialContract (mkCode $ decipher c)+ & set #origStorage (contract'.origStorage)+ & set #balance (contract'.balance)+ & set #nonce (contract'.nonce)+ & set #external (contract'.external)++ (_, _, Just c) ->+ pure ((`abstractContract` address) . mkCode $ decipher c)+ (_, _, Nothing) ->+ error "Error: must provide at least (rpc + address) or code"++ vm <- stToIO $ vm0 baseFee miner ts blockNum prevRan calldata callvalue caller contract+ pure $ EVM.Transaction.initTx vm++ where+ decipher = hexByteString "bytes" . strip0x+ block = maybe Fetch.Latest Fetch.BlockNumber cmd.block+ origin = eaddr (.origin) (SymAddr "origin")+ mkCode bs = if cmd.create+ then InitCode bs mempty+ else RuntimeCode (ConcreteRuntimeCode bs)+ address = eaddr (.address) (SymAddr "entrypoint")+ vm0 baseFee miner ts blockNum prevRan cd callvalue caller c = makeVm $ VMOpts+ { contract = c+ , otherContracts = []+ , calldata = cd+ , value = callvalue+ , address = address+ , caller = caller+ , origin = origin+ , gas = word64 (.gas) 0xffffffffffffffff+ , gaslimit = word64 (.gaslimit) 0xffffffffffffffff+ , baseFee = baseFee+ , priorityFee = word (.priorityFee) 0+ , coinbase = eaddr (.coinbase) miner+ , number = word (.number) blockNum+ , timestamp = ts+ , blockGaslimit = word64 (.gaslimit) 0xffffffffffffffff+ , gasprice = word (.gasprice) 0+ , maxCodeSize = word (.maxcodesize) 0xffffffff+ , prevRandao = word (.prevRandao) prevRan+ , schedule = feeSchedule+ , chainId = word (.chainid) 1+ , create = (.create) cmd+ , baseState = maybe AbstractBase parseInitialStorage (cmd.initialStorage)+ , txAccessList = mempty+ , allowFFI = False+ }+ word f def = fromMaybe def (f cmd)+ word64 f def = fromMaybe def (f cmd)+ eaddr f def = maybe def LitAddr (f cmd)++unitTestOptions :: Command Options.Unwrapped -> SolverGroup -> Maybe BuildOutput -> IO (UnitTestOptions RealWorld)+unitTestOptions cmd solvers buildOutput = do+ root <- getRoot cmd+ let srcInfo = maybe emptyDapp (dappInfo root) buildOutput++ let rpcinfo = case (cmd.number, cmd.rpc) of+ (Just block, Just url) -> Just (Fetch.BlockNumber block, url)+ (Nothing, Just url) -> Just (Fetch.Latest, url)+ _ -> Nothing+ params <- paramsFromRpc rpcinfo++ let+ testn = params.number+ block' = if 0 == testn+ then Fetch.Latest+ else Fetch.BlockNumber testn++ pure UnitTestOptions+ { solvers = solvers+ , rpcInfo = case cmd.rpc of+ Just url -> Just (block', url)+ Nothing -> Nothing+ , maxIter = cmd.maxIterations+ , askSmtIters = cmd.askSmtIterations+ , smtDebug = cmd.smtdebug+ , smtTimeout = cmd.smttimeout+ , solver = cmd.solver+ , verbose = cmd.verbose+ , match = T.pack $ fromMaybe ".*" cmd.match+ , testParams = params+ , dapp = srcInfo+ , ffiAllowed = cmd.ffi+ }+parseInitialStorage :: InitialStorage -> BaseState+parseInitialStorage Empty = EmptyBase+parseInitialStorage Abstract = AbstractBase
− hevm-cli/hevm-cli.hs
@@ -1,716 +0,0 @@--- Main file of the hevm CLI program--{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE TemplateHaskell #-}--module Main where--import Control.Monad (void, when, forM_, unless)-import Control.Monad.State.Strict (liftIO)-import Data.Bifunctor (second)-import Data.ByteString (ByteString)-import Data.ByteString qualified as ByteString-import Data.ByteString.Char8 qualified as Char8-import Data.ByteString.Lazy qualified as LazyByteString-import Data.DoubleWord (Word256)-import Data.List (intersperse)-import Data.Map qualified as Map-import Data.Maybe (fromMaybe, mapMaybe)-import Data.Text (pack)-import Data.Text qualified as T-import Data.Text.IO qualified as T-import Data.Version (showVersion)-import Data.Word (Word64)-import GHC.Conc (getNumProcessors)-import Numeric.Natural (Natural)-import Optics.Core ((&), (%), set)-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.FilePath ((</>))-import System.Exit (exitFailure, exitWith, ExitCode(..))--import EVM (initialContract, makeVm)-import EVM.Concrete (createAddress)-import EVM.Dapp (findUnitTests, dappInfo, DappInfo, emptyDapp)-import EVM.Debug (Mode(..))-import EVM.Expr qualified as Expr-import EVM.Facts qualified as Facts-import EVM.Facts.Git qualified as Git-import GitHash-import EVM.FeeSchedule qualified as FeeSchedule-import EVM.Fetch qualified-import EVM.Format (hexByteString, strip0x, showTraceTree, formatExpr)-import EVM.Solidity-import EVM.Solvers-import EVM.Stepper qualified-import EVM.SymExec-import EVM.Transaction qualified-import EVM.TTY qualified as TTY-import EVM.Types hiding (word)-import EVM.UnitTest---- This record defines the program's command-line options--- automatically via the `optparse-generic` package.-data Command w- = Symbolic -- Symbolically explore an abstract program, or specialized with specified env & calldata- -- vm opts- { code :: w ::: Maybe ByteString <?> "Program bytecode"- , calldata :: w ::: Maybe ByteString <?> "Tx: calldata"- , address :: w ::: Maybe Addr <?> "Tx: address"- , caller :: w ::: Maybe Addr <?> "Tx: caller"- , origin :: w ::: Maybe Addr <?> "Tx: origin"- , coinbase :: w ::: Maybe Addr <?> "Block: coinbase"- , value :: w ::: Maybe W256 <?> "Tx: Eth amount"- , nonce :: w ::: Maybe W256 <?> "Nonce of origin"- , gas :: w ::: Maybe Word64 <?> "Tx: gas amount"- , number :: w ::: Maybe W256 <?> "Block: number"- , timestamp :: w ::: Maybe W256 <?> "Block: timestamp"- , basefee :: w ::: Maybe W256 <?> "Block: base fee"- , priorityFee :: w ::: Maybe W256 <?> "Tx: priority fee"- , gaslimit :: w ::: Maybe Word64 <?> "Tx: gas limit"- , gasprice :: w ::: Maybe W256 <?> "Tx: gas price"- , create :: w ::: Bool <?> "Tx: creation"- , maxcodesize :: w ::: Maybe W256 <?> "Block: max code size"- , prevRandao :: w ::: Maybe W256 <?> "Block: prevRandao"- , chainid :: w ::: Maybe W256 <?> "Env: chainId"- -- remote state opts- , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"- , block :: w ::: Maybe W256 <?> "Block state is be fetched from"- , state :: w ::: Maybe String <?> "Path to state repository"- , cache :: w ::: Maybe String <?> "Path to rpc cache repository"-- -- symbolic execution opts- , root :: w ::: Maybe String <?> "Path to project root directory (default: . )"- , projectType :: w ::: Maybe ProjectType <?> "Is this a Foundry or DappTools project (default: Foundry)"- , initialStorage :: w ::: Maybe (InitialStorage) <?> "Starting state for storage: Empty, Abstract, Concrete <STORE> (default Abstract)"- , sig :: w ::: Maybe Text <?> "Signature of types to decode / encode"- , arg :: w ::: [String] <?> "Values to encode"- , debug :: w ::: Bool <?> "Run interactively"- , getModels :: w ::: Bool <?> "Print example testcase for each execution path"- , showTree :: w ::: Bool <?> "Print branches explored in tree view"- , 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"- , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"- , assertions :: w ::: Maybe [Word256] <?> "Comma seperated list of solc panic codes to check for (default: everything except arithmetic overflow)"- , 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)"- , 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"- }- | Equivalence -- prove equivalence between two programs- { codeA :: w ::: ByteString <?> "Bytecode of the first program"- , codeB :: w ::: ByteString <?> "Bytecode of the second program"- , sig :: w ::: Maybe Text <?> "Signature of types to decode / encode"- , arg :: w ::: [String] <?> "Values to encode"- , 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"- , smtoutput :: w ::: Bool <?> "Print verbose smt output"- , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"- , 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)"- , loopDetectionHeuristic :: w ::: LoopHeuristic <!> "StackBased" <?> "Which heuristic should be used to determine if we are in a loop: StackBased (default) or Naive"- }- | Exec -- Execute a given program with specified env & calldata- { code :: w ::: Maybe ByteString <?> "Program bytecode"- , calldata :: w ::: Maybe ByteString <?> "Tx: calldata"- , address :: w ::: Maybe Addr <?> "Tx: address"- , caller :: w ::: Maybe Addr <?> "Tx: caller"- , origin :: w ::: Maybe Addr <?> "Tx: origin"- , coinbase :: w ::: Maybe Addr <?> "Block: coinbase"- , value :: w ::: Maybe W256 <?> "Tx: Eth amount"- , nonce :: w ::: Maybe W256 <?> "Nonce of origin"- , gas :: w ::: Maybe Word64 <?> "Tx: gas amount"- , number :: w ::: Maybe W256 <?> "Block: number"- , timestamp :: w ::: Maybe W256 <?> "Block: timestamp"- , basefee :: w ::: Maybe W256 <?> "Block: base fee"- , priorityFee :: w ::: Maybe W256 <?> "Tx: priority fee"- , gaslimit :: w ::: Maybe Word64 <?> "Tx: gas limit"- , gasprice :: w ::: Maybe W256 <?> "Tx: gas price"- , create :: w ::: Bool <?> "Tx: creation"- , maxcodesize :: w ::: Maybe W256 <?> "Block: max code size"- , prevRandao :: w ::: Maybe W256 <?> "Block: prevRandao"- , chainid :: w ::: Maybe W256 <?> "Env: chainId"- , debug :: w ::: Bool <?> "Run interactively"- , jsontrace :: w ::: Bool <?> "Print json trace output at every step"- , trace :: w ::: Bool <?> "Dump trace"- , state :: w ::: Maybe String <?> "Path to state repository"- , cache :: w ::: Maybe String <?> "Path to rpc cache repository"- , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"- , block :: w ::: Maybe W256 <?> "Block state is be fetched from"- , root :: w ::: Maybe String <?> "Path to project root directory (default: . )"- , projectType :: w ::: Maybe ProjectType <?> "Is this a Foundry or DappTools project (default: Foundry)"- }- | Test -- Run DSTest unit tests- { root :: w ::: Maybe String <?> "Path to project root directory (default: . )"- , projectType :: w ::: Maybe ProjectType <?> "Is this a Foundry or DappTools project (default: Foundry)"- , debug :: w ::: Bool <?> "Run interactively"- , jsontrace :: w ::: Bool <?> "Print json trace output at every step"- , fuzzRuns :: w ::: Maybe Int <?> "Number of times to run fuzz tests"- , depth :: w ::: Maybe Int <?> "Number of transactions to explore"- , replay :: w ::: Maybe (Text, ByteString) <?> "Custom fuzz case to run/debug"- , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"- , verbose :: w ::: Maybe Int <?> "Append call trace: {1} failures {2} all"- , coverage :: w ::: Bool <?> "Coverage analysis"- , state :: w ::: Maybe String <?> "Path to state repository"- , cache :: w ::: Maybe String <?> "Path to rpc cache repository"- , match :: w ::: Maybe String <?> "Test case filter - only run methods matching regex"- , covMatch :: w ::: Maybe String <?> "Coverage filter - only print coverage for files matching regex"- , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc5"- , smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"- , 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"- , 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-- deriving (Options.Generic)--type URL = Text----- For some reason haskell can't derive a--- parseField instance for (Text, ByteString)-instance Options.ParseField (Text, ByteString)--deriving instance Options.ParseField Word256-deriving instance Options.ParseField [Word256]--instance Options.ParseRecord (Command Options.Wrapped) where- parseRecord =- Options.parseRecordWithModifiers Options.lispCaseModifiers--data InitialStorage- = Empty- | Concrete [(W256, [(W256, W256)])]- | Abstract- deriving (Show, Read, Options.ParseField)--optsMode :: Command Options.Unwrapped -> Mode-optsMode x- | x.debug = Debug- | x.jsontrace = JsonTrace- | otherwise = Run--applyCache :: (Maybe String, Maybe String) -> IO (VM -> VM)-applyCache (state, cache) =- let applyState = flip Facts.apply- applyCache' = flip Facts.applyCache- in case (state, cache) of- (Nothing, Nothing) -> do- pure id- (Nothing, Just cachePath) -> do- facts <- Git.loadFacts (Git.RepoAt cachePath)- pure $ applyCache' facts- (Just statePath, Nothing) -> do- facts <- Git.loadFacts (Git.RepoAt statePath)- pure $ applyState facts- (Just statePath, Just cachePath) -> do- cacheFacts <- Git.loadFacts (Git.RepoAt cachePath)- stateFacts <- Git.loadFacts (Git.RepoAt statePath)- pure $ (applyState stateFacts) . (applyCache' cacheFacts)--unitTestOptions :: Command Options.Unwrapped -> SolverGroup -> Maybe BuildOutput -> IO UnitTestOptions-unitTestOptions cmd solvers buildOutput = do- root <- getRoot cmd- let srcInfo = maybe emptyDapp (dappInfo root) buildOutput-- vmModifier <- applyCache (cmd.state, cmd.cache)-- params <- getParametersFromEnvironmentVariables cmd.rpc-- let- testn = params.number- block' = if 0 == testn- then EVM.Fetch.Latest- else EVM.Fetch.BlockNumber testn-- pure UnitTestOptions- { solvers = solvers- , rpcInfo = case cmd.rpc of- Just url -> Just (block', url)- Nothing -> Nothing- , maxIter = cmd.maxIterations- , askSmtIters = cmd.askSmtIterations- , smtDebug = cmd.smtdebug- , smtTimeout = cmd.smttimeout- , solver = cmd.solver- , covMatch = pack <$> cmd.covMatch- , verbose = cmd.verbose- , match = pack $ fromMaybe ".*" cmd.match- , maxDepth = cmd.depth- , fuzzRuns = fromMaybe 100 cmd.fuzzRuns- , replay = do- arg' <- cmd.replay- pure (fst arg', LazyByteString.fromStrict (hexByteString "--replay" $ strip0x $ snd arg'))- , vmModifier = vmModifier- , testParams = params- , dapp = srcInfo- , ffiAllowed = cmd.ffi- }--getFullVersion :: [Char]-getFullVersion = showVersion Paths.version <> " [" <> gitVersion <> "]"- where- gitInfo = $$tGitInfoCwdTry- gitVersion = case gitInfo of- Right val -> "git rev " <> giBranch val <> "@" <> giHash val- Left _ -> "no git revision present"--main :: IO ()-main = do- cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator"- case cmd of- Version {} ->putStrLn getFullVersion- Symbolic {} -> do- root <- getRoot cmd- withCurrentDirectory root $ assert cmd- Equivalence {} -> equivalence cmd- Exec {} ->- 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)- case (cmd.coverage, optsMode cmd) of- (False, Run) -> do- res <- unitTest testOpts out.contracts cmd.cache- unless res exitFailure- (False, Debug) -> liftIO $ TTY.main testOpts root (Just out)- (False, JsonTrace) -> internalError "json traces not implemented for dappTest"- (True, _) -> liftIO $ dappCoverage testOpts (optsMode cmd) out---equivalence :: Command Options.Unwrapped -> IO ()-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- , rpcInfo = Nothing- }- calldata <- buildCalldata cmd- solver <- getSolver cmd- withSolvers solver 3 Nothing $ \s -> do- res <- equivalenceCheck s bytecodeA bytecodeB veriOpts calldata- case any isCex res of- False -> do- putStrLn "No discrepancies found"- when (any isTimeout res) $ do- putStrLn "But timeout(s) occurred"- exitFailure- True -> do- let cexs = mapMaybe getCex res- T.putStrLn . T.unlines $- [ "Not equivalent. The following inputs result in differing behaviours:"- , "" , "-----", ""- ] <> (intersperse (T.unlines [ "", "-----" ]) $ fmap (formatCex (AbstractBuf "txdata")) cexs)- exitFailure--getSolver :: Command Options.Unwrapped -> IO Solver-getSolver cmd = case cmd.solver of- Nothing -> pure Z3- Just s -> case T.unpack s of- "z3" -> pure Z3- "cvc5" -> pure CVC5- input -> do- putStrLn $ "unrecognised solver: " <> input- exitFailure--getSrcInfo :: Command Options.Unwrapped -> IO DappInfo-getSrcInfo cmd = do- root <- getRoot cmd- withCurrentDirectory root $ do- outExists <- doesDirectoryExist (root </> "out")- if outExists- then do- buildOutput <- readBuildOutput root (getProjectType cmd)- case buildOutput of- Left _ -> pure emptyDapp- Right o -> pure $ dappInfo root o- else pure emptyDapp--getProjectType :: Command Options.Unwrapped -> ProjectType-getProjectType cmd = fromMaybe Foundry cmd.projectType--getRoot :: Command Options.Unwrapped -> IO FilePath-getRoot cmd = maybe getCurrentDirectory pure (cmd.root)----- | Builds a buffer representing calldata based on the given cli arguments-buildCalldata :: Command Options.Unwrapped -> IO (Expr Buf, [Prop])-buildCalldata cmd = case (cmd.calldata, cmd.sig) of- -- fully abstract calldata- (Nothing, Nothing) -> pure $ mkCalldata Nothing []- -- fully concrete calldata- (Just c, Nothing) -> pure (ConcreteBuf (hexByteString "bytes" . strip0x $ c), [])- -- calldata according to given abi with possible specializations from the `arg` list- (Nothing, Just sig') -> do- method' <- functionAbi sig'- pure $ mkCalldata (Just (Sig method'.methodSignature (snd <$> method'.inputs))) cmd.arg- -- both args provided- (_, _) -> do- putStrLn "incompatible options provided: --calldata and --sig"- exitFailure----- If function signatures are known, they should always be given for best results.-assert :: Command Options.Unwrapped -> IO ()-assert cmd = do- let block' = maybe EVM.Fetch.Latest EVM.Fetch.BlockNumber cmd.block- rpcinfo = (,) block' <$> cmd.rpc- calldata <- buildCalldata cmd- preState <- symvmFromCommand cmd calldata- let errCodes = fromMaybe defaultPanicCodes cmd.assertions- cores <- unsafeInto <$> getNumProcessors- let solverCount = fromMaybe cores cmd.numSolvers- solver <- getSolver cmd- withSolvers solver solverCount cmd.smttimeout $ \solvers -> do- if cmd.debug then do- srcInfo <- getSrcInfo cmd- void $ TTY.runFromVM- solvers- rpcinfo- cmd.maxIterations- srcInfo- preState- else do- let opts = VeriOpts {- simp = True,- debug = cmd.smtdebug,- maxIter = cmd.maxIterations,- askSmtIters = cmd.askSmtIterations,- loopHeuristic = cmd.loopDetectionHeuristic,- rpcInfo = rpcinfo- }- (expr, res) <- verify solvers opts preState (Just $ checkAssertions errCodes)- case res of- [Qed _] -> do- putStrLn "\nQED: No reachable property violations discovered\n"- showExtras solvers cmd calldata expr- _ -> do- let cexs = snd <$> mapMaybe getCex res- timeouts = mapMaybe getTimeout res- counterexamples- | null cexs = []- | otherwise =- [ ""- , "Discovered the following counterexamples:"- , ""- ] <> fmap (formatCex (fst calldata)) cexs- unknowns- | null timeouts = []- | otherwise =- [ ""- , "Could not determine reachability of the following end states:"- , ""- ] <> fmap (formatExpr) timeouts- T.putStrLn $ T.unlines (counterexamples <> unknowns)- showExtras solvers cmd calldata expr- exitFailure--showExtras :: SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> IO ()-showExtras solvers cmd calldata expr = do- when cmd.showTree $ 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 ""- when cmd.getModels $ do- putStrLn $ "=== Models for " <> show (Expr.numBranches expr) <> " branches ===\n"- ms <- produceModels solvers expr- forM_ ms (showModel (fst calldata))--getCex :: ProofResult a b c -> Maybe b-getCex (Cex c) = Just c-getCex _ = Nothing--getTimeout :: ProofResult a b c -> Maybe c-getTimeout (Timeout c) = Just c-getTimeout _ = Nothing--dappCoverage :: UnitTestOptions -> Mode -> BuildOutput -> IO ()-dappCoverage opts _ bo@(BuildOutput (Contracts cs) cache) = do- let unitTests = findUnitTests opts.match $ Map.elems cs- covs <- mconcat <$> mapM- (coverageForUnitTestContract opts cs cache) unitTests- let- dapp = dappInfo "." bo- f (k, vs) = do- when (shouldPrintCoverage opts.covMatch (T.pack k)) $ do- putStr ("\x1b[0m" ++ "————— hevm coverage for ") -- Prefixed with color reset- putStrLn (k ++ " —————")- putStrLn ""- forM_ vs $ \(n, bs) -> do- case ByteString.find (\x -> x /= 0x9 && x /= 0x20 && x /= 0x7d) bs of- Nothing -> putStr "\x1b[38;5;240m" -- Gray (Coverage status isn't relevant)- Just _ ->- case n of- -1 -> putStr "\x1b[38;5;240m" -- Gray (Coverage status isn't relevant)- 0 -> putStr "\x1b[31m" -- Red (Uncovered)- _ -> putStr "\x1b[32m" -- Green (Covered)- Char8.putStrLn bs- putStrLn ""- mapM_ f (Map.toList (coverageReport dapp covs))--shouldPrintCoverage :: Maybe Text -> Text -> Bool-shouldPrintCoverage (Just covMatch) file = regexMatches covMatch file-shouldPrintCoverage Nothing file = not (isTestOrLib file)--isTestOrLib :: Text -> Bool-isTestOrLib file = T.isSuffixOf ".t.sol" file || areAnyPrefixOf ["src/test/", "src/tests/", "lib/"] file--areAnyPrefixOf :: [Text] -> Text -> Bool-areAnyPrefixOf prefixes t = any (flip T.isPrefixOf t) prefixes--launchExec :: Command Options.Unwrapped -> IO ()-launchExec cmd = do- dapp <- getSrcInfo cmd- vm <- vmFromCommand cmd- -- TODO: we shouldn't need solvers to execute this code- withSolvers Z3 0 Nothing $ \solvers -> do- case optsMode cmd of- Run -> do- vm' <- EVM.Stepper.interpret (EVM.Fetch.oracle solvers rpcinfo) vm EVM.Stepper.runFully- when cmd.trace $ T.hPutStr stderr (showTraceTree dapp vm')- case vm'.result of- Just (VMFailure (Revert msg)) -> do- let res = case msg of- ConcreteBuf bs -> bs- _ -> "<symbolic>"- putStrLn $ "Revert: " <> (show $ ByteStringS res)- exitWith (ExitFailure 2)- Just (VMFailure err) -> 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- let msg = case buf of- ConcreteBuf msg' -> msg'- _ -> "<symbolic>"- print $ "Return: " <> (show $ ByteStringS msg)- case cmd.state of- Nothing -> pure ()- Just path ->- Git.saveFacts (Git.RepoAt path) (Facts.vmFacts vm')- case cmd.cache of- Nothing -> pure ()- Just path ->- Git.saveFacts (Git.RepoAt path) (Facts.cacheFacts vm'.cache)- _ ->- internalError "no EVM result"-- Debug -> void $ TTY.runFromVM solvers rpcinfo Nothing dapp vm- --JsonTrace -> void $ execStateT (interpretWithTrace fetcher EVM.Stepper.runFully) vm- _ -> internalError "TODO"- where block = maybe EVM.Fetch.Latest EVM.Fetch.BlockNumber cmd.block- rpcinfo = (,) block <$> cmd.rpc---- | Creates a (concrete) VM from command line options-vmFromCommand :: Command Options.Unwrapped -> IO VM-vmFromCommand cmd = do- withCache <- applyCache (cmd.state, cmd.cache)-- (miner,ts,baseFee,blockNum,prevRan) <- case cmd.rpc of- Nothing -> pure (0,Lit 0,0,0,0)- Just url -> EVM.Fetch.fetchBlockFrom block url >>= \case- Nothing -> error "Error: Could not fetch block"- Just Block{..} -> pure ( coinbase- , timestamp- , baseFee- , number- , prevRandao- )-- contract <- case (cmd.rpc, cmd.address, cmd.code) of- (Just url, Just addr', Just c) -> do- EVM.Fetch.fetchContractFrom block url addr' >>= \case- Nothing ->- error $ "Error: contract not found: " <> show address- Just contract ->- -- if both code and url is given,- -- fetch the contract and overwrite the code- pure $- initialContract (mkCode $ hexByteString "--code" $ strip0x c)- & set #balance (contract.balance)- & set #nonce (contract.nonce)- & set #external (contract.external)-- (Just url, Just addr', Nothing) ->- EVM.Fetch.fetchContractFrom block url addr' >>= \case- Nothing ->- error $ "Error: contract not found: " <> show address- Just contract -> pure contract-- (_, _, Just c) ->- pure $- initialContract (mkCode $ hexByteString "--code" $ strip0x c)-- (_, _, Nothing) ->- error "Error: must provide at least (rpc + address) or code"-- let ts' = case maybeLitWord ts of- Just t -> t- Nothing -> internalError "unexpected symbolic timestamp when executing vm test"-- pure $ EVM.Transaction.initTx $ withCache (vm0 baseFee miner ts' blockNum prevRan contract)- where- block = maybe EVM.Fetch.Latest EVM.Fetch.BlockNumber cmd.block- value = word (.value) 0- caller = addr (.caller) 0- origin = addr (.origin) 0- calldata = ConcreteBuf $ bytes (.calldata) ""- decipher = hexByteString "bytes" . strip0x- mkCode bs = if cmd.create- then InitCode bs mempty- else RuntimeCode (ConcreteRuntimeCode bs)- address = if cmd.create- then addr (.address) (createAddress origin (word (.nonce) 0))- else addr (.address) 0xacab-- vm0 baseFee miner ts blockNum prevRan c = makeVm $ VMOpts- { contract = c- , calldata = (calldata, [])- , value = Lit value- , address = address- , caller = Expr.litAddr caller- , origin = origin- , gas = word64 (.gas) 0xffffffffffffffff- , baseFee = baseFee- , priorityFee = word (.priorityFee) 0- , gaslimit = word64 (.gaslimit) 0xffffffffffffffff- , coinbase = addr (.coinbase) miner- , number = word (.number) blockNum- , timestamp = Lit $ word (.timestamp) ts- , blockGaslimit = word64 (.gaslimit) 0xffffffffffffffff- , gasprice = word (.gasprice) 0- , maxCodeSize = word (.maxcodesize) 0xffffffff- , prevRandao = word (.prevRandao) prevRan- , schedule = FeeSchedule.berlin- , chainId = word (.chainid) 1- , create = (.create) cmd- , initialStorage = EmptyStore- , txAccessList = mempty -- TODO: support me soon- , allowFFI = False- }- word f def = fromMaybe def (f cmd)- word64 f def = fromMaybe def (f cmd)- addr f def = fromMaybe def (f cmd)- bytes f def = maybe def decipher (f cmd)--symvmFromCommand :: Command Options.Unwrapped -> (Expr Buf, [Prop]) -> IO (VM)-symvmFromCommand cmd calldata = do- (miner,blockNum,baseFee,prevRan) <- case cmd.rpc of- Nothing -> pure (0,0,0,0)- Just url -> EVM.Fetch.fetchBlockFrom block url >>= \case- Nothing -> error "Error: Could not fetch block"- Just Block{..} -> pure ( coinbase- , number- , baseFee- , prevRandao- )-- let- caller = Caller 0- ts = maybe Timestamp Lit cmd.timestamp- callvalue = maybe (CallValue 0) Lit cmd.value- -- TODO: rework this, ConcreteS not needed anymore- let store = maybe AbstractStore parseInitialStorage (cmd.initialStorage)- withCache <- applyCache (cmd.state, cmd.cache)-- contract <- case (cmd.rpc, cmd.address, cmd.code) of- (Just url, Just addr', _) ->- EVM.Fetch.fetchContractFrom block url addr' >>= \case- Nothing ->- error "Error: contract not found."- Just contract' -> pure contract''- where- contract'' = case cmd.code of- Nothing -> contract'- -- if both code and url is given,- -- fetch the contract and overwrite the code- Just c -> initialContract (mkCode $ decipher c)- -- TODO: fix this- -- & set EVM.origStorage (view EVM.origStorage contract')- & set #balance (contract'.balance)- & set #nonce (contract'.nonce)- & set #external (contract'.external)-- (_, _, Just c) ->- pure (initialContract . mkCode $ decipher c)- (_, _, Nothing) ->- error "Error: must provide at least (rpc + address) or code"-- pure $ (EVM.Transaction.initTx $ withCache $ vm0 baseFee miner ts blockNum prevRan calldata callvalue caller contract)- & set (#env % #storage) store-- where- decipher = hexByteString "bytes" . strip0x- block = maybe EVM.Fetch.Latest EVM.Fetch.BlockNumber cmd.block- origin = addr (.origin) 0- mkCode bs = if cmd.create- then InitCode bs mempty- else RuntimeCode (ConcreteRuntimeCode bs)- address = if cmd.create- then addr (.address) (createAddress origin (word (.nonce) 0))- else addr (.address) 0xacab- vm0 baseFee miner ts blockNum prevRan cd callvalue caller c = makeVm $ VMOpts- { contract = c- , calldata = cd- , value = callvalue- , address = address- , caller = caller- , origin = origin- , gas = word64 (.gas) 0xffffffffffffffff- , gaslimit = word64 (.gaslimit) 0xffffffffffffffff- , baseFee = baseFee- , priorityFee = word (.priorityFee) 0- , coinbase = addr (.coinbase) miner- , number = word (.number) blockNum- , timestamp = ts- , blockGaslimit = word64 (.gaslimit) 0xffffffffffffffff- , gasprice = word (.gasprice) 0- , maxCodeSize = word (.maxcodesize) 0xffffffff- , prevRandao = word (.prevRandao) prevRan- , schedule = FeeSchedule.berlin- , chainId = word (.chainid) 1- , create = (.create) cmd- , initialStorage = maybe AbstractStore parseInitialStorage (cmd.initialStorage)- , txAccessList = mempty- , allowFFI = False- }- word f def = fromMaybe def (f cmd)- addr f def = fromMaybe def (f cmd)- word64 f def = fromMaybe def (f cmd)--parseInitialStorage :: InitialStorage -> Expr Storage-parseInitialStorage = \case- Empty -> EmptyStore- Concrete s -> ConcreteStore (Map.fromList $ fmap (second Map.fromList) s)- Abstract -> AbstractStore
hevm.cabal view
@@ -2,44 +2,24 @@ name: hevm version:- 0.51.3+ 0.52.0 synopsis:- Ethereum virtual machine evaluator-description:- Hevm implements the Ethereum virtual machine semantics.- .- It can be used as a library, and it also comes with an executable- that can run unit test suites, optionally with a visual TTY debugger.+ Symbolic EVM Evaluator homepage:- https://github.com/dapphub/dapptools+ https://github.com/ethereum/hevm license: AGPL-3.0-only author:- Mikael Brockman, Martin Lundfall, dxo+ dxo, Martin Lundfall, Mikael Brockman maintainer:- mikael@brockman.se, martin.lundfall@gmail.com, git@d-xo.org+ git@d-xo.org category: Ethereum build-type: Simple extra-source-files: CHANGELOG.md- bench/contracts/erc20.sol- test/contracts/lib/test.sol- test/contracts/lib/erc20.sol- test/contracts/pass/trivial.sol- test/contracts/pass/abstract.sol- test/contracts/pass/cheatCodes.sol- test/contracts/pass/constantinople.sol- test/contracts/pass/dsProvePass.sol- test/contracts/pass/invariants.sol- test/contracts/pass/libraries.sol- test/contracts/pass/loops.sol- test/contracts/pass/rpc.sol- test/contracts/fail/trivial.sol- test/contracts/fail/cheatCodes.sol- test/contracts/fail/dsProveFail.sol- test/contracts/fail/invariantFail.sol+ bench/contracts/*.sol test/scripts/convert_trace_to_json.sh flag ci@@ -78,6 +58,7 @@ -Wno-unticked-promoted-constructors -Wno-orphans -Wno-ambiguous-fields+ -optc-Wno-ignored-attributes default-language: GHC2021 default-extensions: DuplicateRecordFields@@ -89,6 +70,7 @@ RecordWildCards TypeFamilies ViewPatterns+ DataKinds library import: shared@@ -98,25 +80,18 @@ EVM.Assembler, EVM.Concrete, EVM.Dapp,- EVM.Debug,- EVM.Dev, EVM.Expr, EVM.SMT, EVM.Solvers, EVM.Exec,- EVM.Facts,- EVM.Facts.Git, EVM.Format, EVM.Fetch, EVM.FeeSchedule,- EVM.Hexdump, EVM.Op,- EVM.Patricia, EVM.Precompiled, EVM.RLP, EVM.Solidity, EVM.Stepper,- EVM.StorageLayout, EVM.SymExec, EVM.Traversals, EVM.CSE,@@ -125,16 +100,21 @@ EVM.Types, EVM.UnitTest, EVM.Sign,- if !os(windows)- exposed-modules:- EVM.TTY,- EVM.TTYCenteredList other-modules: Paths_hevm autogen-modules: Paths_hevm- if os(linux) || os(windows)+ if impl(ghc >= 9.4) && !os(darwin)+ -- darwin is skipped because it produces this error when building+ -- > ghc: loadArchive: Neither an archive, nor a fat archive: `/nix/store/l3lkdfm7sg1wwc850451cikqds766h15-clang-wrapper-11.1.0/bin/clang++'+ build-depends: system-cxx-std-lib+ elif !os(darwin) extra-libraries: stdc+++ else+ -- extra-libraries: c+++ -- https://gitlab.haskell.org/ghc/ghc/-/issues/11829+ ld-options: -Wl,-keep_dwarf_unwind+ ghc-options: -fcompact-unwind extra-libraries: secp256k1, ff, gmp c-sources:@@ -161,7 +141,6 @@ text >= 1.2.3 && < 2.1, unordered-containers >= 0.2.10 && < 0.3, vector >= 0.12.1 && < 0.14,- ansi-wl-pprint >= 0.6.9 && < 0.7, base16 >= 0.3.2.0 && < 0.3.3.0, megaparsec >= 9.0.0 && < 10.0, mtl >= 2.2.2 && < 2.3,@@ -183,6 +162,7 @@ multiset >= 0.3.4 && < 0.4, operational >= 0.2.3 && < 0.3, optparse-generic >= 1.3.1 && < 1.5,+ pretty-hex >= 1.1 && < 1.2, quickcheck-text >= 0.1.2 && < 0.2, restless-git >= 0.7 && < 0.8, rosezipper >= 0.2 && < 0.3,@@ -192,7 +172,6 @@ regex-tdfa >= 1.2.3 && < 1.4, base >= 4.9 && < 5, here >= 1.2.13 && < 1.3,- tuple >= 0.3.0.2 && < 0.4, smt2-parser >= 0.1.0.1, word-wrap >= 0.5 && < 0.6, spool >= 0.1 && < 0.2,@@ -210,22 +189,17 @@ executable hevm import: shared hs-source-dirs:- hevm-cli+ cli main-is:- hevm-cli.hs+ cli.hs ghc-options: -threaded -with-rtsopts=-N other-modules: Paths_hevm if os(darwin) extra-libraries: c++- ld-options: -Wl,-keep_dwarf_unwind- ghc-options: -fcompact-unwind- else- extra-libraries: stdc++ build-depends: QuickCheck, aeson,- ansi-wl-pprint, async, base, base16,@@ -266,8 +240,6 @@ import: shared hs-source-dirs: test- extra-libraries:- secp256k1 other-modules: Paths_hevm autogen-modules:@@ -336,11 +308,6 @@ buildable: False if os(darwin) extra-libraries: c++- -- https://gitlab.haskell.org/ghc/ghc/-/issues/11829- ld-options: -Wl,-keep_dwarf_unwind- ghc-options: -fcompact-unwind- else- extra-libraries: stdc++ --- Test Suites --- @@ -383,9 +350,7 @@ ghc-options: -O2 if os(darwin)- extra-libraries: c++- else- extra-libraries: stdc+++ extra-libraries: c++ other-modules: Paths_hevm autogen-modules:
src/EVM.hs view
@@ -12,2319 +12,2630 @@ import Optics.Operators.Unsafe import EVM.ABI-import EVM.Concrete (createAddress, create2Address)-import EVM.Expr (readStorage, writeStorage, readByte, readWord, writeWord,- writeByte, bufLength, indexWord, litAddr, readBytes, word256At, copySlice)-import EVM.Expr qualified as Expr-import EVM.FeeSchedule (FeeSchedule (..))-import EVM.Op-import EVM.Precompiled qualified-import EVM.Solidity-import EVM.Types-import EVM.Sign qualified--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)-import Data.Set (insert, member, fromList)-import Data.Sequence (Seq)-import Data.Sequence qualified as Seq-import Data.Text (unpack)-import Data.Text.Encoding (decodeUtf8, encodeUtf8)-import Data.Tree-import Data.Tree.Zipper qualified as Zipper-import Data.Tuple.Curry-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.Word (Word8, Word32, Word64)-import Witch (into, unsafeInto)--import Crypto.Hash (Digest, SHA256, RIPEMD160)-import Crypto.Hash qualified as Crypto-import Crypto.Number.ModArithmetic (expFast)--blankState :: FrameState-blankState = FrameState- { contract = 0- , codeContract = 0- , code = RuntimeCode (ConcreteRuntimeCode "")- , pc = 0- , stack = mempty- , memory = mempty- , memorySize = 0- , calldata = mempty- , callvalue = Lit 0- , caller = Lit 0- , gas = 0- , returndata = mempty- , static = False- }---- | An "external" view of a contract's bytecode, appropriate for--- e.g. @EXTCODEHASH@.-bytecode :: Getter Contract (Expr Buf)-bytecode = #contractcode % to f- where f (InitCode _ _) = mempty- f (RuntimeCode (ConcreteRuntimeCode bs)) = ConcreteBuf bs- f (RuntimeCode (SymbolicRuntimeCode ops)) = Expr.fromList ops---- * Data accessors--currentContract :: VM -> Maybe Contract-currentContract vm =- Map.lookup vm.state.codeContract vm.env.contracts---- * Data constructors--makeVm :: VMOpts -> VM-makeVm o =- let txaccessList = o.txAccessList- txorigin = o.origin- txtoAddr = o.address- initialAccessedAddrs = fromList $ [txorigin, txtoAddr, o.coinbase] ++ [1..9] ++ (Map.keys txaccessList)- initialAccessedStorageKeys = fromList $ foldMap (uncurry (map . (,))) (Map.toList txaccessList)- touched = if o.create then [txorigin] else [txorigin, txtoAddr]- in- 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- --, _accessList = txaccessList- , isCreate = o.create- , txReversion = Map.fromList- [(o.address , o.contract )]- }- , 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 = mempty- , memorySize = 0- , code = o.contract.contractcode- , 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- , storage = o.initialStorage- , origStorage = mempty- , contracts = Map.fromList- [(o.address, o.contract )]- }- , cache = Cache mempty mempty mempty- , burned = 0- , constraints = snd o.calldata- , keccakEqs = mempty- , iterations = mempty- , allowFFI = o.allowFFI- , overrideCaller = Nothing- }---- | Initialize empty contract with given code-initialContract :: ContractCode -> Contract-initialContract contractCode = Contract- { contractcode = contractCode- , codehash = hashcode contractCode- , balance = 0- , nonce = if creation then 1 else 0- , opIxMap = mkOpIxMap contractCode- , codeOps = mkCodeOps contractCode- , external = False- } where- creation = case contractCode of- InitCode _ _ -> True- RuntimeCode _ -> False---- * Opcode dispatch (exec1)---- | Update program counter-next :: (?op :: Word8) => EVM ()-next = modifying (#state % #pc) (+ (opSize ?op))---- | Executes the EVM one step-exec1 :: EVM ()-exec1 = do- vm <- get-- let- -- Convenient aliases- mem = vm.state.memory- 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)-- if self > 0x0 && self <= 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 self 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- 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- 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- let (topics, xs') = splitAt (into n) xs- bytes = readMemory xOffset' xSize' vm- logs' = (LogEntry (litAddr self) bytes topics) : vm.logs- burn (g_log + g_logdata * (unsafeInto xSize) + into n * g_logtopic) $- accessMemoryRange xOffset xSize $ do- traceTopLog logs'- next- assign (#state % #stack) xs'- assign #logs logs'- _ ->- underrun-- OpStop -> doStop-- OpAdd -> stackOp2 g_verylow (uncurry Expr.add)- OpMul -> stackOp2 g_low (uncurry Expr.mul)- OpSub -> stackOp2 g_verylow (uncurry Expr.sub)-- OpDiv -> stackOp2 g_low (uncurry Expr.div)-- OpSdiv -> stackOp2 g_low (uncurry Expr.sdiv)-- OpMod -> stackOp2 g_low (uncurry Expr.mod)-- OpSmod -> stackOp2 g_low (uncurry Expr.smod)- OpAddmod -> stackOp3 g_mid (uncurryN Expr.addmod)- OpMulmod -> stackOp3 g_mid (uncurryN Expr.mulmod)-- OpLt -> stackOp2 g_verylow (uncurry Expr.lt)- OpGt -> stackOp2 g_verylow (uncurry Expr.gt)- OpSlt -> stackOp2 g_verylow (uncurry Expr.slt)- OpSgt -> stackOp2 g_verylow (uncurry Expr.sgt)-- OpEq -> stackOp2 g_verylow (uncurry Expr.eq)- OpIszero -> stackOp1 g_verylow Expr.iszero-- OpAnd -> stackOp2 g_verylow (uncurry Expr.and)- OpOr -> stackOp2 g_verylow (uncurry Expr.or)- OpXor -> stackOp2 g_verylow (uncurry Expr.xor)- OpNot -> stackOp1 g_verylow Expr.not-- OpByte -> stackOp2 g_verylow (\(i, w) -> Expr.padByte $ Expr.indexWord i w)-- OpShl -> stackOp2 g_verylow (uncurry Expr.shl)- OpShr -> stackOp2 g_verylow (uncurry Expr.shr)- OpSar -> stackOp2 g_verylow (uncurry 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 <- case readMemory xOffset' xSize' vm of- 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 >> push (into self))-- OpBalance ->- case stk of- x':xs -> forceConcrete x' "BALANCE" $ \x ->- accessAndBurn (unsafeInto x) $- fetchAccount (unsafeInto x) $ \c -> do- next- assign (#state % #stack) xs- push c.balance- [] ->- underrun-- OpOrigin ->- limitStack 1 . burn g_base $- next >> push (into vm.tx.origin)-- OpCaller ->- limitStack 1 . burn g_base $- next >> pushSym 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- copyBytesToMemory (toBuf vm.state.code) n' codeOffset memOffset'- else vmError IllegalOverflow- _ -> underrun-- OpGasprice ->- limitStack 1 . burn g_base $- next >> push vm.tx.gasprice-- OpExtcodesize ->- case stk of- x':xs -> case x' of- Lit x -> if x == into cheatCode- then do- next- assign (#state % #stack) xs- pushSym (Lit 1)- else- accessAndBurn (unsafeInto x) $- fetchAccount (unsafeInto x) $ \c -> do- next- assign (#state % #stack) xs- pushSym (bufLength (view bytecode c))- _ -> do- assign (#state % #stack) xs- pushSym (CodeSize x')- next- [] ->- underrun-- OpExtcodecopy ->- case stk of- extAccount':memOffset':codeOffset:codeSize':xs ->- forceConcrete3 (extAccount', memOffset', codeSize') "EXTCODECOPY" $- \(extAccount, memOffset, codeSize) -> do- acc <- accessAccountForGas (unsafeInto 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 (unsafeInto extAccount) $ \c -> do- next- assign (#state % #stack) xs- copyBytesToMemory (view bytecode c) codeSize' codeOffset memOffset'- _ -> 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)- loc <- codeloc- branch loc (Expr.or oob overflow) jump- _ -> underrun-- OpExtcodehash ->- case stk of- x':xs -> forceConcrete x' "EXTCODEHASH" $ \x ->- accessAndBurn (unsafeInto x) $ do- next- assign (#state % #stack) xs- fetchAccount (unsafeInto x) $ \c ->- if accountEmpty c- then push 0- else pushSym $ keccak (view bytecode c)- [] ->- 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 >> push (into 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 >> push 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- assign (#state % #stack) (readWord (Lit x) mem : xs)- _ -> underrun-- OpMstore ->- case stk of- x':y:xs -> forceConcrete x' "MSTORE index" $ \x ->- burn g_verylow $- accessMemoryWord x $ do- next- assign (#state % #memory) (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- modifying (#state % #memory) (writeByte (Lit x) yByte)- 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 readStorage (litAddr self) x (ConcreteStore vm.env.origStorage) of- Just (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 % #storage) (writeStorage (litAddr self) 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 ()- jump False = assign (#state % #stack) xs >> next- jump _ = case toInt x' of- Nothing -> vmError BadJumpDestination- Just i -> checkJump i xs- in do- loc <- codeloc- branch loc 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 (uncurry Expr.sex)-- OpCreate ->- notStatic $- case stk of- xValue':xOffset':xSize':xs -> forceConcrete3 (xValue', xOffset', xSize') "CREATE" $- \(xValue, xOffset, xSize) -> do- accessMemoryRange xOffset xSize $ do- availableGas <- use (#state % #gas)- let- newAddr = createAddress self this.nonce- (cost, gas') = costOfCreate fees availableGas xSize False- _ <- accessAccountForGas newAddr- burn cost $ do- let initCode = readMemory xOffset' xSize' vm- create self this xSize gas' xValue xs newAddr initCode- _ -> underrun-- OpCall ->- case stk of- xGas':xTo:xValue':xInOffset':xInSize':xOutOffset':xOutSize':xs ->- forceConcrete6 (xGas', xValue', xInOffset', xInSize', xOutOffset', xOutSize') "CALL" $- \(xGas, xValue, xInOffset, xInSize, xOutOffset, xOutSize) ->- (if xValue > 0 then notStatic else id) $- delegateCall this (unsafeInto xGas) xTo xTo xValue xInOffset xInSize xOutOffset xOutSize xs $ \callee -> do- let from' = fromMaybe self vm.overrideCaller- zoom #state $ do- assign #callvalue (Lit xValue)- assign #caller (litAddr from')- assign #contract callee- assign #overrideCaller Nothing- touchAccount from'- touchAccount callee- transfer from' callee xValue- _ ->- underrun-- OpCallcode ->- case stk of- xGas':xTo:xValue':xInOffset':xInSize':xOutOffset':xOutSize':xs ->- forceConcrete6 (xGas', xValue', xInOffset', xInSize', xOutOffset', xOutSize') "CALLCODE" $- \(xGas, xValue, xInOffset, xInSize, xOutOffset, xOutSize) ->- delegateCall this (unsafeInto xGas) xTo (litAddr self) xValue xInOffset xInSize xOutOffset xOutSize xs $ \_ -> do- zoom #state $ do- assign #callvalue (Lit xValue)- assign #caller $ litAddr $ fromMaybe self vm.overrideCaller- assign #overrideCaller Nothing- touchAccount self- _ ->- underrun-- OpReturn ->- case stk of- xOffset':xSize':_ -> forceConcrete2 (xOffset', xSize') "RETURN" $ \(xOffset, xSize) ->- accessMemoryRange xOffset xSize $ do- let- output = readMemory xOffset' xSize' vm- 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 -> do- loc <- codeloc- branch loc (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) ->- delegateCall this (unsafeInto xGas) xTo (litAddr self) 0 xInOffset xInSize xOutOffset xOutSize xs $ \_ -> do- touchAccount self- _ -> underrun-- OpCreate2 -> notStatic $- case stk of- xValue':xOffset':xSize':xSalt':xs ->- forceConcrete4 (xValue', xOffset', xSize', xSalt') "CREATE2" $- \(xValue, xOffset, xSize, xSalt) ->- accessMemoryRange xOffset xSize $ do- availableGas <- use (#state % #gas)-- forceConcreteBuf (readMemory xOffset' xSize' vm) "CREATE2" $- \initCode -> do- let- newAddr = create2Address self xSalt initCode- (cost, gas') = costOfCreate fees availableGas xSize True- _ <- 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- delegateCall this (unsafeInto xGas) xTo xTo 0 xInOffset xInSize xOutOffset xOutSize xs $ \callee -> do- zoom #state $ do- assign #callvalue (Lit 0)- assign #caller $ litAddr $ fromMaybe self (vm.overrideCaller)- assign #contract callee- assign #static True- assign #overrideCaller Nothing- touchAccount self- touchAccount callee- _ ->- underrun-- OpSelfdestruct ->- notStatic $- case stk of- [] -> underrun- (xTo':_) -> forceConcrete xTo' "SELFDESTRUCT" $ \(unsafeInto -> xTo) -> do- acc <- accessAccountForGas xTo- let cost = if acc then 0 else g_cold_account_access- funds = this.balance- recipientExists = accountExists xTo vm- c_new = if not recipientExists && funds /= 0- then g_selfdestruct_newaccount- else 0- burn (g_selfdestruct + c_new + cost) $ do- selfdestruct self- touchAccount xTo-- if funds /= 0- then fetchAccount xTo $ \_ -> do- #env % #contracts % ix xTo % #balance %= (+ funds)- assign (#env % #contracts % ix self % #balance) 0- doStop- else doStop-- OpRevert ->- case stk of- xOffset':xSize':_ -> forceConcrete2 (xOffset', xSize') "REVERT" $ \(xOffset, xSize) ->- accessMemoryRange xOffset xSize $ do- let output = readMemory xOffset' xSize' vm- finishFrame (FrameReverted output)- _ -> underrun-- OpUnknown xxx ->- vmError $ UnrecognizedOpcode xxx--transfer :: Addr -> Addr -> W256 -> EVM ()-transfer _ _ 0 = pure ()-transfer xFrom xTo xValue = do- sb <- preuse $ #env % #contracts % ix xFrom % #balance- case sb of- Just srcBal ->- if xValue > srcBal- then vmError $ BalanceTooLow xValue srcBal- else do- (#env % #contracts % ix xFrom % #balance) %= (subtract xValue)- (#env % #contracts % ix xTo % #balance) %= (+ xValue)- Nothing -> vmError $ BalanceTooLow xValue 0---- | Checks a *CALL for failure; OOG, too many callframes, memory access etc.-callChecks- :: (?op :: Word8)- => Contract -> Word64 -> Addr -> Addr -> W256 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord]- -- continuation with gas available for call- -> (Word64 -> EVM ())- -> EVM ()-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') $ do- if xValue > this.balance- then do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- pushTrace $ ErrorTrace (BalanceTooLow xValue this.balance)- next- else 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- -> W256- -> W256 -> W256 -> W256 -> W256- -> [Expr EWord]- -> EVM ()-precompiledContract this xGas precompileAddr recipient xValue inOffset inSize outOffset outSize xs =- callChecks this xGas recipient 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 recipient $ \_ -> do- transfer self recipient xValue- touchAccount self- touchAccount recipient- _ -> partial $ UnexpectedSymbolicArg pc' "unexpected return value from precompile" (wrap [x])- _ -> underrun- _ -> pure ()--executePrecompile- :: (?op :: Word8)- => Addr- -> Word64 -> W256 -> W256 -> W256 -> W256 -> [Expr EWord]- -> EVM ()-executePrecompile preCompileAddr gasCap inOffset inSize outOffset outSize xs = do- vm <- get- let input = readMemory (Lit inOffset) (Lit inSize) vm- 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 0) (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 -> CodeLocation-getCodeLocation vm = (vm.state.contract, vm.state.pc)--query :: Query -> EVM ()-query = assign #result . Just . HandleEffect . Query--choose :: Choose -> EVM ()-choose = assign #result . Just . HandleEffect . Choose--branch :: CodeLocation -> Expr EWord -> (Bool -> EVM ()) -> EVM ()-branch loc cond continue = do- pathconds <- use #constraints- query $ PleaseAskSMT cond pathconds choosePath- where- choosePath (Case v) = do- assign #result Nothing- pushTo #constraints $ if v then (cond ./= Lit 0) else (cond .== 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 Unknown =- choose . PleaseChoosePath cond $ choosePath . Case---- | Construct RPC Query and halt execution until resolved-fetchAccount :: Addr -> (Contract -> EVM ()) -> EVM ()-fetchAccount addr continue =- use (#env % #contracts % at addr) >>= \case- Just c -> continue c- Nothing ->- use (#cache % #fetchedContracts % at addr) >>= \case- Just c -> do- assign (#env % #contracts % at addr) (Just c)- continue c- Nothing -> do- assign (#result) . Just . HandleEffect . Query $- PleaseFetchContract addr- (\c -> do assign (#cache % #fetchedContracts % at addr) (Just c)- assign (#env % #contracts % at addr) (Just c)- assign #result Nothing- continue c)--accessStorage- :: Addr- -> Expr EWord- -> (Expr EWord -> EVM ())- -> EVM ()-accessStorage addr slot continue = do- store <- (.env.storage) <$> get- use (#env % #contracts % at addr) >>= \case- Just c ->- case readStorage (litAddr addr) slot store of- -- Notice that if storage is symbolic, we always continue straight away- Just x ->- continue x- Nothing ->- if c.external then- forceConcrete slot "cannot read symbolic slots via RPC" $ \litSlot -> do- -- check if the slot is cached- cachedStore <- (.cache.fetchedStorage) <$> get- case Map.lookup (into addr) cachedStore >>= Map.lookup litSlot of- Nothing -> mkQuery litSlot- Just val -> continue (Lit val)- else do- -- TODO: is this actually needed?- modifying (#env % #storage) (writeStorage (litAddr addr) slot (Lit 0))- continue $ Lit 0- Nothing ->- fetchAccount addr $ \_ ->- accessStorage addr slot continue- where- mkQuery s = query $- PleaseFetchSlot addr s- (\x -> do- modifying (#cache % #fetchedStorage % ix (into addr)) (Map.insert s x)- modifying (#env % #storage) (writeStorage (litAddr addr) slot (Lit x))- assign #result Nothing- continue (Lit x))--accountExists :: Addr -> VM -> 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.contractcode of- RuntimeCode (ConcreteRuntimeCode "") -> True- RuntimeCode (SymbolicRuntimeCode b) -> null b- _ -> False- && c.nonce == 0- && c.balance == 0---- * How to finalize a transaction-finalize :: EVM ()-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 (+ originPay)) tx.origin)- modifying (#env % #contracts)- (Map.adjust (over #balance (+ 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 :: Addr -> EVM ()-loadContract target =- preuse (#env % #contracts % ix target % #contractcode) >>=- \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 () -> EVM ()-limitStack n continue = do- stk <- use (#state % #stack)- if length stk + n > 1024- then vmError StackLimitExceeded- else continue--notStatic :: EVM () -> EVM ()-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 () -> EVM ()-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)--forceConcrete :: Expr EWord -> String -> (W256 -> EVM ()) -> EVM ()-forceConcrete n msg continue = case maybeLitWord n of- Nothing -> do- vm <- get- partial $ UnexpectedSymbolicArg vm.state.pc msg (wrap [n])- Just c -> continue c--forceConcrete2 :: (Expr EWord, Expr EWord) -> String -> ((W256, W256) -> EVM ()) -> EVM ()-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 ()) -> EVM ()-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 ()) -> EVM ()-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 ()) -> EVM ()-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 ()) -> EVM ()-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 ()) -> EVM ()-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 ()-refund n = do- self <- use (#state % #contract)- pushTo (#tx % #substate % #refunds) (self, n)--unRefund :: Word64 -> EVM ()-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 :: Addr -> EVM()-touchAccount = pushTo ((#tx % #substate) % #touchedAccounts)--selfdestruct :: Addr -> EVM()-selfdestruct = pushTo ((#tx % #substate) % #selfdestructs)--accessAndBurn :: Addr -> EVM () -> EVM ()-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 :: Addr -> EVM 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 :: Addr -> Expr EWord -> EVM 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 :: Addr-cheatCode = unsafeInto (keccak' "hevm cheat code")--cheat- :: (?op :: Word8)- => (W256, W256) -> (W256, W256)- -> EVM ()-cheat (inOffset, inSize) (outOffset, outSize) = do- mem <- use (#state % #memory)- vm <- get- let- abi = readBytes 4 (Lit inOffset) mem- input = readMemory (Lit $ inOffset + 4) (Lit $ inSize - 4) vm- pushTrace $ FrameTrace (CallContext cheatCode cheatCode inOffset inSize (Lit 0) (maybeLitWord abi) input (vm.env.contracts, vm.env.storage) 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 = Expr EWord -> Expr EWord -> Expr Buf -> EVM ()--cheatActions :: Map FunctionSelector CheatAction-cheatActions =- Map.fromList- [ action "ffi(string[])" $- \sig outOffset outSize input -> do- vm <- get- if vm.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 = encodeUtf8 "ffi disabled: run again with --ffi if you want to allow tests to call external scripts"- in vmError . Revert . ConcreteBuf $- abiMethod "Error(string)" (AbiTuple . V.fromList $ [AbiString msg]),-- action "warp(uint256)" $- \sig _ _ input -> case decodeStaticArgs 0 1 input of- [x] -> assign (#block % #timestamp) x- _ -> 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] ->- forceConcrete a "cannot store at a symbolic address" $ \(unsafeInto -> a') ->- fetchAccount a' $ \_ -> do- modifying (#env % #storage) (writeStorage (litAddr a') slot new)- _ -> vmError (BadCheatCode sig),-- action "load(address,bytes32)" $- \sig outOffset _ input -> case decodeStaticArgs 0 2 input of- [a, slot] ->- forceConcrete a "cannot load from a symbolic address" $ \(unsafeInto -> a') ->- accessStorage a' slot $ \res -> do- assign (#state % #returndata % word256At (Lit 0)) res- assign (#state % #memory % word256At outOffset) res- _ -> 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- assign (#state % #memory % word256At outOffset) expAddr- _ -> vmError (BadCheatCode sig),-- action "prank(address)" $- \sig _ _ input -> case decodeStaticArgs 0 1 input of- [addr] -> assign #overrideCaller (Expr.exprToAddr addr)- _ -> 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 EWord -> Expr EWord -> W256 -> W256 -> W256 -> W256 -> W256- -> [Expr EWord]- -> (Addr -> EVM ())- -> EVM ()-delegateCall this gasGiven xTo xContext xValue xInOffset xInSize xOutOffset xOutSize xs continue =- forceConcrete2 (xTo, xContext) "cannot delegateCall with symbolic target or context" $- \((unsafeInto -> xTo'), (unsafeInto -> xContext')) ->- if xTo' > 0 && xTo' <= 9- then precompiledContract this gasGiven xTo' xContext' xValue xInOffset xInSize xOutOffset xOutSize xs- else if xTo' == cheatCode then- do- assign (#state % #stack) xs- cheat (xInOffset, xInSize) (xOutOffset, xOutSize)- else- callChecks this gasGiven xContext' xTo' xValue xInOffset xInSize xOutOffset xOutSize xs $- \xGas -> do- vm0 <- get- fetchAccount xTo' $ \target ->- burn xGas $ do- let newContext = CallContext- { target = xTo'- , context = xContext'- , offset = xOutOffset- , size = xOutSize- , codehash = target.codehash- , callreversion = (vm0.env.contracts, vm0.env.storage)- , subState = vm0.tx.substate- , abi =- if xInSize >= 4- then (maybeLitWord $ readBytes 4 (Lit xInOffset) vm0.state.memory)- else Nothing- , calldata = (readMemory (Lit xInOffset) (Lit xInSize) vm0)- }-- 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-- zoom #state $ do- assign #gas xGas- assign #pc 0- assign #code (clearInitCode target.contractcode)- assign #codeContract xTo'- assign #stack mempty- assign #memory mempty- assign #memorySize 0- assign #returndata mempty- assign #calldata (copySlice (Lit xInOffset) (Lit 0) (Lit xInSize) vm0.state.memory mempty)-- continue xTo'---- -- * Contract creation---- EIP 684-collision :: Maybe Contract -> Bool-collision c' = case c' of- Just c -> c.nonce /= 0 || case c.contractcode of- RuntimeCode (ConcreteRuntimeCode "") -> False- RuntimeCode (SymbolicRuntimeCode b) -> not $ null b- _ -> True- Nothing -> False--create :: (?op :: Word8)- => Addr -> Contract- -> W256 -> Word64 -> W256 -> [Expr EWord] -> Addr -> Expr Buf -> EVM ()-create self this xSize xGas xValue xs newAddr initCode = do- vm0 <- get- if this.nonce == into (maxBound :: Word64)- then do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- pushTrace $ ErrorTrace NonceOverflow- next- else if xValue > this.balance- then do- assign (#state % #stack) (Lit 0 : xs)- assign (#state % #returndata) mempty- pushTrace $ ErrorTrace $ BalanceTooLow xValue this.balance- next- else 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 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) succ- next- else burn xGas $ do- touchAccount self- touchAccount newAddr- let- -- unfortunately we have to apply some (pretty hacky)- -- heuristics here to parse the unstructured buffer read- -- from memory into a code and data section- let contract' = do- prefixLen <- Expr.concPrefix initCode- prefix <- Expr.toList $ Expr.take (unsafeInto prefixLen) initCode- let sym = Expr.drop (unsafeInto prefixLen) initCode- conc <- mapM maybeLitByte prefix- pure $ InitCode (BS.pack $ V.toList conc) sym- case contract' 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 0 (.balance) oldAcc-- assign (at newAddr) (Just (newContract & #balance .~ oldBal))- modifying (ix self % #nonce) succ-- let resetStorage = \case- ConcreteStore s -> ConcreteStore (Map.delete (into newAddr) s)- AbstractStore -> AbstractStore- EmptyStore -> EmptyStore- SStore {} -> internalError "trying to reset symbolic storage with writes in create"- GVar _ -> internalError "unexpected global variable"-- modifying (#env % #storage) resetStorage- modifying (#env % #origStorage) (Map.delete (into newAddr))-- transfer self newAddr xValue-- pushTrace (FrameTrace newContext)- next- vm1 <- get- pushTo #frames $ Frame- { context = newContext- , state = vm1.state { stack = xs }- }-- assign #state $- blankState- & set #contract newAddr- & set #codeContract newAddr- & set #code c- & set #callvalue (Lit xValue)- & set #caller (litAddr self)- & set #gas xGas---- | Replace a contract's code, like when CREATE returns--- from the constructor code.-replaceCode :: Addr -> ContractCode -> EVM ()-replaceCode target newCode =- zoom (#env % #contracts % at target) $- get >>= \case- Just now -> case now.contractcode of- InitCode _ _ ->- put . Just $- (initialContract newCode)- { balance = now.balance- , nonce = now.nonce- }- RuntimeCode _ ->- internalError $ "can't replace code of deployed contract " <> show target- Nothing ->- internalError "can't replace code of nonexistent contract"--replaceCodeOfSelf :: ContractCode -> EVM ()-replaceCodeOfSelf newCode = do- vm <- get- replaceCode vm.state.contract newCode--resetState :: EVM ()-resetState =- modify' $ \vm -> vm { result = Nothing- , frames = []- , state = blankState }---- * VM error implementation--vmError :: EvmError -> EVM ()-vmError e = finishFrame (FrameErrored e)--partial :: PartialExec -> EVM ()-partial e = assign #result (Just (Unfinished e))--wrap :: Typeable a => [Expr a] -> [SomeExpr]-wrap = fmap SomeExpr--underrun :: EVM ()-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 ()-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 (3 ==) touched)) substate'- (contractsReversion, storageReversion) = reversion- revertContracts = assign (#env % #contracts) contractsReversion- revertStorage = assign (#env % #storage) storageReversion- revertSubstate = assign (#tx % #substate) substate''-- case how of- -- Case 1: Returning from a call?- FrameReturned output -> do- assign (#state % #returndata) output- copyCallBytesToMemory output outSize (Lit 0) outOffset- reclaimRemainingGasAllowance- push 1-- -- Case 2: Reverting during a call?- FrameReverted output -> do- revertContracts- revertStorage- revertSubstate- assign (#state % #returndata) output- copyCallBytesToMemory output outSize (Lit 0) outOffset- reclaimRemainingGasAllowance- push 0-- -- Case 3: Error during a call?- FrameErrored _ -> do- revertContracts- revertStorage- 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 (+ 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- push (into 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 ()- -> EVM ()-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 ()- -> EVM ()-accessMemoryRange _ 0 continue = continue-accessMemoryRange f l continue =- case (,) <$> toWord64 f <*> toWord64 l of- Nothing -> vmError IllegalOverflow- Just (f64, l64) ->- if f64 + l64 < l64- then vmError IllegalOverflow- else accessUnboundedMemoryRange f64 l64 continue--accessMemoryWord- :: W256 -> EVM () -> EVM ()-accessMemoryWord x = accessMemoryRange x 32--copyBytesToMemory- :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM ()-copyBytesToMemory bs size xOffset yOffset =- if size == Lit 0 then noop- else do- mem <- use (#state % #memory)- assign (#state % #memory) $- copySlice xOffset yOffset size bs mem--copyCallBytesToMemory- :: Expr Buf -> Expr EWord -> Expr EWord -> Expr EWord -> EVM ()-copyCallBytesToMemory bs size xOffset yOffset =- if size == Lit 0 then noop- else do- mem <- use (#state % #memory)- assign (#state % #memory) $- copySlice xOffset yOffset (Expr.min size (bufLength bs)) bs mem--readMemory :: Expr EWord -> Expr EWord -> VM -> Expr Buf-readMemory offset size vm = copySlice offset (Lit 0) size vm.state.memory mempty---- * Tracing--withTraceLocation :: TraceData -> EVM 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 ()-pushTrace x = do- trace <- withTraceLocation x- modifying #traces $- \t -> Zipper.children $ Zipper.insert (Node trace []) t--insertTrace :: TraceData -> EVM ()-insertTrace x = do- trace <- withTraceLocation x- modifying #traces $- \t -> Zipper.nextSpace $ Zipper.insert (Node trace []) t--popTrace :: EVM ()-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 -> 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 Addr 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 ()-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 ()-push = pushSym . Lit--pushSym :: Expr EWord -> EVM ()-pushSym x = #state % #stack %= (x :)--stackOp1- :: (?op :: Word8)- => Word64- -> ((Expr EWord) -> (Expr EWord))- -> EVM ()-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 ()-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 ()-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 -> a) -> EVM a-use' f = do- vm <- get- pure (f vm)--checkJump :: Int -> [Expr EWord] -> EVM ()-checkJump x xs = do- vm <- get- case isValidJumpDest vm x of- True -> do- #state % #stack .= xs- #state % #pc .= x- False -> vmError BadJumpDestination--isValidJumpDest :: VM -> 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- 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 (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 -> Maybe Op-vmOp vm =- let i = vm ^. #state % #pc- code' = vm ^. #state % #code- (op, pushdata) = case code' of- 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 -> 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- 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 -> W256 -> Word64 -> Word64 -> Addr- -> EVM (Word64, Word64)-costOfCall (FeeSchedule {..}) recipientExists 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)---- 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 (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 (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 c@(InitCode {}) = bufLength $ toBuf c-codelen (RuntimeCode (ConcreteRuntimeCode ops)) = Lit . unsafeInto $ BS.length ops-codelen (RuntimeCode (SymbolicRuntimeCode ops)) = Lit . unsafeInto $ length ops--toBuf :: ContractCode -> Expr Buf-toBuf (InitCode ops args) = ConcreteBuf ops <> args-toBuf (RuntimeCode (ConcreteRuntimeCode ops)) = ConcreteBuf ops-toBuf (RuntimeCode (SymbolicRuntimeCode ops)) = Expr.fromList ops--codeloc :: EVM CodeLocation-codeloc = do- vm <- get- pure (vm.state.contract, vm.state.pc)---- * 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+import EVM.Expr (readStorage, writeStorage, readByte, readWord, writeWord,+ writeByte, bufLength, indexWord, litAddr, readBytes, word256At, copySlice, wordToAddr)+import EVM.Expr qualified as Expr+import EVM.FeeSchedule (FeeSchedule (..))+import EVM.Op+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
src/EVM/ABI.hs view
@@ -37,6 +37,7 @@ , SolError (..) , Anonymity (..) , Indexed (..)+ , Sig(..) , putAbi , getAbi , getAbiSeq@@ -82,13 +83,17 @@ import Data.Vector qualified as Vector import Data.Word (Word32) import GHC.Generics (Generic)- import Test.QuickCheck hiding ((.&.), label)+ import Text.Megaparsec qualified as P import Text.Megaparsec.Char qualified as P import Text.ParserCombinators.ReadP import Witch (unsafeInto, into) +-- | A method name, and the (ordered) types of it's arguments+data Sig = Sig Text [AbiType]+ deriving (Show, Eq)+ data AbiValue = AbiUInt Int Word256 | AbiInt Int Int256@@ -410,80 +415,6 @@ <* skip ((roundTo32Bytes n) - n) where n = fromIntegral i --- QuickCheck instances--genAbiValue :: AbiType -> Gen AbiValue-genAbiValue = \case- AbiUIntType n -> AbiUInt n <$> genUInt n- AbiIntType n -> do- x <- genUInt n- pure $ AbiInt n (signedWord (x - 2^(n-1)))- AbiAddressType ->- AbiAddress . fromIntegral <$> genUInt 20- AbiBoolType ->- elements [AbiBool False, AbiBool True]- AbiBytesType n ->- do xs <- replicateM n arbitrary- pure (AbiBytes n (BS.pack xs))- AbiBytesDynamicType ->- AbiBytesDynamic . BS.pack <$> listOf arbitrary- AbiStringType ->- AbiString . BS.pack <$> listOf arbitrary- AbiArrayDynamicType t ->- do xs <- listOf1 (scale (`div` 2) (genAbiValue t))- pure (AbiArrayDynamic t (Vector.fromList xs))- AbiArrayType n t ->- AbiArray n t . Vector.fromList <$>- replicateM n (scale (`div` 2) (genAbiValue t))- AbiTupleType ts ->- AbiTuple <$> mapM genAbiValue ts- AbiFunctionType ->- do xs <- replicateM 24 arbitrary- pure (AbiFunction (BS.pack xs))- where- genUInt :: Int -> Gen Word256- genUInt n = arbitraryIntegralWithMax (2^n-1) :: Gen Word256--instance Arbitrary AbiType where- arbitrary = oneof- [ (AbiUIntType . (* 8)) <$> choose (1, 32)- , (AbiIntType . (* 8)) <$> choose (1, 32)- , pure AbiAddressType- , pure AbiBoolType- , AbiBytesType <$> choose (1,32)- , pure AbiBytesDynamicType- , pure AbiStringType- , AbiArrayDynamicType <$> scale (`div` 2) arbitrary- , AbiArrayType- <$> (getPositive <$> arbitrary)- <*> scale (`div` 2) arbitrary- ]--instance Arbitrary AbiValue where- arbitrary = arbitrary >>= genAbiValue- shrink = \case- AbiArrayDynamic t v ->- Vector.toList v ++- map (AbiArrayDynamic t . Vector.fromList)- (shrinkList shrink (Vector.toList v))- AbiBytesDynamic b -> AbiBytesDynamic . BS.pack <$> shrinkList shrinkIntegral (BS.unpack b)- AbiString b -> AbiString . BS.pack <$> shrinkList shrinkIntegral (BS.unpack b)- AbiBytes n a | n <= 32 -> shrink $ AbiUInt (n * 8) (word256 a)- --bytesN for N > 32 don't really exist right now anyway..- AbiBytes _ _ | otherwise -> []- AbiArray _ t v ->- Vector.toList v ++- map (\x -> AbiArray (length x) t (Vector.fromList x))- (shrinkList shrink (Vector.toList v))- AbiTuple v -> Vector.toList $ AbiTuple . Vector.fromList . shrink <$> v- AbiUInt n a -> AbiUInt n <$> (shrinkIntegral a)- AbiInt n a -> AbiInt n <$> (shrinkIntegral a)- AbiBool b -> AbiBool <$> shrink b- AbiAddress a -> [AbiAddress 0xacab, AbiAddress 0xdeadbeef, AbiAddress 0xbabeface]- <> (AbiAddress <$> shrinkIntegral a)- AbiFunction b -> shrink $ AbiBytes 24 b-- -- Bool synonym with custom read instance -- to be able to parse lower case 'false' and 'true' newtype Boolz = Boolz Bool@@ -557,7 +488,7 @@ then NoVals else let vs = decodeStaticArgs 0 (length tps) buf- allLit = Prelude.and . (fmap isLitWord) $ vs+ allLit = Prelude.and (fmap isLitWord vs) asBS = mconcat $ fmap word256Bytes (mapMaybe maybeLitWord vs) in if not allLit then SAbi vs@@ -573,6 +504,78 @@ decodeStaticArgs offset numArgs b = [readWord (Lit . unsafeInto $ i) b | i <- [offset,(offset+32) .. (offset + (numArgs-1)*32)]] +-- QuickCheck instances++genAbiValue :: AbiType -> Gen AbiValue+genAbiValue = \case+ AbiUIntType n -> AbiUInt n <$> genUInt n+ AbiIntType n -> do+ x <- genUInt n+ pure $ AbiInt n (signedWord (x - 2^(n-1)))+ AbiAddressType ->+ AbiAddress . fromIntegral <$> genUInt 20+ AbiBoolType ->+ elements [AbiBool False, AbiBool True]+ AbiBytesType n ->+ do xs <- replicateM n arbitrary+ pure (AbiBytes n (BS.pack xs))+ AbiBytesDynamicType ->+ AbiBytesDynamic . BS.pack <$> listOf arbitrary+ AbiStringType ->+ AbiString . BS.pack <$> listOf arbitrary+ AbiArrayDynamicType t ->+ do xs <- listOf1 (scale (`div` 2) (genAbiValue t))+ pure (AbiArrayDynamic t (Vector.fromList xs))+ AbiArrayType n t ->+ AbiArray n t . Vector.fromList <$>+ replicateM n (scale (`div` 2) (genAbiValue t))+ AbiTupleType ts ->+ AbiTuple <$> mapM genAbiValue ts+ AbiFunctionType ->+ do xs <- replicateM 24 arbitrary+ pure (AbiFunction (BS.pack xs))+ where+ genUInt :: Int -> Gen Word256+ genUInt n = arbitraryIntegralWithMax (2^n-1) :: Gen Word256++instance Arbitrary AbiType where+ arbitrary = oneof+ [ (AbiUIntType . (* 8)) <$> choose (1, 32)+ , (AbiIntType . (* 8)) <$> choose (1, 32)+ , pure AbiAddressType+ , pure AbiBoolType+ , AbiBytesType <$> choose (1,32)+ , pure AbiBytesDynamicType+ , pure AbiStringType+ , AbiArrayDynamicType <$> scale (`div` 2) arbitrary+ , AbiArrayType+ <$> (getPositive <$> arbitrary)+ <*> scale (`div` 2) arbitrary+ ]++instance Arbitrary AbiValue where+ arbitrary = arbitrary >>= genAbiValue+ shrink = \case+ AbiArrayDynamic t v ->+ Vector.toList v +++ map (AbiArrayDynamic t . Vector.fromList)+ (shrinkList shrink (Vector.toList v))+ AbiBytesDynamic b -> AbiBytesDynamic . BS.pack <$> shrinkList shrinkIntegral (BS.unpack b)+ AbiString b -> AbiString . BS.pack <$> shrinkList shrinkIntegral (BS.unpack b)+ AbiBytes n a | n <= 32 -> shrink $ AbiUInt (n * 8) (word256 a)+ --bytesN for N > 32 don't really exist right now anyway..+ AbiBytes _ _ | otherwise -> []+ AbiArray _ t v ->+ Vector.toList v +++ map (\x -> AbiArray (length x) t (Vector.fromList x))+ (shrinkList shrink (Vector.toList v))+ AbiTuple v -> Vector.toList $ AbiTuple . Vector.fromList . shrink <$> v+ AbiUInt n a -> AbiUInt n <$> (shrinkIntegral a)+ AbiInt n a -> AbiInt n <$> (shrinkIntegral a)+ AbiBool b -> AbiBool <$> shrink b+ AbiAddress a -> [AbiAddress 0xacab, AbiAddress 0xdeadbeef, AbiAddress 0xbabeface]+ <> (AbiAddress <$> shrinkIntegral a)+ AbiFunction b -> shrink $ AbiBytes 24 b -- A modification of 'arbitrarySizedBoundedIntegral' quickcheck library -- which takes the maxbound explicitly rather than relying on a Bounded instance.
src/EVM/Concrete.hs view
@@ -1,34 +1,23 @@+{-# Language DataKinds #-}+ module EVM.Concrete where +import Prelude hiding (Word) import EVM.RLP import EVM.Types -import Data.Bits (Bits(..), shiftR)-import Data.ByteString (ByteString, (!?))+import Data.ByteString (ByteString) import Data.ByteString qualified as BS-import Data.Maybe (fromMaybe)-import Data.Word (Word8)-import Witch (unsafeInto)--wordAt :: Int -> ByteString -> W256-wordAt i bs =- word (padRight 32 (BS.drop i bs))--readByteOrZero :: Int -> ByteString -> Word8-readByteOrZero i bs = fromMaybe 0 (bs !? i)+import Witch (unsafeInto, into) byteStringSliceWithDefaultZeroes :: Int -> Int -> ByteString -> ByteString byteStringSliceWithDefaultZeroes offset size bs = if size == 0 then ""- -- else if offset > BS.length bs- -- then BS.replicate size 0- -- todo: this ^^ should work, investigate why it causes more GST fails else let bs' = BS.take size (BS.drop offset bs) in bs' <> BS.replicate (size - BS.length bs') 0 - sliceMemory :: W256 -> W256 -> ByteString -> ByteString sliceMemory o s = byteStringSliceWithDefaultZeroes (unsafeInto o) (unsafeInto s)@@ -49,24 +38,9 @@ in a <> a' <> c <> b' --- Copied from the standard library just to get specialization.--- We also use bit operations instead of modulo and multiply.--- (This operation was significantly slow.)-(^) :: W256 -> W256 -> W256-x0 ^ y0 | y0 < 0 = errorWithoutStackTrace "Negative exponent"- | y0 == 0 = 1- | otherwise = f x0 y0- where- f x y | not (testBit y 0) = f (x * x) (y `shiftR` 1)- | y == 1 = x- | otherwise = g (x * x) ((y - 1) `shiftR` 1) x- g x y z | not (testBit y 0) = g (x * x) (y `shiftR` 1) z- | y == 1 = x * z- | otherwise = g (x * x) ((y - 1) `shiftR` 1) (x * z)--createAddress :: Addr -> W256 -> Addr-createAddress a n = unsafeInto $ keccak' $ rlpList [rlpAddrFull a, rlpWord256 n]+createAddress :: Addr -> W64 -> Expr EAddr+createAddress a n = LitAddr . unsafeInto . keccak' . rlpList $ [rlpAddrFull a, rlpWord256 (into n)] -create2Address :: Addr -> W256 -> ByteString -> Addr-create2Address a s b = unsafeInto $ keccak' $ mconcat+create2Address :: Addr -> W256 -> ByteString -> Expr EAddr+create2Address a s b = LitAddr $ unsafeInto $ keccak' $ mconcat [BS.singleton 0xff, word160Bytes a, word256Bytes s, word256Bytes $ keccak' b]
src/EVM/Dapp.hs view
@@ -1,24 +1,25 @@+{-# Language DataKinds #-}+ module EVM.Dapp where import EVM.ABI import EVM.Concrete-import EVM.Debug (srcMapCodePos) import EVM.Solidity import EVM.Types -import Control.Arrow ((>>>))+import Control.Arrow ((>>>), second) import Data.Aeson (Value)-import Data.Bifunctor (first) import Data.ByteString (ByteString) import Data.ByteString qualified as BS import Data.List (find, sort) import Data.Map (Map) import Data.Map qualified as Map-import Data.Maybe (isJust, fromJust, mapMaybe)+import Data.Maybe (mapMaybe) import Data.Sequence qualified as Seq-import Data.Text (Text, isPrefixOf, pack, unpack)+import Data.Text (Text, isPrefixOf, pack) import Data.Text.Encoding (encodeUtf8) import Data.Vector qualified as V+import Optics.Core import Witch (unsafeInto) data DappInfo = DappInfo@@ -27,7 +28,7 @@ , solcByHash :: Map W256 (CodeType, SolcContract) , solcByCode :: [(Code, SolcContract)] -- for contracts with `immutable` vars. , sources :: SourceCache- , unitTests :: [(Text, [(Test, [AbiType])])]+ , unitTests :: [(Text, [Sig])] , abiMap :: Map FunctionSelector Method , eventMap :: Map W256 Event , errorMap :: Map W256 SolError@@ -44,14 +45,9 @@ data DappContext = DappContext { info :: DappInfo- , env :: Map Addr Contract+ , env :: Map (Expr EAddr) Contract } -data Test = ConcreteTest Text | SymbolicTest Text | InvariantTest Text--instance Show Test where- show t = unpack $ extractSig t- dappInfo :: FilePath -> BuildOutput -> DappInfo dappInfo root (BuildOutput (Contracts cs) sources) = let@@ -87,29 +83,28 @@ emptyDapp = dappInfo "" mempty -- Dapp unit tests are detected by searching within abi methods--- that begin with "test" or "prove", that are in a contract with+-- that begin with "check" or "prove", that are in a contract with -- the "IS_TEST()" abi marker, for a given regular expression. -- -- The regex is matched on the full test method name, including path -- and contract, i.e. "path/to/file.sol:TestContract.test_name()".------ Tests beginning with "test" are interpreted as concrete tests, whereas--- tests beginning with "prove" are interpreted as symbolic tests. unitTestMarkerAbi :: FunctionSelector unitTestMarkerAbi = abiKeccak (encodeUtf8 "IS_TEST()") -findAllUnitTests :: [SolcContract] -> [(Text, [(Test, [AbiType])])]-findAllUnitTests = findUnitTests ".*:.*\\.(test|prove|invariant).*"+findAllUnitTests :: [SolcContract] -> [(Text, [Sig])]+findAllUnitTests = findUnitTests ".*:.*\\.(check|prove).*" -mkTest :: Text -> Maybe Test-mkTest sig- | "test" `isPrefixOf` sig = Just (ConcreteTest sig)- | "prove" `isPrefixOf` sig = Just (SymbolicTest sig)- | "invariant" `isPrefixOf` sig = Just (InvariantTest sig)+mkSig :: Method -> Maybe Sig+mkSig method+ | "prove" `isPrefixOf` testname = Just (Sig testname argtypes)+ | "check" `isPrefixOf` testname = Just (Sig testname argtypes) | otherwise = Nothing+ where+ testname = method.methodSignature+ argtypes = snd <$> method.inputs -findUnitTests :: Text -> ([SolcContract] -> [(Text, [(Test, [AbiType])])])+findUnitTests :: Text -> ([SolcContract] -> [(Text, [Sig])]) findUnitTests match = concatMap $ \c -> case Map.lookup unitTestMarkerAbi c.abiMap of@@ -118,25 +113,16 @@ let testNames = unitTestMethodsFiltered (regexMatches match) c in [(c.contractName, testNames) | not (BS.null c.runtimeCode) && not (null testNames)] -unitTestMethodsFiltered :: (Text -> Bool) -> (SolcContract -> [(Test, [AbiType])])+unitTestMethodsFiltered :: (Text -> Bool) -> (SolcContract -> [Sig]) unitTestMethodsFiltered matcher c =- let- testName method = c.contractName <> "." <> (extractSig (fst method))- in- filter (matcher . testName) (unitTestMethods c)+ let testName (Sig n _) = c.contractName <> "." <> n+ in filter (matcher . testName) (unitTestMethods c) -unitTestMethods :: SolcContract -> [(Test, [AbiType])]+unitTestMethods :: SolcContract -> [Sig] unitTestMethods = (.abiMap) >>> Map.elems- >>> map (\f -> (mkTest f.methodSignature, snd <$> f.inputs))- >>> filter (isJust . fst)- >>> fmap (first fromJust)--extractSig :: Test -> Text-extractSig (ConcreteTest sig) = sig-extractSig (SymbolicTest sig) = sig-extractSig (InvariantTest sig) = sig+ >>> mapMaybe mkSig traceSrcMap :: DappInfo -> Trace -> Maybe SrcMap traceSrcMap dapp trace = srcMap dapp trace.contract trace.opIx@@ -144,10 +130,11 @@ srcMap :: DappInfo -> Contract -> Int -> Maybe SrcMap srcMap dapp contr opIndex = do sol <- findSrc contr dapp- case contr.contractcode of- (InitCode _ _) ->- Seq.lookup opIndex sol.creationSrcmap- (RuntimeCode _) ->+ case contr.code of+ UnknownCode _ -> Nothing+ InitCode _ _ ->+ Seq.lookup opIndex sol.creationSrcmap+ RuntimeCode _ -> Seq.lookup opIndex sol.runtimeSrcmap findSrc :: Contract -> DappInfo -> Maybe SolcContract@@ -155,10 +142,11 @@ hash <- maybeLitWord c.codehash case Map.lookup hash dapp.solcByHash of Just (_, v) -> Just v- Nothing -> lookupCode c.contractcode dapp+ Nothing -> lookupCode c.code dapp lookupCode :: ContractCode -> DappInfo -> Maybe SolcContract+lookupCode (UnknownCode _) _ = Nothing lookupCode (InitCode c _) a = snd <$> Map.lookup (keccak' (stripBytecodeMetadata c)) a.solcByHash lookupCode (RuntimeCode (ConcreteRuntimeCode c)) a =@@ -174,7 +162,7 @@ compareCode :: ByteString -> Code -> Bool compareCode raw (Code template locs) = let holes' = sort [(start, len) | (Reference start len) <- locs]- insert at' len' bs = writeMemory (BS.replicate len' 0) (unsafeInto len') 0 (unsafeInto at') bs+ insert loc len' bs = writeMemory (BS.replicate len' 0) (unsafeInto len') 0 (unsafeInto loc) bs refined = foldr (\(start, len) acc -> insert start len acc) raw holes' in BS.length raw == BS.length template && template == refined @@ -187,3 +175,15 @@ Nothing -> Left "<source not found>" Just (fileName, lineIx) -> Right (pack fileName <> ":" <> pack (show lineIx))++srcMapCodePos :: SourceCache -> SrcMap -> Maybe (FilePath, Int)+srcMapCodePos cache sm =+ fmap (second f) $ cache.files ^? ix sm.file+ where+ f v = BS.count 0xa (BS.take sm.offset v) + 1++srcMapCode :: SourceCache -> SrcMap -> Maybe ByteString+srcMapCode cache sm =+ fmap f $ cache.files ^? ix sm.file+ where+ f (_, v) = BS.take (min 80 sm.length) (BS.drop sm.offset v)
− src/EVM/Debug.hs
@@ -1,52 +0,0 @@-module EVM.Debug where--import EVM (bytecode)-import EVM.Expr (bufLength)-import EVM.Solidity (SrcMap(..), SourceCache(..))-import EVM.Types (Contract(..), Addr)--import Control.Arrow (second)-import Data.ByteString (ByteString)-import Data.ByteString qualified as ByteString-import Data.Map (Map)-import Data.Map qualified as Map-import Optics.Core-import Text.PrettyPrint.ANSI.Leijen-import Witch (unsafeInto)--data Mode = Debug | Run | JsonTrace deriving (Eq, Show)--object :: [(Doc, Doc)] -> Doc-object xs =- group $ lbrace- <> line- <> indent 2 (sep (punctuate (char ';') [k <+> equals <+> v | (k, v) <- xs]))- <> line- <> rbrace--prettyContract :: Contract -> Doc-prettyContract c =- object- [ (text "codesize", text . show $ (bufLength (c ^. bytecode)))- , (text "codehash", text (show c.codehash))- , (text "balance", int (unsafeInto c.balance))- , (text "nonce", int (unsafeInto c.nonce))- ]--prettyContracts :: Map Addr Contract -> Doc-prettyContracts x =- object- (map (\(a, b) -> (text (show a), prettyContract b))- (Map.toList x))--srcMapCodePos :: SourceCache -> SrcMap -> Maybe (FilePath, Int)-srcMapCodePos cache sm =- fmap (second f) $ cache.files ^? ix sm.file- where- f v = ByteString.count 0xa (ByteString.take sm.offset v) + 1--srcMapCode :: SourceCache -> SrcMap -> Maybe ByteString-srcMapCode cache sm =- fmap f $ cache.files ^? ix sm.file- where- f (_, v) = ByteString.take (min 80 sm.length) (ByteString.drop sm.offset v)
− src/EVM/Dev.hs
@@ -1,498 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE QuasiQuotes #-}--{-|-Module: EVM.Dev-Description: Helpers for repl driven hevm hacking--}-module EVM.Dev where--import Control.Monad.State.Strict hiding (state)-import Data.ByteString hiding (writeFile, zip)-import Data.Maybe (fromJust)-import Data.String.Here-import Data.Text.IO qualified as T-import Data.Text.Lazy.IO qualified as TL-import Data.Typeable (Typeable)-import System.Directory (withCurrentDirectory)-import System.Exit (exitFailure)--import EVM-import EVM.Dapp (dappInfo)-import EVM.Expr (numBranches, simplify)-import EVM.Fetch qualified as Fetch-import EVM.FeeSchedule qualified as FeeSchedule-import EVM.Format (formatExpr)-import EVM.SMT-import EVM.Solvers-import EVM.Solidity-import EVM.SymExec-import EVM.Types-import EVM.UnitTest-import GHC.Conc-import Witch (unsafeInto)--checkEquiv :: (Typeable a) => Expr a -> Expr a -> IO ()-checkEquiv a b = withSolvers Z3 1 Nothing $ \s -> do- let smt = assertProps [a ./= b]- res <- checkSat s smt- print res--runDappTest :: FilePath -> IO ()-runDappTest root =- withCurrentDirectory root $ do- cores <- unsafeInto <$> getNumProcessors- let testFile = root <> "/out/dapp.sol.json"- Right (BuildOutput contracts _) <- readSolc DappTools root testFile- withSolvers Z3 cores Nothing $ \solvers -> do- opts <- testOpts solvers root testFile- res <- unitTest opts contracts Nothing- unless res exitFailure--testOpts :: SolverGroup -> FilePath -> FilePath -> IO UnitTestOptions-testOpts solvers root testFile = do- srcInfo <- readSolc DappTools root testFile >>= \case- Left e -> internalError e- Right out ->- pure $ dappInfo root out-- params <- getParametersFromEnvironmentVariables Nothing- pure EVM.UnitTest.UnitTestOptions- { EVM.UnitTest.solvers = solvers- , EVM.UnitTest.rpcInfo = Nothing- , EVM.UnitTest.maxIter = Nothing- , EVM.UnitTest.askSmtIters = 1- , EVM.UnitTest.smtTimeout = Nothing- , EVM.UnitTest.smtDebug = False- , EVM.UnitTest.solver = Nothing- , EVM.UnitTest.covMatch = Nothing- , EVM.UnitTest.verbose = Nothing- , EVM.UnitTest.match = ".*"- , EVM.UnitTest.maxDepth = Nothing- , EVM.UnitTest.fuzzRuns = 100- , EVM.UnitTest.replay = Nothing- , EVM.UnitTest.vmModifier = id- , EVM.UnitTest.testParams = params- , EVM.UnitTest.dapp = srcInfo- , EVM.UnitTest.ffiAllowed = True- }--doTest :: IO ()-doTest = do- c <- testContract- reachable' False c- --e <- simplify <$> buildExpr c- --Prelude.putStrLn (formatExpr e)--analyzeDai :: IO ()-analyzeDai = do- d <- dai- reachable' False d--daiExpr :: IO (Expr End)-daiExpr = do- d <- dai- withSolvers Z3 1 Nothing $ \s -> buildExpr s d--analyzeVat :: IO ()-analyzeVat = do- putStrLn "starting"- v <- vat- withSolvers Z3 1 Nothing $ \s -> do- e <- buildExpr s v- putStrLn $ "done (" <> show (numBranches e) <> " branches)"- reachable' False v--analyzeDeposit :: IO ()-analyzeDeposit = 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);- }- }- |]- withSolvers Z3 1 Nothing $ \s -> do- putStrLn "Exploring Contract"- e <- simplify <$> buildExpr s c- putStrLn "Writing AST"- T.writeFile "full.ast" (formatExpr e)---reachable' :: Bool -> ByteString -> IO ()-reachable' smtdebug c = do- putStrLn "Exploring contract"- withSolvers Z3 4 Nothing $ \s -> do- full <- simplify <$> buildExpr s c- putStrLn $ "Explored contract (" <> (show $ numBranches full) <> " branches)"- --putStrLn $ formatExpr full- T.writeFile "full.ast" $ formatExpr full- putStrLn "Dumped to full.ast"- putStrLn "Checking reachability"- (qs, less) <- reachable s full- putStrLn $ "Checked reachability (" <> (show $ numBranches less) <> " reachable branches)"- T.writeFile "reachable.ast" $ formatExpr less- putStrLn "Dumped to reachable.ast"- --putStrLn $ formatExpr less- when smtdebug $ do- putStrLn "\n\nQueries\n\n"- forM_ qs $ \q -> do- putStrLn "\n\n-- Query --"- TL.putStrLn $ formatSMT2 q---showExpr :: ByteString -> IO ()-showExpr c = do- withSolvers Z3 1 Nothing $ \s -> do- e <- buildExpr s c- T.putStrLn $ formatExpr (simplify e)--summaryStore :: IO ByteString-summaryStore = do- let src =- [i|- contract A {- uint x;- function f(uint256 y) public {- unchecked {- x += y;- x += y;- }- }- }- |]- fmap fromJust (solcRuntime "A" src)--safeAdd :: IO ByteString-safeAdd = do- let src =- [i|- contract SafeAdd {- function add(uint x, uint y) public pure returns (uint z) {- require((z = x + y) >= x);- }- }- |]- fmap fromJust (solcRuntime "SafeAdd" src)----testContract :: IO ByteString-testContract = do- let src =- [i|- contract C {- uint x;- function set(uint v) public {- x = v + v;- }- }- |]- fmap fromJust (solcRuntime "C" src)--vat :: IO ByteString-vat = do- let src =- [i|- /// vat.sol -- Dai CDP database-- // Copyright (C) 2018 Rain <rainbreak@riseup.net>- //- // This program is free software: you can redistribute it and/or modify- // it under the terms of the GNU Affero General Public License as published by- // the Free Software Foundation, either version 3 of the License, or- // (at your option) any later version.- //- // This program is distributed in the hope that it will be useful,- // but WITHOUT ANY WARRANTY; without even the implied warranty of- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- // GNU Affero General Public License for more details.- //- // You should have received a copy of the GNU Affero General Public License- // along with this program. If not, see <https://www.gnu.org/licenses/>.-- // FIXME: This contract was altered compared to the production version.- // It doesn't use LibNote anymore.- // New deployments of this contract will need to include custom events (TO DO).-- contract Vat {- // --- Auth ---- mapping (address => uint) public wards;- function rely(address usr) external auth { require(live == 1, "Vat/not-live"); wards[usr] = 1; }- function deny(address usr) external auth { require(live == 1, "Vat/not-live"); wards[usr] = 0; }- modifier auth {- require(wards[msg.sender] == 1, "Vat/not-authorized");- _;- }-- mapping(address => mapping (address => uint)) public can;- function hope(address usr) external { can[msg.sender][usr] = 1; }- function nope(address usr) external { can[msg.sender][usr] = 0; }- function wish(address bit, address usr) internal view returns (bool) {- return either(bit == usr, can[bit][usr] == 1);- }-- // --- Data ---- struct Ilk {- uint256 Art; // Total Normalised Debt [wad]- uint256 rate; // Accumulated Rates [ray]- uint256 spot; // Price with Safety Margin [ray]- uint256 line; // Debt Ceiling [rad]- uint256 dust; // Urn Debt Floor [rad]- }- struct Urn {- uint256 ink; // Locked Collateral [wad]- uint256 art; // Normalised Debt [wad]- }-- mapping (bytes32 => Ilk) public ilks;- mapping (bytes32 => mapping (address => Urn )) public urns;- mapping (bytes32 => mapping (address => uint)) public gem; // [wad]- mapping (address => uint256) public dai; // [rad]- mapping (address => uint256) public sin; // [rad]-- uint256 public debt; // Total Dai Issued [rad]- uint256 public vice; // Total Unbacked Dai [rad]- uint256 public Line; // Total Debt Ceiling [rad]- uint256 public live; // Active Flag-- // --- Init ---- constructor() public {- wards[msg.sender] = 1;- live = 1;- }-- // --- Math ---- function _add(uint x, int y) internal pure returns (uint z) {- z = x + uint(y);- require(y >= 0 || z <= x);- require(y <= 0 || z >= x);- }- function _sub(uint x, int y) internal pure returns (uint z) {- z = x - uint(y);- require(y <= 0 || z <= x);- require(y >= 0 || z >= x);- }- function _mul(uint x, int y) internal pure returns (int z) {- z = int(x) * y;- require(int(x) >= 0);- require(y == 0 || z / y == int(x));- }- function _add(uint x, uint y) internal pure returns (uint z) {- require((z = x + y) >= x);- }- function _sub(uint x, uint y) internal pure returns (uint z) {- require((z = x - y) <= x);- }- function _mul(uint x, uint y) internal pure returns (uint z) {- require(y == 0 || (z = x * y) / y == x);- }-- // --- Administration ---- function init(bytes32 ilk) external auth {- require(ilks[ilk].rate == 0, "Vat/ilk-already-init");- ilks[ilk].rate = 10 ** 27;- }- function file(bytes32 what, uint data) external auth {- require(live == 1, "Vat/not-live");- if (what == "Line") Line = data;- else revert("Vat/file-unrecognized-param");- }- function file(bytes32 ilk, bytes32 what, uint data) external auth {- require(live == 1, "Vat/not-live");- if (what == "spot") ilks[ilk].spot = data;- else if (what == "line") ilks[ilk].line = data;- else if (what == "dust") ilks[ilk].dust = data;- else revert("Vat/file-unrecognized-param");- }- function cage() external auth {- live = 0;- }-- // --- Fungibility ---- function slip(bytes32 ilk, address usr, int256 wad) external auth {- gem[ilk][usr] = _add(gem[ilk][usr], wad);- }- function flux(bytes32 ilk, address src, address dst, uint256 wad) external {- require(wish(src, msg.sender), "Vat/not-allowed");- gem[ilk][src] = _sub(gem[ilk][src], wad);- gem[ilk][dst] = _add(gem[ilk][dst], wad);- }- function move(address src, address dst, uint256 rad) external {- require(wish(src, msg.sender), "Vat/not-allowed");- dai[src] = _sub(dai[src], rad);- dai[dst] = _add(dai[dst], rad);- }-- function either(bool x, bool y) internal pure returns (bool z) {- assembly{ z := or(x, y)}- }- function both(bool x, bool y) internal pure returns (bool z) {- assembly{ z := and(x, y)}- }-- // --- CDP Confiscation ---- function grab(bytes32 i, address u, address v, address w, int dink, int dart) external auth {- Urn storage urn = urns[i][u];- Ilk storage ilk = ilks[i];-- urn.ink = _add(urn.ink, dink);- urn.art = _add(urn.art, dart);- ilk.Art = _add(ilk.Art, dart);-- int dtab = _mul(ilk.rate, dart);-- gem[i][v] = _sub(gem[i][v], dink);- sin[w] = _sub(sin[w], dtab);- vice = _sub(vice, dtab);- }- }- |]- fmap fromJust (solcRuntime "Vat" src)--initVm :: ByteString -> VM-initVm bs = vm- where- contractCode = RuntimeCode (ConcreteRuntimeCode bs)- c = Contract- { contractcode = contractCode- , balance = 0- , nonce = 0- , codehash = keccak (ConcreteBuf bs)- , opIxMap = mkOpIxMap contractCode- , codeOps = mkCodeOps contractCode- , external = False- }- vm = makeVm $ VMOpts- { contract = c- , calldata = (AbstractBuf "txdata", [])- , value = CallValue 0- , address = Addr 0xffffffffffffffff- , caller = Lit 0- , origin = Addr 0xffffffffffffffff- , gas = 0xffffffffffffffff- , gaslimit = 0xffffffffffffffff- , initialStorage = AbstractStore- , baseFee = 0- , priorityFee = 0- , coinbase = 0- , number = 0- , timestamp = Var "timestamp"- , blockGaslimit = 0- , gasprice = 0- , maxCodeSize = 0xffffffff- , prevRandao = 420- , schedule = FeeSchedule.berlin- , chainId = 1- , create = False- , txAccessList = mempty- , allowFFI = False- }----- | Builds the Expr for the given evm bytecode object-buildExpr :: SolverGroup -> ByteString -> IO (Expr End)-buildExpr solvers bs = interpret (Fetch.oracle solvers Nothing) Nothing 1 Naive (initVm bs) runExpr--dai :: IO ByteString-dai = do- let src =- [i|- contract Dai {- // --- Auth ---- mapping (address => uint) public wards;- function rely(address guy) external auth { wards[guy] = 1; }- function deny(address guy) external auth { wards[guy] = 0; }- modifier auth {- require(wards[msg.sender] == 1, "Dai/not-authorized");- _;- }-- // --- ERC20 Data ---- string public constant name = "Dai Stablecoin";- string public constant symbol = "DAI";- string public constant version = "1";- uint8 public constant decimals = 18;- uint256 public totalSupply;-- mapping (address => uint) public balanceOf;- mapping (address => mapping (address => uint)) public allowance;-- event Approval(address indexed src, address indexed guy, uint wad);- event Transfer(address indexed src, address indexed dst, uint wad);-- // --- Math ---- function add(uint x, uint y) internal pure returns (uint z) {- require((z = x + y) >= x);- }- function sub(uint x, uint y) internal pure returns (uint z) {- require((z = x - y) <= x);- }-- // --- EIP712 niceties ---- constructor() public {- wards[msg.sender] = 1;- }-- // --- Token ---- function transfer(address dst, uint wad) external returns (bool) {- return transferFrom(msg.sender, dst, wad);- }- function transferFrom(address src, address dst, uint wad)- public returns (bool)- {- require(balanceOf[src] >= wad, "Dai/insufficient-balance");- if (src != msg.sender && allowance[src][msg.sender] != type(uint).max) {- require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance");- allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);- }- balanceOf[src] = sub(balanceOf[src], wad);- balanceOf[dst] = add(balanceOf[dst], wad);- emit Transfer(src, dst, wad);- return true;- }- function mint(address usr, uint wad) external auth {- balanceOf[usr] = add(balanceOf[usr], wad);- totalSupply = add(totalSupply, wad);- emit Transfer(address(0), usr, wad);- }- function burn(address usr, uint wad) external {- require(balanceOf[usr] >= wad, "Dai/insufficient-balance");- if (usr != msg.sender && allowance[usr][msg.sender] != type(uint).max) {- require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance");- allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad);- }- balanceOf[usr] = sub(balanceOf[usr], wad);- totalSupply = sub(totalSupply, wad);- emit Transfer(usr, address(0), wad);- }- function approve(address usr, uint wad) external returns (bool) {- allowance[msg.sender][usr] = wad;- emit Approval(msg.sender, usr, wad);- return true;- }-- // --- Alias ---- function push(address usr, uint wad) external {- transferFrom(msg.sender, usr, wad);- }- function pull(address usr, uint wad) external {- transferFrom(usr, msg.sender, wad);- }- function move(address src, address dst, uint wad) external {- transferFrom(src, dst, wad);- }- }- |]- fmap fromJust (solcRuntime "Dai" src)
src/EVM/Exec.hs view
@@ -1,32 +1,33 @@ module EVM.Exec where -import EVM+import EVM hiding (createAddress) import EVM.Concrete (createAddress)-import EVM.FeeSchedule qualified as FeeSchedule-import EVM.Expr (litAddr)+import EVM.FeeSchedule (feeSchedule) import EVM.Types import Control.Monad.Trans.State.Strict (get, State) import Data.ByteString (ByteString) import Data.Maybe (isNothing) import Optics.Core+import Control.Monad.ST (ST) ethrunAddress :: Addr ethrunAddress = Addr 0x00a329c0648769a73afac7f9381e08fb43dbea72 -vmForEthrunCreation :: ByteString -> VM+vmForEthrunCreation :: ByteString -> ST s (VM s) vmForEthrunCreation creationCode = (makeVm $ VMOpts { contract = initialContract (InitCode creationCode mempty)+ , otherContracts = [] , calldata = mempty- , value = (Lit 0)- , initialStorage = EmptyStore+ , value = Lit 0+ , baseState = EmptyBase , address = createAddress ethrunAddress 1- , caller = litAddr ethrunAddress- , origin = ethrunAddress- , coinbase = 0+ , caller = LitAddr ethrunAddress+ , origin = LitAddr ethrunAddress+ , coinbase = LitAddr 0 , number = 0- , timestamp = (Lit 0)+ , timestamp = Lit 0 , blockGaslimit = 0 , gasprice = 0 , prevRandao = 42069@@ -35,29 +36,29 @@ , baseFee = 0 , priorityFee = 0 , maxCodeSize = 0xffffffff- , schedule = FeeSchedule.berlin+ , schedule = feeSchedule , chainId = 1 , create = False , txAccessList = mempty , allowFFI = False- }) & set (#env % #contracts % at ethrunAddress)+ }) <&> set (#env % #contracts % at (LitAddr ethrunAddress)) (Just (initialContract (RuntimeCode (ConcreteRuntimeCode "")))) -exec :: State VM VMResult+exec :: EVM s (VMResult s) exec = do vm <- get case vm.result of Nothing -> exec1 >> exec Just r -> pure r -run :: State VM VM+run :: EVM s (VM s) run = do vm <- get case vm.result of Nothing -> exec1 >> run Just _ -> pure vm -execWhile :: (VM -> Bool) -> State VM Int+execWhile :: (VM s -> Bool) -> State (VM s) Int execWhile p = go 0 where go i = do
src/EVM/Expr.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE PatternSynonyms #-} {-| Helper functions for working with Expr instances.@@ -7,26 +8,36 @@ module EVM.Expr where import Prelude hiding (LT, GT)+import Control.Monad.ST import Data.Bits hiding (And, Xor) import Data.ByteString (ByteString) import Data.ByteString qualified as BS import Data.DoubleWord (Int256, Word256(Word256), Word128(Word128)) import Data.List import Data.Map.Strict qualified as Map-import Data.Maybe (mapMaybe)+import Data.Maybe (mapMaybe, isJust, fromMaybe) import Data.Semigroup (Any, Any(..), getAny) import Data.Vector qualified as V+import Data.Vector (Vector)+import Data.Vector.Mutable qualified as MV+import Data.Vector.Mutable (MVector) import Data.Vector.Storable qualified as VS import Data.Vector.Storable.ByteString import Data.Word (Word8, Word32) import Witch (unsafeInto, into, tryFrom)+import Data.Containers.ListUtils (nubOrd)+import Control.Monad.State import Optics.Core import EVM.Traversals import EVM.Types +-- ** Constants ** +maxLit :: W256+maxLit = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff+ -- ** Stack Ops ** --------------------------------------------------------------------------------- @@ -335,7 +346,7 @@ -- concrete indicies & 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 = let+ | dstOffset < maxBytes, size < maxBytes, srcOffset + (size-1) > srcOffset = let hd = padRight (unsafeInto dstOffset) $ BS.take (unsafeInto dstOffset) dst sl = [readByte (Lit i) src | i <- [srcOffset .. srcOffset + (size - 1)]] tl = BS.drop (unsafeInto dstOffset + unsafeInto size) dst@@ -362,11 +373,14 @@ writeWord :: Expr EWord -> Expr EWord -> Expr Buf -> Expr Buf+writeWord o@(Lit offset) (WAddr (LitAddr val)) b@(ConcreteBuf _)+ | offset < maxBytes && offset + 32 < maxBytes+ = writeWord o (Lit $ into val) b writeWord (Lit offset) (Lit val) (ConcreteBuf "")- | offset + 32 < maxBytes+ | offset < maxBytes && offset + 32 < maxBytes = ConcreteBuf $ BS.replicate (unsafeInto offset) 0 <> word256Bytes val writeWord o@(Lit offset) v@(Lit val) buf@(ConcreteBuf src)- | offset + 32 < maxBytes+ | offset < maxBytes && offset + 32 < maxBytes = ConcreteBuf $ (padRight (unsafeInto offset) $ BS.take (unsafeInto offset) src) <> word256Bytes val <> BS.drop ((unsafeInto offset) + 32) src@@ -418,31 +432,6 @@ Nothing -> internalError "cannot compute length of open expression" go l (GVar (BufVar a)) = EVM.Expr.max l (BufLength (GVar (BufVar a))) --- | If a buffer has a concrete prefix, we return it's length here-concPrefix :: Expr Buf -> Maybe Integer-concPrefix (CopySlice (Lit srcOff) (Lit _) (Lit _) src (ConcreteBuf "")) = do- sz <- go 0 src- pure . into $ (unsafeInto sz) - srcOff- where- go :: W256 -> Expr Buf -> Maybe Integer- -- base cases- go _ (AbstractBuf _) = Nothing- go l (ConcreteBuf b) = Just . into $ Prelude.max (unsafeInto . BS.length $ b) l-- -- writes to a concrete index- go l (WriteWord (Lit idx) (Lit _) b) = go (Prelude.max l (idx + 32)) b- go l (WriteByte (Lit idx) (LitByte _) b) = go (Prelude.max l (idx + 1)) b- go l (CopySlice _ (Lit dstOffset) (Lit size) _ dst) = go (Prelude.max (dstOffset + size) l) dst-- -- writes to an abstract index are ignored- go l (WriteWord _ _ b) = go l b- go l (WriteByte _ _ b) = go l b- go _ (CopySlice _ _ _ _ _) = internalError "cannot compute a concrete prefix length for nested copySlice expressions"- go _ (GVar _) = internalError "cannot calculate a concrete prefix of an open expression"-concPrefix (ConcreteBuf b) = Just (into . BS.length $ b)-concPrefix e = internalError $ "cannot compute a concrete prefix length for: " <> show e-- -- | Return the minimum possible length of a buffer. In the case of an -- abstract buffer, it is the largest write that is made on a concrete -- location. Parameterized by an environment for buffer variables.@@ -465,6 +454,51 @@ b <- Map.lookup a bufEnv go l b +-- returns the largest prefix that is guaranteed to be concrete (if one exists)+-- partial: will hard error if we encounter an input buf with a concrete size > 500mb+-- partial: will hard error if the prefix is > 500mb+concretePrefix :: Expr Buf -> Vector Word8+concretePrefix b = V.create $ do+ v <- MV.new (fromMaybe 1024 inputLen)+ (filled, v') <- go 0 v+ pure $ MV.take filled v'+ where++ -- if our prefix is > 500mb then we have other issues and should just bail...+ maxIdx :: Num i => i+ maxIdx = 500 * (10 ^ (6 :: Int))++ -- attempts to compute a concrete length for the input buffer+ inputLen :: Maybe Int+ inputLen = case bufLength b of+ Lit s -> if s > maxIdx+ then internalError "concretePrefix: input buffer size exceeds 500mb"+ -- unafeInto: 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+ -- 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+ -- if the prefix is very large then bail+ | i >= maxIdx = internalError "concretePrefix: prefix size exceeds 500mb"+ -- if the input buffer has a concrete size, then don't read past the end+ | Just mr <- inputLen, i >= mr = pure (i, v)+ -- double the size of the vector if we've reached the end+ | i >= MV.length v = do+ v' <- MV.grow v (MV.length v)+ go i v'+ -- read the byte at `i` in `b` into `v` if it is concrete, or halt if we've reached a symbolic byte+ -- unsafeInto: i will always be positive+ | otherwise = case readByte (Lit . unsafeInto $ i) b of+ LitByte byte -> do+ MV.write v i byte+ go (i+1) v+ _ -> pure (i, v)++ word256At :: Expr EWord -> Lens (Expr Buf) (Expr Buf) (Expr EWord) (Expr EWord) word256At i = lens getter setter where getter = readWord i@@ -555,12 +589,17 @@ WriteByte i v prev -> WriteByte i v (stripWrites off size prev) WriteWord i v prev -> WriteWord i v (stripWrites off size prev) CopySlice srcOff dstOff size' src dst -> CopySlice srcOff dstOff size' src dst- GVar _ -> internalError "unexpected GVar in stripWrites"+ GVar _ -> internalError "Unexpected GVar in stripWrites" -- ** Storage ** ----------------------------------------------------------------------------------- +readStorage' :: Expr EWord -> Expr Storage -> Expr EWord+readStorage' loc store = case readStorage loc store of+ Just v -> v+ Nothing -> Lit 0+ -- | Reads the word at the given slot from the given storage expression. -- -- Note that we return a Nothing instead of a 0x0 if we are reading from a@@ -568,74 +607,159 @@ -- no explicit writes to the requested slot. This makes implementing rpc -- storage lookups much easier. If the store is backed by an AbstractStore we -- always return a symbolic value.-readStorage :: Expr EWord -> Expr EWord -> Expr Storage -> Maybe (Expr EWord)-readStorage _ _ EmptyStore = Nothing-readStorage addr slot store@(ConcreteStore s) = case (addr, slot) of- (Lit a, Lit l) -> do- ctrct <- Map.lookup a s- val <- Map.lookup l ctrct- pure $ Lit val- _ -> Just $ SLoad addr slot store-readStorage addr' slot' s@AbstractStore = Just $ SLoad addr' slot' s-readStorage addr' slot' s@(SStore addr slot val prev) =- if addr == addr'- then if slot == slot'- -- if address and slot match then we return the val in this write- then Just val- else case (slot, slot') of- -- if the slots don't match and are lits, we can skip this write- (Lit _, Lit _) -> readStorage addr' slot' prev- -- if the slots don't match syntactically and are abstract then we can't skip this write- _ -> Just $ SLoad addr' slot' s- else case (addr, addr') of- -- if the the addresses don't match and are lits, we can skip this write- (Lit _, Lit _) -> readStorage addr' slot' prev- -- if the the addresses don't match syntactically and are abstract then we can't skip this write- _ -> Just $ SLoad addr' slot' s-readStorage _ _ (GVar _) = internalError "Can't read from a GVar"+readStorage :: Expr EWord -> Expr Storage -> Maybe (Expr EWord)+readStorage w st = go (simplify w) st+ 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 (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+ -- if address and slot match then we return the val in this write+ _ | prevSlot == slot -> Just val -readStorage' :: Expr EWord -> Expr EWord -> Expr Storage -> Expr EWord-readStorage' addr loc store = case readStorage addr loc store of- Just v -> v- Nothing -> Lit 0+ -- if the slots don't match (see previous guard) and are lits, we can skip this write+ (Lit _, Lit _) -> go slot prev + -- slot is for a map + map -> skip write+ (MappingSlot idA _, MappingSlot idB _) | idsDontMatch idA idB -> go slot prev+ (MappingSlot _ keyA, MappingSlot _ keyB) | surelyNotEq keyA keyB -> go slot prev + -- special case of array + map -> skip write+ (ArraySlotWithOffset idA _, Keccak64Bytes) | BS.length idA /= 64 -> go slot prev+ (ArraySlotZero idA, Keccak64Bytes) | BS.length idA /= 64 -> go slot prev++ -- special case of array + map -> skip write+ (Keccak64Bytes, ArraySlotWithOffset idA _) | BS.length idA /= 64 -> go slot prev+ (Keccak64Bytes, ArraySlotZero idA) | BS.length idA /= 64 -> go slot prev++ -- Fixed SMALL value will never match Keccak (well, it might, but that's VERY low chance)+ (Lit a, Keccak _) | a < 256 -> go slot prev+ (Keccak _, Lit a) | a < 256 -> go slot prev++ -- 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+ -- 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++ -- 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+ (Add (Lit a2) (Keccak _), (Keccak _)) | a2 > 0, a2 < 256 -> go slot prev+ ((Keccak _), Add (Lit b2) (Keccak _)) | b2 > 0, b2 < 256 -> go slot prev++ -- case of array + array, but different id's or different concrete offsets+ -- zero offs vs zero offs+ (ArraySlotZero idA, ArraySlotZero idB) | idA /= idB -> go slot prev+ -- zero offs vs non-zero offs+ (ArraySlotZero idA, ArraySlotWithOffset idB _) | idA /= idB -> go slot prev+ (ArraySlotZero _, ArraySlotWithOffset _ (Lit offB)) | offB /= 0 -> go slot prev+ -- non-zero offs vs zero offs+ (ArraySlotWithOffset idA _, ArraySlotZero idB) | idA /= idB -> go slot prev+ (ArraySlotWithOffset _ (Lit offA), ArraySlotZero _) | offA /= 0 -> go slot prev+ -- non-zero offs vs non-zero offs+ (ArraySlotWithOffset idA _, ArraySlotWithOffset idB _) | idA /= idB -> go slot prev++ (ArraySlotWithOffset _ offA, ArraySlotWithOffset _ offB) | surelyNotEq offA offB -> go slot prev++ -- we are unable to determine statically whether or not we can safely move deeper in the write chain, so return an abstract term+ _ -> Just $ SLoad slot s++ surelyNotEq :: Expr a -> Expr a -> Bool+ surelyNotEq (Lit a) (Lit b) = a /= b+ -- never equal: x+y (y is concrete) vs x+z (z is concrete), y!=z+ surelyNotEq (Add (Lit l1) v1) (Add (Lit l2) v2) = l1 /= l2 && v1 == v2+ -- never equal: x+y (y is concrete, non-zero) vs x+ surelyNotEq v1 (Add (Lit l2) v2) = l2 /= 0 && v1 == v2+ surelyNotEq (Add (Lit l1) v1) v2 = l1 /= 0 && v1 == v2+ surelyNotEq _ _ = False++ maxW32 :: W256+ maxW32 = into (maxBound :: Word32)++-- storage slots for maps are determined by (keccak (bytes32(key) ++ bytes32(id)))+pattern MappingSlot :: ByteString -> Expr EWord -> Expr EWord+pattern MappingSlot id key = Keccak (CopySlice (Lit 0) (Lit 0) (Lit 64) (WriteWord (Lit 0) key (ConcreteBuf id)) (ConcreteBuf ""))++-- keccak of any 64 bytes value+pattern Keccak64Bytes :: Expr EWord+pattern Keccak64Bytes <- Keccak (CopySlice (Lit 0) (Lit 0) (Lit 64) _ (ConcreteBuf ""))++-- storage slots for arrays are determined by (keccak(bytes32(id)) + offset)+pattern ArraySlotWithOffset :: ByteString -> Expr EWord -> Expr EWord+pattern ArraySlotWithOffset id offset = Add (Keccak (ConcreteBuf id)) offset++-- special pattern to match the 0th element because the `Add` term gets simplified out+pattern ArraySlotZero :: ByteString -> Expr EWord+pattern ArraySlotZero id = Keccak (ConcreteBuf id)++-- checks if two mapping ids match or not+idsDontMatch :: ByteString -> ByteString -> Bool+idsDontMatch a b = BS.length a >= 64 && BS.length b >= 64 && diff32to64Byte a b+ where+ diff32to64Byte :: ByteString -> ByteString -> Bool+ diff32to64Byte x y = x32 /= y32+ where+ x32 = BS.take 32 $ BS.drop 32 x+ y32 = BS.take 32 $ BS.drop 32 y++slotPos :: Word8 -> ByteString+slotPos pos = BS.pack ((replicate 31 (0::Word8))++[pos])++-- | Turns Literals into keccak(bytes32(id)) + offset (i.e. writes to arrays)+structureArraySlots :: Expr a -> Expr a+structureArraySlots e = mapExpr go e+ where+ go :: Expr a -> Expr a+ go orig@(Lit key) = case litToArrayPreimage key of+ Just (array, offset) -> ArraySlotWithOffset (slotPos array) (Lit offset)+ _ -> orig+ go a = a++-- Takes in value, checks if it's within 256 of a pre-computed array hash value+-- if it is, it returns (array_number, offset)+litToArrayPreimage :: W256 -> Maybe (Word8, W256)+litToArrayPreimage val = go preImages+ where+ go :: [(W256, Word8)] -> Maybe (Word8, W256)+ go ((image, preimage):ax) = if val >= image && val-image <= 255 then Just (preimage, val-image)+ else go ax+ go [] = Nothing+ -- | Writes a value to a key in a storage expression. -- -- Concrete writes on top of a concrete or empty store will produce a new -- ConcreteStore, otherwise we add a new write to the storage expression.-writeStorage :: Expr EWord -> Expr EWord -> Expr EWord -> Expr Storage -> Expr Storage-writeStorage a@(Lit addr) k@(Lit key) v@(Lit val) store = case store of- EmptyStore -> ConcreteStore (Map.singleton addr (Map.singleton key val))- ConcreteStore s -> let- ctrct = Map.findWithDefault Map.empty addr s- in ConcreteStore (Map.insert addr (Map.insert key val ctrct) s)- _ -> SStore a k v store-writeStorage addr key val store@(SStore addr' key' val' prev)- | addr == addr'+writeStorage :: Expr EWord -> Expr EWord -> Expr Storage -> Expr Storage+writeStorage k@(Lit key) v@(Lit val) store = case store of+ ConcreteStore s -> ConcreteStore (Map.insert key val s)+ _ -> SStore k v store+writeStorage key val store@(SStore key' val' prev) = if key == key' -- if we're overwriting an existing location, then drop the write- then SStore addr key val prev- else case (addr, addr', key, key') of+ then SStore key val prev+ else case (key, key') of -- if we can know statically that the new write doesn't overlap with the existing write, then we continue down the write chain -- we impose an ordering relation on the writes that we push down to ensure termination when this routine is called from the simplifier- (Lit a, Lit a', Lit k, Lit k') -> if a > a' || (a == a' && k > k')- then SStore addr' key' val' (writeStorage addr key val prev)- else SStore addr key val store+ (Lit k, Lit k') -> if k > k'+ then SStore key' val' (writeStorage key val prev)+ else SStore key val store -- otherwise stack a new write on top of the the existing write chain- _ -> SStore addr key val store- | otherwise- = case (addr, addr') of- -- if we can know statically that the new write doesn't overlap with the existing write, then we continue down the write chain- -- once again we impose an ordering relation on the pushed down writes to ensure termination- (Lit a, Lit a') -> if a > a'- then SStore addr' key' val' (writeStorage addr key val prev)- else SStore addr key val store- -- otherwise stack a new write on top of the the existing write chain- _ -> SStore addr key val store-writeStorage addr key val store = SStore addr key val store+ _ -> SStore key val store+writeStorage key val store = SStore key val store +getAddr :: Expr Storage -> Maybe (Expr EAddr)+getAddr (SStore _ _ p) = getAddr p+getAddr (AbstractStore a) = Just a+getAddr (ConcreteStore _) = Nothing+getAddr (GVar _) = internalError "cannot determine addr of a GVar"++ -- ** Whole Expression Simplification ** ----------------------------------------------------------- @@ -644,21 +768,27 @@ simplify :: Expr a -> Expr a simplify e = if (mapExpr go e == e) then e- else simplify (mapExpr go e)+ else simplify (mapExpr go (structureArraySlots e)) where go :: Expr a -> Expr a++ go (Failure a b c) = Failure (simplifyProps a) b c+ go (Partial a b c) = Partial (simplifyProps a) b c+ go (Success a b c d) = Success (simplifyProps a) b c d+ -- redundant CopySlice go (CopySlice (Lit 0x0) (Lit 0x0) (Lit 0x0) _ dst) = dst -- simplify storage- go (SLoad addr slot store) = readStorage' addr slot store- go (SStore addr slot val store) = writeStorage addr slot val store+ go (SLoad slot store) = readStorage' slot store+ go (SStore slot val store) = writeStorage slot val store -- simplify buffers go o@(ReadWord (Lit _) _) = simplifyReads o go (ReadWord idx buf) = readWord idx buf go o@(ReadByte (Lit _) _) = simplifyReads o go (ReadByte idx buf) = readByte idx buf+ go (BufLength buf) = bufLength buf -- We can zero out any bytes in a base ConcreteBuf that we know will be overwritten by a later write -- TODO: make this fully general for entire write chains, not just a single write.@@ -673,6 +803,20 @@ go (WriteWord a b c) = writeWord a b c go (WriteByte a b c) = writeByte a b c++ -- truncate some concrete source buffers to the portion relevant for the CopySlice if we're copying a fully concrete region+ go orig@(CopySlice srcOff@(Lit n) dstOff size@(Lit sz)+ -- It doesn't matter what wOffs we write to, because only the first+ -- n+sz of ConcreteBuf will be used by CopySlice+ (WriteWord wOff value (ConcreteBuf buf)) dst)+ -- Let's not deal with overflow+ | n+sz >= n+ , n+sz >= sz+ , n+sz <= maxBytes+ = (CopySlice srcOff dstOff size+ (WriteWord wOff value (ConcreteBuf simplifiedBuf)) dst)+ | otherwise = orig+ where simplifiedBuf = BS.take (unsafeInto (n+sz)) buf go (CopySlice a b c d f) = copySlice a b c d f go (IndexWord a b) = indexWord a b@@ -684,26 +828,31 @@ go (EVM.Types.LT _ (Lit 0)) = Lit 0 -- normalize all comparisons in terms of LT- go (EVM.Types.GT a b) = EVM.Types.LT b a- go (EVM.Types.GEq a b) = EVM.Types.LEq b a- go (EVM.Types.LEq a b) = EVM.Types.IsZero (EVM.Types.GT a b)-+ go (EVM.Types.GT a b) = lt b a+ 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 -- syntactic Eq reduction go (Eq (Lit a) (Lit b)) | a == b = Lit 1 | otherwise = Lit 0- go (Eq (Lit 0) (Sub a b)) = Eq a b- go o@(Eq a b)+ go (Eq (Lit 0) (Sub a b)) = eq a b+ go (Eq a b) | a == b = Lit 1- | otherwise = o+ | otherwise = eq a b -- redundant ITE go (ITE (Lit x) a b) | x == 0 = b | otherwise = a + -- address masking+ go (And (Lit 0xffffffffffffffffffffffffffffffffffffffff) a@(WAddr _)) = a++ -- literal addresses+ go (WAddr (LitAddr a)) = Lit $ into a+ -- simple div/mod/add/sub go (Div o1@(Lit _) o2@(Lit _)) = EVM.Expr.div o1 o2 go (SDiv o1@(Lit _) o2@(Lit _)) = EVM.Expr.sdiv o1 o2@@ -727,28 +876,28 @@ go (Add (Sub orig (Lit x)) (Lit y)) = Add orig (Lit (y-x)) -- redundant add / sub- go o@(Sub (Add a b) c)+ go (Sub (Add a b) c) | a == c = b | b == c = a- | otherwise = o+ | otherwise = sub (add a b) c -- add / sub identities- go o@(Add a b)+ go (Add a b) | b == (Lit 0) = a | a == (Lit 0) = b- | otherwise = o- go o@(Sub a b)+ | otherwise = add a b+ go (Sub a b) | a == b = Lit 0 | b == (Lit 0) = a- | otherwise = o+ | otherwise = sub a b -- SHL / SHR by 0- go o@(SHL a v)+ go (SHL a v) | a == (Lit 0) = v- | otherwise = o- go o@(SHR a v)+ | otherwise = shl a v+ go (SHR a v) | a == (Lit 0) = v- | otherwise = o+ | otherwise = shr a v -- doubled And go o@(And a (And b c))@@ -760,8 +909,9 @@ go o@(And (Lit x) _) | x == 0 = Lit 0 | otherwise = o- go o@(And _ (Lit x))+ go o@(And v (Lit x)) | x == 0 = Lit 0+ | x == maxLit = v | otherwise = o go o@(Or (Lit x) b) | x == 0 = b@@ -790,9 +940,23 @@ go (EVM.Types.Not (EVM.Types.Not a)) = a -- Some trivial min / max eliminations- go (Max (Lit 0) a) = a- go (Min (Lit 0) _) = Lit 0+ go (Max a b) = case (a, b) of+ (Lit 0, _) -> b+ _ -> EVM.Expr.max a b+ go (Min a b) = case (a, b) of+ (Lit 0, _) -> Lit 0+ _ -> EVM.Expr.min a b + -- 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+ go (Div a (Lit 1)) = a+ -- If a >= b then the value of the `Max` expression can never be < b go o@(LT (Max (Lit a) _) (Lit b)) | a >= b = Lit 0@@ -809,6 +973,109 @@ go a = a +-- ** Prop Simplification ** -----------------------------------------------------------------------+++simplifyProps :: [Prop] -> [Prop]+simplifyProps ps = if canBeSat then simplified else [PBool False]+ where+ simplified = remRedundantProps . map simplifyProp . flattenProps $ ps+ canBeSat = constFoldProp simplified++-- | Evaluate the provided proposition down to its most concrete result+simplifyProp :: Prop -> Prop+simplifyProp prop =+ let new = mapProp' go (simpInnerExpr prop)+ in if (new == prop) then prop else simplifyProp new+ where+ go :: Prop -> Prop++ -- LT/LEq comparisions+ go (PLT (Var _) (Lit 0)) = PBool False+ go (PLEq (Lit 0) (Var _)) = PBool True+ go (PLT (Lit val) (Var _)) | val == maxLit = PBool False+ go (PLEq (Var _) (Lit val)) | val == maxLit = PBool True+ go (PLT (Lit l) (Lit r)) = PBool (l < r)+ 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++ -- negations+ go (PNeg (PBool b)) = PBool (Prelude.not b)+ go (PNeg (PNeg a)) = a++ -- solc specific stuff++ -- iszero(a) -> (a == 0)+ -- iszero(iszero(a))) -> ~(a == 0) -> a > 0+ -- iszero(iszero(a)) == 0 -> ~~(a == 0) -> a == 0+ -- ~(iszero(iszero(a)) == 0) -> ~~~(a == 0) -> ~(a == 0) -> a > 0+ go (PNeg (PEq (IsZero (IsZero a)) (Lit 0))) = PGT a (Lit 0)++ -- iszero(a) -> (a == 0)+ -- iszero(a) == 0 -> ~(a == 0)+ -- ~(iszero(a) == 0) -> ~~(a == 0) -> a == 0+ go (PNeg (PEq (IsZero a) (Lit 0))) = PEq a (Lit 0)++ -- a < b == 0 -> ~(a < b)+ -- ~(a < b == 0) -> ~~(a < b) -> a < b+ go (PNeg (PEq (LT a b) (Lit 0x0))) = PLT a b++ -- And/Or+ go (PAnd (PBool l) (PBool r)) = PBool (l && r)+ go (PAnd (PBool False) _) = PBool False+ go (PAnd _ (PBool False)) = PBool False+ go (POr (PBool True) _) = PBool True+ go (POr _ (PBool True)) = PBool True+ go (POr (PBool l) (PBool r)) = PBool (l || r)++ -- Imply+ go (PImpl _ (PBool True)) = PBool True+ go (PImpl (PBool True) b) = b+ go (PImpl (PBool False) _) = PBool True++ -- Eq+ go (PEq (Eq a b) (Lit 0)) = PNeg (PEq a b)+ go (PEq (Eq a b) (Lit 1)) = PEq a b+ go (PEq (Sub a b) (Lit 0)) = PEq a b+ go (PEq (Lit l) (Lit r)) = PBool (l == r)+ go o@(PEq l r)+ | l == r = PBool True+ | otherwise = o+ go p = p+++ -- Applies `simplify` to the inner part of a Prop, e.g.+ -- (PEq (Add (Lit 1) (Lit 2)) (Var "a")) becomes+ -- (PEq (Lit 3) (Var "a")+ simpInnerExpr :: Prop -> Prop+ -- rewrite everything as LEq or LT+ simpInnerExpr (PGEq a b) = simpInnerExpr (PLEq b a)+ simpInnerExpr (PGT a b) = simpInnerExpr (PLT b a)+ -- simplifies the inner expression+ simpInnerExpr (PEq a b) = PEq (simplify a) (simplify b)+ simpInnerExpr (PLT a b) = PLT (simplify a) (simplify b)+ simpInnerExpr (PLEq a b) = PLEq (simplify a) (simplify b)+ simpInnerExpr (PNeg a) = PNeg (simpInnerExpr a)+ simpInnerExpr (PAnd a b) = PAnd (simpInnerExpr a) (simpInnerExpr b)+ simpInnerExpr (POr a b) = POr (simpInnerExpr a) (simpInnerExpr b)+ simpInnerExpr (PImpl a b) = PImpl (simpInnerExpr a) (simpInnerExpr b)+ simpInnerExpr orig@(PBool _) = orig++-- Makes [PAnd a b] into [a,b]+flattenProps :: [Prop] -> [Prop]+flattenProps [] = []+flattenProps (a:ax) = case a of+ PAnd x1 x2 -> x1:x2:flattenProps ax+ x -> x:flattenProps ax++-- removes redundant (constant True/False) props+remRedundantProps :: [Prop] -> [Prop]+remRedundantProps p = collapseFalse . filter (\x -> x /= PBool True) . nubOrd $ p+ where+ collapseFalse ps = if isJust $ find (== PBool False) ps then [PBool False] else ps++ -- ** Conversions ** ------------------------------------------------------------------------------- @@ -819,6 +1086,16 @@ exprToAddr (Lit x) = Just (unsafeInto x) exprToAddr _ = Nothing +-- TODO: make this smarter, probably we will need to use the solver here?+wordToAddr :: Expr EWord -> Maybe (Expr EAddr)+wordToAddr = go . simplify+ where+ go :: Expr EWord -> Maybe (Expr EAddr)+ go = \case+ WAddr a -> Just a+ Lit a -> Just $ LitAddr (truncateToAddr a)+ _ -> Nothing+ litCode :: BS.ByteString -> [Expr Byte] litCode bs = fmap LitByte (BS.unpack bs) @@ -837,8 +1114,24 @@ -- Is the given expr a literal word? isLitWord :: Expr EWord -> Bool isLitWord (Lit _) = True+isLitWord (WAddr (LitAddr _)) = True isLitWord _ = False +isSuccess :: Expr End -> Bool+isSuccess = \case+ Success {} -> True+ _ -> False++isFailure :: Expr End -> Bool+isFailure = \case+ Failure {} -> True+ _ -> False++isPartial :: Expr End -> Bool+isPartial = \case+ Partial {} -> True+ _ -> False+ -- | Returns the byte at idx from the given word. indexWord :: Expr EWord -> Expr EWord -> Expr Byte -- Simplify masked reads:@@ -997,3 +1290,46 @@ inRange :: Int -> Expr EWord -> Prop inRange sz e = PAnd (PGEq e (Lit 0)) (PLEq e (Lit $ 2 ^ sz - 1))+++-- | images of keccak(bytes32(x)) where 0 <= x < 256+preImages :: [(W256, Word8)]+preImages = [(keccak' (word256Bytes . into $ i), i) | i <- [0..255]]++data ConstState = ConstState+ { values :: Map.Map (Expr EWord) W256+ , canBeSat :: Bool+ }+ deriving (Show)++-- | Folds constants+constFoldProp :: [Prop] -> Bool+constFoldProp ps = oneRun ps (ConstState mempty True)+ where+ oneRun ps2 startState = (execState (mapM (go . simplifyProp) ps2) startState).canBeSat+ go :: Prop -> State ConstState ()+ go x = case x of+ PEq (Lit l) a -> do+ s <- get+ case Map.lookup a s.values of+ Just l2 -> case l==l2 of+ True -> pure ()+ False -> put ConstState {canBeSat=False, values=mempty}+ Nothing -> do+ let vs' = Map.insert a l s.values+ put $ s{values=vs'}+ PEq a b@(Lit _) -> go (PEq b a)+ PAnd a b -> do+ go a+ go b+ POr a b -> do+ s <- get+ let+ v1 = oneRun [a] s+ v2 = oneRun [b] s+ when (Prelude.not v1) $ go b+ when (Prelude.not v2) $ go a+ s2 <- get+ put $ s{canBeSat=(s2.canBeSat && (v1 || v2))}+ PBool False -> put $ ConstState {canBeSat=False, values=mempty}+ _ -> pure ()
− src/EVM/Facts.hs
@@ -1,231 +0,0 @@-{-# LANGUAGE PatternSynonyms #-}---- Converts between Ethereum contract states and simple trees of--- texts. Dumps and loads such trees as Git repositories (the state--- gets serialized as commits with folders and files).------ Example state file hierarchy:------ /0123...abc/balance says "0x500"--- /0123...abc/code says "60023429..."--- /0123...abc/nonce says "0x3"--- /0123...abc/storage/0x1 says "0x1"--- /0123...abc/storage/0x2 says "0x0"------ This format could easily be serialized into any nested record--- syntax, e.g. JSON.--module EVM.Facts- ( File (..)- , Fact (..)- , Data (..)- , Path (..)- , apply- , applyCache- , cacheFacts- , contractFacts- , vmFacts- , factToFile- , fileToFact- ) where--import EVM (bytecode)-import EVM qualified-import EVM.Expr (writeStorage, litAddr)-import EVM.Types--import Optics.Core-import Optics.State--import Control.Monad.State.Strict (execState, when)-import Data.ByteString (ByteString)-import Data.ByteString.Base16 qualified as BS16-import Data.ByteString qualified as BS-import Data.ByteString.Char8 qualified as Char8-import Data.Map (Map)-import Data.Map qualified as Map-import Data.Set (Set)-import Data.Set qualified as Set-import Data.Ord (comparing)-import Text.Read (readMaybe)-import Witch (into)---- We treat everything as ASCII byte strings because--- we only use hex digits (and the letter 'x').-type ASCII = ByteString---- When using string literals, default to infer the ASCII type.-default (ASCII)---- We use the word "fact" to mean one piece of serializable--- information about the state.------ Note that Haskell allows this kind of union of records.--- It's convenient here, but typically avoided.-data Fact- = BalanceFact { addr :: Addr, what :: W256 }- | NonceFact { addr :: Addr, what :: W256 }- | StorageFact { addr :: Addr, what :: W256, which :: W256 }- | CodeFact { addr :: Addr, blob :: ByteString }- deriving (Eq, Show)---- A fact path means something like "/0123...abc/storage/0x1",--- or alternatively "contracts['0123...abc'].storage['0x1']".-data Path = Path [ASCII] ASCII- deriving (Eq, Ord, Show)---- A fact data is the content of a file. We encapsulate it--- with a newtype to make it easier to change the representation--- (to use bytestrings, some sum type, or whatever).-newtype Data = Data { dataASCII :: ASCII }- deriving (Eq, Ord, Show)---- We use the word "file" to denote a serialized value at a path.-data File = File { filePath :: Path, fileData :: Data }- deriving (Eq, Ord, Show)--class AsASCII a where- dump :: a -> ASCII- load :: ASCII -> Maybe a--instance AsASCII Addr where- dump = Char8.pack . show- load = readMaybe . Char8.unpack--instance AsASCII W256 where- dump = Char8.pack . show- load = readMaybe . Char8.unpack--instance AsASCII ByteString where- dump x = BS16.encodeBase16' x <> "\n"- load x =- case BS16.decodeBase16 . mconcat . BS.split 10 $ x of- Right y -> Just y- _ -> Nothing--contractFacts :: Addr -> Contract -> Map W256 (Map W256 W256) -> [Fact]-contractFacts a x store = case view bytecode x of- ConcreteBuf b ->- storageFacts a store ++- [ BalanceFact a x.balance- , NonceFact a x.nonce- , CodeFact a b- ]- _ ->- -- here simply ignore storing the bytecode- storageFacts a store ++- [ BalanceFact a x.balance- , NonceFact a x.nonce- ]---storageFacts :: Addr -> Map W256 (Map W256 W256) -> [Fact]-storageFacts a store = map f (Map.toList (Map.findWithDefault Map.empty (into a) store))- where- f :: (W256, W256) -> Fact- f (k, v) = StorageFact- { addr = a- , what = v- , which = k- }--cacheFacts :: Cache -> Set Fact-cacheFacts c = Set.fromList $ do- (k, v) <- Map.toList c.fetchedContracts- contractFacts k v c.fetchedStorage--vmFacts :: VM -> Set Fact-vmFacts vm = Set.fromList $ do- (k, v) <- Map.toList vm.env.contracts- case vm.env.storage of- EmptyStore -> contractFacts k v Map.empty- ConcreteStore s -> contractFacts k v s- _ -> internalError "cannot serialize an abstract store"---- Somewhat stupidly, this function demands that for each contract,--- the code fact for that contract comes before the other facts for--- that contract. This is an incidental thing because right now we--- always initialize contracts starting with the code (to calculate--- the code hash and so on).------ Therefore, we need to make sure to sort the fact set in such a way.-apply1 :: VM -> Fact -> VM-apply1 vm fact =- case fact of- CodeFact {..} -> flip execState vm $ do- assign (#env % #contracts % at addr) (Just (EVM.initialContract (RuntimeCode (ConcreteRuntimeCode blob))))- when (vm.state.contract == addr) $ EVM.loadContract addr- StorageFact {..} ->- vm & over (#env % #storage) (writeStorage (litAddr addr) (Lit which) (Lit what))- BalanceFact {..} ->- vm & set (#env % #contracts % ix addr % #balance) what- NonceFact {..} ->- vm & set (#env % #contracts % ix addr % #nonce) what--apply2 :: VM -> Fact -> VM-apply2 vm fact =- case fact of- CodeFact {..} -> flip execState vm $ do- assign (#cache % #fetchedContracts % at addr) (Just (EVM.initialContract (RuntimeCode (ConcreteRuntimeCode blob))))- when (vm.state.contract == addr) $ EVM.loadContract addr- StorageFact {..} -> let- store = vm.cache.fetchedStorage- ctrct = Map.findWithDefault Map.empty (into addr) store- in- vm & set (#cache % #fetchedStorage) (Map.insert (into addr) (Map.insert which what ctrct) store)- BalanceFact {..} ->- vm & set (#cache % #fetchedContracts % ix addr % #balance) what- NonceFact {..} ->- vm & set (#cache % #fetchedContracts % ix addr % #nonce) what---- Sort facts in the right order for `apply1` to work.-instance Ord Fact where- compare = comparing f- where- f :: Fact -> (Int, Addr, W256)- f (CodeFact a _) = (0, a, 0)- f (BalanceFact a _) = (1, a, 0)- f (NonceFact a _) = (2, a, 0)- f (StorageFact a _ x) = (3, a, x)---- Applies a set of facts to a VM.-apply :: VM -> Set Fact -> VM-apply =- -- The set's ordering is relevant; see `apply1`.- foldl apply1------ Applies a set of facts to a VM.-applyCache :: VM -> Set Fact -> VM-applyCache =- -- The set's ordering is relevant; see `apply1`.- foldl apply2--factToFile :: Fact -> File-factToFile fact = case fact of- StorageFact {..} -> mk ["storage"] (dump which) what- BalanceFact {..} -> mk [] "balance" what- NonceFact {..} -> mk [] "nonce" what- CodeFact {..} -> mk [] "code" blob- where- mk :: AsASCII a => [ASCII] -> ASCII -> a -> File- mk prefix base a =- File (Path (dump fact.addr : prefix) base)- (Data $ dump a)---- This lets us easier pattern match on serialized things.--- Uses language extensions: `PatternSynonyms` and `ViewPatterns`.-pattern Load :: AsASCII a => a -> ASCII-pattern Load x <- (load -> Just x)--fileToFact :: File -> Maybe Fact-fileToFact = \case- File (Path [Load a] "code") (Data (Load x))- -> Just (CodeFact a x)- File (Path [Load a] "balance") (Data (Load x))- -> Just (BalanceFact a x)- File (Path [Load a] "nonce") (Data (Load x))- -> Just (NonceFact a x)- File (Path [Load a, "storage"] (Load x)) (Data (Load y))- -> Just (StorageFact a y x)- _- -> Nothing
− src/EVM/Facts/Git.hs
@@ -1,48 +0,0 @@---- This is a backend for the fact representation that uses a Git--- repository as the store.--module EVM.Facts.Git- ( saveFacts- , loadFacts- , RepoAt (..)- ) where--import EVM.Facts (Fact (..), File (..), Path (..), Data (..), fileToFact, factToFile)--import Optics.Core-import Data.Set (Set)-import Data.Set qualified as Set-import Data.Maybe (catMaybes)-import Restless.Git qualified as Git--newtype RepoAt = RepoAt String- deriving (Eq, Ord, Show)---- For modularity reasons, we have our own file data type that is--- isomorphic with the one in the `restless-git` library. We declare--- the isomorphism so we can go between them easily.-fileRepr :: Iso' File Git.File-fileRepr = iso f g- where- f :: File -> Git.File- f (File (Path ps p) (Data x)) =- Git.File (Git.Path ps p) x-- g :: Git.File -> File- g (Git.File (Git.Path ps p) x) =- File (Path ps p) (Data x)--saveFacts :: RepoAt -> Set Fact -> IO ()-saveFacts (RepoAt repo) facts =- Git.save repo "hevm execution"- (Set.map (view fileRepr . factToFile) facts)--prune :: Ord a => Set (Maybe a) -> Set a-prune = Set.fromList . catMaybes . Set.toList--loadFacts :: RepoAt -> IO (Set Fact)-loadFacts (RepoAt src) =- fmap- (prune . Set.map (fileToFact . review fileRepr))- (Git.load src)
src/EVM/FeeSchedule.hs view
@@ -53,58 +53,36 @@ , g_access_list_storage_key :: n } deriving Show --- For the purposes of this module, we define an EIP as just a fee--- schedule modification.-type EIP n = Num n => FeeSchedule n -> FeeSchedule n---- EIP150: Gas cost changes for IO-heavy operations--- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md>-eip150 :: EIP n-eip150 fees = fees- { g_extcode = 700- , g_balance = 400- , g_sload = 200- , g_call = 700- , g_selfdestruct = 5000- , g_selfdestruct_newaccount = 25000- }---- EIP160: EXP cost increase--- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-160.md>-eip160 :: EIP n-eip160 fees = fees- { g_expbyte = 50 }--homestead :: Num n => FeeSchedule n-homestead = FeeSchedule+feeSchedule :: Num n => FeeSchedule n+feeSchedule = FeeSchedule { g_zero = 0 , g_base = 2 , g_verylow = 3 , g_low = 5 , g_mid = 8 , g_high = 10- , g_extcode = 20- , g_balance = 20- , g_sload = 50+ , g_extcode = 2600+ , g_balance = 2600+ , g_sload = 100 , g_jumpdest = 1 , g_sset = 20000- , g_sreset = 5000+ , g_sreset = 2900 , r_sclear = 15000- , g_selfdestruct = 0- , g_selfdestruct_newaccount = 0+ , g_selfdestruct = 5000+ , g_selfdestruct_newaccount = 25000 , r_selfdestruct = 24000 , g_create = 32000 , g_codedeposit = 200- , g_call = 40+ , g_call = 2600 , g_callvalue = 9000 , g_callstipend = 2300 , g_newaccount = 25000 , g_exp = 10- , g_expbyte = 10+ , g_expbyte = 50 , g_memory = 3 , g_txcreate = 32000 , g_txdatazero = 4- , g_txdatanonzero = 68+ , g_txdatanonzero = 16 , g_transaction = 21000 , g_log = 375 , g_logdata = 8@@ -114,12 +92,12 @@ , g_initcodeword = 2 , g_copy = 3 , g_blockhash = 20- , g_extcodehash = 400+ , g_extcodehash = 2600 , g_quaddivisor = 20- , g_ecadd = 500- , g_ecmul = 40000- , g_pairing_point = 80000- , g_pairing_base = 100000+ , g_ecadd = 150+ , g_ecmul = 6000+ , g_pairing_point = 34000+ , g_pairing_base = 45000 , g_fround = 1 , r_block = 2000000000000000000 , g_cold_sload = 2100@@ -128,60 +106,3 @@ , g_access_list_address = 2400 , g_access_list_storage_key = 1900 }--metropolis :: Num n => FeeSchedule n-metropolis = eip160 . eip150 $ homestead---- EIP1108: Reduce alt_bn128 precompile gas costs--- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1108.md>-eip1108 :: EIP n-eip1108 fees = fees- { g_ecadd = 150- , g_ecmul = 6000- , g_pairing_point = 34000- , g_pairing_base = 45000- }---- EIP1884: Repricing for trie-size-dependent opcodes--- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1884.md>-eip1884 :: EIP n-eip1884 fees = fees- { g_sload = 800- , g_balance = 700- , g_extcodehash = 700- }---- EIP2028: Transaction data gas cost reduction--- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2028.md>-eip2028 :: EIP n-eip2028 fees = fees- { g_txdatanonzero = 16- }---- EIP2200: Structured definitions for gas metering--- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2200.md>-eip2200 :: EIP n-eip2200 fees = fees- { g_sload = 800- , g_sset = 20000 -- not changed- , g_sreset = 5000 -- not changed- , r_sclear = 15000 -- not changed- }--istanbul :: Num n => FeeSchedule n-istanbul = eip1108 . eip1884 . eip2028 . eip2200 $ metropolis-- -- EIP2929: Gas cost increases for state access opcodes- -- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2929.md>-eip2929 :: EIP n-eip2929 fees = fees- { g_sload = 100- , g_sreset = 5000 - 2100- , g_call = 2600- , g_balance = 2600- , g_extcode = 2600- , g_extcodehash = 2600- }--berlin :: Num n => FeeSchedule n-berlin = eip2929 istanbul
src/EVM/Fetch.hs view
@@ -2,13 +2,14 @@ module EVM.Fetch where -import EVM (initialContract)+import EVM (initialContract, unknownContract) import EVM.ABI-import EVM.FeeSchedule qualified as FeeSchedule+import EVM.FeeSchedule (feeSchedule) import EVM.Format (hexText) import EVM.SMT import EVM.Solvers import EVM.Types+import EVM (emptyContract) import Optics.Core @@ -32,7 +33,7 @@ QueryCode :: Addr -> RpcQuery BS.ByteString QueryBlock :: RpcQuery Block QueryBalance :: Addr -> RpcQuery W256- QueryNonce :: Addr -> RpcQuery W256+ QueryNonce :: Addr -> RpcQuery W64 QuerySlot :: Addr -> W256 -> RpcQuery W256 QueryChainId :: RpcQuery W256 @@ -109,7 +110,7 @@ parseBlock :: (AsValue s, Show s) => s -> Maybe Block parseBlock j = do- coinbase <- readText <$> j ^? key "miner" % _String+ coinbase <- LitAddr . readText <$> j ^? key "miner" % _String timestamp <- Lit . readText <$> j ^? key "timestamp" % _String number <- readText <$> j ^? key "number" % _String gasLimit <- readText <$> j ^? key "gasLimit" % _String@@ -127,7 +128,7 @@ (Nothing, Just _, Just d) -> d _ -> internalError "block contains both difficulty and prevRandao" -- default codesize, default gas limit, default feescedule- pure $ Block coinbase timestamp number prd gasLimit (fromMaybe 0 baseFee) 0xffffffff FeeSchedule.berlin+ pure $ Block coinbase timestamp number prd gasLimit (fromMaybe 0 baseFee) 0xffffffff feeSchedule fetchWithSession :: Text -> Session -> Value -> IO (Maybe Value) fetchWithSession url sess x = do@@ -141,14 +142,14 @@ fetch :: Show a => RpcQuery a -> IO (Maybe a) fetch = fetchQuery n (fetchWithSession url sess) - theCode <- MaybeT $ fetch (QueryCode addr)- theNonce <- MaybeT $ fetch (QueryNonce addr)- theBalance <- MaybeT $ fetch (QueryBalance addr)+ code <- MaybeT $ fetch (QueryCode addr)+ nonce <- MaybeT $ fetch (QueryNonce addr)+ balance <- MaybeT $ fetch (QueryBalance addr) pure $- initialContract (RuntimeCode (ConcreteRuntimeCode theCode))- & set #nonce theNonce- & set #balance theBalance+ initialContract (RuntimeCode (ConcreteRuntimeCode code))+ & set #nonce (Just nonce)+ & set #balance (Lit balance) & set #external True fetchSlotWithSession@@ -181,18 +182,18 @@ sess <- Session.newAPISession fetchQuery Latest (fetchWithSession url sess) QueryChainId -http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher+http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher s http smtjobs smttimeout n url q = withSolvers Z3 smtjobs smttimeout $ \s -> oracle s (Just (n, url)) q -zero :: Natural -> Maybe Natural -> Fetcher+zero :: Natural -> Maybe Natural -> Fetcher s zero smtjobs smttimeout q = withSolvers Z3 smtjobs smttimeout $ \s -> oracle s Nothing q -- smtsolving + (http or zero)-oracle :: SolverGroup -> RpcInfo -> Fetcher+oracle :: SolverGroup -> RpcInfo -> Fetcher s oracle solvers info q = do case q of PleaseDoFFI vals continue -> case vals of@@ -207,12 +208,14 @@ -- Is is possible to satisfy the condition? continue <$> checkBranch solvers (branchcondition ./= (Lit 0)) pathconds - -- if we are using a symbolic storage model,- -- we generate a new array to the fetched contract here- PleaseFetchContract addr continue -> do+ PleaseFetchContract addr base continue -> do contract <- case info of- Nothing -> pure $ Just $ initialContract (RuntimeCode (ConcreteRuntimeCode ""))- Just (n, url) -> fetchContractFrom n url addr+ Nothing -> let+ c = case base of+ AbstractBase -> unknownContract (LitAddr addr)+ EmptyBase -> emptyContract+ in pure $ Just c+ Just (n, url) -> fetchContractFrom n url addr case contract of Just x -> pure $ continue x Nothing -> internalError $ "oracle error: " ++ show q@@ -226,7 +229,7 @@ Nothing -> internalError $ "oracle error: " ++ show q -type Fetcher = Query -> IO (EVM ())+type Fetcher s = Query s -> IO (EVM s ()) -- | Checks which branches are satisfiable, checking the pathconditions for consistency -- if the third argument is true.@@ -235,20 +238,20 @@ -- will be pruned anyway. checkBranch :: SolverGroup -> Prop -> Prop -> IO BranchCondition checkBranch solvers branchcondition pathconditions = do- checkSat solvers (assertProps [(branchcondition .&& pathconditions)]) >>= \case+ checkSat solvers (assertProps abstRefineDefault [(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 [(pathconditions .&& (PNeg branchcondition))]) >>= \case+ checkSat solvers (assertProps abstRefineDefault [(pathconditions .&& (PNeg branchcondition))]) >>= \case -- No. The condition must hold Unsat -> pure $ Case True -- Yes. Both branches possible Sat _ -> pure EVM.Types.Unknown -- Explore both branches in case of timeout EVM.Solvers.Unknown -> pure EVM.Types.Unknown- Error e -> error $ "Internal Error: SMT Solver pureed with an error: " <> T.unpack e+ Error e -> internalError $ "SMT Solver pureed with an error: " <> T.unpack e -- If the query times out, we simply explore both paths EVM.Solvers.Unknown -> pure EVM.Types.Unknown Error e -> internalError $ "SMT Solver pureed with an error: " <> T.unpack e
src/EVM/Format.hs view
@@ -30,14 +30,16 @@ , hexByteString , hexText , bsToHex+ , showVal ) where +import Prelude hiding (LT, GT)+ import EVM.Types-import EVM (cheatCode, traceForest, traceForest', traceContext)+import EVM (traceForest, traceForest', traceContext, cheatCode) import EVM.ABI (getAbiSeq, parseTypeName, AbiValue(..), AbiType(..), SolError(..), Indexed(..), Event(..)) import EVM.Dapp (DappContext(..), DappInfo(..), showTraceLocation) import EVM.Expr qualified as Expr-import EVM.Hexdump (prettyHex, paddedShowHex) import EVM.Solidity (SolcContract(..), Method(..), contractName, abiMap) import Control.Arrow ((>>>))@@ -60,17 +62,18 @@ import Data.Text.Encoding qualified as T import Data.Tree.View (showTree) import Data.Vector (Vector)+import Hexdump (prettyHex) import Numeric (showHex) import Data.ByteString.Char8 qualified as Char8 import Data.ByteString.Base16 qualified as BS16-import Witch (into, unsafeInto)+import Witch (into, unsafeInto, tryFrom) data Signedness = Signed | Unsigned deriving (Show) showDec :: Signedness -> W256 -> Text showDec signed (W256 w)- | i == into cheatCode = "<hevm cheat address>"+ | Right i' <- tryFrom i, LitAddr i' == cheatCode = "<hevm cheat address>" | (i :: Integer) == 2 ^ (256 :: Integer) - 1 = "MAX_UINT256" | otherwise = T.pack (show (i :: Integer)) where@@ -78,7 +81,7 @@ Signed -> into (signedWord w) Unsigned -> into w -showWordExact :: W256 -> Text+showWordExact :: Integral i => i -> Text showWordExact w = humanizeInteger (toInteger w) showWordExplanation :: W256 -> DappInfo -> Text@@ -110,7 +113,7 @@ showAbiValue (AbiAddress addr) = let dappinfo = ?context.info contracts = ?context.env- name = case Map.lookup addr contracts of+ name = case Map.lookup (LitAddr addr) contracts of Nothing -> "" Just contract -> let hash = maybeLitWord contract.codehash@@ -142,7 +145,7 @@ showCall :: (?context :: DappContext) => [AbiType] -> Expr Buf -> Text showCall ts (ConcreteBuf bs) = showValues ts $ ConcreteBuf (BS.drop 4 bs)-showCall _ _ = "<symbolic>"+showCall _ _ = "(<symbolic>)" showError :: (?context :: DappContext) => Expr Buf -> Text showError (ConcreteBuf bs) =@@ -187,14 +190,14 @@ formatSBinary (AbstractBuf t) = "<" <> t <> " abstract buf>" formatSBinary _ = internalError "formatSBinary: implement me" -showTraceTree :: DappInfo -> VM -> Text+showTraceTree :: DappInfo -> VM s -> Text showTraceTree dapp vm = let forest = traceForest vm traces = fmap (fmap (unpack . showTrace dapp (vm.env.contracts))) forest in pack $ concatMap showTree traces showTraceTree' :: DappInfo -> Expr End -> Text-showTraceTree' _ (ITE {}) = error "Internal Error: ITE does not contain a trace"+showTraceTree' _ (ITE {}) = internalError "ITE does not contain a trace" showTraceTree' dapp leaf = let forest = traceForest' leaf traces = fmap (fmap (unpack . showTrace dapp (traceContext leaf))) forest@@ -203,7 +206,7 @@ unindexed :: [(Text, AbiType, Indexed)] -> [AbiType] unindexed ts = [t | (_, t, NotIndexed) <- ts] -showTrace :: DappInfo -> Map Addr Contract -> Trace -> Text+showTrace :: DappInfo -> Map (Expr EAddr) Contract -> Trace -> Text showTrace dapp env trace = let ?context = DappContext { info = dapp, env = env } in let@@ -299,12 +302,12 @@ FrameTrace (CreationContext addr (Lit hash) _ _ ) -> -- FIXME: irrefutable pattern "create " <> maybeContractName (preview (ix hash % _2) dapp.solcByHash)- <> "@" <> pack (show addr)+ <> "@" <> formatAddr addr <> pos FrameTrace (CreationContext addr _ _ _ ) -> "create " <> "<unknown contract>"- <> "@" <> pack (show addr)+ <> "@" <> formatAddr addr <> pos FrameTrace (CallContext target context _ _ hash abi calldata _ _) -> let calltype = if target == context@@ -315,8 +318,8 @@ Nothing -> calltype <> case target of- 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D -> "HEVM"- _ -> pack (show target)+ LitAddr 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D -> "HEVM"+ _ -> formatAddr target <> pack "::" <> case Map.lookup (unsafeInto (fromMaybe 0x00 abi)) fullAbiMap of Just m ->@@ -342,6 +345,12 @@ <> "\x1b[0m" <> pos +formatAddr :: Expr EAddr -> Text+formatAddr = \case+ LitAddr a -> pack (show a)+ SymAddr a -> "symbolic(" <> a <> ")"+ GVar _ -> internalError "Unexpected GVar"+ getAbiTypes :: Text -> [Maybe AbiType] getAbiTypes abi = map (parseTypeName mempty) types where@@ -427,7 +436,8 @@ , indent 2 $ T.unlines . fmap formatSomeExpr $ args ] ]- MaxIterationsReached pc addr -> T.pack $ "Max Iterations Reached in contract: " <> show addr <> " pc: " <> show pc+ MaxIterationsReached pc addr -> "Max Iterations Reached in contract: " <> formatAddr addr <> " pc: " <> pack (show pc)+ JumpIntoSymbolicCode pc idx -> "Encountered a jump into a potentially symbolic code region while executing initcode. pc: " <> pack (show pc) <> " jump dst: " <> pack (show idx) formatSomeExpr :: SomeExpr -> Text formatSomeExpr (SomeExpr e) = formatExpr e@@ -436,35 +446,54 @@ formatExpr = go where go :: Expr a -> Text- go = \case- Lit w -> T.pack $ show w+ go x = T.stripEnd $ case x of+ Lit w -> T.pack $ show (into w :: Integer)+ (Var v) -> "(Var " <> T.pack (show v) <> ")"+ (GVar v) -> "(GVar " <> T.pack (show v) <> ")" LitByte w -> T.pack $ show w - ITE c t f -> rstrip . T.unlines $- [ "(ITE (" <> formatExpr c <> ")"- , indent 2 (formatExpr t)- , indent 2 (formatExpr f)+ ITE c t f -> T.unlines+ [ "(ITE"+ , indent 2 $ T.unlines+ [ formatExpr c+ , formatExpr t+ , formatExpr f+ ] , ")"] Success asserts _ buf store -> T.unlines- [ "(Return"+ [ "(Success" , indent 2 $ T.unlines [ "Data:" , indent 2 $ formatExpr buf , ""- , "Store:"- , indent 2 $ formatExpr store+ , "State:"+ , indent 2 $ T.unlines (fmap (\(k,v) ->+ T.unlines+ [ formatExpr k <> ":"+ , indent 2 $ formatExpr v+ ]) (Map.toList store)) , "Assertions:"- , indent 2 $ T.pack $ show asserts+ , indent 2 . T.unlines $ fmap formatProp asserts ] , ")" ]+ Partial asserts _ err -> T.unlines+ [ "(Partial"+ , indent 2 $ T.unlines+ [ "Reason:"+ , indent 2 $ formatPartial err+ , "Assertions:"+ , indent 2 . T.unlines $ fmap formatProp asserts+ ]+ , ")"+ ] Failure asserts _ err -> T.unlines [ "(Failure" , indent 2 $ T.unlines [ "Error:" , indent 2 $ formatError err , "Assertions:"- , indent 2 $ T.pack $ show asserts+ , indent 2 . T.unlines $ fmap formatProp asserts ] , ")" ]@@ -489,35 +518,153 @@ ] , ")" ]+ ReadByte idx buf -> T.unlines+ [ "(ReadByte"+ , indent 2 $ T.unlines+ [ "idx:"+ , indent 2 $ formatExpr idx+ , "buf: "+ , indent 2 $ formatExpr buf+ ]+ , ")"+ ] - And a b -> T.unlines- [ "(And"+ Add a b -> fmt "Add" [a, b]+ Sub a b -> fmt "Sub" [a, b]+ Mul a b -> fmt "Mul" [a, b]+ Div a b -> fmt "Div" [a, b]+ SDiv a b -> fmt "SDiv" [a, b]+ Mod a b -> fmt "Mod" [a, b]+ SMod a b -> fmt "SMod" [a, b]+ AddMod a b c -> fmt "AddMod" [a, b, c]+ MulMod a b c -> fmt "MulMod" [a, b, c]+ Exp a b -> fmt "Exp" [a, b]+ SEx a b -> fmt "SEx" [a, b]+ Min a b -> fmt "Min" [a, b]+ Max a b -> fmt "Max" [a, b]++ LT a b -> fmt "LT" [a, b]+ GT a b -> fmt "GT" [a, b]+ LEq a b -> fmt "LEq" [a, b]+ GEq a b -> fmt "GEq" [a, b]+ SLT a b -> fmt "SLT" [a, b]+ SGT a b -> fmt "SGT" [a, b]+ Eq a b -> fmt "Eq" [a, b]+ EqByte a b -> fmt "EqByte" [a, b]+ IsZero a -> fmt "IsZero" [a]++ And a b -> fmt "And" [a, b]+ Or a b -> fmt "Or" [a, b]+ Xor a b -> fmt "Xor" [a, b]+ Not a -> fmt "Not" [a]+ SHL a b -> fmt "SHL" [a, b]+ SHR a b -> fmt "SHR" [a, b]+ SAR a b -> fmt "SAR" [a, b]++ e@Origin -> T.pack (show e)+ e@Coinbase -> T.pack (show e)+ e@Timestamp -> T.pack (show e)+ e@BlockNumber -> T.pack (show e)+ e@PrevRandao -> T.pack (show e)+ e@GasLimit -> T.pack (show e)+ e@ChainId -> T.pack (show e)+ e@BaseFee -> T.pack (show e)+ e@TxValue -> T.pack (show e)+ e@(Gas {}) -> "(" <> T.pack (show e) <> ")"++ BlockHash a -> fmt "BlockHash" [a]+ Balance a -> fmt "Balance" [a]+ CodeSize a -> fmt "CodeSize" [a]+ CodeHash a -> fmt "CodeHash" [a]+++ JoinBytes zero 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 -> fmt "JoinBytes"+ [ zero+ , 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+ ]++ LogEntry addr dat topics -> T.unlines+ [ "(LogEntry" , indent 2 $ T.unlines- [ formatExpr a- , formatExpr b+ [ "addr:"+ , indent 2 $ formatExpr addr+ , "data:"+ , indent 2 $ formatExpr dat+ , "topics:"+ , indent 2 . T.unlines $ fmap formatExpr topics ] , ")" ] + a@(SymAddr {}) -> "(" <> T.pack (show a) <> ")"+ LitAddr a -> T.pack (show a)+ WAddr a -> fmt "WAddr" [a]++ BufLength b -> fmt "BufLength" [b]++ C code store bal nonce -> T.unlines+ [ "(Contract"+ , indent 2 $ T.unlines+ [ "code:"+ , indent 2 $ formatCode code+ , "storage:"+ , indent 2 $ formatExpr store+ , "balance:"+ , indent 2 $ formatExpr bal+ , "nonce:"+ , indent 2 $ formatNonce nonce+ ]+ , ")"+ ]+ -- Stores- SLoad addr slot store -> T.unlines+ SLoad slot storage -> T.unlines [ "(SLoad" , indent 2 $ T.unlines- [ "addr:"- , indent 2 $ formatExpr addr- , "slot:"+ [ "slot:" , indent 2 $ formatExpr slot- , "store:"- , indent 2 $ formatExpr store+ , "storage:"+ , indent 2 $ formatExpr storage ] , ")" ]- SStore addr slot val prev -> T.unlines+ SStore slot val prev -> T.unlines [ "(SStore" , indent 2 $ T.unlines- [ "addr:"- , indent 2 $ formatExpr addr- , "slot:"+ [ "slot:" , indent 2 $ formatExpr slot , "val:" , indent 2 $ formatExpr val@@ -525,11 +672,18 @@ , ")" , formatExpr prev ]- ConcreteStore s -> T.unlines- [ "(ConcreteStore"- , indent 2 $ T.unlines $ fmap (T.pack . show) $ Map.toList $ fmap (T.pack . show . Map.toList) s- , ")"- ]+ AbstractStore a ->+ "(AbstractStore " <> formatExpr a <> ")"+ ConcreteStore s -> if null s+ then "(ConcreteStore <empty>)"+ else T.unlines+ [ "(ConcreteStore"+ , indent 2 $ T.unlines+ [ "vals:"+ , indent 2 $ T.unlines $ fmap (T.pack . show) $ Map.toList s+ ]+ , ")"+ ] -- Buffers @@ -569,20 +723,67 @@ "" -> "(ConcreteBuf \"\")" _ -> T.unlines [ "(ConcreteBuf"- , indent 2 $ T.pack $ prettyHex 0 bs+ , indent 2 $ T.pack $ prettyHex bs , ")" ]-+ b@(AbstractBuf _) -> "(" <> T.pack (show b) <> ")" -- Hashes- Keccak b -> T.unlines- [ "(Keccak"- , indent 2 $ formatExpr b- , ")"- ]+ Keccak b -> fmt "Keccak" [b]+ SHA256 b -> fmt "SHA256" [b]+ where+ fmt nm args = T.unlines+ [ "(" <> nm+ , indent 2 $ T.unlines $ fmap formatExpr args+ , ")"+ ] - a -> T.pack $ show a+formatProp :: Prop -> Text+formatProp x = T.stripEnd $ case x of+ PEq a b -> fmt "PEq" [a, b]+ PLT a b -> fmt "PLT" [a, b]+ PGT a b -> fmt "PGT" [a, b]+ PGEq a b -> fmt "PGEq" [a, b]+ PLEq a b -> fmt "PLEq" [a, b]+ PNeg a -> fmt' "PNeg" [a]+ PAnd a b -> fmt' "PAnd" [a, b]+ POr a b -> fmt' "POr" [a, b]+ PImpl a b -> fmt' "PImpl" [a, b]+ PBool a -> T.pack (show a)+ where+ fmt nm args = T.unlines+ [ "(" <> nm+ , indent 2 $ T.unlines $ fmap formatExpr args+ , ")"+ ]+ fmt' nm args = T.unlines+ [ "(" <> nm+ , indent 2 $ T.unlines $ fmap formatProp args+ , ")"+ ] +formatNonce :: Maybe W64 -> Text+formatNonce = \case+ Just w -> T.pack $ show w+ Nothing -> "symbolic"++formatCode :: ContractCode -> Text+formatCode = \case+ UnknownCode _ -> "Unknown"+ InitCode c d -> T.unlines+ [ "(InitCode"+ , indent 2 $ T.unlines+ [ "code: " <> T.pack (bsToHex c)+ , "data: " <> formatExpr d+ ]+ , ")"+ ]+ RuntimeCode (ConcreteRuntimeCode c)+ -> "(RuntimeCode " <> T.pack (bsToHex c) <> ")"+ RuntimeCode (SymbolicRuntimeCode bs)+ -> "(RuntimeCode " <> T.pack (show (fmap formatExpr bs)) <> ")"++ strip0x :: ByteString -> ByteString strip0x bs = if "0x" `Char8.isPrefixOf` bs then Char8.drop 2 bs else bs @@ -604,4 +805,7 @@ bsToHex :: ByteString -> String bsToHex bs = concatMap (paddedShowHex 2) (BS.unpack bs) -+showVal :: AbiValue -> Text+showVal (AbiBytes _ bs) = formatBytes bs+showVal (AbiAddress addr) = T.pack . show $ addr+showVal v = T.pack . show $ v
− src/EVM/Hexdump.hs
@@ -1,129 +0,0 @@--- Copyright (c) 2011, Galois Inc. All rights reserved.--- --- Redistribution and use in source and binary forms, with or without--- modification, are permitted provided that the following conditions are met:--- --- * Redistributions of source code must retain the above copyright--- notice, this list of conditions and the following disclaimer.--- --- * Redistributions in binary form must reproduce the above--- copyright notice, this list of conditions and the following--- disclaimer in the documentation and/or other materials provided--- with the distribution.--- --- * Neither the name of Trevor Elliott nor the names of other--- contributors may be used to endorse or promote products derived--- from this software without specific prior written permission.--- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS--- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT--- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR--- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT--- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,--- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT--- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,--- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY--- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE--- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.-----module EVM.Hexdump (prettyHex, simpleHex, paddedShowHex) where--import Data.ByteString (ByteString)-import qualified Data.ByteString as B (length, unpack)-import qualified Data.ByteString.Char8 as B8 (unpack)-import Data.Char (isAscii, isControl)-import Data.List (intercalate, transpose, unfoldr)-import Numeric (showHex)--byteWidth :: Num a => a-byteWidth = 2 -- Width of an padded 'Word8'--numWordBytes :: Num a => a-numWordBytes = 4 -- Number of bytes to group into a 32-bit word---- |'prettyHex' renders a 'ByteString' as a multi-line 'String' complete with--- addressing, hex digits, and ASCII representation.------ Sample output------ @Length: 100 (0x64) bytes---0000: 4b c1 ad 8a 5b 47 d7 57 48 64 e7 cc 5e b5 2f 6e K...[G.WHd..^./n---0010: c5 b3 a4 73 44 3b 97 53 99 2d 54 e7 1b 2f 91 12 ...sD;.S.-T../..---0020: c8 1a ff c4 3b 2b 72 ea 97 e2 9f e2 93 ad 23 79 ....;+r.......#y---0030: e8 0f 08 54 02 14 fa 09 f0 2d 34 c9 08 6b e1 64 ...T.....-4..k.d---0040: d1 c5 98 7e d6 a1 98 e2 97 da 46 68 4e 60 11 15 ...~......FhN`..---0050: d8 32 c6 0b 70 f5 2e 76 7f 8d f2 3b ed de 90 c6 .2..p..v...;....---0060: 93 12 9c e1 ....@-prettyHex :: Int -> ByteString -> String-prettyHex hexDisplayWidth bs = unlines (header : body)- where- numLineWords = 4 -- Number of words to group onto a line- addressWidth = 4 -- Minimum width of a padded address- numLineBytes = numLineWords * numWordBytes -- Number of bytes on a line- replacementChar = '.' -- 'Char' to use for non-printable characters-- header = "Length: " ++ show (B.length bs)- ++ " (0x" ++ showHex (B.length bs) ") bytes"-- body = map (intercalate " ")- $ transpose [mkLineNumbers, mkHexDisplay bs, mkAsciiDump bs]-- mkHexDisplay- = padLast hexDisplayWidth- . map (intercalate " ") . group numLineWords- . map (intercalate " ") . group numWordBytes- . map (paddedShowHex byteWidth)- . B.unpack-- mkAsciiDump = group numLineBytes . cleanString . B8.unpack-- cleanString = map go- where- go x | isWorthPrinting x = x- | otherwise = replacementChar-- mkLineNumbers = [paddedShowHex addressWidth (x * numLineBytes) ++ ":"- | x <- [0 .. (B.length bs - 1) `div` numLineBytes] ]-- padLast w [x] = [x ++ replicate (w - length x) ' ']- padLast w (x:xs) = x : padLast w xs- padLast _ [] = []---- |'paddedShowHex' displays a number in hexidecimal 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- where- str = showHex n ""- pad = replicate (w - length str) '0'----- |'simpleHex' converts a 'ByteString' to a 'String' showing the octets--- grouped in 32-bit words.------ Sample output------ @4b c1 ad 8a 5b 47 d7 57@-simpleHex :: ByteString -> String-simpleHex = intercalate " "- . map (intercalate " ") . group numWordBytes- . map (paddedShowHex byteWidth)- . B.unpack---- |'isWorthPrinting' returns 'True' for non-control ascii characters.--- These characters will all fit in a single character when rendered.-isWorthPrinting :: Char -> Bool-isWorthPrinting x = isAscii x && not (isControl x)---- |'group' breaks up a list into sublists of size @n@. The last group--- may be smaller than @n@ elements. When @n@ less not positive the--- list is returned as one sublist.-group :: Int -> [a] -> [[a]]-group n- | n <= 0 = (:[])- | otherwise = unfoldr go- where- go [] = Nothing- go xs = Just (splitAt n xs)
src/EVM/Keccak.hs view
@@ -4,7 +4,7 @@ Module: EVM.Keccak Description: Expr passes to determine Keccak assumptions -}-module EVM.Keccak (keccakAssumptions) where+module EVM.Keccak (keccakAssumptions, keccakCompute) where import Control.Monad.State import Data.Set (Set)@@ -12,8 +12,9 @@ import EVM.Traversals import EVM.Types+import EVM.Expr -data BuilderState = BuilderState+newtype BuilderState = BuilderState { keccaks :: Set (Expr EWord) } deriving (Show) @@ -50,12 +51,12 @@ combine' xs (xcomb:acc) minProp :: Expr EWord -> Prop-minProp k@(Keccak _) = PGT k (Lit 50)+minProp k@(Keccak _) = PGT k (Lit 256) minProp _ = internalError "expected keccak expression" injProp :: (Expr EWord, Expr EWord) -> Prop injProp (k1@(Keccak b1), k2@(Keccak b2)) =- POr (PEq b1 b2) (PNeg (PEq k1 k2))+ 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:@@ -72,3 +73,24 @@ injectivity = fmap injProp $ combine (Set.toList st.keccaks) minValue = fmap minProp (Set.toList st.keccaks)++compute :: forall a. Expr a -> [Prop]+compute = \case+ e@(Keccak buf) -> do+ let b = simplify buf+ case keccak b of+ lit@(Lit _) -> [PEq e lit]+ _ -> []+ _ -> []++computeKeccakExpr :: forall a. Expr a -> [Prop]+computeKeccakExpr e = foldExpr compute [] e++computeKeccakProp :: Prop -> [Prop]+computeKeccakProp p = foldProp compute [] p++keccakCompute :: [Prop] -> [Expr Buf] -> [Expr Storage] -> [Prop]+keccakCompute ps buf stores =+ concatMap computeKeccakProp ps <>+ concatMap computeKeccakExpr buf <>+ concatMap computeKeccakExpr stores
− src/EVM/Patricia.hs
@@ -1,226 +0,0 @@-module EVM.Patricia where--import EVM.RLP-import EVM.Types--import Control.Monad.Free-import Control.Monad.State-import Data.ByteString (ByteString)-import Data.ByteString qualified as BS-import Data.Foldable (toList)-import Data.List (stripPrefix)-import Data.Map qualified as Map-import Data.Sequence (Seq)-import Data.Sequence qualified as Seq-import Witch (into)--data KV k v a- = Put k v a- | Get k (v -> a)- deriving (Functor)--newtype DB k v a = DB (Free (KV k v) a)- deriving (Functor, Applicative, Monad)--insertDB :: k -> v -> DB k v ()-insertDB k v = DB $ liftF $ Put k v ()--lookupDB :: k -> DB k v v-lookupDB k = DB $ liftF $ Get k id---- Collapses a series of puts and gets down to the monad of your choice-runDB :: Monad m- => (k -> v -> m ()) -- ^ The 'put' function for our desired monad- -> (k -> m v) -- ^ The 'get' function for the same monad- -> DB k v a -- ^ The puts and gets to execute- -> m a-runDB putt gett (DB ops) = go ops- where- go (Pure a) = pure a- go (Free (Put k v next)) = putt k v >> go next- go (Free (Get k handler)) = gett k >>= go . handler--type Path = [Nibble]--data Ref = Hash ByteString | Literal Node- deriving (Eq)--instance Show Ref where- show (Hash d) = show (ByteStringS d)- show (Literal n) = show n--data Node = Empty- | Shortcut Path (Either Ref ByteString)- | Full (Seq Ref) ByteString- deriving (Show, Eq)---- the function HP from Appendix C of yellow paper-encodePath :: Path -> Bool -> ByteString-encodePath p isTerminal | even (length p)- = packNibbles $ Nibble flag : Nibble 0 : p- | otherwise- = packNibbles $ Nibble (flag + 1) : p- where flag = if isTerminal then 2 else 0--rlpRef :: Ref -> RLP-rlpRef (Hash d) = BS d-rlpRef (Literal n) = rlpNode n--rlpNode :: Node -> RLP-rlpNode Empty = BS mempty-rlpNode (Shortcut path (Right val)) = List [BS $ encodePath path True, BS val]-rlpNode (Shortcut path (Left ref)) = List [BS $ encodePath path False, rlpRef ref]-rlpNode (Full refs val) = List $ toList (fmap rlpRef refs) <> [BS val]--type NodeDB = DB ByteString Node--instance Show (NodeDB Node) where- show = show--putNode :: Node -> NodeDB Ref-putNode node =- let bytes = rlpencode $ rlpNode node- digest = word256Bytes $ keccak' bytes- in if BS.length bytes < 32- then pure $ Literal node- else do- insertDB digest node- pure $ Hash digest--getNode :: Ref -> NodeDB Node-getNode (Hash d) = lookupDB d-getNode (Literal n) = pure n--lookupPath :: Ref -> Path -> NodeDB ByteString-lookupPath root path = getNode root >>= getVal path--getVal :: Path -> Node -> NodeDB ByteString-getVal _ Empty = pure BS.empty-getVal path (Shortcut nodePath ref) =- case (stripPrefix nodePath path, ref) of- (Just [], Right value) -> pure value- (Just remaining, Left key) -> lookupPath key remaining- _ -> pure BS.empty--getVal [] (Full _ val) = pure val-getVal (p:ps) (Full refs _) = lookupPath (refs `Seq.index` (into p)) ps--emptyRef :: Ref-emptyRef = Literal Empty--emptyRefs :: Seq Ref-emptyRefs = Seq.replicate 16 emptyRef--addPrefix :: Path -> Node -> NodeDB Node-addPrefix _ Empty = pure Empty-addPrefix [] node = pure node-addPrefix path (Shortcut p v) = pure $ Shortcut (path <> p) v-addPrefix path n = Shortcut path . Left <$> putNode n--insertRef :: Ref -> Path -> ByteString -> NodeDB Ref-insertRef ref p val = do root <- getNode ref- newNode <- if val == BS.empty- then delete root p- else update root p val- putNode newNode--update :: Node -> Path -> ByteString -> NodeDB Node-update Empty p new = pure $ Shortcut p (Right new)-update (Full refs _) [] new = pure (Full refs new)-update (Full refs old) (p:ps) new = do- newRef <- insertRef (refs `Seq.index` (into p)) ps new- pure $ Full (Seq.update (into p) newRef refs) old-update (Shortcut (o:os) (Right old)) [] new = do- newRef <- insertRef emptyRef os old- pure $ Full (Seq.update (into o) newRef emptyRefs) new-update (Shortcut [] (Right old)) (p:ps) new = do- newRef <- insertRef emptyRef ps new- pure $ Full (Seq.update (into p) newRef emptyRefs) old-update (Shortcut [] (Right _)) [] new =- pure $ Shortcut [] (Right new)-update (Shortcut (o:os) to) (p:ps) new | o == p- = update (Shortcut os to) ps new >>= addPrefix [o]- | otherwise = do- oldRef <- case to of- (Left ref) -> getNode ref >>= addPrefix os >>= putNode- (Right val) -> insertRef emptyRef os val- newRef <- insertRef emptyRef ps new- let refs = Seq.update (into p) newRef $ Seq.update (into o) oldRef emptyRefs- pure $ Full refs BS.empty-update (Shortcut (o:os) (Left ref)) [] new = do- newRef <- getNode ref >>= addPrefix os >>= putNode- pure $ Full (Seq.update (into o) newRef emptyRefs) new-update (Shortcut cut (Left ref)) ps new = do- newRef <- insertRef ref ps new- pure $ Shortcut cut (Left newRef)--delete :: Node -> Path -> NodeDB Node-delete Empty _ = pure Empty-delete (Shortcut [] (Right _)) [] = pure Empty-delete n@(Shortcut [] (Right _)) _ = pure n-delete (Shortcut [] (Left ref)) p = do node <- getNode ref- delete node p-delete n@(Shortcut _ _) [] = pure n-delete n@(Shortcut (o:os) to) (p:ps) | p == o- = delete (Shortcut os to) ps >>= addPrefix [o]- | otherwise- = pure n-delete (Full refs _) [] | refs == emptyRefs- = pure Empty- | otherwise- = pure (Full refs BS.empty)-delete (Full refs val) (p:ps) = do- newRef <- insertRef (refs `Seq.index` (into p)) ps BS.empty- let newRefs = Seq.update (into p) newRef refs- nonEmpties = filter (\(_, ref) -> ref /= emptyRef) $ zip [0..15] $ toList newRefs- case (nonEmpties, BS.null val) of- ([], True) -> pure Empty- ([(n, ref)], True) -> getNode ref >>= addPrefix [Nibble n]- _ -> pure $ Full newRefs val--insert :: Ref -> ByteString -> ByteString -> NodeDB Ref-insert ref key = insertRef ref (unpackNibbles key)--lookupIn :: Ref -> ByteString -> NodeDB ByteString-lookupIn ref bs = lookupPath ref $ unpackNibbles bs--type Trie = StateT Ref NodeDB--runTrie :: DB ByteString ByteString a -> Trie a-runTrie = runDB putDB getDB- where- putDB key val = do- ref <- get- newRef <- lift $ insert ref key val- put newRef- getDB key = do- ref <- get- lift $ lookupIn ref key--type MapDB k v a = StateT (Map.Map k v) Maybe a--runMapDB :: Ord k => DB k v a -> MapDB k v a-runMapDB = runDB putDB getDB- where- getDB key = do- mmap <- get- lift $ Map.lookup key mmap- putDB key value = do- mmap <- get- let newMap = Map.insert key value mmap- put newMap---insertValues :: [(ByteString, ByteString)] -> Maybe Ref-insertValues inputs =- let trie = runTrie $ mapM_ insertPair inputs- mapDB = runMapDB $ runStateT trie (Literal Empty)- result = snd <$> evalStateT mapDB Map.empty- insertPair (key, value) = insertDB key value- in result--calcRoot :: [(ByteString, ByteString)] -> Maybe ByteString-calcRoot vs = case insertValues vs of- Just (Hash b) -> Just b- Just (Literal n) -> Just $ word256Bytes $ keccak' $ rlpencode $ rlpNode n- Nothing -> Nothing
src/EVM/SMT.hs view
@@ -12,15 +12,16 @@ import Control.Monad import Data.Containers.ListUtils (nubOrd) import Data.ByteString (ByteString)-import Data.Bifunctor (second) import Data.ByteString qualified as BS import Data.List qualified as List import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty qualified as NonEmpty import Data.String.Here-import Data.Maybe (fromJust)-import Data.Map (Map)-import Data.Map qualified as Map+import Data.Maybe (fromJust, fromMaybe)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Set (Set)+import Data.Set qualified as Set import Data.Word (Word8) import Data.Text.Lazy (Text) import Data.Text qualified as TS@@ -30,11 +31,12 @@ 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 EVM.CSE import EVM.Expr (writeByte, bufLengthEnv, containsNode, bufLength, minLength, inRange) import EVM.Expr qualified as Expr-import EVM.Keccak (keccakAssumptions)+import EVM.Keccak (keccakAssumptions, keccakCompute) import EVM.Traversals import EVM.Types @@ -42,22 +44,30 @@ -- ** Encoding ** ---------------------------------------------------------------------------------- --- variable names in SMT that we want to get values for+-- | Data that we need to construct a nice counterexample data CexVars = CexVars- { calldata :: [Text]- , buffers :: Map Text (Expr EWord) -- buffers and guesses at their maximum size- , storeReads :: [(Expr EWord, Expr EWord)] -- a list of relevant store reads+ { -- | variable names that we need models for to reconstruct calldata+ calldata :: [Text]+ -- | symbolic address names+ , addrs :: [Text]+ -- | buffer names and guesses at their maximum size+ , buffers :: Map Text (Expr EWord)+ -- | reads from abstract storage+ , storeReads :: Map (Expr EAddr) (Set (Expr EWord))+ -- | the names of any block context variables , blockContext :: [Text]+ -- | the names of any tx context variables , txContext :: [Text] } deriving (Eq, Show) instance Semigroup CexVars where- (CexVars a b c d e) <> (CexVars a2 b2 c2 d2 e2) = CexVars (a <> a2) (b <> b2) (c <> c2) (d <> d2) (e <> e2)+ (CexVars a b c d e f) <> (CexVars a2 b2 c2 d2 e2 f2) = CexVars (a <> a2) (b <> b2) (c <> c2) (d <> d2) (e <> e2) (f <> f2) instance Monoid CexVars where mempty = CexVars { calldata = mempty+ , addrs = mempty , buffers = mempty , storeReads = mempty , blockContext = mempty@@ -83,13 +93,19 @@ -- | a final post shrinking cex, buffers here are all represented as concrete bytestrings data SMTCex = SMTCex { vars :: Map (Expr EWord) W256+ , addrs :: Map (Expr EAddr) Addr , buffers :: Map (Expr Buf) BufModel- , store :: Map W256 (Map W256 W256)+ , store :: Map (Expr EAddr) (Map W256 W256) , blockContext :: Map (Expr EWord) W256 , txContext :: Map (Expr EWord) W256 } deriving (Eq, Show) ++-- | 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)+ flattenBufs :: SMTCex -> Maybe SMTCex flattenBufs cex = do bs <- mapM collapse cex.buffers@@ -109,17 +125,17 @@ getVar :: SMTCex -> TS.Text -> W256 getVar cex name = fromJust $ Map.lookup (Var name) cex.vars -data SMT2 = SMT2 [Builder] CexVars+data SMT2 = SMT2 [Builder] RefinementEqs CexVars deriving (Eq, Show) instance Semigroup SMT2 where- (SMT2 a b) <> (SMT2 a2 b2) = SMT2 (a <> a2) (b <> b2)+ (SMT2 a (RefinementEqs b) c) <> (SMT2 a2 (RefinementEqs b2) c2) = SMT2 (a <> a2) (RefinementEqs (b <> b2)) (c <> c2) instance Monoid SMT2 where- mempty = SMT2 mempty mempty+ mempty = SMT2 mempty mempty mempty formatSMT2 :: SMT2 -> Text-formatSMT2 (SMT2 ls _) = T.unlines (fmap toLazyText ls)+formatSMT2 (SMT2 ls _ _) = T.unlines (fmap toLazyText ls) -- | Reads all intermediate variables from the builder state and produces SMT declaring them as constants declareIntermediates :: BufEnv -> StoreEnv -> SMT2@@ -128,113 +144,175 @@ encBs = Map.mapWithKey encodeBuf bufs sorted = List.sortBy compareFst $ Map.toList $ encSs <> encBs decls = fmap snd sorted- in SMT2 ([fromText "; intermediate buffers & stores"] <> decls) mempty+ smt2 = (SMT2 [fromText "; intermediate buffers & stores"] mempty mempty):decls+ in foldr (<>) (SMT2[""] mempty mempty) smt2 where compareFst (l, _) (r, _) = compare l r encodeBuf n expr =- "(define-const buf" <> (fromString . show $ n) <> " Buf " <> exprToSMT expr <> ")\n" <> encodeBufLen n expr+ SMT2 ["(define-const buf" <> (fromString . show $ n) <> " Buf " <> exprToSMT expr <> ")\n"] mempty mempty <> encodeBufLen n expr encodeBufLen n expr =- "(define-const buf" <> (fromString . show $ n) <>"_length" <> " (_ BitVec 256) " <> exprToSMT (bufLengthEnv bufs True expr) <> ")"+ SMT2 ["(define-const buf" <> (fromString . show $ n) <>"_length" <> " (_ BitVec 256) " <> exprToSMT (bufLengthEnv bufs True expr) <> ")"] mempty mempty encodeStore n expr =- "(define-const store" <> (fromString . show $ n) <> " Storage " <> exprToSMT expr <> ")"+ SMT2 ["(define-const store" <> (fromString . show $ n) <> " Storage " <> exprToSMT expr <> ")"] mempty mempty -assertProps :: [Prop] -> SMT2-assertProps ps =- let encs = map propToSMT ps_elim+data AbstState = AbstState+ { words :: Map (Expr EWord) Int+ , count :: Int+ }+ deriving (Show)++abstractAwayProps :: AbstRefineConfig -> [Prop] -> ([Prop], AbstState)+abstractAwayProps abstRefineConfig 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 -> pure e+ where+ abstrExpr e = do+ s <- get+ case Map.lookup e s.words of+ Just v -> pure (Var (TS.pack ("abst_" ++ show v)))+ Nothing -> do+ let+ next = s.count+ bs' = Map.insert e next s.words+ put $ s{words=bs', count=next+1}+ pure $ Var (TS.pack ("abst_" ++ show next))++smt2Line :: Builder -> SMT2+smt2Line txt = SMT2 [txt] mempty mempty++assertProps :: AbstRefineConfig -> [Prop] -> SMT2+assertProps conf ps = assertPropsNoSimp conf (Expr.simplifyProps ps)++-- Note: we need a version that does NOT call simplify or simplifyProps,+-- 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 =+ let encs = map propToSMT psElimAbst+ abstSMT = map propToSMT abstProps intermediates = declareIntermediates bufs stores in prelude- <> (declareBufs ps_elim bufs stores)- <> SMT2 [""] mempty+ <> (declareAbstractStores abstractStores)+ <> smt2Line ""+ <> (declareAddrs addresses)+ <> smt2Line ""+ <> (declareBufs psElim bufs stores)+ <> smt2Line "" <> (declareVars . nubOrd $ foldl (<>) [] allVars)- <> SMT2 [""] mempty+ <> smt2Line "" <> (declareFrameContext . nubOrd $ foldl (<>) [] frameCtx)- <> SMT2 [""] mempty+ <> smt2Line "" <> (declareBlockContext . nubOrd $ foldl (<>) [] blockCtx)- <> SMT2 [""] mempty+ <> smt2Line "" <> intermediates- <> SMT2 [""] mempty+ <> smt2Line "" <> keccakAssumes <> readAssumes- <> SMT2 [""] mempty- <> SMT2 (fmap (\p -> "(assert " <> p <> ")") encs) mempty- <> SMT2 [] mempty{ storeReads = storageReads }+ <> smt2Line ""+ <> SMT2 (fmap (\p -> "(assert " <> p <> ")") encs) mempty mempty+ <> SMT2 mempty (RefinementEqs $ fmap (\p -> "(assert " <> p <> ")") abstSMT) mempty+ <> SMT2 mempty mempty mempty { storeReads = storageReads } where- (ps_elim, bufs, stores) = eliminateProps ps+ (psElim, bufs, stores) = eliminateProps ps+ (psElimAbst, abst@(AbstState abstExprToInt _)) = if abstRefineConfig.arith || abstRefineConfig.mem+ then abstractAwayProps abstRefineConfig psElim+ else (psElim, AbstState mempty 0) - allVars = fmap referencedVars' ps_elim <> fmap referencedVars bufVals <> fmap referencedVars storeVals- frameCtx = fmap referencedFrameContext' ps_elim <> fmap referencedFrameContext bufVals <> fmap referencedFrameContext storeVals- blockCtx = fmap referencedBlockContext' ps_elim <> fmap referencedBlockContext bufVals <> fmap referencedBlockContext storeVals+ abstProps = map toProp (Map.toList abstExprToInt)+ where+ 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++ abstrVars :: AbstState -> [Builder]+ abstrVars (AbstState b _) = map ((\v->fromString ("abst_" ++ show v)) . snd) (Map.toList b)+ bufVals = Map.elems bufs storeVals = Map.elems stores-- storageReads = nubOrd $ concatMap findStorageReads ps+ storageReads = Map.unionsWith (<>) $ fmap findStorageReads ps+ abstractStores = Set.toList $ Set.unions (fmap referencedAbstractStores ps)+ addresses = Set.toList $ Set.unions (fmap referencedWAddrs ps) keccakAssumes- = SMT2 ["; keccak assumptions"] mempty- <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakAssumptions ps_elim bufVals storeVals)) mempty+ = smt2Line "; keccak assumptions"+ <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakAssumptions psElim bufVals storeVals)) mempty mempty+ <> smt2Line "; keccak computations"+ <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakCompute psElim bufVals storeVals)) mempty mempty readAssumes- = SMT2 ["; read assumptions"] mempty- <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (assertReads ps_elim bufs stores)) mempty--referencedBufsGo :: Expr a -> [Builder]-referencedBufsGo = \case- AbstractBuf s -> [fromText s]- _ -> []--referencedBufs :: Expr a -> [Builder]-referencedBufs expr = nubOrd $ foldExpr referencedBufsGo [] expr--referencedBufs' :: Prop -> [Builder]-referencedBufs' prop = nubOrd $ foldProp referencedBufsGo [] prop--referencedVarsGo :: Expr a -> [Builder]-referencedVarsGo = \case- Var s -> [fromText s]- _ -> []--referencedVars :: Expr a -> [Builder]-referencedVars expr = nubOrd $ foldExpr referencedVarsGo [] expr--referencedVars' :: Prop -> [Builder]-referencedVars' prop = nubOrd $ foldProp referencedVarsGo [] prop--referencedFrameContextGo :: Expr a -> [(Builder, [Prop])]-referencedFrameContextGo = \case- CallValue a -> [(fromLazyText $ T.append "callvalue_" (T.pack . show $ a), [])]- Caller a -> [(fromLazyText $ T.append "caller_" (T.pack . show $ a), [inRange 160 (Caller a)])]- Address a -> [(fromLazyText $ T.append "address_" (T.pack . show $ a), [inRange 160 (Address a)])]- Balance {} -> internalError "TODO: BALANCE"- SelfBalance {} -> internalError "TODO: SELFBALANCE"- Gas {} -> internalError "TODO: GAS"- _ -> []+ = smt2Line "; read assumptions"+ <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (assertReads psElim bufs stores)) mempty mempty -referencedFrameContext :: Expr a -> [(Builder, [Prop])]-referencedFrameContext expr = nubOrd $ foldExpr referencedFrameContextGo [] expr+referencedAbstractStores :: TraversableTerm a => a -> Set Builder+referencedAbstractStores term = foldTerm go mempty term+ where+ go = \case+ AbstractStore s -> Set.singleton (storeName s)+ _ -> mempty -referencedFrameContext' :: Prop -> [(Builder, [Prop])]-referencedFrameContext' prop = nubOrd $ foldProp referencedFrameContextGo [] prop+referencedWAddrs :: TraversableTerm a => a -> Set Builder+referencedWAddrs term = foldTerm go mempty term+ where+ go = \case+ WAddr(a@(SymAddr _)) -> Set.singleton (formatEAddr a)+ _ -> mempty +referencedBufs :: TraversableTerm a => a -> [Builder]+referencedBufs expr = nubOrd $ foldTerm go [] expr+ where+ go :: Expr a -> [Builder]+ go = \case+ AbstractBuf s -> [fromText s]+ _ -> [] -referencedBlockContextGo :: Expr a -> [(Builder, [Prop])]-referencedBlockContextGo = \case- Origin -> [("origin", [inRange 160 Origin])]- Coinbase -> [("coinbase", [inRange 160 Coinbase])]- Timestamp -> [("timestamp", [])]- BlockNumber -> [("blocknumber", [])]- PrevRandao -> [("prevrandao", [])]- GasLimit -> [("gaslimit", [])]- ChainId -> [("chainid", [])]- BaseFee -> [("basefee", [])]- _ -> []+referencedVars :: TraversableTerm a => a -> [Builder]+referencedVars expr = nubOrd $ foldTerm go [] expr+ where+ go :: Expr a -> [Builder]+ go = \case+ Var s -> [fromText s]+ _ -> [] -referencedBlockContext :: Expr a -> [(Builder, [Prop])]-referencedBlockContext expr = nubOrd $ foldExpr referencedBlockContextGo [] expr+referencedFrameContext :: TraversableTerm a => a -> [(Builder, [Prop])]+referencedFrameContext expr = nubOrd $ foldTerm go [] expr+ where+ go :: Expr a -> [(Builder, [Prop])]+ go = \case+ TxValue -> [(fromString "txvalue", [])]+ v@(Balance a) -> [(fromString "balance_" <> formatEAddr a, [PLT v (Lit $ 2 ^ (96 :: Int))])]+ Gas {} -> internalError "TODO: GAS"+ _ -> [] -referencedBlockContext' :: Prop -> [(Builder, [Prop])]-referencedBlockContext' prop = nubOrd $ foldProp referencedBlockContextGo [] prop+referencedBlockContext :: TraversableTerm a => a -> [(Builder, [Prop])]+referencedBlockContext expr = nubOrd $ foldTerm go [] expr+ where+ go :: Expr a -> [(Builder, [Prop])]+ go = \case+ Origin -> [("origin", [inRange 160 Origin])]+ Coinbase -> [("coinbase", [inRange 160 Coinbase])]+ Timestamp -> [("timestamp", [])]+ BlockNumber -> [("blocknumber", [])]+ PrevRandao -> [("prevrandao", [])]+ GasLimit -> [("gaslimit", [])]+ ChainId -> [("chainid", [])]+ BaseFee -> [("basefee", [])]+ _ -> [] -- | This function overapproximates the reads from the abstract -- storage. Potentially, it can return locations that do not read a@@ -242,18 +320,18 @@ -- 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 -> [(Expr EWord, Expr EWord)]-findStorageReads = foldProp go []+findStorageReads :: Prop -> Map (Expr EAddr) (Set (Expr EWord))+findStorageReads p = Map.fromListWith (<>) $ foldProp go mempty p where- go :: Expr a -> [(Expr EWord, Expr EWord)]+ go :: Expr a -> [(Expr EAddr, Set (Expr EWord))] go = \case- SLoad addr slot storage -> [(addr, slot) | containsNode isAbstractStore storage]+ SLoad slot store ->+ [((fromMaybe (error $ "Internal Error: could not extract address from: " <> show store) (Expr.getAddr 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)] findBufferAccess = foldl (foldTerm go) mempty where@@ -295,7 +373,7 @@ allReads = nubOrd $ findBufferAccess props <> findBufferAccess (Map.elems benv) <> findBufferAccess (Map.elems senv) -- we can have buffers that are not read from but are still mentioned via BufLength in some branch condition -- we assign a default read hint of 4 to start with in these cases (since in most cases we will need at least 4 bytes to produce a counterexample)- allBufs = Map.fromList . fmap (, Lit 4) . fmap toLazyText . nubOrd . concat $ fmap referencedBufs' props <> fmap referencedBufs (Map.elems benv) <> fmap referencedBufs (Map.elems senv)+ allBufs = Map.fromList . fmap (, Lit 4) . fmap toLazyText . nubOrd . concat $ fmap referencedBufs props <> fmap referencedBufs (Map.elems benv) <> fmap referencedBufs (Map.elems senv) bufMap = Map.unionWith Expr.max (foldl addBound mempty allReads) allBufs @@ -317,7 +395,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)) cexvars+declareBufs props bufEnv storeEnv = SMT2 ("; buffers" : fmap declareBuf allBufs <> ("; buffer lengths" : fmap declareLength allBufs)) mempty cexvars where cexvars = (mempty :: CexVars){ buffers = discoverMaxReads props bufEnv storeEnv } allBufs = fmap fromLazyText $ Map.keys cexvars.buffers@@ -326,30 +404,41 @@ -- Given a list of variable names, create an SMT2 object with the variables declared declareVars :: [Builder] -> SMT2-declareVars names = SMT2 (["; variables"] <> fmap declare names) cexvars+declareVars names = SMT2 (["; variables"] <> fmap declare names) mempty cexvars 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+ 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) cexvars+declareFrameContext names = SMT2 (["; frame context"] <> concatMap declare names) mempty cexvars 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+ where+ declare n = "(declare-const " <> n <> " Storage)" declareBlockContext :: [(Builder, [Prop])] -> SMT2-declareBlockContext names = SMT2 (["; block context"] <> concatMap declare names) cexvars+declareBlockContext names = SMT2 (["; block context"] <> concatMap declare names) mempty cexvars 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 = (flip SMT2) mempty $ fmap (fromLazyText . T.drop 2) . T.lines $ [i|+prelude = SMT2 src mempty mempty+ where+ src = fmap (fromLazyText . T.drop 2) . T.lines $ [i| ; logic ; TODO: this creates an error when used with z3? ;(set-logic QF_AUFBV)@@ -358,15 +447,15 @@ ; types (define-sort Byte () (_ BitVec 8)) (define-sort Word () (_ BitVec 256))+ (define-sort Addr () (_ BitVec 160)) (define-sort Buf () (Array Word Byte)) - ; address -> slot -> value- (define-sort Storage () (Array Word (Array Word Word)))+ ; slot -> value+ (define-sort Storage () (Array Word Word)) ; hash functions (declare-fun keccak (Buf) Word) (declare-fun sha256 (Buf) Word)- (define-fun max ((a (_ BitVec 256)) (b (_ BitVec 256))) (_ BitVec 256) (ite (bvult a b) b a)) ; word indexing@@ -442,9 +531,6 @@ )))))))))))))))))))))))))))))))) ) - ; buffers- (define-const emptyBuf Buf ((as const Buf) #b00000000))- (define-fun readWord ((idx Word) (buf Buf)) Word (concat (select buf idx)@@ -521,6 +607,7 @@ ; block context (declare-fun blockhash (Word) Word)+ (declare-fun codesize (Addr) Word) ; macros (define-fun signext ( (b Word) (val Word)) Word@@ -555,23 +642,14 @@ (ite (= b (_ bv28 256)) ((_ sign_extend 24 ) ((_ extract 231 0) val)) (ite (= b (_ bv29 256)) ((_ sign_extend 16 ) ((_ extract 239 0) val)) (ite (= b (_ bv30 256)) ((_ sign_extend 8 ) ((_ extract 247 0) val)) val))))))))))))))))))))))))))))))))-- ; storage- (declare-const abstractStore Storage)- (define-const emptyStore Storage ((as const Storage) ((as const (Array (_ BitVec 256) (_ BitVec 256))) #x0000000000000000000000000000000000000000000000000000000000000000)))-- (define-fun sstore ((addr Word) (key Word) (val Word) (storage Storage)) Storage (store storage addr (store (select storage addr) key val)))-- (define-fun sload ((addr Word) (key Word) (storage Storage)) Word (select (select storage addr) key)) |] - exprToSMT :: Expr a -> Builder exprToSMT = \case Lit w -> fromLazyText $ "(_ bv" <> (T.pack $ show (into w :: Integer)) <> " 256)" Var s -> fromText s- GVar (BufVar n) -> fromLazyText $ "buf" <> (T.pack . show $ n)- GVar (StoreVar n) -> fromLazyText $ "store" <> (T.pack . show $ n)+ GVar (BufVar n) -> fromString $ "buf" <> (show n)+ GVar (StoreVar n) -> fromString $ "store" <> (show n) JoinBytes z o two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen@@ -660,14 +738,16 @@ let enc = exprToSMT a in "(sha256 " <> enc <> ")" - CallValue a -> fromLazyText $ T.append "callvalue_" (T.pack . show $ a)- Caller a -> fromLazyText $ T.append "caller_" (T.pack . show $ a)- Address a -> fromLazyText $ T.append "address_" (T.pack . show $ a)+ TxValue -> fromString "txvalue"+ Balance a -> fromString "balance_" <> formatEAddr a Origin -> "origin" BlockHash a -> let enc = exprToSMT a in "(blockhash " <> enc <> ")"+ CodeSize a ->+ let enc = exprToSMT a in+ "(codesize " <> enc <> ")" Coinbase -> "coinbase" Timestamp -> "timestamp" BlockNumber -> "blocknumber"@@ -676,6 +756,9 @@ ChainId -> "chainid" BaseFee -> "basefee" + a@(SymAddr _) -> formatEAddr a+ WAddr(a@(SymAddr _)) -> "(concat (_ bv0 96)" `sp` exprToSMT a `sp` ")"+ LitByte b -> fromLazyText $ "(_ bv" <> T.pack (show (into b :: Integer)) <> " 8)" IndexWord idx w -> case idx of Lit n -> if n >= 0 && n < 32@@ -686,7 +769,7 @@ _ -> op2 "indexWord" idx w ReadByte idx src -> op2 "select" src idx - ConcreteBuf "" -> "emptyBuf"+ ConcreteBuf "" -> "((as const Buf) #b00000000)" ConcreteBuf bs -> writeBytes bs mempty AbstractBuf s -> fromText s ReadWord idx prev -> op2 "readWord" idx prev@@ -705,16 +788,14 @@ "(writeWord " <> encIdx `sp` encVal `sp` encPrev <> ")" CopySlice srcIdx dstIdx size src dst -> copySlice srcIdx dstIdx size (exprToSMT src) (exprToSMT dst)- EmptyStore -> "emptyStore" ConcreteStore s -> encodeConcreteStore s- AbstractStore -> "abstractStore"- SStore addr idx val prev ->- let encAddr = exprToSMT addr- encIdx = exprToSMT idx+ AbstractStore a -> storeName a+ SStore idx val prev ->+ let encIdx = exprToSMT idx encVal = exprToSMT val encPrev = exprToSMT prev in- "(sstore" `sp` encAddr `sp` encIdx `sp` encVal `sp` encPrev <> ")"- SLoad addr idx store -> op3 "sload" addr idx store+ "(store" `sp` encPrev `sp` encIdx `sp` encVal <> ")"+ SLoad idx store -> op2 "select" store idx a -> internalError $ "TODO: implement: " <> show a where@@ -725,11 +806,6 @@ let aenc = exprToSMT a benc = exprToSMT b in "(" <> op `sp` aenc `sp` benc <> ")"- op3 op a b c =- let aenc = exprToSMT a- benc = exprToSMT b- cenc = exprToSMT c in- "(" <> op `sp` aenc `sp` benc `sp` cenc <> ")" op2CheckZero op a b = let aenc = exprToSMT a benc = exprToSMT b in@@ -825,20 +901,28 @@ idxSMT = exprToSMT . Lit . unsafeInto $ idx in (idx + 1, "(store " <> inner `sp` idxSMT `sp` byteSMT <> ")") -encodeConcreteStore :: Map W256 (Map W256 W256) -> Builder-encodeConcreteStore s = foldl encodeWrite "emptyStore" writes+encodeConcreteStore :: Map W256 W256 -> Builder+encodeConcreteStore s = foldl encodeWrite "((as const Storage) #x0000000000000000000000000000000000000000000000000000000000000000)" (Map.toList s) where- asList = fmap (second Map.toList) $ Map.toList s- writes = concatMap (\(addr, ws) -> fmap (\(k, v) -> (addr, k, v)) ws) asList- encodeWrite prev (addr, key, val) = let- encAddr = exprToSMT (Lit addr)+ encodeWrite prev (key, val) = let encKey = exprToSMT (Lit key) encVal = exprToSMT (Lit val)- in "(sstore " <> encAddr `sp` encKey `sp` encVal `sp` prev <> ")"+ in "(store " <> prev `sp` encKey `sp` encVal <> ")" +storeName :: Expr EAddr -> Builder+storeName a = fromString ("baseStore_") <> formatEAddr a +formatEAddr :: Expr EAddr -> Builder+formatEAddr = \case+ LitAddr a -> fromString ("litaddr_" <> show a)+ SymAddr a -> fromText ("symaddr_" <> a)+ GVar _ -> internalError "Unexpected GVar"++ -- ** Cex parsing ** -------------------------------------------------------------------------------- +parseAddr :: SpecConstant -> Addr+parseAddr = parseSC parseW256 :: SpecConstant -> W256 parseW256 = parseSC@@ -860,6 +944,12 @@ parseVar :: TS.Text -> Expr EWord parseVar = Var +parseEAddr :: TS.Text -> Expr EAddr+parseEAddr name+ | Just a <- TS.stripPrefix "litaddr_" name = LitAddr (read (TS.unpack a))+ | Just a <- TS.stripPrefix "symaddr_" name = SymAddr a+ | otherwise = internalError $ "cannot parse: " <> show name+ parseBlockCtx :: TS.Text -> Expr EWord parseBlockCtx "origin" = Origin parseBlockCtx "coinbase" = Coinbase@@ -871,33 +961,35 @@ parseBlockCtx "basefee" = BaseFee parseBlockCtx t = internalError $ "cannot parse " <> (TS.unpack t) <> " into an Expr" -parseFrameCtx :: TS.Text -> Expr EWord-parseFrameCtx name = case TS.unpack name of- ('c':'a':'l':'l':'v':'a':'l':'u':'e':'_':frame) -> CallValue (read frame)- ('c':'a':'l':'l':'e':'r':'_':frame) -> Caller (read frame)- ('a':'d':'d':'r':'e':'s':'s':'_':frame) -> Address (read frame)- t -> internalError $ "cannot parse " <> t <> " into an Expr"+parseTxCtx :: TS.Text -> Expr EWord+parseTxCtx name+ | name == "txvalue" = TxValue+ | Just a <- TS.stripPrefix "balance_" name = Balance (parseEAddr a)+ | otherwise = internalError $ "cannot parse " <> (TS.unpack name) <> " into an Expr" +getAddrs :: (TS.Text -> Expr EAddr) -> (Text -> IO Text) -> [TS.Text] -> IO (Map (Expr EAddr) Addr)+getAddrs parseName getVal names = Map.mapKeys parseName <$> foldM (getOne parseAddr getVal) mempty names+ getVars :: (TS.Text -> Expr EWord) -> (Text -> IO Text) -> [TS.Text] -> IO (Map (Expr EWord) W256)-getVars parseFn getVal names = Map.mapKeys parseFn <$> foldM getOne mempty names- where- getOne :: Map TS.Text W256 -> TS.Text -> IO (Map TS.Text W256)- getOne acc name = do- raw <- getVal (T.fromStrict name)- let- parsed = case parseCommentFreeFileMsg getValueRes (T.toStrict raw) of- Right (ResSpecific (valParsed :| [])) -> valParsed- r -> parseErr r- val = case parsed of- (TermQualIdentifier (- Unqualified (IdSymbol symbol)),- TermSpecConstant sc)- -> if symbol == name- then parseW256 sc- else internalError "solver did not return model for requested value"- r -> parseErr r- pure $ Map.insert name val acc+getVars parseName getVal names = Map.mapKeys parseName <$> foldM (getOne parseW256 getVal) mempty names +getOne :: (SpecConstant -> a) -> (Text -> IO Text) -> Map TS.Text a -> TS.Text -> IO (Map TS.Text a)+getOne parseVal getVal acc name = do+ raw <- getVal (T.fromStrict name)+ let+ parsed = case parseCommentFreeFileMsg getValueRes (T.toStrict raw) of+ Right (ResSpecific (valParsed :| [])) -> valParsed+ r -> parseErr r+ val = case parsed of+ (TermQualIdentifier (+ Unqualified (IdSymbol symbol)),+ TermSpecConstant sc)+ -> if symbol == name+ then parseVal sc+ else internalError "solver did not return model for requested value"+ r -> parseErr r+ pure $ Map.insert name val acc+ -- | Queries the solver for models for each of the expressions representing the -- max read index for a given buffer queryMaxReads :: (Text -> IO Text) -> Map Text (Expr EWord) -> IO (Map Text W256)@@ -973,35 +1065,35 @@ <> " in environment mapping" p -> parseErr p -getStore :: (Text -> IO Text) -> [(Expr EWord, Expr EWord)] -> IO (Map W256 (Map W256 W256))-getStore getVal sreads = do- raw <- getVal "abstractStore"- let parsed = case parseCommentFreeFileMsg getValueRes (T.toStrict raw) of- Right (ResSpecific (valParsed :| [])) -> valParsed- r -> parseErr r- -- first interpret SMT term as a function- fun = case parsed of- (TermQualIdentifier (Unqualified (IdSymbol symbol)), term) ->- if symbol == "abstractStore"- then interpret2DArray Map.empty term- else internalError "solver did not return model for requested value"- r -> parseErr r-- -- then create a map by adding only the locations that are read by the program- foldM (\m (addr, slot) -> do- addr' <- queryValue getVal addr- slot' <- queryValue getVal slot- pure $ addElem addr' slot' m fun) Map.empty sreads-- where-- addElem :: W256 -> W256 -> Map W256 (Map W256 W256) -> (W256 -> W256 -> W256) -> Map W256 (Map W256 W256)- addElem addr slot store fun =- case Map.lookup addr store of- Just m -> Map.insert addr (Map.insert slot (fun addr slot) m) store- Nothing -> Map.insert addr (Map.singleton slot (fun addr slot)) store+-- | 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+-- concretized storage+getStore+ :: (Text -> IO Text)+ -> Map (Expr EAddr) (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)+ raw <- getVal name+ let parsed = case parseCommentFreeFileMsg getValueRes (T.toStrict raw) of+ Right (ResSpecific (valParsed :| [])) -> valParsed+ r -> parseErr r+ -- first interpret SMT term as a function+ fun = case parsed of+ (TermQualIdentifier (Unqualified (IdSymbol symbol)), term) ->+ if symbol == (T.toStrict name)+ then interpret1DArray Map.empty term+ else internalError "solver did not return model for requested value"+ r -> parseErr r + -- then create a map by adding only the locations that are read by the program+ store <- foldM (\m slot -> do+ slot' <- queryValue getVal slot+ pure $ Map.insert slot' (fun slot') m) Map.empty slots+ pure (addr, store) +-- | Ask the solver to give us the concrete value of an arbitrary abstract word queryValue :: (Text -> IO Text) -> Expr EWord -> IO W256 queryValue _ (Lit w) = pure w queryValue getVal w = do@@ -1014,8 +1106,6 @@ _ -> internalError $ "cannot parse model for: " <> show w r -> parseErr r -- -- | Interpret an N-dimensional array as a value of type a. -- Parameterized by an interpretation function for array values. interpretNDArray :: (Map Symbol Term -> Term -> a) -> (Map Symbol Term) -> Term -> (W256 -> a)@@ -1057,7 +1147,3 @@ Just t -> interpretW256 env t Nothing -> internalError "unknown identifier, cannot parse array" interpretW256 _ t = internalError $ "cannot parse array value. Unexpected term: " <> (show t)---- | Interpret an 2-dimensional array as a function-interpret2DArray :: (Map Symbol Term) -> Term -> (W256 -> W256 -> W256)-interpret2DArray = interpretNDArray interpret1DArray
src/EVM/Solidity.hs view
@@ -1,13 +1,10 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE QuasiQuotes #-} module EVM.Solidity ( solidity , solcRuntime- , solidity'- , yul' , yul , yulRuntime , JumpType (..)@@ -72,11 +69,10 @@ import Data.Maybe import Data.Semigroup import Data.Sequence (Seq)-import Data.String.Here qualified as Here import Data.Text (pack, intercalate) import Data.Text qualified as T import Data.Text.Encoding (encodeUtf8, decodeUtf8)-import Data.Text.IO (readFile, writeFile)+import Data.Text.IO (readFile) import Data.Vector (Vector) import Data.Vector qualified as Vector import Data.Word (Word8)@@ -84,8 +80,6 @@ import Prelude hiding (readFile, writeFile) import System.FilePattern.Directory import System.FilePath.Posix-import System.IO hiding (readFile, writeFile)-import System.IO.Temp import System.Process import Text.Read (readMaybe) import Witch (unsafeInto)@@ -357,38 +351,41 @@ pure (Right (BuildOutput contracts sourceCache)) yul :: Text -> Text -> IO (Maybe ByteString)-yul contract src = do- (json, path) <- yul' src- let f = (json ^?! key "contracts") ^?! key (Key.fromText path)- c = f ^?! key (Key.fromText $ if T.null contract then "object" else contract)+yul contractName src = do+ json <- solc Yul src+ let f = (json ^?! key "contracts") ^?! key (Key.fromText "hevm.sol")+ c = f ^?! key (Key.fromText $ if T.null contractName then "object" else contractName) bytecode = c ^?! key "evm" ^?! key "bytecode" ^?! key "object" % _String- pure $ toCode <$> (Just bytecode)+ pure $ (toCode contractName) <$> (Just bytecode) yulRuntime :: Text -> Text -> IO (Maybe ByteString)-yulRuntime contract src = do- (json, path) <- yul' src- let f = (json ^?! key "contracts") ^?! key (Key.fromText path)- c = f ^?! key (Key.fromText $ if T.null contract then "object" else contract)+yulRuntime contractName src = do+ json <- solc Yul src+ let f = (json ^?! key "contracts") ^?! key (Key.fromText "hevm.sol")+ c = f ^?! key (Key.fromText $ if T.null contractName then "object" else contractName) bytecode = c ^?! key "evm" ^?! key "deployedBytecode" ^?! key "object" % _String- pure $ toCode <$> (Just bytecode)+ pure $ (toCode contractName) <$> (Just bytecode) solidity :: Text -> Text -> IO (Maybe ByteString) solidity contract src = do- (json, path) <- solidity' src+ json <- solc Solidity src let (Contracts sol, _, _) = fromJust $ readStdJSON json- pure $ Map.lookup (path <> ":" <> contract) sol <&> (.creationCode)+ pure $ Map.lookup ("hevm.sol:" <> contract) sol <&> (.creationCode) solcRuntime :: Text -> Text -> IO (Maybe ByteString) solcRuntime contract src = do- (json, path) <- solidity' src- let (Contracts sol, _, _) = fromJust $ readStdJSON json- pure $ Map.lookup (path <> ":" <> contract) sol <&> (.runtimeCode)+ json <- solc Solidity src+ case readStdJSON json of+ Just (Contracts sol, _, _) -> pure $ Map.lookup ("hevm.sol:" <> contract) sol <&> (.runtimeCode)+ Nothing -> internalError $ "unable to parse solidity output:\n" <> (T.unpack json) functionAbi :: Text -> IO Method functionAbi f = do- (json, path) <- solidity' ("contract ABI { function " <> f <> " public {}}")- let (Contracts sol, _, _) = fromJust $ readStdJSON json- case Map.toList $ (fromJust (Map.lookup (path <> ":ABI") sol)).abiMap of+ json <- solc Solidity ("contract ABI { function " <> f <> " public {}}")+ let (Contracts sol, _, _) = fromMaybe+ (internalError . T.unpack $ "unable to parse solc output:\n" <> json)+ (readStdJSON json)+ case Map.toList $ (fromJust (Map.lookup "hevm.sol:ABI" sol)).abiMap of [(_,b)] -> pure b _ -> internalError "unexpected abi format" @@ -404,12 +401,16 @@ readFoundryJSON :: Text -> Text -> Maybe (Contracts, Asts, Sources) readFoundryJSON contractName json = do runtime <- json ^? key "deployedBytecode"- runtimeCode <- toCode . strip0x'' <$> runtime ^? key "object" % _String- runtimeSrcMap <- makeSrcMaps =<< runtime ^? key "sourceMap" % _String+ runtimeCode <- (toCode contractName) . strip0x'' <$> runtime ^? key "object" % _String+ runtimeSrcMap <- case runtime ^? key "sourceMap" % _String of+ Nothing -> makeSrcMaps ""+ smap -> makeSrcMaps =<< smap creation <- json ^? key "bytecode"- creationCode <- toCode . strip0x'' <$> creation ^? key "object" % _String- creationSrcMap <- makeSrcMaps =<< creation ^? key "sourceMap" % _String+ creationCode <- (toCode contractName) . strip0x'' <$> creation ^? key "object" % _String+ creationSrcMap <- case creation ^? key "sourceMap" % _String of+ Nothing -> makeSrcMaps ""+ smap -> makeSrcMaps =<< smap ast <- json ^? key "ast" path <- ast ^? key "absolutePath" % _String@@ -430,7 +431,7 @@ , runtimeSrcmap = runtimeSrcMap , creationSrcmap = creationSrcMap , constructorInputs = mkConstructor abi- , storageLayout = mempty -- TODO: foundry doesn't expose this?+ , storageLayout = mkStorageLayout $ json ^? key "storageLayout" , immutableReferences = mempty -- TODO: foundry doesn't expose this? } pure ( Contracts $ Map.singleton (path <> ":" <> contractName) contract@@ -460,10 +461,11 @@ h s (c, x) = let evmstuff = x ^?! key "evm"+ sc = s <> ":" <> c runtime = evmstuff ^?! key "deployedBytecode" creation = evmstuff ^?! key "bytecode"- theRuntimeCode = toCode $ fromMaybe "" $ runtime ^? key "object" % _String- theCreationCode = toCode $ fromMaybe "" $ creation ^? key "object" % _String+ theRuntimeCode = (toCode sc) $ fromMaybe "" $ runtime ^? key "object" % _String+ theCreationCode = (toCode sc) $ fromMaybe "" $ creation ^? key "object" % _String srcContents :: Maybe (HMap.HashMap Text Text) srcContents = do metadata <- x ^? key "metadata" % _String srcs <- KeyMap.toHashMapText <$> metadata ^? key "sources" % _Object@@ -472,14 +474,14 @@ (HMap.filter (isJust . preview (key "content")) srcs) abis = force ("abi key not found in " <> show x) $ toList <$> x ^? key "abi" % _Array- in (s <> ":" <> c, (SolcContract {+ in (sc, (SolcContract { runtimeCode = theRuntimeCode, creationCode = theCreationCode, runtimeCodehash = keccak' (stripBytecodeMetadata theRuntimeCode), creationCodehash = keccak' (stripBytecodeMetadata theCreationCode), runtimeSrcmap = force "srcmap-runtime" (makeSrcMaps (runtime ^?! key "sourceMap" % _String)), creationSrcmap = force "srcmap" (makeSrcMaps (creation ^?! key "sourceMap" % _String)),- contractName = s <> ":" <> c,+ contractName = sc, constructorInputs = mkConstructor abis, abiMap = mkAbiMap abis, eventMap = mkEventMap abis,@@ -508,8 +510,8 @@ f x = Map.fromList . HMap.toList $ HMap.mapWithKey g x g s x = let- theRuntimeCode = toCode (x ^?! key "bin-runtime" % _String)- theCreationCode = toCode (x ^?! key "bin" % _String)+ theRuntimeCode = (toCode s) (x ^?! key "bin-runtime" % _String)+ theCreationCode = (toCode s) (x ^?! key "bin" % _String) abis = toList $ case (x ^?! key "abi") ^? _Array of Just v -> v -- solc >= 0.8 Nothing -> (x ^?! key "abi" % _String) ^?! _Array -- solc < 0.8@@ -646,87 +648,19 @@ containsLinkerHole :: Text -> Bool containsLinkerHole = regexMatches "__\\$[a-z0-9]{34}\\$__" -toCode :: Text -> ByteString-toCode t = case BS16.decodeBase16 (encodeUtf8 t) of+toCode :: Text -> Text -> ByteString+toCode contractName t = case BS16.decodeBase16 (encodeUtf8 t) of Right d -> d Left e -> if containsLinkerHole t- then error "Error: unlinked libraries detected in bytecode"- else error $ T.unpack e--solidity' :: Text -> IO (Text, Text)-solidity' src = withSystemTempFile "hevm.sol" $ \path handle -> do- hClose handle- writeFile path ("//SPDX-License-Identifier: UNLICENSED\n" <> "pragma solidity ^0.8.6;\n" <> src)- writeFile (path <> ".json")- [Here.i|- {- "language": "Solidity",- "sources": {- ${path}: {- "urls": [- ${path}- ]- }- },- "settings": {- "outputSelection": {- "*": {- "*": [- "metadata",- "evm.bytecode",- "evm.deployedBytecode",- "abi",- "storageLayout",- "evm.bytecode.sourceMap",- "evm.bytecode.linkReferences",- "evm.bytecode.generatedSources",- "evm.deployedBytecode.sourceMap",- "evm.deployedBytecode.linkReferences",- "evm.deployedBytecode.generatedSources"- ],- "": [- "ast"- ]- }- }- }- }- |]- x <- pack <$>- readProcess- "solc"- ["--allow-paths", path, "--standard-json", (path <> ".json")]- ""- pure (x, pack path)--yul' :: Text -> IO (Text, Text)-yul' src = withSystemTempFile "hevm.yul" $ \path handle -> do- hClose handle- writeFile path src- writeFile (path <> ".json")- [Here.i|- {- "language": "Yul",- "sources": { ${path}: { "urls": [ ${path} ] } },- "settings": { "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } } }- }- |]- x <- pack <$>- readProcess- "solc"- ["--allow-paths", path, "--standard-json", (path <> ".json")]- ""- pure (x, pack path)+ then error $ T.unpack ("Error toCode: unlinked libraries detected in bytecode, in " <> contractName)+ else error $ T.unpack ("Error toCode:" <> e <> ", in " <> contractName) solc :: Language -> Text -> IO Text solc lang src =- withSystemTempFile "hevm.sol" $ \path handle -> do- hClose handle- writeFile path (stdjson lang src)- T.pack <$> readProcess- "solc"- ["--standard-json", path]- ""+ T.pack <$> readProcess+ "solc"+ ["--standard-json"]+ (T.unpack $ stdjson lang src) data Language = Solidity | Yul deriving (Show)
src/EVM/Solvers.hs view
@@ -112,21 +112,31 @@ _ <- forkIO $ runTask task inst avail orchestrate queue avail - runTask (Task (SMT2 cmds cexvars) r) inst availableInstances = do+ 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) cexvars)+ 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 <- case sat of- "sat" -> Sat <$> getModel inst cexvars- "unsat" -> pure Unsat- "timeout" -> pure Unknown- "unknown" -> pure Unknown- _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> 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 -- put the instance back in the list of available instances@@ -147,11 +157,12 @@ getRaw :: IO SMTCex getRaw = do vars <- getVars parseVar (getValue inst) (fmap T.toStrict cexvars.calldata)+ addrs <- getAddrs parseEAddr (getValue inst) (fmap T.toStrict cexvars.addrs) buffers <- getBufs (getValue inst) (Map.keys cexvars.buffers) storage <- getStore (getValue inst) cexvars.storeReads blockctx <- getVars parseBlockCtx (getValue inst) (fmap T.toStrict cexvars.blockContext)- txctx <- getVars parseFrameCtx (getValue inst) (fmap T.toStrict cexvars.txContext)- pure $ SMTCex vars buffers storage blockctx txctx+ txctx <- getVars parseTxCtx (getValue inst) (fmap T.toStrict cexvars.txContext)+ pure $ SMTCex vars addrs buffers storage blockctx txctx -- sometimes the solver might give us back a model for the max read index -- that is too high to be a useful cex (e.g. in the case of reads from a@@ -182,17 +193,17 @@ shrinkBuf buf hint = do let encBound = "(_ bv" <> (T.pack $ show (into hint :: Integer)) <> " 256)" sat <- liftIO $ do- sendLine' inst "(push)"- sendLine' inst $ "(assert (bvule " <> buf <> "_length " <> encBound <> "))"+ checkCommand inst "(push)"+ checkCommand inst $ "(assert (bvule " <> buf <> "_length " <> encBound <> "))" sendLine inst "(check-sat)" case sat of "sat" -> do model <- liftIO getRaw put model "unsat" -> do- liftIO $ sendLine' inst "(pop)"+ liftIO $ checkCommand inst "(pop)" shrinkBuf buf (if hint == 0 then hint + 1 else hint * 2)- _ -> internalError "SMT solver returned unexpected result (neither sat/unsat), which HEVM currently cannot handle"+ e -> internalError $ "Unexpected solver output: " <> (T.unpack e) -- Collapses the abstract description of a models buffers down to a bytestring mkConcrete :: SMTCex -> SMTCex@@ -224,6 +235,8 @@ CVC5 -> [ "--lang=smt" , "--produce-models"+ , "--print-success"+ , "--interactive" , "--tlimit-per=" <> mkTimeout timeout ] Custom _ -> []@@ -235,10 +248,12 @@ (Just stdin, Just stdout, Just stderr, process) <- createProcess cmd hSetBuffering stdin (BlockBuffering (Just 1000000)) let solverInstance = SolverInstance solver stdin stdout stderr process+ case solver of CVC5 -> pure solverInstance _ -> do _ <- sendLine' solverInstance $ "(set-option :timeout " <> mkTimeout timeout <> ")"+ _ <- sendLine solverInstance "(set-option :print-success true)" pure solverInstance -- | Cleanly shutdown a running solver instnace@@ -247,10 +262,24 @@ -- | 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- sendLine' solver (splitSExpr $ fmap toLazyText cmds)- pure $ Right()+sendScript solver (SMT2 cmds _ _) = do+ let sexprs = splitSExpr $ fmap toLazyText cmds+ go sexprs+ where+ go [] = pure $ Right ()+ go (c:cs) = do+ out <- sendCommand solver c+ case out of+ "success" -> go cs+ e -> pure $ Left $ "Solver returned an error:\n" <> e <> "\nwhile sending the following line: " <> c +checkCommand :: SolverInstance -> Text -> IO ()+checkCommand inst cmd = do+ res <- sendCommand inst cmd+ case res of+ "success" -> pure ()+ _ -> internalError $ "Unexpected solver output: " <> T.unpack res+ -- | Sends a single command to the solver, returns the first available line from the output buffer sendCommand :: SolverInstance -> Text -> IO Text sendCommand inst cmd = do@@ -302,11 +331,11 @@ -- From a list of lines, take each separate SExpression and put it in -- its own list, after removing comments.-splitSExpr :: [Text] -> Text+splitSExpr :: [Text] -> [Text] splitSExpr ls = -- split lines, strip comments, and append everything to a single line let text = T.intercalate " " $ T.takeWhile (/= ';') <$> concatMap T.lines ls in- T.unlines $ filter (/= "") $ go text []+ filter (/= "") $ go text [] where go "" acc = reverse acc go text acc =
src/EVM/Stepper.hs view
@@ -11,7 +11,6 @@ , ask , evm , evmIO- , entering , enter , interpret )@@ -24,60 +23,58 @@ -- as the framework for monadic interpretation. import Control.Monad.Operational (Program, ProgramViewT(..), ProgramView, singleton, view)-import Control.Monad.State.Strict (StateT, execState, runState, runStateT)+import Control.Monad.State.Strict (execStateT, runStateT, get) import Data.Text (Text) import EVM qualified import EVM.Exec qualified import EVM.Fetch qualified as Fetch import EVM.Types+import Control.Monad.ST (stToIO, RealWorld) -- | The instruction type of the operational monad-data Action a where+data Action s a where -- | Keep executing until an intermediate result is reached- Exec :: Action VMResult-- -- | Keep executing until an intermediate state is reached- Run :: Action VM+ Exec :: Action s (VMResult s) -- | Wait for a query to be resolved- Wait :: Query -> Action ()+ Wait :: Query s -> Action s () -- | Multiple things can happen- Ask :: Choose -> Action ()+ Ask :: Choose s -> Action s () -- | Embed a VM state transformation- EVM :: EVM a -> Action a+ EVM :: EVM s a -> Action s a -- | Perform an IO action- IOAct :: StateT VM IO a -> Action a -- they should all just be this?+ IOAct :: IO a -> Action s a -- | Type alias for an operational monad of @Action@-type Stepper a = Program Action a+type Stepper s a = Program (Action s) a -- Singleton actions -exec :: Stepper VMResult+exec :: Stepper s (VMResult s) exec = singleton Exec -run :: Stepper VM-run = singleton Run+run :: Stepper s (VM s)+run = exec >> evm get -wait :: Query -> Stepper ()+wait :: Query s -> Stepper s () wait = singleton . Wait -ask :: Choose -> Stepper ()+ask :: Choose s -> Stepper s () ask = singleton . Ask -evm :: EVM a -> Stepper a+evm :: EVM s a -> Stepper s a evm = singleton . EVM -evmIO :: StateT VM IO a -> Stepper a+evmIO :: IO a -> Stepper s a evmIO = singleton . IOAct -- | Run the VM until final result, resolving all queries-execFully :: Stepper (Either EvmError (Expr Buf))+execFully :: Stepper s (Either EvmError (Expr Buf)) execFully = exec >>= \case HandleEffect (Query q) ->@@ -92,7 +89,7 @@ -> internalError $ "partial execution encountered during concrete execution: " <> show x -- | Run the VM until its final state-runFully :: Stepper VM+runFully :: Stepper s (VM s) runFully = do vm <- run case vm.result of@@ -104,41 +101,33 @@ Just _ -> pure vm -entering :: Text -> Stepper a -> Stepper a-entering t stepper = do- evm (EVM.pushTrace (EntryTrace t))- x <- stepper- evm EVM.popTrace- pure x--enter :: Text -> Stepper ()+enter :: Text -> Stepper s () enter t = evm (EVM.pushTrace (EntryTrace t)) -interpret :: Fetch.Fetcher -> VM -> Stepper a -> IO a+interpret :: Fetch.Fetcher RealWorld -> VM RealWorld -> Stepper RealWorld a -> IO a interpret fetcher vm = eval . view where- eval :: ProgramView Action a -> IO a+ eval :: ProgramView (Action RealWorld) a -> IO a eval (Return x) = pure x eval (action :>>= k) = case action of- Exec ->- let (r, vm') = runState EVM.Exec.exec vm- in interpret fetcher vm' (k r)- Run ->- let vm' = execState EVM.Exec.run vm- in interpret fetcher vm' (k vm')- Wait (PleaseAskSMT (Lit c) _ continue) ->- let (r, vm') = runState (continue (Case (c > 0))) vm- in interpret fetcher vm' (k r)+ 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+ 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- let vm' = execState m vm+ vm' <- stToIO $ execStateT m vm interpret fetcher vm' (k ()) Ask _ -> internalError "cannot make choices with this interpreter" IOAct m -> do- (r, vm') <- runStateT m vm+ r <- m+ interpret fetcher vm (k r)+ EVM m -> do+ (r, vm') <- stToIO $ runStateT m vm interpret fetcher vm' (k r)- EVM m ->- let (r, vm') = runState m vm- in interpret fetcher vm' (k r)
− src/EVM/StorageLayout.hs
@@ -1,152 +0,0 @@-module EVM.StorageLayout where---- Figures out the layout of storage slots for Solidity contracts.--import EVM.Dapp (DappInfo(..))-import EVM.Solidity (SolcContract, creationSrcmap, SlotType(..))-import EVM.ABI (AbiType (..), parseTypeName)--import Optics.Core-import Data.Aeson (Value (..))-import Data.Aeson.Optics-import Data.Foldable (toList)-import Data.List.NonEmpty qualified as NonEmpty-import Data.Map qualified as Map-import Data.Maybe (fromMaybe, isJust)-import Data.Sequence qualified as Seq-import Data.Text (Text, unpack, pack, words)-import EVM.Types (internalError)--import Prelude hiding (words)---- A contract has all the slots of its inherited contracts.------ The slot order is determined by the inheritance linearization order,--- so we first have to calculate that.------ This information is available in the abstract syntax tree.--findContractDefinition :: DappInfo -> SolcContract -> Maybe Value-findContractDefinition dapp solc =- -- The first source mapping in the contract's creation code- -- corresponds to the source field of the contract definition.- case Seq.viewl solc.creationSrcmap of- firstSrcMap Seq.:< _ ->- dapp.astSrcMap firstSrcMap- _ ->- Nothing--storageLayout :: DappInfo -> SolcContract -> [Text]-storageLayout dapp solc =- let- root :: Value- root =- fromMaybe Null- (findContractDefinition dapp solc)- in- case preview ( key "attributes"- % key "linearizedBaseContracts"- % _Array- ) root of- Nothing ->- []- Just ((reverse . toList) -> linearizedBaseContracts) ->- flip concatMap linearizedBaseContracts- (\case- Number i -> fromMaybe (internalError "malformed AST JSON") $- storageVariablesForContract =<<- Map.lookup (floor i) dapp.astIdMap- _ ->- internalError "malformed AST JSON")--storageVariablesForContract :: Value -> Maybe [Text]-storageVariablesForContract node = do- name <- preview (ix "attributes" % key "name" % _String) node- vars <-- fmap- (filter isStorageVariableDeclaration . toList)- (preview (ix "children" % _Array) node)-- pure . flip map vars $- \x ->- case preview (key "attributes" % key "name" % _String) x of- Just variableName ->- mconcat- [ variableName- , " (", name, ")"- , "\n", " Type: "- , pack $ show (slotTypeForDeclaration x)- ]- Nothing ->- internalError "malformed variable declaration"--nodeIs :: Text -> Value -> Bool-nodeIs t x = isSourceNode && hasRightName- where- isSourceNode =- isJust (preview (key "src") x)- hasRightName =- Just t == preview (key "name" % _String) x--isStorageVariableDeclaration :: Value -> Bool-isStorageVariableDeclaration x =- nodeIs "VariableDeclaration" x- && preview (key "attributes" % key "constant" % _Bool) x /= Just True--slotTypeForDeclaration :: Value -> SlotType-slotTypeForDeclaration node =- case toList <$> preview (key "children" % _Array) node of- Just (x:_) ->- grokDeclarationType x- _ ->- internalError "malformed AST"--grokDeclarationType :: Value -> SlotType-grokDeclarationType x =- case preview (key "name" % _String) x of- Just "Mapping" ->- case preview (key "children" % _Array) x of- Just (toList -> xs) ->- grokMappingType xs- _ ->- internalError "malformed AST"- Just _ ->- StorageValue (grokValueType x)- _ ->- internalError ("malformed AST " ++ show x)--grokMappingType :: [Value] -> SlotType-grokMappingType [s, t] =- case (grokDeclarationType s, grokDeclarationType t) of- (StorageValue s', StorageMapping t' x) ->- StorageMapping (NonEmpty.cons s' t') x- (StorageValue s', StorageValue t') ->- StorageMapping (pure s') t'- (StorageMapping _ _, _) ->- internalError "unexpected mapping as mapping key"-grokMappingType _ =- internalError "unexpected AST child count for mapping"--grokValueType :: Value -> AbiType-grokValueType x =- case ( preview (key "name" % _String) x- , preview (key "children" % _Array) x- , preview (key "attributes" % key "type" % _String) x- ) of- (Just "ElementaryTypeName", _, Just typeName) ->- fromMaybe (internalError $ "ungrokked value type: " ++ show typeName)- (parseTypeName mempty (head (words typeName)))- (Just "UserDefinedTypeName", _, _) ->- AbiAddressType- (Just "ArrayTypeName", fmap toList -> Just [t], _)->- AbiArrayDynamicType (grokValueType t)- (Just "ArrayTypeName", fmap toList -> Just [t, n], _)->- case ( preview (key "name" % _String) n- , preview (key "attributes" % key "value" % _String) n- ) of- (Just "Literal", Just ((read . unpack) -> i)) ->- AbiArrayType i (grokValueType t)- _ ->- internalError "malformed AST"- _ ->- internalError ("unknown value type " ++ show x)
src/EVM/SymExec.hs view
@@ -7,6 +7,7 @@ import Control.Concurrent.Spawn (parMapIO, pool) import Control.Concurrent.STM (atomically, TVar, readTVarIO, readTVar, newTVarIO, writeTVar) import Control.Monad.Operational qualified as Operational+import Control.Monad.ST (RealWorld, stToIO, ST) import Control.Monad.State.Strict import Data.Bifunctor (second) import Data.ByteString (ByteString)@@ -15,8 +16,9 @@ import Data.DoubleWord (Word256) import Data.List (foldl', sortBy) import Data.Maybe (fromMaybe, mapMaybe)-import Data.Map (Map)-import Data.Map qualified as Map+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Map.Merge.Strict qualified as Map import Data.Set (Set, isSubsetOf, size) import Data.Set qualified as Set import Data.Text (Text)@@ -26,12 +28,12 @@ import Data.Text.Lazy.IO qualified as TL import Data.Tree.Zipper qualified as Zipper import Data.Tuple (swap)-import EVM (makeVm, initialContract, getCodeLocation, isValidJumpDest)+import EVM (makeVm, abstractContract, initialContract, getCodeLocation, isValidJumpDest) import EVM.Exec import EVM.Fetch qualified as Fetch import EVM.ABI import EVM.Expr qualified as Expr-import EVM.Format (formatExpr, formatPartial)+import EVM.Format (formatExpr, formatPartial, showVal, bsToHex) import EVM.SMT (SMTCex(..), SMT2(..), assertProps, formatSMT2) import EVM.SMT qualified as SMT import EVM.Solvers@@ -39,8 +41,7 @@ import EVM.Stepper qualified as Stepper import EVM.Traversals import EVM.Types-import EVM.Concrete (createAddress)-import EVM.FeeSchedule qualified as FeeSchedule+import EVM.FeeSchedule (feeSchedule) import EVM.Format (indent, formatBinary) import GHC.Conc (getNumProcessors) import GHC.Generics (Generic)@@ -48,9 +49,6 @@ import Options.Generic (ParseField, ParseFields, ParseRecord) import Witch (into, unsafeInto) --- | A method name, and the (ordered) types of it's arguments-data Sig = Sig Text [AbiType]- data LoopHeuristic = Naive | StackBased@@ -79,6 +77,7 @@ , maxIter :: Maybe Integer , askSmtIters :: Integer , loopHeuristic :: LoopHeuristic+ , abstRefineConfig :: AbstRefineConfig , rpcInfo :: Fetch.RpcInfo } deriving (Eq, Show)@@ -90,6 +89,7 @@ , maxIter = Nothing , askSmtIters = 1 , loopHeuristic = StackBased+ , abstRefineConfig = abstRefineDefault , rpcInfo = Nothing } @@ -99,6 +99,9 @@ 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@@ -119,7 +122,7 @@ then let v = Var name in St [Expr.inRange n v] v else internalError "bad type" AbiBoolType -> let v = Var name in St [bool v] v- AbiAddressType -> let v = Var name in St [Expr.inRange 160 v] v+ AbiAddressType -> St [] (WAddr (SymAddr name)) AbiBytesType n -> if n > 0 && n <= 32 then let v = Var name in St [Expr.inRange (n * 8) v] v@@ -189,37 +192,35 @@ abstractVM :: (Expr Buf, [Prop]) -> ByteString- -> Maybe Precondition- -> Expr Storage- -> VM-abstractVM cd contractCode maybepre store = finalVm- where- caller' = Caller 0- value' = CallValue 0- code' = RuntimeCode (ConcreteRuntimeCode contractCode)- vm' = loadSymVM code' store caller' value' cd- precond = case maybepre of+ -> Maybe (Precondition s)+ -> Bool+ -> ST s (VM s)+abstractVM cd contractCode maybepre create = do+ let value = TxValue+ let code = if create then InitCode contractCode (fst cd) else RuntimeCode (ConcreteRuntimeCode contractCode)+ vm <- loadSymVM code value (if create then mempty else cd) create+ let precond = case maybepre of Nothing -> []- Just p -> [p vm']- finalVm = vm' & over #constraints (<> precond)+ Just p -> [p vm]+ pure $ vm & over #constraints (<> precond) loadSymVM :: ContractCode- -> Expr Storage -> Expr EWord- -> Expr EWord -> (Expr Buf, [Prop])- -> VM-loadSymVM x initStore addr callvalue' cd =+ -> Bool+ -> ST s (VM s)+loadSymVM x callvalue cd create = (makeVm $ VMOpts- { contract = initialContract x+ { contract = if create then initialContract x else abstractContract x (SymAddr "entrypoint")+ , otherContracts = [] , calldata = cd- , value = callvalue'- , initialStorage = initStore- , address = createAddress ethrunAddress 1- , caller = addr- , origin = ethrunAddress --todo: generalize- , coinbase = 0+ , value = callvalue+ , baseState = AbstractBase+ , address = SymAddr "entrypoint"+ , caller = SymAddr "caller"+ , origin = SymAddr "origin"+ , coinbase = SymAddr "coinbase" , number = 0 , timestamp = Lit 0 , blockGaslimit = 0@@ -230,29 +231,28 @@ , baseFee = 0 , priorityFee = 0 , maxCodeSize = 0xffffffff- , schedule = FeeSchedule.berlin+ , schedule = feeSchedule , chainId = 1- , create = False+ , create = create , txAccessList = mempty , allowFFI = False- }) & set (#env % #contracts % at (createAddress ethrunAddress 1))- (Just (initialContract x))+ }) -- | Interpreter which explores all paths at branching points. Returns an -- 'Expr End' representing the possible executions. interpret- :: Fetch.Fetcher+ :: Fetch.Fetcher RealWorld -> Maybe Integer -- max iterations -> Integer -- ask smt iterations -> LoopHeuristic- -> VM- -> Stepper (Expr End)+ -> VM RealWorld+ -> Stepper RealWorld (Expr End) -> IO (Expr End) interpret fetcher maxIter askSmtIters heuristic vm = eval . Operational.view where eval- :: Operational.ProgramView Stepper.Action (Expr End)+ :: Operational.ProgramView (Stepper.Action RealWorld) (Expr End) -> IO (Expr End) eval (Operational.Return x) = pure x@@ -260,30 +260,34 @@ eval (action Operational.:>>= k) = case action of Stepper.Exec -> do- let (r, vm') = runState exec vm+ (r, vm') <- stToIO $ runStateT exec vm interpret fetcher maxIter askSmtIters heuristic vm' (k r)- Stepper.Run -> do- let vm' = execState exec vm- interpret fetcher maxIter askSmtIters heuristic vm' (k vm') Stepper.IOAct q -> do- (r, vm') <- runStateT q vm- interpret fetcher maxIter askSmtIters heuristic vm' (k r)+ r <- q+ interpret fetcher maxIter askSmtIters heuristic vm (k r) Stepper.Ask (PleaseChoosePath cond continue) -> do (a, b) <- concurrently- (let (ra, vma) = runState (continue True) vm { result = Nothing }- in interpret fetcher maxIter askSmtIters heuristic vma (k ra))- (let (rb, vmb) = runState (continue False) vm { result = Nothing }- in interpret fetcher maxIter askSmtIters heuristic vmb (k rb))+ (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)+ ) pure $ ITE cond a b Stepper.Wait q -> do let performQuery = do m <- liftIO (fetcher q)- let (r, vm') = runState m vm+ (r, vm') <- stToIO $ runStateT m vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) case q of- PleaseAskSMT cond _ continue -> do- case cond of+ PleaseAskSMT cond preconds continue -> do+ let+ simpCond = Expr.simplify cond+ simpProps = Expr.simplifyProps ((simpCond ./= Lit 0):preconds)+ case simpCond of -- is the condition concrete? Lit c -> -- have we reached max iterations, are we inside a loop?@@ -292,9 +296,9 @@ (Just _, Just True) -> pure $ Partial vm.keccakEqs (Traces (Zipper.toForest vm.traces) vm.env.contracts) $ MaxIterationsReached vm.state.pc vm.state.contract -- No. keep executing- _ ->- let (r, vm') = runState (continue (Case (c > 0))) vm- in interpret fetcher maxIter askSmtIters heuristic vm' (k r)+ _ -> do+ (r, vm') <- stToIO $ runStateT (continue (Case (c > 0))) vm+ interpret fetcher maxIter askSmtIters heuristic vm' (k r) -- the condition is symbolic _ ->@@ -304,25 +308,27 @@ (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- let (r, vm') = runState (continue (Case $ not n)) vm+ (r, vm') <- 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)) -- we're in a loop and askSmtIters has been reached (Just True, True, _) -> -- ask the smt solver about the loop condition performQuery- -- otherwise just try both branches and don't ask the solver- _ ->- let (r, vm') = runState (continue EVM.Types.Unknown) vm- in interpret fetcher maxIter askSmtIters heuristic vm' (k r)-+ _ -> 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+ -- otherwise we explore both branches+ _ -> stToIO $ runStateT (continue EVM.Types.Unknown) vm+ interpret fetcher maxIter askSmtIters heuristic vm' (k r) _ -> performQuery Stepper.EVM m -> do- let (r, vm') = runState m vm+ (r, vm') <- stToIO $ runStateT m vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) -maxIterationsReached :: VM -> Maybe Integer -> Maybe Bool+maxIterationsReached :: VM s -> Maybe Integer -> Maybe Bool maxIterationsReached _ Nothing = Nothing maxIterationsReached vm (Just maxIter) = let codelocation = getCodeLocation vm@@ -331,7 +337,7 @@ then Map.lookup (codelocation, iters - 1) vm.cache.path else Nothing -askSmtItersReached :: VM -> Integer -> Bool+askSmtItersReached :: VM s -> Integer -> Bool askSmtItersReached vm askSmtIters = let codelocation = getCodeLocation vm (iters, _) = view (at codelocation % non (0, [])) vm.iterations@@ -345,7 +351,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 -> Maybe Bool+isLoopHead :: LoopHeuristic -> VM s -> Maybe Bool isLoopHead Naive _ = Just True isLoopHead StackBased vm = let loc = getCodeLocation vm@@ -356,8 +362,8 @@ Just (_, oldStack) -> Just $ filter isValid oldStack == filter isValid vm.state.stack Nothing -> Nothing -type Precondition = VM -> Prop-type Postcondition = VM -> Expr End -> Prop+type Precondition s = VM s -> Prop+type Postcondition s = VM s -> Expr End -> Prop checkAssert :: SolverGroup@@ -368,8 +374,20 @@ -> VeriOpts -> IO (Expr End, [VerifyResult]) checkAssert solvers errs c signature' concreteArgs opts =- verifyContract solvers c signature' concreteArgs opts AbstractStore Nothing (Just $ checkAssertions errs)+ verifyContract solvers c signature' concreteArgs opts Nothing (Just $ checkAssertions errs) +getExpr+ :: SolverGroup+ -> ByteString+ -> Maybe Sig+ -> [String]+ -> VeriOpts+ -> IO (Expr End)+getExpr solvers c signature' concreteArgs opts = do+ preState <- 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+ {- | Checks if an assertion violation has been encountered hevm recognises the following as an assertion violation:@@ -389,7 +407,7 @@ see: https://docs.soliditylang.org/en/v0.8.6/control-structures.html?highlight=Panic#panic-via-assert-and-error-via-require -}-checkAssertions :: [Word256] -> Postcondition+checkAssertions :: [Word256] -> Postcondition s checkAssertions errs _ = \case Failure _ _ (Revert (ConcreteBuf msg)) -> PBool $ msg `notElem` (fmap panicMsg errs) Failure _ _ (Revert b) -> foldl' PAnd (PBool True) (fmap (PNeg . PEq b . ConcreteBuf . panicMsg) errs)@@ -426,25 +444,28 @@ -> Maybe Sig -> [String] -> VeriOpts- -> Expr Storage- -> Maybe Precondition- -> Maybe Postcondition+ -> Maybe (Precondition RealWorld)+ -> Maybe (Postcondition RealWorld) -> IO (Expr End, [VerifyResult])-verifyContract solvers theCode signature' concreteArgs opts initStore maybepre maybepost =- let preState = abstractVM (mkCalldata signature' concreteArgs) theCode maybepre initStore- in verify solvers opts preState maybepost+verifyContract solvers theCode signature' concreteArgs opts maybepre maybepost = do+ preState <- 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 (Expr End)+runExpr :: Stepper.Stepper RealWorld (Expr End) runExpr = do vm <- Stepper.runFully let asserts = vm.keccakEqs <> vm.constraints+ traces = Traces (Zipper.toForest vm.traces) vm.env.contracts pure $ case vm.result of- Just (VMSuccess buf) -> Success asserts (Traces (Zipper.toForest vm.traces) vm.env.contracts) buf vm.env.storage- Just (VMFailure e) -> Failure asserts (Traces (Zipper.toForest vm.traces) vm.env.contracts) e- Just (Unfinished p) -> Partial asserts (Traces (Zipper.toForest vm.traces) vm.env.contracts) p+ 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 _ -> internalError "vm in intermediate state after call to runFully" +toEContract :: Contract -> Expr EContract+toEContract c = C c.code c.storage c.balance c.nonce+ -- | Converts a given top level expr into a list of final states and the -- associated path conditions for each state. flattenExpr :: Expr End -> [Expr End]@@ -453,9 +474,9 @@ go :: [Prop] -> Expr End -> [Expr End] go pcs = \case ITE c t f -> go (PNeg ((PEq c (Lit 0))) : pcs) t <> go (PEq c (Lit 0) : pcs) f- Success ps trace msg store -> [Success (ps <> pcs) trace msg store]- Failure ps trace e -> [Failure (ps <> pcs) trace e]- Partial ps trace p -> [Partial (ps <> pcs) trace p]+ Success ps trace msg store -> [Success (nubOrd $ ps <> pcs) trace msg store]+ Failure ps trace e -> [Failure (nubOrd $ ps <> pcs) trace e]+ Partial ps trace p -> [Partial (nubOrd $ ps <> pcs) trace p] GVar _ -> internalError "cannot flatten an Expr containing a GVar" -- | Strips unreachable branches from a given expr@@ -491,47 +512,13 @@ (Nothing, Nothing) -> Nothing pure (fst tres <> fst fres, subexpr) leaf -> do- let query = assertProps pcs+ let query = assertProps abstRefineDefault pcs res <- checkSat solvers query case res of Sat _ -> pure ([query], Just leaf) Unsat -> pure ([query], Nothing) r -> internalError $ "Invalid solver result: " <> show r ---- | Evaluate the provided proposition down to its most concrete result-evalProp :: Prop -> Prop-evalProp = \case- o@(PBool _) -> o- o@(PNeg p) -> case p of- (PBool b) -> PBool (not b)- _ -> o- o@(PEq l r) -> if l == r- then PBool True- else o- o@(PLT (Lit l) (Lit r)) -> if l < r- then PBool True- else o- o@(PGT (Lit l) (Lit r)) -> if l > r- then PBool True- else o- o@(PGEq (Lit l) (Lit r)) -> if l >= r- then PBool True- else o- o@(PLEq (Lit l) (Lit r)) -> if l <= r- then PBool True- else o- o@(PAnd l r) -> case (evalProp l, evalProp r) of- (PBool True, PBool True) -> PBool True- (PBool _, PBool _) -> PBool False- _ -> o- o@(POr l r) -> case (evalProp l, evalProp r) of- (PBool False, PBool False) -> PBool False- (PBool _, PBool _) -> PBool True- _ -> o- o -> o-- -- | Extract contraints stored in Expr End nodes extractProps :: Expr End -> [Prop] extractProps = \case@@ -558,8 +545,8 @@ verify :: SolverGroup -> VeriOpts- -> VM- -> Maybe Postcondition+ -> VM RealWorld+ -> Maybe (Postcondition RealWorld) -> IO (Expr End, [VerifyResult]) verify solvers opts preState maybepost = do putStrLn "Exploring contract"@@ -568,7 +555,7 @@ when opts.debug $ T.writeFile "unsimplified.expr" (formatExpr exprInter) putStrLn "Simplifying expression"- expr <- if opts.simp then (pure $ Expr.simplify exprInter) else pure exprInter+ 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)"@@ -586,12 +573,12 @@ let -- Filter out any leaves that can be statically shown to be safe canViolate = flip filter flattened $- \leaf -> case evalProp (post preState leaf) of+ \leaf -> case Expr.simplifyProp (post preState leaf) of PBool True -> False _ -> True assumes = preState.constraints withQueries = canViolate <&> \leaf ->- (assertProps (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf)+ (assertProps opts.abstRefineConfig (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf) putStrLn $ "Checking for reachability of " <> show (length withQueries) <> " potential property violation(s)"@@ -610,11 +597,21 @@ where toVRes :: (CheckSatResult, Expr End) -> VerifyResult toVRes (res, leaf) = case res of- Sat model -> Cex (leaf, model)+ Sat model -> Cex (leaf, expandCex preState model) EVM.Solvers.Unknown -> Timeout leaf Unsat -> Qed () Error e -> internalError $ "solver responded with error: " <> show e +expandCex :: VM s -> SMTCex -> SMTCex+expandCex prestate c = c { store = Map.union c.store concretePreStore }+ where+ concretePreStore = Map.mapMaybe (maybeConcreteStore . (.storage))+ . Map.filter (\v -> Expr.containsNode isConcreteStore v.storage)+ $ (prestate.env.contracts)+ isConcreteStore = \case+ ConcreteStore _ -> True+ _ -> False+ type UnsatCache = TVar [Set Prop] -- | Compares two contract runtimes for trace equivalence by running two VMs@@ -641,9 +638,8 @@ -- decompiles the given bytecode into a list of branches getBranches :: ByteString -> IO [Expr End] getBranches bs = do- let- bytecode = if BS.null bs then BS.pack [0] else bs- prestate = abstractVM calldata bytecode Nothing AbstractStore+ let bytecode = if BS.null bs then BS.pack [0] else bs+ prestate <- 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@@ -694,10 +690,10 @@ -- used or not check :: UnsatCache -> (Set Prop) -> Int -> IO (EquivResult, Bool) check knownUnsat props idx = do- let smt = assertProps $ Set.toList props+ let smt = assertProps opts.abstRefineConfig (Set.toList props) -- if debug is on, write the query to a file- when opts.debug $ TL.writeFile- ("equiv-query-" <> show idx <> ".smt2") (formatSMT2 smt <> "\n\n(check-sat)")+ let filename = "equiv-query-" <> show idx <> ".smt2"+ when opts.debug $ TL.writeFile filename (formatSMT2 smt <> "\n\n(check-sat)") ku <- readTVarIO knownUnsat res <- if subsetAny props ku@@ -714,7 +710,7 @@ atomically $ readTVar knownUnsat >>= writeTVar knownUnsat . (props :) pure (Qed (), False) (_, EVM.Solvers.Unknown) -> pure (Timeout (), False)- (_, Error txt) -> internalError $ "issue while running solver: `" <> T.unpack txt -- <> "` SMT file was: `" <> filename <> "`"+ (_, Error txt) -> internalError $ "solver returned: " <> (T.unpack txt) <> if opts.debug then "\n SMT file was: " <> filename <> "" else "" -- 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@@ -733,23 +729,7 @@ -- a differing result in each branch distinct :: Expr End -> Expr End -> Maybe (Set Prop) distinct aEnd bEnd =- let- differingResults = case (aEnd, bEnd) of- (Success _ _ aOut aStore, Success _ _ bOut bStore) ->- if aOut == bOut && aStore == bStore- then PBool False- else aStore ./= bStore .|| aOut ./= bOut- (Failure _ _ (Revert a), Failure _ _ (Revert b)) -> if a == b then PBool False else a ./= b- (Failure _ _ a, Failure _ _ b) -> if a == b then PBool False else PBool True- -- partial end states can't be compared to actual end states, so we always ignore them- (Partial {}, _) -> PBool False- (_, Partial {}) -> PBool False- (ITE _ _ _, _) -> internalError "Expressions must be flattened"- (_, ITE _ _ _) -> internalError "Expressions must be flattened"- (a, b) -> if a == b- then PBool False- else PBool True- in case differingResults of+ case resultsDiffer aEnd bEnd of -- if the end states are the same, then they can never produce a -- different result under any circumstances PBool False -> Nothing@@ -760,15 +740,56 @@ -- if we cannot statically determine whether or not the end states -- differ, then we ask the solver if the end states can differ if both -- sets of path conditions are satisfiable- _ -> Just . Set.fromList $ differingResults : extractProps aEnd <> extractProps bEnd+ _ -> Just . Set.fromList $ resultsDiffer aEnd bEnd : extractProps aEnd <> extractProps bEnd + resultsDiffer :: Expr End -> Expr End -> Prop+ resultsDiffer aEnd bEnd = case (aEnd, bEnd) of+ (Success _ _ aOut aState, Success _ _ bOut bState) ->+ case (aOut == bOut, aState == bState) of+ (True, True) -> PBool False+ (False, True) -> aOut ./= bOut+ (True, False) -> statesDiffer aState bState+ (False, False) -> statesDiffer aState bState .|| aOut ./= bOut+ (Failure _ _ (Revert a), Failure _ _ (Revert b)) -> if a == b then PBool False else a ./= b+ (Failure _ _ a, Failure _ _ b) -> if a == b then PBool False else PBool True+ -- partial end states can't be compared to actual end states, so we always ignore them+ (Partial {}, _) -> PBool False+ (_, Partial {}) -> PBool False+ (ITE _ _ _, _) -> internalError "Expressions must be flattened"+ (_, ITE _ _ _) -> internalError "Expressions must be flattened"+ (a, b) -> if a == b+ then PBool False+ else PBool True++ statesDiffer :: Map (Expr EAddr) (Expr EContract) -> Map (Expr EAddr) (Expr EContract) -> Prop+ statesDiffer aState bState+ = if Set.fromList (Map.keys aState) /= Set.fromList (Map.keys bState)+ -- TODO: consider possibility of aliased symbolic addresses+ then PBool True+ else let+ merged = (Map.merge Map.dropMissing Map.dropMissing (Map.zipWithMatched (\_ x y -> (x,y))) aState bState)+ in Map.foldl' (\a (ac, bc) -> a .|| contractsDiffer ac bc) (PBool False) merged++ contractsDiffer :: Expr EContract -> Expr EContract -> Prop+ contractsDiffer ac bc = let+ balsDiffer = case (ac.balance, bc.balance) of+ (Lit ab, Lit bb) -> PBool $ ab /= bb+ (ab, bb) -> if ab == bb then PBool False else ab ./= bb+ -- TODO: is this sound? do we need a more sophisticated nonce representation?+ noncesDiffer = PBool (ac.nonce /= bc.nonce)+ storesDiffer = case (ac.storage, bc.storage) of+ (ConcreteStore as, ConcreteStore bs) -> PBool $ as /= bs+ (as, bs) -> if as == bs then PBool False else as ./= bs+ in balsDiffer .|| storesDiffer .|| noncesDiffer++ both' :: (a -> b) -> (a, a) -> (b, b) both' f (x, y) = (f x, f y) produceModels :: SolverGroup -> Expr End -> IO [(Expr End, CheckSatResult)] produceModels solvers expr = do let flattened = flattenExpr expr- withQueries = fmap (\e -> (assertProps . extractProps $ e, e)) flattened+ withQueries = fmap (\e -> ((assertProps abstRefineDefault) . extractProps $ e, e)) flattened results <- flip mapConcurrently withQueries $ \(query, leaf) -> do res <- checkSat solvers query pure (res, leaf)@@ -791,7 +812,7 @@ putStrLn "" putStrLn "Inputs:" putStrLn ""- T.putStrLn $ indent 2 $ formatCex cd cex+ T.putStrLn $ indent 2 $ formatCex cd Nothing cex putStrLn "" putStrLn "End State:" putStrLn ""@@ -799,8 +820,8 @@ putStrLn "" -formatCex :: Expr Buf -> SMTCex -> Text-formatCex cd m@(SMTCex _ _ store blockContext txContext) = T.unlines $+formatCex :: Expr Buf -> Maybe Sig -> SMTCex -> Text+formatCex cd sig m@(SMTCex _ _ _ store blockContext txContext) = T.unlines $ [ "Calldata:" , indent 2 cd' , ""@@ -816,7 +837,9 @@ -- 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' = prettyBuf $ Expr.simplify $ subModel m cd+ cd' = case sig of+ Nothing -> prettyBuf . Expr.simplify . defaultSymbolicValues $ subModel m cd+ Just (Sig n ts) -> prettyCalldata m cd n ts storeCex :: [Text] storeCex@@ -824,7 +847,7 @@ | otherwise = [ "Storage:" , indent 2 $ T.unlines $ Map.foldrWithKey (\key val acc ->- ("Addr " <> (T.pack $ show (unsafeInto key :: Addr))+ ("Addr " <> (T.pack . show $ key) <> ": " <> (T.pack $ show (Map.toList val))) : acc ) mempty store , ""@@ -843,9 +866,7 @@ -- strips the frame arg from frame context vars to make them easier to read showTxCtx :: Expr EWord -> Text- showTxCtx (CallValue _) = "CallValue"- showTxCtx (Caller _) = "Caller"- showTxCtx (Address _) = "Address"+ showTxCtx (TxValue) = "TxValue" showTxCtx x = T.pack $ show x -- strips all frame context that doesn't come from the top frame@@ -853,11 +874,8 @@ filterSubCtx = Map.filterWithKey go where go :: Expr EWord -> W256 -> Bool- go (CallValue x) _ = x == 0- go (Caller x) _ = x == 0- go (Address x) _ = x == 0+ go (TxValue) _ = True go (Balance {}) _ = internalError "TODO: BALANCE"- go (SelfBalance {}) _ = internalError "TODO: SELFBALANCE" go (Gas {}) _ = internalError "TODO: Gas" go _ _ = False @@ -875,23 +893,73 @@ prettyBuf :: Expr Buf -> Text prettyBuf (ConcreteBuf "") = "Empty" prettyBuf (ConcreteBuf bs) = formatBinary bs- prettyBuf _ = "Any"+ prettyBuf b = internalError $ "Unexpected symbolic buffer:\n" <> T.unpack (formatExpr b) --- | Takes a buffer and a Cex and replaces all abstract values in the buf with+prettyCalldata :: SMTCex -> Expr Buf -> Text -> [AbiType] -> Text+prettyCalldata cex buf sig types = head (T.splitOn "(" sig) <> "(" <> body <> ")"+ where+ argdata = Expr.drop 4 . Expr.simplify . defaultSymbolicValues $ subModel cex buf+ body = case decodeBuf types argdata of+ CAbi v -> T.intercalate "," (fmap showVal v)+ NoVals -> case argdata of+ ConcreteBuf c -> T.pack (bsToHex c)+ _ -> err+ SAbi _ -> err+ err = internalError $ "unable to produce a concrete model for calldata: " <> show buf++-- | 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+-- 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+defaultSymbolicValues :: Expr a -> Expr a+defaultSymbolicValues e = subBufs (foldTerm symbufs mempty e)+ . subVars (foldTerm symwords mempty e)+ . subAddrs (foldTerm symaddrs mempty e) $ e+ where+ symaddrs :: Expr a -> Map (Expr EAddr) Addr+ symaddrs = \case+ a@(SymAddr _) -> Map.singleton a (Addr 0x1312)+ _ -> mempty+ symbufs :: Expr a -> Map (Expr Buf) ByteString+ symbufs = \case+ a@(AbstractBuf _) -> Map.singleton a ""+ _ -> mempty+ symwords :: Expr a -> Map (Expr EWord) W256+ symwords = \case+ a@(Var _) -> Map.singleton a 0+ a@Origin -> Map.singleton a 0+ a@Coinbase -> Map.singleton a 0+ a@Timestamp -> Map.singleton a 0+ a@BlockNumber -> Map.singleton a 0+ a@PrevRandao -> Map.singleton a 0+ a@GasLimit -> Map.singleton a 0+ a@ChainId -> Map.singleton a 0+ a@BaseFee -> Map.singleton a 0+ _ -> mempty++-- | Takes an expression and a Cex and replaces all abstract values in the buf with -- concrete ones from the Cex. subModel :: SMTCex -> Expr a -> Expr a-subModel c expr =- subBufs (fmap forceFlattened c.buffers) . subVars c.vars . subStore c.store- . subVars c.blockContext . subVars c.txContext $ expr+subModel c+ = subBufs (fmap forceFlattened c.buffers)+ . subStores c.store+ . subVars c.vars+ . subVars c.blockContext+ . subVars c.txContext+ . subAddrs c.addrs where forceFlattened (SMT.Flat bs) = bs forceFlattened b@(SMT.Comp _) = forceFlattened $ fromMaybe (internalError $ "cannot flatten buffer: " <> show b) (SMT.collapse b) - subVars model b = Map.foldlWithKey subVar b model+subVars :: Map (Expr EWord) W256 -> Expr a -> Expr a+subVars model b = Map.foldlWithKey subVar b model+ where subVar :: Expr a -> Expr EWord -> W256 -> Expr a- subVar b var val = mapExpr go b+ subVar a var val = mapExpr go a where go :: Expr a -> Expr a go = \case@@ -900,9 +968,24 @@ else v e -> e - subBufs model b = Map.foldlWithKey subBuf b model+subAddrs :: Map (Expr EAddr) Addr -> Expr a -> Expr a+subAddrs model b = Map.foldlWithKey subAddr b model+ where+ subAddr :: Expr a -> Expr EAddr -> Addr -> Expr a+ subAddr a var val = mapExpr go a+ where+ go :: Expr a -> Expr a+ go = \case+ v@(SymAddr _) -> if v == var+ then LitAddr val+ else v+ e -> e++subBufs :: Map (Expr Buf) ByteString -> Expr a -> Expr a+subBufs model b = Map.foldlWithKey subBuf b model+ where subBuf :: Expr a -> Expr Buf -> ByteString -> Expr a- subBuf b var val = mapExpr go b+ subBuf x var val = mapExpr go x where go :: Expr a -> Expr a go = \case@@ -911,10 +994,24 @@ else a e -> e - subStore :: Map W256 (Map W256 W256) -> Expr a -> Expr a- subStore m b = mapExpr go b+subStores :: Map (Expr EAddr) (Map W256 W256) -> Expr a -> Expr a+subStores model b = Map.foldlWithKey subStore b model+ where+ subStore :: Expr a -> Expr EAddr -> Map W256 W256 -> Expr a+ subStore x var val = mapExpr go x where go :: Expr a -> Expr a go = \case- AbstractStore -> ConcreteStore m+ v@(AbstractStore a)+ -> if a == var+ then ConcreteStore val+ else v e -> e++getCex :: ProofResult a b c -> Maybe b+getCex (Cex c) = Just c+getCex _ = Nothing++getTimeout :: ProofResult a b c -> Maybe c+getTimeout (Timeout c) = Just c+getTimeout _ = Nothing
− src/EVM/TTY.hs
@@ -1,1047 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE ImplicitParams #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE UndecidableInstances #-}--module EVM.TTY where--import Brick-import Brick.Widgets.Border-import Brick.Widgets.Center-import Brick.Widgets.List--import EVM-import EVM.ABI (decodeAbiValue, emptyAbi, abiTypeSolidity, AbiType(..))-import EVM.SymExec (maxIterationsReached, symCalldata)-import EVM.Expr (simplify)-import EVM.Dapp (DappInfo(..), emptyDapp, dappInfo, Test, extractSig, Test(..), srcMap, unitTestMethods)-import EVM.Debug-import EVM.Fetch (Fetcher)-import EVM.Fetch qualified as Fetch-import EVM.Format (showWordExact, showWordExplanation, contractNamePart,- contractPathPart, showTraceTree, prettyIfConcreteWord, formatExpr)-import EVM.Hexdump (prettyHex)-import EVM.Solvers (SolverGroup)-import EVM.Op-import EVM.Solidity hiding (storageLayout)-import EVM.Types hiding (padRight, Max)-import EVM.UnitTest-import EVM.Stepper (Stepper)-import EVM.Stepper qualified as Stepper-import EVM.StorageLayout-import EVM.TTYCenteredList qualified as Centered--import Optics.Core-import Optics.State-import Optics.TH--import Control.Monad.Operational qualified as Operational-import Control.Monad.State.Strict hiding (state)-import Data.Aeson.Optics-import Data.ByteString (ByteString)-import Data.ByteString qualified as BS-import Data.List (sort, find)-import Data.Maybe (isJust, fromJust, fromMaybe, isNothing)-import Data.Map (Map, insert, lookupLT, singleton, filter, (!?))-import Data.Map qualified as Map-import Data.Text (Text, pack)-import Data.Text qualified as T-import Data.Text qualified as Text-import Data.Text.Encoding (decodeUtf8)-import Data.Vector qualified as Vec-import Data.Vector.Storable qualified as SVec-import Data.Version (showVersion)-import Graphics.Vty qualified as V-import System.Console.Haskeline qualified as Readline-import Paths_hevm qualified as Paths-import Text.Wrap-import Witch (into)--data Name- = AbiPane- | StackPane- | BytecodePane- | TracePane- | SolidityPane- | TestPickerPane- | BrowserPane- | Pager- deriving (Eq, Show, Ord)--type UiWidget = Widget Name--data UiVmState = UiVmState- { vm :: VM- , step :: Int- , snapshots :: Map Int (VM, Stepper ())- , stepper :: Stepper ()- , showMemory :: Bool- , testOpts :: UnitTestOptions- }--data UiTestPickerState = UiTestPickerState- { tests :: List Name (Text, Text)- , dapp :: DappInfo- , opts :: UnitTestOptions- }--data UiBrowserState = UiBrowserState- { contracts :: List Name (Addr, Contract)- , vm :: UiVmState- }--data UiState- = ViewVm UiVmState- | ViewContracts UiBrowserState- | ViewPicker UiTestPickerState- | ViewHelp UiVmState--makeFieldLabelsNoPrefix ''UiVmState-makeFieldLabelsNoPrefix ''UiTestPickerState-makeFieldLabelsNoPrefix ''UiBrowserState-makePrisms ''UiState---- caching VM states lets us backstep efficiently-snapshotInterval :: Int-snapshotInterval = 50--type Pred a = a -> Bool--data StepMode- = Step !Int -- ^ Run a specific number of steps- | StepUntil (Pred VM) -- ^ Finish when a VM predicate holds---- | Each step command in the terminal should finish immediately--- with one of these outcomes.-data Continuation a- = Stopped a -- ^ Program finished- | Continue (Stepper a) -- ^ Took one step; more steps to go----- | This turns a @Stepper@ into a state action usable--- from within the TTY loop, yielding a @StepOutcome@ depending on the @StepMode@.-interpret- :: (?fetcher :: Fetcher- , ?maxIter :: Maybe Integer)- => StepMode- -> Stepper a- -> StateT UiVmState IO (Continuation a)-interpret mode =-- -- Like the similar interpreters in @EVM.UnitTest@ and @EVM.VMTest@,- -- this one is implemented as an "operational monad interpreter".-- eval . Operational.view- where- eval- :: Operational.ProgramView Stepper.Action a- -> StateT UiVmState IO (Continuation a)-- eval (Operational.Return x) =- pure (Stopped x)-- eval (action Operational.:>>= k) =- case action of-- Stepper.Run -> do- -- Have we reached the final result of this action?- use (#vm % #result) >>= \case- Just _ -> do- -- Yes, proceed with the next action.- vm <- use #vm- interpret mode (k vm)- Nothing -> do- -- No, keep performing the current action- keepExecuting mode (Stepper.run >>= k)-- -- Stepper wants to keep executing?- Stepper.Exec -> do- -- Have we reached the final result of this action?- use (#vm % #result) >>= \case- Just r ->- -- Yes, proceed with the next action.- interpret mode (k r)- Nothing -> do- -- No, keep performing the current action- keepExecuting mode (Stepper.exec >>= k)-- -- Stepper is waiting for user input from a query- Stepper.Ask (PleaseChoosePath _ cont) -> do- -- ensure we aren't stepping past max iterations- vm <- use #vm- case maxIterationsReached vm ?maxIter of- Nothing -> pure $ Continue (k ())- Just n -> interpret mode (Stepper.evm (cont (not n)) >>= k)-- -- Stepper wants to make a query and wait for the results?- Stepper.Wait (PleaseAskSMT (Lit c) _ continue) ->- interpret mode (Stepper.evm (continue (Case (c > 0))) >>= k)- Stepper.Wait q -> do- do m <- liftIO (?fetcher q)- interpret mode (Stepper.evm m >>= k)-- -- Stepper wants to make a query and wait for the results?- Stepper.IOAct q -> do- Brick.zoom (toLensVL #vm) (StateT (runStateT q)) >>= interpret mode . k-- -- Stepper wants to modify the VM.- Stepper.EVM m -> do- vm <- use #vm- let (r, vm1) = runState m vm- assign #vm vm1- interpret mode (Stepper.exec >> (k r))--keepExecuting :: (?fetcher :: Fetcher- , ?maxIter :: Maybe Integer)- => StepMode- -> Stepper a- -> StateT UiVmState IO (Continuation a)-keepExecuting mode restart = case mode of- Step 0 -> do- -- We come here when we've continued while stepping,- -- either from a query or from a return;- -- we should pause here and wait for the user.- pure (Continue restart)-- Step i -> do- -- Run one instruction and recurse- stepOneOpcode restart- interpret (Step (i - 1)) restart-- StepUntil p -> do- vm <- use #vm- if p vm- then- interpret (Step 0) restart- else do- -- Run one instruction and recurse- stepOneOpcode restart- interpret (StepUntil p) restart--isUnitTestContract :: Text -> DappInfo -> Bool-isUnitTestContract name dapp =- elem name (map fst dapp.unitTests)--mkVty :: IO V.Vty-mkVty = do- vty <- V.mkVty V.defaultConfig- V.setMode (V.outputIface vty) V.BracketedPaste True- pure vty--runFromVM :: SolverGroup -> Fetch.RpcInfo -> Maybe Integer -> DappInfo -> VM -> IO VM-runFromVM solvers rpcInfo maxIter' dappinfo vm = do-- let- opts = UnitTestOptions- { solvers = solvers- , rpcInfo = rpcInfo- , verbose = Nothing- , maxIter = maxIter'- , askSmtIters = 1- , smtTimeout = Nothing- , smtDebug = False- , solver = Nothing- , maxDepth = Nothing- , match = ""- , fuzzRuns = 1- , replay = internalError "irrelevant"- , vmModifier = id- , testParams = internalError "irrelevant"- , dapp = dappinfo- , ffiAllowed = False- , covMatch = Nothing- }- ui0 = initUiVmState vm opts (void Stepper.execFully)-- v <- mkVty- ui2 <- customMain v mkVty Nothing (app opts) (ViewVm ui0)- case ui2 of- ViewVm ui -> pure ui.vm- _ -> internalError "customMain returned prematurely"---initUiVmState :: VM -> UnitTestOptions -> Stepper () -> UiVmState-initUiVmState vm0 opts script =- UiVmState- { vm = vm0- , stepper = script- , step = 0- , snapshots = singleton 0 (vm0, script)- , showMemory = False- , testOpts = opts- }----- filters out fuzztests, unless they have--- explicitly been given an argument by `replay`-debuggableTests :: UnitTestOptions -> (Text, [(Test, [AbiType])]) -> [(Text, Text)]-debuggableTests UnitTestOptions{..} (contractname, tests) = case replay of- Nothing -> [(contractname, extractSig $ fst x) | x <- tests, not $ isFuzzTest x]- Just (sig, _) -> [(contractname, extractSig $ fst x) | x <- tests, not (isFuzzTest x) || extractSig (fst x) == sig]--isFuzzTest :: (Test, [AbiType]) -> Bool-isFuzzTest (SymbolicTest _, _) = False-isFuzzTest (ConcreteTest _, []) = False-isFuzzTest (ConcreteTest _, _) = True-isFuzzTest (InvariantTest _, _) = True--main :: UnitTestOptions -> FilePath -> Maybe BuildOutput -> IO ()-main opts root buildOutput = do- let- dapp = maybe emptyDapp (dappInfo root) buildOutput- ui = ViewPicker $ UiTestPickerState- { tests =- list- TestPickerPane- (Vec.fromList- (concatMap- (debuggableTests opts)- dapp.unitTests))- 1- , dapp = dapp- , opts = opts- }- v <- mkVty- _ <- customMain v mkVty Nothing (app opts) (ui :: UiState)- pure ()--takeStep- :: (?fetcher :: Fetcher- ,?maxIter :: Maybe Integer)- => UiVmState- -> StepMode- -> EventM n UiState ()-takeStep ui mode =- liftIO nxt >>= \case- (Stopped (), _) ->- pure ()- (Continue steps, ui') ->- put (ViewVm (ui' & set #stepper steps))- where- m = interpret mode ui.stepper- nxt = runStateT m ui--backstepUntil- :: (?fetcher :: Fetcher- ,?maxIter :: Maybe Integer)- => (UiVmState -> Pred VM) -> EventM n UiState ()-backstepUntil p = get >>= \case- ViewVm s ->- case s.step of- 0 -> pure ()- n -> do- s1 <- liftIO $ backstep s- let- -- find a previous vm that satisfies the predicate- snapshots' = Data.Map.filter (p s1 . fst) s1.snapshots- case lookupLT n snapshots' of- -- If no such vm exists, go to the beginning- Nothing ->- let- (step', (vm', stepper')) = fromJust $ lookupLT (n - 1) s.snapshots- s2 = s1- & set #vm vm'- & set (#vm % #cache) s1.vm.cache- & set #step step'- & set #stepper stepper'- in takeStep s2 (Step 0)- -- step until the predicate doesn't hold- Just (step', (vm', stepper')) ->- let- s2 = s1- & set #vm vm'- & set (#vm % #cache) s1.vm.cache- & set #step step'- & set #stepper stepper'- in takeStep s2 (StepUntil (not . p s1))- _ -> pure ()--backstep- :: (?fetcher :: Fetcher- ,?maxIter :: Maybe Integer)- => UiVmState -> IO UiVmState-backstep s =- case s.step of- -- We're already at the first step; ignore command.- 0 -> pure s- -- To step backwards, we revert to the previous snapshot- -- and execute n - 1 `mod` snapshotInterval steps from there.-- -- We keep the current cache so we don't have to redo- -- any blocking queries, and also the memory view.- n ->- let- (step, (vm, stepper)) = fromJust $ lookupLT n s.snapshots- s1 = s- & set #vm vm- & set (#vm % #cache) s.vm.cache- & set #step step- & set #stepper stepper- stepsToTake = n - step - 1-- in- runStateT (interpret (Step stepsToTake) stepper) s1 >>= \case- (Continue steps, ui') -> pure $ ui' & set #stepper steps- _ -> internalError "unexpected end"--appEvent- :: (?fetcher::Fetcher, ?maxIter :: Maybe Integer) =>- BrickEvent Name e ->- EventM Name UiState ()---- Contracts: Down - list down-appEvent (VtyEvent e@(V.EvKey V.KDown [])) = get >>= \case- ViewContracts _s -> do- Brick.zoom- (traverseOf $ _ViewContracts % #contracts)- (handleListEvent e)- pure ()- ViewPicker _s -> do- Brick.zoom- (traverseOf $ _ViewPicker % #tests)- (handleListEvent e)- pure()- _ -> pure ()---- Contracts: Up - list up--- Page: Up - scroll-appEvent (VtyEvent e@(V.EvKey V.KUp [])) = get >>= \case- ViewContracts _s -> do- Brick.zoom- (traverseOf $ _ViewContracts % #contracts)- (handleListEvent e)- ViewPicker _s -> do- Brick.zoom- (traverseOf $ _ViewPicker % #tests)- (handleListEvent e)- pure()- _ -> pure ()---- Vm Overview: Esc - return to test picker or exit--- Any: Esc - return to Vm Overview or Exit-appEvent (VtyEvent (V.EvKey V.KEsc [])) = get >>= \case- ViewVm s -> do- let opts = s ^. #testOpts- dapp = opts.dapp- tests = concatMap (debuggableTests opts) dapp.unitTests- case tests of- [] -> halt- ts ->- put $ ViewPicker $ UiTestPickerState- { tests = list TestPickerPane (Vec.fromList ts) 1- , dapp = dapp- , opts = opts- }- ViewHelp s -> put (ViewVm s)- ViewContracts s -> put (ViewVm $ s ^. #vm)- _ -> halt---- Vm Overview: Enter - open contracts view--- UnitTest Picker: Enter - select from list-appEvent (VtyEvent (V.EvKey V.KEnter [])) = get >>= \case- ViewVm s ->- put . ViewContracts $ UiBrowserState- { contracts =- list- BrowserPane- (Vec.fromList (Map.toList s.vm.env.contracts))- 2- , vm = s- }- ViewPicker s ->- case listSelectedElement s.tests of- Nothing -> internalError "nothing selected"- Just (_, x) -> do- let initVm = initialUiVmStateForTest s.opts x- put (ViewVm initVm)- _ -> pure ()---- Vm Overview: m - toggle memory pane-appEvent (VtyEvent (V.EvKey (V.KChar 'm') [])) = get >>= \case- ViewVm s -> put (ViewVm $ over #showMemory not s)- _ -> pure ()---- Vm Overview: h - open help view-appEvent (VtyEvent (V.EvKey (V.KChar 'h') [])) = get >>= \case- ViewVm s -> put (ViewHelp s)- _ -> pure ()---- Vm Overview: spacebar - read input-appEvent (VtyEvent (V.EvKey (V.KChar ' ') [])) =- let- loop = do- Readline.getInputLine "% " >>= \case- Just hey -> Readline.outputStrLn hey- Nothing -> pure ()- Readline.getInputLine "% " >>= \case- Just hey' -> Readline.outputStrLn hey'- Nothing -> pure ()- in do- s <- get- suspendAndResume $ do- Readline.runInputT Readline.defaultSettings loop- pure s---- todo refactor to zipper step forward--- Vm Overview: n - step-appEvent (VtyEvent (V.EvKey (V.KChar 'n') [])) = get >>= \case- ViewVm s ->- when (isNothing (s ^. #vm % #result)) $- takeStep s (Step 1)- _ -> pure ()---- Vm Overview: N - step-appEvent (VtyEvent (V.EvKey (V.KChar 'N') [])) = get >>= \case- ViewVm s ->- when (isNothing (s ^. #vm % #result)) $- takeStep s (StepUntil (isNextSourcePosition s))- _ -> pure ()---- Vm Overview: C-n - step-appEvent (VtyEvent (V.EvKey (V.KChar 'n') [V.MCtrl])) = get >>= \case- ViewVm s ->- when (isNothing (s ^. #vm % #result)) $- takeStep s (StepUntil (isNextSourcePositionWithoutEntering s))- _ -> pure ()---- Vm Overview: e - step-appEvent (VtyEvent (V.EvKey (V.KChar 'e') [])) = get >>= \case- ViewVm s ->- when (isNothing (s ^. #vm % #result)) $- takeStep s (StepUntil (isExecutionHalted s))- _ -> pure ()---- Vm Overview: a - step-appEvent (VtyEvent (V.EvKey (V.KChar 'a') [])) = get >>= \case- ViewVm s ->- -- We keep the current cache so we don't have to redo- -- any blocking queries.- let- (vm, stepper) = fromJust (Map.lookup 0 s.snapshots)- s' = s- & set #vm vm- & set (#vm % #cache) s.vm.cache- & set #step 0- & set #stepper stepper-- in takeStep s' (Step 0)- _ -> pure ()---- Vm Overview: p - backstep-appEvent (VtyEvent (V.EvKey (V.KChar 'p') [])) = get >>= \case- ViewVm s ->- case s.step of- 0 ->- -- We're already at the first step; ignore command.- pure ()- n -> do- -- To step backwards, we revert to the previous snapshot- -- and execute n - 1 `mod` snapshotInterval steps from there.-- -- We keep the current cache so we don't have to redo- -- any blocking queries, and also the memory view.- let- (step, (vm, stepper)) = fromJust $ lookupLT n s.snapshots- s1 = s- & set #vm vm -- set the vm to the one from the snapshot- & set (#vm % #cache) s.vm.cache -- persist the cache- & set #step step- & set #stepper stepper- stepsToTake = n - step - 1-- takeStep s1 (Step stepsToTake)- _ -> pure ()---- Vm Overview: P - backstep to previous source-appEvent (VtyEvent (V.EvKey (V.KChar 'P') [])) =- backstepUntil isNextSourcePosition---- Vm Overview: c-p - backstep to previous source avoiding CALL and CREATE-appEvent (VtyEvent (V.EvKey (V.KChar 'p') [V.MCtrl])) =- backstepUntil isNextSourcePositionWithoutEntering---- Vm Overview: 0 - choose no jump-appEvent (VtyEvent (V.EvKey (V.KChar '0') [])) = get >>= \case- ViewVm s ->- case view (#vm % #result) s of- Just (HandleEffect (Choose (PleaseChoosePath _ contin))) ->- takeStep (s & set #stepper (Stepper.evm (contin True) >> s.stepper))- (Step 1)- _ -> pure ()- _ -> pure ()---- Vm Overview: 1 - choose jump-appEvent (VtyEvent (V.EvKey (V.KChar '1') [])) = get >>= \case- ViewVm s ->- case s.vm.result of- Just (HandleEffect (Choose (PleaseChoosePath _ contin))) ->- takeStep (s & set #stepper (Stepper.evm (contin False) >> s.stepper))- (Step 1)- _ -> pure ()- _ -> pure ()---- Page: C-f - Page down-appEvent (VtyEvent (V.EvKey (V.KChar 'f') [V.MCtrl])) =- vScrollPage (viewportScroll TracePane) Down---- Page: C-b - Page up-appEvent (VtyEvent (V.EvKey (V.KChar 'b') [V.MCtrl])) =- vScrollPage (viewportScroll TracePane) Up---- UnitTest Picker: (main) - render list-appEvent (VtyEvent e) = do- Brick.zoom (traverseOf (_ViewPicker % #tests))- (handleListEvent e)---- Default-appEvent _ = pure ()--app :: UnitTestOptions -> App UiState () Name-app UnitTestOptions{..} =- let ?fetcher = Fetch.oracle solvers rpcInfo- ?maxIter = maxIter- in App- { appDraw = drawUi- , appChooseCursor = neverShowCursor- , appHandleEvent = appEvent- , appStartEvent = pure ()- , appAttrMap = const (attrMap V.defAttr myTheme)- }--initialUiVmStateForTest- :: UnitTestOptions- -> (Text, Text)- -> UiVmState-initialUiVmStateForTest opts@UnitTestOptions{..} (theContractName, theTestName) = initUiVmState vm0 opts script- where- cd = case test of- SymbolicTest _ -> symCalldata theTestName types [] (AbstractBuf "txdata")- _ -> (internalError "unreachable", error $ internalError "unreachable")- (test, types) = fromJust $ find (\(test',_) -> extractSig test' == theTestName) $ unitTestMethods testContract- testContract = fromJust $ Map.lookup theContractName dapp.solcByName- vm0 =- initialUnitTestVm opts testContract- script = do- Stepper.evm . pushTrace . EntryTrace $- "test " <> theTestName <> " (" <> theContractName <> ")"- initializeUnitTest opts testContract- case test of- ConcreteTest _ -> do- let args = case replay of- Nothing -> emptyAbi- Just (sig, callData) ->- if theTestName == sig- then decodeAbiValue (AbiTupleType (Vec.fromList types)) callData- else emptyAbi- void (runUnitTest opts theTestName args)- SymbolicTest _ -> do- void (execSymTest opts theTestName cd)- InvariantTest _ -> do- targets <- getTargetContracts opts- let randomRun = initialExplorationStepper opts theTestName [] targets (fromMaybe 20 maxDepth)- void $ case replay of- Nothing -> randomRun- Just (sig, cd') ->- if theTestName == sig- then initialExplorationStepper opts theTestName (decodeCalls cd') targets (length (decodeCalls cd'))- else randomRun--myTheme :: [(AttrName, V.Attr)]-myTheme =- [ (selectedAttr, V.defAttr `V.withStyle` V.standout)- , (dimAttr, V.defAttr `V.withStyle` V.dim)- , (borderAttr, V.defAttr `V.withStyle` V.dim)- , (wordAttr, fg V.yellow)- , (boldAttr, V.defAttr `V.withStyle` V.bold)- , (activeAttr, V.defAttr `V.withStyle` V.standout)- ]--drawUi :: UiState -> [UiWidget]-drawUi (ViewVm s) = drawVm s-drawUi (ViewPicker s) = drawTestPicker s-drawUi (ViewContracts s) = drawVmBrowser s-drawUi (ViewHelp _) = drawHelpView--drawHelpView :: [UiWidget]-drawHelpView =- [ center . borderWithLabel version .- padLeftRight 4 . padTopBottom 2 . str $- "Esc Exit the debugger\n\n" <>- "a Step to start\n" <>- "e Step to end\n" <>- "n Step fwds by one instruction\n" <>- "N Step fwds to the next source position\n" <>- "C-n Step fwds to the next source position skipping CALL & CREATE\n" <>- "p Step back by one instruction\n\n" <>- "P Step back to the previous source position\n\n" <>- "C-p Step back to the previous source position skipping CALL & CREATE\n\n" <>- "m Toggle memory pane\n" <>- "0 Choose the branch which does not jump \n" <>- "1 Choose the branch which does jump \n" <>- "Down Step to next entry in the callstack / Scroll memory pane\n" <>- "Up Step to previous entry in the callstack / Scroll memory pane\n" <>- "C-f Page memory pane fwds\n" <>- "C-b Page memory pane back\n\n" <>- "Enter Contracts browser"- ]- where- version =- txt "Hevm " <+>- str (showVersion Paths.version) <+>- txt " - Key bindings"--drawTestPicker :: UiTestPickerState -> [UiWidget]-drawTestPicker ui =- [ center . borderWithLabel (txt "Unit tests") .- hLimit 80 $- renderList- (\selected (x, y) ->- withHighlight selected $- txt " Debug " <+> txt (contractNamePart x) <+> txt "::" <+> txt y)- True- ui.tests- ]--drawVmBrowser :: UiBrowserState -> [UiWidget]-drawVmBrowser ui =- [ hBox- [ borderWithLabel (txt "Contracts") .- hLimit 60 $- renderList- (\selected (k, c') ->- withHighlight selected . txt . mconcat $- [ fromMaybe "<unknown contract>" $- Map.lookup (maybeHash c') dapp.solcByHash <&> (.contractName) . snd- , "\n"- , " ", pack (show k)- ])- True- ui.contracts- , case snd <$> Map.lookup (maybeHash c) dapp.solcByHash of- Nothing ->- hBox- [ borderWithLabel (txt "Contract information") . padBottom Max . padRight Max $ vBox- [ txt ("Codehash: " <> pack (show c.codehash))- , txt ("Nonce: " <> showWordExact c.nonce)- , txt ("Balance: " <> showWordExact c.balance)- --, txt ("Storage: " <> storageDisplay (view storage c)) -- TODO: fix this- ]- ]- Just sol ->- hBox- [ borderWithLabel (txt "Contract information") . padBottom Max . padRight (Pad 2) $ vBox- [ txt "Name: " <+> txt (contractNamePart sol.contractName)- , txt "File: " <+> txt (contractPathPart sol.contractName)- , txt " "- , txt "Constructor inputs:"- , vBox . flip map sol.constructorInputs $- \(name, abiType) -> txt (" " <> name <> ": " <> abiTypeSolidity abiType)- , txt "Public methods:"- , vBox . flip map (sort (Map.elems sol.abiMap)) $- \method -> txt (" " <> method.methodSignature)- --, txt ("Storage:" <> storageDisplay (view storage c)) -- TODO: fix this- ]- , borderWithLabel (txt "Storage slots") . padBottom Max . padRight Max $ vBox- (map txt (storageLayout dapp sol))- ]- ]- ]- where- dapp = ui.vm.testOpts.dapp- (_, (_, c)) = fromJust $ listSelectedElement ui.contracts--- currentContract = view (dappSolcByHash . ix ) dapp- maybeHash ch = fromJust (internalError "cannot find concrete codehash for partially symbolic code") (maybeLitWord ch.codehash)--drawVm :: UiVmState -> [UiWidget]-drawVm ui =- -- EVM debugging needs a lot of space because of the 256-bit words- -- in both the bytecode and the stack .- --- -- If on a very tall display, prefer a vertical layout.- --- -- Actually the horizontal layout would be preferrable if the display- -- is both very tall and very wide, but this is okay for now.- [ ifTallEnough (20 * 4)- ( vBox- [ vLimit 20 $ drawBytecodePane ui- , vLimit 20 $ drawStackPane ui- , drawSolidityPane ui- , vLimit 20 $ drawTracePane ui- , vLimit 2 drawHelpBar- ]- )- ( vBox- [ hBox- [ vLimit 20 $ drawBytecodePane ui- , vLimit 20 $ drawStackPane ui- ]- , hBox- [ drawSolidityPane ui- , drawTracePane ui- ]- , vLimit 2 drawHelpBar- ]- )- ]--drawHelpBar :: UiWidget-drawHelpBar = hBorder <=> hCenter help- where- help =- hBox (map (\(k, v) -> txt k <+> dim (txt (" (" <> v <> ") "))) helps)-- helps =- [- ("n", "step")- , ("p", "step back")- , ("a", "step to start")- , ("e", "step to end")- , ("m", "toggle memory")- , ("Esc", "exit")- , ("h", "more help")- ]--stepOneOpcode :: Stepper a -> StateT UiVmState IO ()-stepOneOpcode restart = do- n <- use #step- when (n > 0 && n `mod` snapshotInterval == 0) $ do- vm <- use #vm- modifying #snapshots (insert n (vm, void restart))- modifying #vm (execState exec1)- modifying #step (+ 1)--isNewTraceAdded- :: UiVmState -> Pred VM-isNewTraceAdded ui vm =- let- currentTraceTree = length <$> traceForest ui.vm- newTraceTree = length <$> traceForest vm- in currentTraceTree /= newTraceTree--isNextSourcePosition- :: UiVmState -> Pred VM-isNextSourcePosition ui vm =- let dapp = ui.testOpts.dapp- initialPosition = currentSrcMap dapp ui.vm- in currentSrcMap dapp vm /= initialPosition--isNextSourcePositionWithoutEntering- :: UiVmState -> Pred VM-isNextSourcePositionWithoutEntering ui vm =- let- dapp = ui.testOpts.dapp- vm0 = ui.vm- initialPosition = currentSrcMap dapp vm0- initialHeight = length vm0.frames- in- case currentSrcMap dapp vm of- Nothing ->- False- Just here ->- let- moved = Just here /= initialPosition- deeper = length vm.frames > initialHeight- boring =- case srcMapCode dapp.sources here of- Just bs ->- BS.isPrefixOf "contract " bs- Nothing ->- True- in- moved && not deeper && not boring--isExecutionHalted :: UiVmState -> Pred VM-isExecutionHalted _ vm = isJust vm.result--currentSrcMap :: DappInfo -> VM -> Maybe SrcMap-currentSrcMap dapp vm = do- this <- currentContract vm- i <- this.opIxMap SVec.!? vm.state.pc- srcMap dapp this i--drawStackPane :: UiVmState -> UiWidget-drawStackPane ui =- let- gasText = showWordExact (into ui.vm.state.gas)- labelText = txt ("Gas available: " <> gasText <> "; stack:")- stackList = list StackPane (Vec.fromList $ zip [(1 :: Int)..] (simplify <$> ui.vm.state.stack)) 2- in hBorderWithLabel labelText <=>- renderList- (\_ (i, w) ->- vBox- [ withHighlight True (str ("#" ++ show i ++ " "))- <+> ourWrap (Text.unpack $ prettyIfConcreteWord w)- , dim (txt (" " <> case maybeLitWord w of- Nothing -> ""- Just u -> showWordExplanation u ui.testOpts.dapp))- ])- False- stackList--message :: VM -> String-message vm =- case vm.result of- Just (VMSuccess (ConcreteBuf msg)) ->- "VMSuccess: " <> (show $ ByteStringS msg)- Just (VMSuccess (msg)) ->- "VMSuccess: <symbolicbuffer> " <> (show msg)- Just (VMFailure (Revert msg)) ->- "VMFailure: " <> (show msg)- Just (VMFailure err) ->- "VMFailure: " <> show err- Just (Unfinished p) ->- "Could not continue execution: " <> show p- Just (HandleEffect e) ->- "Handling side effect: " <> show e- Nothing ->- "Executing EVM code in " <> show vm.state.contract---drawBytecodePane :: UiVmState -> UiWidget-drawBytecodePane ui =- let- vm = ui.vm- move = maybe id listMoveTo $ vmOpIx vm- in- hBorderWithLabel (str $ message vm) <=>- Centered.renderList- (\active x -> if not active- then withDefAttr dimAttr (opWidget x)- else withDefAttr boldAttr (opWidget x))- False- (move $ list BytecodePane- (maybe mempty (.codeOps) (currentContract vm))- 1)---dim :: Widget n -> Widget n-dim = withDefAttr dimAttr--withHighlight :: Bool -> Widget n -> Widget n-withHighlight False = withDefAttr dimAttr-withHighlight True = withDefAttr boldAttr--prettyIfConcrete :: Expr Buf -> String-prettyIfConcrete (ConcreteBuf x) = prettyHex 40 x-prettyIfConcrete x = T.unpack $ formatExpr $ simplify x--drawTracePane :: UiVmState -> UiWidget-drawTracePane s =- let vm = s.vm- dapp = s.testOpts.dapp- traceList =- list- TracePane- (Vec.fromList- . Text.lines- . showTraceTree dapp- $ vm)- 1-- in case s.showMemory of- True -> viewport TracePane Vertical $- hBorderWithLabel (txt "Calldata")- <=> ourWrap (prettyIfConcrete vm.state.calldata)- <=> hBorderWithLabel (txt "Returndata")- <=> ourWrap (prettyIfConcrete vm.state.returndata)- <=> hBorderWithLabel (txt "Output")- <=> ourWrap (maybe "" show vm.result)- <=> hBorderWithLabel (txt "Cache")- <=> ourWrap (show vm.cache.path)- <=> hBorderWithLabel (txt "Path Conditions")- <=> (ourWrap $ show $ vm.constraints)- <=> hBorderWithLabel (txt "Memory")- <=> (ourWrap (prettyIfConcrete vm.state.memory))- False ->- hBorderWithLabel (txt "Trace")- <=> renderList- (\_ x -> txt x)- False- (listMoveTo (length traceList) traceList)--ourWrap :: String -> Widget n-ourWrap = strWrapWith settings- where- settings = WrapSettings- { preserveIndentation = True- , breakLongWords = True- , fillStrategy = NoFill- , fillScope = FillAfterFirst- }--solidityList :: VM -> DappInfo -> List Name (Int, ByteString)-solidityList vm dapp =- list SolidityPane- (case currentSrcMap dapp vm of- Nothing -> mempty- Just x ->- fromMaybe- (internalError "unable to find line for source map")- (preview (- ix x.file- % to (Vec.imap (,)))- dapp.sources.lines))- 1--drawSolidityPane :: UiVmState -> UiWidget-drawSolidityPane ui =- let dapp = ui.testOpts.dapp- dappSrcs = dapp.sources- vm = ui.vm- in case currentSrcMap dapp vm of- Nothing -> padBottom Max (hBorderWithLabel (txt "<no source map>"))- Just sm ->- let- rows = dappSrcs.lines !? sm.file- subrange :: Int -> Maybe (Int, Int)- subrange i = do- rs <- rows- lineSubrange rs (sm.offset, sm.length) i- fileName :: Maybe Text- fileName = T.pack . fst <$> (dapp.sources.files !? sm.file)- lineNo :: Maybe Int- lineNo = ((\a -> Just (a - 1)) . snd) =<< srcMapCodePos dapp.sources sm- in vBox- [ hBorderWithLabel $- txt (fromMaybe "<unknown>" fileName)- <+> str (":" ++ (maybe "?" show lineNo))-- -- Show the AST node type if present- <+> txt (" (" <> fromMaybe "?"- (dapp.astSrcMap sm- >>= preview (key "name" % _String)) <> ")")- , Centered.renderList- (\_ (i, line) ->- let s = case decodeUtf8 line of "" -> " "; y -> y- in case subrange i of- Nothing -> withHighlight False (txt s)- Just (a, b) ->- let (x, y, z) = ( Text.take a s- , Text.take b (Text.drop a s)- , Text.drop (a + b) s- )- in hBox [ withHighlight False (txt x)- , withHighlight True (txt y)- , withHighlight False (txt z)- ])- False- ((maybe id listMoveTo lineNo)- (solidityList vm dapp))- ]--ifTallEnough :: Int -> Widget n -> Widget n -> Widget n-ifTallEnough need w1 w2 =- Widget Greedy Greedy $ do- c <- getContext- if view (lensVL availHeightL) c > need- then render w1- else render w2--opWidget :: (Integral a, Show a) => (a, Op) -> Widget n-opWidget = txt . pack . opString--selectedAttr :: AttrName; selectedAttr = attrName "selected"-dimAttr :: AttrName; dimAttr = attrName "dim"-wordAttr :: AttrName; wordAttr = attrName "word"-boldAttr :: AttrName; boldAttr = attrName "bold"-activeAttr :: AttrName; activeAttr = attrName "active"
− src/EVM/TTYCenteredList.hs
@@ -1,70 +0,0 @@-module EVM.TTYCenteredList where---- Hard fork of brick's List that centers the currently highlighted line.--import Optics.Core-import Data.Maybe (fromMaybe)-import Data.Vector qualified as V--import Brick.Types-import Brick.Widgets.Core-import Brick.Widgets.List---- | Turn a list state value into a widget given an item drawing--- function.-renderList :: (Ord n, Show n)- => (Bool -> e -> Widget n)- -- ^ Rendering function, True for the selected element- -> Bool- -- ^ Whether the list has focus- -> List n e- -- ^ The List to be rendered- -> Widget n- -- ^ rendered widget-renderList drawElem foc l =- withDefAttr listAttr $- drawListElements foc l drawElem--drawListElements :: (Ord n, Show n) => Bool -> List n e -> (Bool -> e -> Widget n) -> Widget n-drawListElements foc l drawElem =- Widget Greedy Greedy $ do- c <- getContext-- let es = V.slice start num (l ^. (lensVL listElementsL))- idx = fromMaybe 0 (l ^. (lensVL listSelectedL))-- start = max 0 $ idx - (initialNumPerHeight `div` 2)- num = min (numPerHeight * 2) (V.length (l ^. (lensVL listElementsL)) - start)-- -- The number of items to show is the available height divided by- -- the item height...- initialNumPerHeight = (c ^. (lensVL availHeightL)) `div` (l ^. (lensVL listItemHeightL))- -- ... but if the available height leaves a remainder of- -- an item height then we need to ensure that we render an- -- extra item to show a partial item at the top or bottom to- -- give the expected result when an item is more than one- -- row high. (Example: 5 rows available with item height- -- of 3 yields two items: one fully rendered, the other- -- rendered with only its top 2 or bottom 2 rows visible,- -- depending on how the viewport state changes.)- numPerHeight = initialNumPerHeight +- if initialNumPerHeight * (l ^. (lensVL listItemHeightL)) == c ^. (lensVL availHeightL)- then 0- else 1-- -- off = start * (l^.listItemHeightL)-- drawnElements = flip V.imap es $ \i e ->- let isSelected = i == (if start == 0 then idx else div initialNumPerHeight 2)- elemWidget = drawElem isSelected e- selItemAttr = if foc- then withDefAttr listSelectedFocusedAttr- else withDefAttr listSelectedAttr- makeVisible = if isSelected- then visible . selItemAttr- else id- in makeVisible elemWidget-- render $ viewport (l ^. (lensVL listNameL)) Vertical $- -- translateBy (Location (0, off)) $- vBox $ V.toList drawnElements
src/EVM/Transaction.hs view
@@ -5,8 +5,8 @@ import EVM.RLP import EVM.Types import EVM.Format (hexText)-import EVM.Expr (litAddr) import EVM.Sign+import qualified EVM.Expr as Expr import Optics.Core hiding (cons) @@ -61,7 +61,7 @@ instance JSON.ToJSON Transaction where toJSON t = JSON.object [ ("input", (JSON.toJSON (ByteStringS t.txdata)))- , ("gas", (JSON.toJSON $ "0x" ++ showHex (toInteger $ t.gasLimit) ""))+ , ("gas", (JSON.toJSON $ "0x" ++ showHex (into @Integer $ t.gasLimit) "")) , ("gasPrice", (JSON.toJSON $ show $ fromJust $ t.gasPrice)) , ("v", (JSON.toJSON $ show $ (t.v)-27)) , ("r", (JSON.toJSON $ show $ t.r))@@ -225,28 +225,28 @@ parseJSON invalid = JSON.typeMismatch "Transaction" invalid -accountAt :: Addr -> Getter (Map Addr Contract) Contract+accountAt :: Expr EAddr -> Getter (Map (Expr EAddr) Contract) Contract accountAt a = (at a) % (to $ fromMaybe newAccount) -touchAccount :: Addr -> Map Addr Contract -> Map Addr Contract+touchAccount :: Expr EAddr -> Map (Expr EAddr) Contract -> Map (Expr EAddr) Contract touchAccount a = Map.insertWith (flip const) a newAccount newAccount :: Contract-newAccount = initialContract $ RuntimeCode (ConcreteRuntimeCode "")+newAccount = initialContract (RuntimeCode (ConcreteRuntimeCode "")) -- | Increments origin nonce and pays gas deposit-setupTx :: Addr -> Addr -> W256 -> Word64 -> Map Addr Contract -> Map Addr Contract+setupTx :: Expr EAddr -> Expr EAddr -> W256 -> Word64 -> Map (Expr EAddr) Contract -> Map (Expr EAddr) Contract setupTx origin coinbase gasPrice gasLimit prestate = let gasCost = gasPrice * (into gasLimit)- in (Map.adjust ((over #nonce (+ 1))- . (over #balance (subtract gasCost))) origin)+ in (Map.adjust ((over #nonce (fmap ((+) 1)))+ . (over #balance (`Expr.sub` (Lit gasCost)))) origin) . touchAccount origin . touchAccount coinbase $ prestate -- | Given a valid tx loaded into the vm state, -- subtract gas payment from the origin, increment the nonce -- and pay receiving address-initTx :: VM -> VM+initTx :: VM s -> VM s initTx vm = let toAddr = vm.state.contract origin = vm.tx.origin@@ -258,23 +258,13 @@ preState = setupTx origin coinbase gasPrice gasLimit vm.env.contracts oldBalance = view (accountAt toAddr % #balance) preState creation = vm.tx.isCreate- initState = (case maybeLitWord value of- Just v -> ((Map.adjust (over #balance (subtract v))) origin)- . (Map.adjust (over #balance (+ v))) toAddr- Nothing -> id)+ initState =+ ((Map.adjust (over #balance (`Expr.sub` value))) origin)+ . (Map.adjust (over #balance (Expr.add value))) toAddr . (if creation- then Map.insert toAddr (toContract & #balance .~ oldBalance)+ then Map.insert toAddr (toContract & (set #balance oldBalance)) else touchAccount toAddr) $ preState-- resetConcreteStore s = if creation then Map.insert (into toAddr) mempty s else s-- resetStore (ConcreteStore s) = ConcreteStore (resetConcreteStore s)- resetStore (SStore a@(Lit _) k v s) = if creation && a == (litAddr toAddr) then resetStore s else (SStore a k v (resetStore s))- resetStore (SStore {}) = internalError "cannot reset storage if it contains symbolic addresses"- resetStore s = s in vm & #env % #contracts .~ initState & #tx % #txReversion .~ preState- & #env % #storage %~ resetStore- & #env % #origStorage %~ resetConcreteStore
src/EVM/Traversals.hs view
@@ -7,6 +7,8 @@ import Prelude hiding (LT, GT) import Control.Monad.Identity+import qualified Data.Map.Strict as Map+import Data.List (foldl') import EVM.Types @@ -26,26 +28,28 @@ POr a b -> go a <> go b PImpl a b -> go a <> go b -foldTrace :: forall b . Monoid b => (forall a . Expr a -> b) -> b -> Trace -> b-foldTrace f acc t = acc <> (go t)- where- go :: Trace -> b- go (Trace _ _ d) = case d of- EventTrace a b c -> foldExpr f mempty a <> foldExpr f mempty b <> (foldl (foldExpr f) mempty c)- FrameTrace a -> go' a- ErrorTrace _ -> mempty- EntryTrace _ -> mempty- ReturnTrace a b -> foldExpr f mempty a <> go' b-- go' :: FrameContext -> b- go' = \case- CreationContext _ b _ _ -> foldExpr f mempty b- CallContext _ _ _ _ e _ g (_, h) _ -> foldExpr f mempty e <> foldExpr f mempty g <> foldExpr f mempty h--foldTraces :: forall b . Monoid b => (forall a . Expr a -> b) -> b -> Traces -> b-foldTraces f acc (Traces a _) = acc <> foldl (foldl (foldTrace f)) mempty a+foldEContract :: forall b . Monoid b => (forall a . Expr a -> b) -> b -> Expr EContract -> b+foldEContract f _ g@(GVar _) = f g+foldEContract f acc (C code storage balance _)+ = acc+ <> foldCode f code+ <> foldExpr f mempty storage+ <> foldExpr f mempty balance +foldContract :: forall b . Monoid b => (forall a . Expr a -> b) -> b -> Contract -> b+foldContract f acc c+ = acc+ <> foldCode f c.code+ <> foldExpr f mempty c.storage+ <> foldExpr f mempty c.origStorage+ <> foldExpr f mempty c.balance +foldCode :: forall b . Monoid b => (forall a . Expr a -> b) -> ContractCode -> b+foldCode f = \case+ RuntimeCode (ConcreteRuntimeCode _) -> mempty+ RuntimeCode (SymbolicRuntimeCode c) -> foldl' (foldExpr f) mempty c+ InitCode _ buf -> foldExpr f mempty buf+ UnknownCode addr -> foldExpr f mempty addr -- | Recursively folds a given function over a given expression -- Recursion schemes do this & a lot more, but defining them over GADT's isn't worth the hassle@@ -62,6 +66,10 @@ e@(Var _) -> f e e@(GVar _) -> f e + -- contracts++ e@(C {}) -> foldEContract f acc e+ -- bytes e@(IndexWord a b) -> f e <> (go a) <> (go b)@@ -86,9 +94,14 @@ -- control flow - e@(Success a b c d) -> f e <> (foldl (foldProp f) mempty a) <> foldTraces f mempty b <> (go c) <> (go d)- e@(Failure a b _) -> f e <> (foldl (foldProp f) mempty a) <> foldTraces f mempty b- e@(Partial a b _) -> f e <> (foldl (foldProp f) mempty a) <> foldTraces f mempty b+ e@(Success a _ c d) -> f e+ <> foldl (foldProp f) mempty a+ <> go c+ <> foldl' (foldExpr f) mempty (Map.keys d)+ <> foldl' (foldEContract f) mempty d+ e@(Failure a _ (Revert c)) -> f e <> (foldl (foldProp f) mempty a) <> go c+ e@(Failure a _ _) -> f e <> (foldl (foldProp f) mempty a)+ e@(Partial a _ _) -> f e <> (foldl (foldProp f) mempty a) e@(ITE a b c) -> f e <> (go a) <> (go b) <> (go c) -- integers@@ -145,89 +158,36 @@ e@(BaseFee) -> f e e@(BlockHash a) -> f e <> (go a) + -- tx context++ e@(TxValue) -> f e+ -- frame context - e@(Caller _) -> f e- e@(CallValue _) -> f e- e@(Address _) -> f e- e@(SelfBalance _ _) -> f e e@(Gas _ _) -> f e e@(Balance {}) -> f e -- code e@(CodeSize a) -> f e <> (go a)- e@(ExtCodeHash a) -> f e <> (go a)+ e@(CodeHash a) -> f e <> (go a) -- logs e@(LogEntry a b c) -> f e <> (go a) <> (go b) <> (foldl (<>) mempty (fmap f c)) - -- Contract Creation-- e@(Create a b c d g h)- -> f e- <> (go a)- <> (go b)- <> (go c)- <> (go d)- <> (foldl (<>) mempty (fmap go g))- <> (go h)- e@(Create2 a b c d g h i)- -> f e- <> (go a)- <> (go b)- <> (go c)- <> (go d)- <> (go g)- <> (foldl (<>) mempty (fmap go h))- <> (go i)-- -- Calls-- e@(Call a b c d g h i j k)- -> f e- <> (go a)- <> (maybe mempty (go) b)- <> (go c)- <> (go d)- <> (go g)- <> (go h)- <> (go i)- <> (foldl (<>) mempty (fmap go j))- <> (go k)-- e@(CallCode a b c d g h i j k)- -> f e- <> (go a)- <> (go b)- <> (go c)- <> (go d)- <> (go g)- <> (go h)- <> (go i)- <> (foldl (<>) mempty (fmap go j))- <> (go k)+ -- storage - e@(DelegeateCall a b c d g h i j k)- -> f e- <> (go a)- <> (go b)- <> (go c)- <> (go d)- <> (go g)- <> (go h)- <> (go i)- <> (foldl (<>) mempty (fmap go j))- <> (go k)+ e@(LitAddr _) -> f e+ e@(WAddr a) -> f e <> go a+ e@(SymAddr _) -> f e -- storage - e@(EmptyStore) -> f e e@(ConcreteStore _) -> f e- e@(AbstractStore) -> f e- e@(SLoad a b c) -> f e <> (go a) <> (go b) <> (go c)- e@(SStore a b c d) -> f e <> (go a) <> (go b) <> (go c) <> (go d)+ 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) -- buffers @@ -260,28 +220,22 @@ POr a b -> POr (mapProp f a) (mapProp f b) PImpl a b -> PImpl (mapProp f a) (mapProp f b) -mapTrace :: (forall a . Expr a -> Expr a) -> Trace -> Trace-mapTrace f (Trace x y z) = Trace x y (go z)- where- go :: TraceData -> TraceData- go = \case- EventTrace a b c -> EventTrace (f a) (f b) (fmap (mapExpr f) c)- FrameTrace a -> FrameTrace (go' a)- ErrorTrace a -> ErrorTrace a- EntryTrace a -> EntryTrace a- ReturnTrace a b -> ReturnTrace (f a) (go' b)-- go' :: FrameContext -> FrameContext- go' = \case- CreationContext a b c d -> CreationContext a (f b) c d- CallContext a b c d e g h (i,j) k -> CallContext a b c d (f e) g (f h) (i,f j) k+mapProp' :: (Prop -> Prop) -> Prop -> Prop+mapProp' f = \case+ PBool b -> f $ PBool b+ PEq a b -> f $ PEq a b+ PLT a b -> f $ PLT a b+ PGT a b -> f $ PGT a b+ PLEq a b -> f $ PLEq a b+ PGEq a b -> f $ PGEq a b+ PNeg a -> f $ PNeg (mapProp' f a)+ PAnd a b -> f $ PAnd (mapProp' f a) (mapProp' f b)+ POr a b -> f $ POr (mapProp' f a) (mapProp' f b)+ PImpl a b -> f $ PImpl (mapProp' f a) (mapProp' f b) --- | Recursively applies a given function to every node in a given expr instance--- Recursion schemes do this & a lot more, but defining them over GADT's isn't worth the hassle mapExpr :: (forall a . Expr a -> Expr a) -> Expr b -> Expr b mapExpr f expr = runIdentity (mapExprM (Identity . f) expr) - mapExprM :: Monad m => (forall a . Expr a -> m (Expr a)) -> Expr b -> m (Expr b) mapExprM f expr = case expr of @@ -292,6 +246,18 @@ Var a -> f (Var a) GVar s -> f (GVar s) + -- addresses++ c@(C {}) -> mapEContractM f c++ -- addresses++ LitAddr a -> f (LitAddr a)+ SymAddr a -> f (SymAddr a)+ WAddr a -> do+ a' <- mapExprM f a+ f (WAddr a')+ -- bytes IndexWord a b -> do@@ -348,19 +314,21 @@ Failure a b c -> do a' <- mapM (mapPropM f) a- b' <- mapTracesM f b- f (Failure a' b' c)+ f (Failure a' b c) Partial a b c -> do a' <- mapM (mapPropM f) a- b' <- mapTracesM f b- f (Partial a' b' c)+ f (Partial a' b c) Success a b c d -> do a' <- mapM (mapPropM f) a- b' <- mapTracesM f b c' <- mapExprM f c- d' <- mapExprM f d- f (Success a' b' c' d')-+ d' <- do+ let x = Map.toList d+ x' <- forM x $ \(k,v) -> do+ k' <- f k+ v' <- mapEContractM f v+ pure (k',v')+ pure $ Map.fromList x'+ f (Success a' b c' d') ITE a b c -> do a' <- mapExprM f a b' <- mapExprM f b@@ -513,25 +481,25 @@ a' <- mapExprM f a f (BlockHash a') + -- tx context++ TxValue -> f TxValue+ -- frame context - Caller a -> f (Caller a)- CallValue a -> f (CallValue a)- Address a -> f (Address a)- SelfBalance a b -> f (SelfBalance a b) Gas a b -> f (Gas a b)- Balance a b c -> do- c' <- mapExprM f c- f (Balance a b c')+ Balance a -> do+ a' <- mapExprM f a+ f (Balance a') -- code CodeSize a -> do a' <- mapExprM f a f (CodeSize a')- ExtCodeHash a -> do+ CodeHash a -> do a' <- mapExprM f a- f (ExtCodeHash a')+ f (CodeHash a') -- logs @@ -541,78 +509,19 @@ c' <- mapM (mapExprM f) c f (LogEntry a' b' c') - -- Contract Creation-- Create a b c d e g -> do- a' <- mapExprM f a- b' <- mapExprM f b- c' <- mapExprM f c- d' <- mapExprM f d- e' <- mapM (mapExprM f) e- g' <- mapExprM f g- f (Create a' b' c' d' e' g')- Create2 a b c d e g h -> do- a' <- mapExprM f a- b' <- mapExprM f b- c' <- mapExprM f c- d' <- mapExprM f d- e' <- mapExprM f e- g' <- mapM (mapExprM f) g- h' <- mapExprM f h- f (Create2 a' b' c' d' e' g' h')-- -- Calls-- Call a b c d e g h i j -> do- a' <- mapExprM f a- b' <- mapM (mapExprM f) b- c' <- mapExprM f c- d' <- mapExprM f d- e' <- mapExprM f e- g' <- mapExprM f g- h' <- mapExprM f h- i' <- mapM (mapExprM f) i- j' <- mapExprM f j- f (Call a' b' c' d' e' g' h' i' j')- CallCode a b c d e g h i j -> do- a' <- mapExprM f a- b' <- mapExprM f b- c' <- mapExprM f c- d' <- mapExprM f d- e' <- mapExprM f e- g' <- mapExprM f g- h' <- mapExprM f h- i' <- mapM (mapExprM f) i- j' <- mapExprM f j- f (CallCode a' b' c' d' e' g' h' i' j')- DelegeateCall a b c d e g h i j -> do- a' <- mapExprM f a- b' <- mapExprM f b- c' <- mapExprM f c- d' <- mapExprM f d- e' <- mapExprM f e- g' <- mapExprM f g- h' <- mapExprM f h- i' <- mapM (mapExprM f) i- j' <- mapExprM f j- f (DelegeateCall a' b' c' d' e' g' h' i' j')- -- storage - EmptyStore -> f EmptyStore- ConcreteStore a -> f (ConcreteStore a)- AbstractStore -> f AbstractStore- SLoad a b c -> do+ ConcreteStore b -> f (ConcreteStore b)+ AbstractStore a -> f (AbstractStore a)+ SLoad a b -> do a' <- mapExprM f a b' <- mapExprM f b- c' <- mapExprM f c- f (SLoad a' b' c')- SStore a b c d -> do+ f (SLoad a' b')+ SStore a b c -> do a' <- mapExprM f a b' <- mapExprM f b c' <- mapExprM f c- d' <- mapExprM f d- f (SStore a' b' c' d')+ f (SStore a' b' c') -- buffers @@ -651,7 +560,6 @@ a' <- mapExprM f a f (BufLength a') - mapPropM :: Monad m => (forall a . Expr a -> m (Expr a)) -> Prop -> m Prop mapPropM f = \case PBool b -> pure $ PBool b@@ -691,48 +599,38 @@ b' <- mapPropM f b pure $ PImpl a' b' -mapTracesM :: forall m . Monad m => (forall a . Expr a -> m (Expr a)) -> Traces -> m Traces-mapTracesM f (Traces a b) = do- a' <- mapM (mapM (mapTraceM f)) a- pure $ Traces a' b -mapTraceM :: forall m . Monad m => (forall a . Expr a -> m (Expr a)) -> Trace -> m Trace-mapTraceM f (Trace x y z) = do- z' <- go z- pure $ Trace x y z'- where- go :: TraceData -> m TraceData- go = \case- EventTrace a b c -> do- a' <- mapExprM f a- b' <- mapExprM f b- c' <- mapM (mapExprM f) c- pure $ EventTrace a' b' c'- FrameTrace a -> do- a' <- go' a- pure $ FrameTrace a'- ReturnTrace a b -> do- a' <- mapExprM f a- b' <- go' b- pure $ ReturnTrace a' b'- a -> pure a+mapEContractM :: Monad m => (forall a . Expr a -> m (Expr a)) -> Expr EContract -> m (Expr EContract)+mapEContractM _ g@(GVar _) = pure g+mapEContractM f (C code storage balance nonce) = do+ code' <- mapCodeM f code+ storage' <- mapExprM f storage+ balance' <- mapExprM f balance+ pure $ C code' storage' balance' nonce - go' :: FrameContext -> m FrameContext- go' = \case- CreationContext a b c d -> do- b' <- mapExprM f b- pure $ CreationContext a b' c d- CallContext a b c d e g h (i,j) k -> do- e' <- mapExprM f e- h' <- mapExprM f h- j' <- mapExprM f j- pure $ CallContext a b c d e' g h' (i,j') k+mapContractM :: Monad m => (forall a . Expr a -> m (Expr a)) -> Contract -> m (Contract)+mapContractM f c = do+ code' <- mapCodeM f c.code+ storage' <- mapExprM f c.storage+ origStorage' <- mapExprM f c.origStorage+ balance' <- mapExprM f c.balance+ pure $ c { code = code', storage = storage', origStorage = origStorage', balance = balance' } +mapCodeM :: Monad m => (forall a . Expr a -> m (Expr a)) -> ContractCode -> m (ContractCode)+mapCodeM f = \case+ UnknownCode a -> fmap UnknownCode (f a)+ c@(RuntimeCode (ConcreteRuntimeCode _)) -> pure c+ RuntimeCode (SymbolicRuntimeCode c) -> do+ c' <- mapM (mapExprM f) c+ pure . RuntimeCode $ SymbolicRuntimeCode c'+ InitCode bs buf -> do+ buf' <- mapExprM f buf+ pure $ InitCode bs buf'+ -- | Generic operations over AST terms class TraversableTerm a where mapTerm :: (forall b. Expr b -> Expr b) -> a -> a foldTerm :: forall c. Monoid c => (forall b. Expr b -> c) -> c -> a -> c- instance TraversableTerm (Expr a) where mapTerm = mapExpr
src/EVM/Types.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-} @@ -8,13 +9,15 @@ import GHC.Stack (HasCallStack, prettyCallStack, callStack) import Control.Arrow ((>>>))-import Control.Monad.State.Strict (State, mzero)+import Control.Monad.ST (ST)+import Control.Monad.State.Strict (StateT, mzero) import Crypto.Hash (hash, Keccak_256, Digest) import Data.Aeson import Data.Aeson qualified as JSON import Data.Aeson.Types qualified as JSON import Data.Bifunctor (first) import Data.Bits (Bits, FiniteBits, shiftR, shift, shiftL, (.&.), (.|.), toIntegralSized)+import Data.Binary qualified as Binary import Data.ByteArray qualified as BA import Data.Char import Data.List (foldl')@@ -41,10 +44,10 @@ import Data.Tree.Zipper qualified as Zipper import Data.Vector qualified as V import Data.Vector.Storable qualified as SV+import Data.Vector.Unboxed.Mutable (STVector) import Numeric (readHex, showHex) import Options.Generic import Optics.TH-import EVM.Hexdump (paddedShowHex) import EVM.FeeSchedule (FeeSchedule (..)) import Text.Regex.TDFA qualified as Regex@@ -59,6 +62,14 @@ mkUnpackedDoubleWord "Word512" ''Word256 "Int512" ''Int256 ''Word256 [''Typeable, ''Data, ''Generic] +++-- Conversions -------------------------------------------------------------------------------------+++-- We ignore hlint to supress the warnings about `fromIntegral` and friends here+#ifndef __HLINT__+ instance From Addr Integer where from = fromIntegral instance From Addr W256 where from = fromIntegral instance From Int256 Integer where from = fromIntegral@@ -68,7 +79,9 @@ instance From Word8 Word256 where from = fromIntegral instance From Word32 W256 where from = fromIntegral instance From Word32 Word256 where from = fromIntegral+instance From Word32 ByteString where from = toStrict . Binary.encode instance From Word64 W256 where from = fromIntegral+instance From W64 W256 where from = fromIntegral instance From Word256 Integer where from = fromIntegral instance From Word256 W256 where from = fromIntegral @@ -76,6 +89,7 @@ instance TryFrom Int Word256 where tryFrom = maybeTryFrom toIntegralSized instance TryFrom Int256 W256 where tryFrom = maybeTryFrom toIntegralSized instance TryFrom Integer W256 where tryFrom = maybeTryFrom toIntegralSized+instance TryFrom Integer Addr where tryFrom = maybeTryFrom toIntegralSized -- TODO: hevm relies on this behavior instance TryFrom W256 Addr where tryFrom = Right . fromIntegral instance TryFrom W256 FunctionSelector where tryFrom = maybeTryFrom toIntegralSized@@ -86,6 +100,7 @@ instance TryFrom W256 Word32 where tryFrom = maybeTryFrom toIntegralSized -- TODO: hevm relies on this behavior instance TryFrom W256 Word64 where tryFrom = Right . fromIntegral+instance TryFrom W256 W64 where tryFrom = Right . fromIntegral instance TryFrom Word160 Word8 where tryFrom = maybeTryFrom toIntegralSized instance TryFrom Word256 Int where tryFrom = maybeTryFrom toIntegralSized instance TryFrom Word256 Int256 where tryFrom = maybeTryFrom toIntegralSized@@ -93,19 +108,27 @@ instance TryFrom Word256 Word32 where tryFrom = maybeTryFrom toIntegralSized instance TryFrom Word512 W256 where tryFrom = maybeTryFrom toIntegralSized +truncateToAddr :: W256 -> Addr+truncateToAddr = fromIntegral++#endif++ -- Symbolic IR ------------------------------------------------------------------------------------- + -- phantom type tags for AST construction data EType = Buf | Storage | Log | EWord+ | EAddr+ | EContract | Byte | End deriving (Typeable) - -- Variables refering to a global environment data GVar (a :: EType) where BufVar :: Int -> GVar Buf@@ -168,8 +191,11 @@ -- identifiers + -- | Literal words Lit :: {-# UNPACK #-} !W256 -> Expr EWord+ -- | Variables Var :: Text -> Expr EWord+ -- | variables introduced during the CSE pass GVar :: GVar a -> Expr a -- bytes@@ -192,7 +218,7 @@ Partial :: [Prop] -> Traces -> PartialExec -> Expr End Failure :: [Prop] -> Traces -> EvmError -> Expr End- Success :: [Prop] -> Traces -> Expr Buf -> Expr Storage -> Expr End+ Success :: [Prop] -> Traces -> Expr Buf -> Map (Expr EAddr) (Expr EContract) -> Expr End ITE :: Expr EWord -> Expr End -> Expr End -> Expr End -- integers@@ -249,25 +275,13 @@ ChainId :: Expr EWord BaseFee :: Expr EWord - -- frame context-- CallValue :: Int -- frame idx- -> Expr EWord-- Caller :: Int -- frame idx- -> Expr EWord+ -- tx context - Address :: Int -- frame idx- -> Expr EWord+ TxValue :: Expr EWord - Balance :: Int -- frame idx- -> Int -- PC (in case we're checking the current contract)- -> Expr EWord -- address- -> Expr EWord+ -- frame context - SelfBalance :: Int -- frame idx- -> Int -- PC- -> Expr EWord+ Balance :: Expr EAddr -> Expr EWord Gas :: Int -- frame idx -> Int -- PC@@ -275,10 +289,10 @@ -- code - CodeSize :: Expr EWord -- address+ CodeSize :: Expr EAddr -- address -> Expr EWord -- size - ExtCodeHash :: Expr EWord -- address+ CodeHash :: Expr EAddr -- address -> Expr EWord -- size -- logs@@ -288,73 +302,37 @@ -> [Expr EWord] -- topics -> Expr Log - -- Contract Creation - Create :: Expr EWord -- value- -> Expr EWord -- offset- -> Expr EWord -- size- -> Expr Buf -- memory- -> [Expr Log] -- logs- -> Expr Storage -- storage- -> Expr EWord -- address-- Create2 :: Expr EWord -- value- -> Expr EWord -- offset- -> Expr EWord -- size- -> Expr EWord -- salt- -> Expr Buf -- memory- -> [Expr Log] -- logs- -> Expr Storage -- storage- -> Expr EWord -- address-- -- Calls+ -- Contract - Call :: Expr EWord -- gas- -> Maybe (Expr EWord) -- target- -> Expr EWord -- value- -> Expr EWord -- args offset- -> Expr EWord -- args size- -> Expr EWord -- ret offset- -> Expr EWord -- ret size- -> [Expr Log] -- logs- -> Expr Storage -- storage- -> Expr EWord -- success+ -- A restricted view of a contract that does not include extraneous metadata+ -- from the full constructor defined in the VM state+ C ::+ { code :: ContractCode+ , storage :: Expr Storage+ , balance :: Expr EWord+ , nonce :: Maybe W64+ } -> Expr EContract - CallCode :: Expr EWord -- gas- -> Expr EWord -- address- -> Expr EWord -- value- -> Expr EWord -- args offset- -> Expr EWord -- args size- -> Expr EWord -- ret offset- -> Expr EWord -- ret size- -> [Expr Log] -- logs- -> Expr Storage -- storage- -> Expr EWord -- success+ -- addresses - DelegeateCall :: Expr EWord -- gas- -> Expr EWord -- address- -> Expr EWord -- value- -> Expr EWord -- args offset- -> Expr EWord -- args size- -> Expr EWord -- ret offset- -> Expr EWord -- ret size- -> [Expr Log] -- logs- -> Expr Storage -- storage- -> Expr EWord -- success+ -- Symbolic addresses are identified with an int. It is important that+ -- semantic equality is the same as syntactic equality here. Additionally all+ -- SAddr's in a given expression should be constrained to differ from any LitAddr's+ SymAddr :: Text -> Expr EAddr+ LitAddr :: Addr -> Expr EAddr+ WAddr :: Expr EAddr -> Expr EWord -- storage - EmptyStore :: Expr Storage- ConcreteStore :: Map W256 (Map W256 W256) -> Expr Storage- AbstractStore :: Expr Storage+ ConcreteStore :: (Map W256 W256) -> Expr Storage+ AbstractStore :: Expr EAddr -> Expr Storage - SLoad :: Expr EWord -- address- -> Expr EWord -- index+ SLoad :: Expr EWord -- key -> Expr Storage -- storage -> Expr EWord -- result - SStore :: Expr EWord -- address- -> Expr EWord -- index+ SStore :: Expr EWord -- key -> Expr EWord -- value -> Expr Storage -- old storage -> Expr Storage -- new storae@@ -514,12 +492,17 @@ _ <= _ = False +isPBool :: Prop -> Bool+isPBool (PBool _) = True+isPBool _ = False++ -- Errors ------------------------------------------------------------------------------------------ -- | Core EVM Error Types data EvmError- = BalanceTooLow W256 W256+ = BalanceTooLow (Expr EWord) (Expr EWord) | UnrecognizedOpcode Word8 | SelfDestruction | StackUnderrun@@ -542,35 +525,36 @@ -- | Sometimes we can only partially execute a given program data PartialExec- = UnexpectedSymbolicArg { pc :: Int, msg :: String, args :: [SomeExpr] }- | MaxIterationsReached { pc :: Int, addr :: Addr }+ = UnexpectedSymbolicArg { pc :: Int, msg :: String, args :: [SomeExpr] }+ | MaxIterationsReached { pc :: Int, addr :: Expr EAddr }+ | JumpIntoSymbolicCode { pc :: Int, jumpDst :: Int } deriving (Show, Eq, Ord) -- | Effect types used by the vm implementation for side effects & control flow-data Effect- = Query Query- | Choose Choose-deriving instance Show Effect+data Effect s+ = Query (Query s)+ | Choose (Choose s)+deriving instance Show (Effect s) -- | Queries halt execution until resolved through RPC calls or SMT queries-data Query where- PleaseFetchContract :: Addr -> (Contract -> EVM ()) -> Query- PleaseFetchSlot :: Addr -> W256 -> (W256 -> EVM ()) -> Query- PleaseAskSMT :: Expr EWord -> [Prop] -> (BranchCondition -> EVM ()) -> Query- PleaseDoFFI :: [String] -> (ByteString -> EVM ()) -> Query+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 -- | Execution could proceed down one of two branches-data Choose where- PleaseChoosePath :: Expr EWord -> (Bool -> EVM ()) -> Choose+data Choose s where+ PleaseChoosePath :: Expr EWord -> (Bool -> EVM s ()) -> Choose s -- | The possible return values of a SMT query data BranchCondition = Case Bool | Unknown deriving Show -instance Show Query where+instance Show (Query s) where showsPrec _ = \case- PleaseFetchContract addr _ ->- (("<EVM.Query: fetch contract " ++ show addr ++ ">") ++)+ PleaseFetchContract addr base _ ->+ (("<EVM.Query: fetch contract " ++ show addr ++ show base ++ ">") ++) PleaseFetchSlot addr slot _ -> (("<EVM.Query: fetch slot " ++ show slot ++ " for "@@ -582,29 +566,29 @@ PleaseDoFFI cmd _ -> (("<EVM.Query: do ffi: " ++ (show cmd)) ++) -instance Show Choose where+instance Show (Choose s) where showsPrec _ = \case PleaseChoosePath _ _ -> (("<EVM.Choice: waiting for user to select path (0,1)") ++) -- | The possible result states of a VM-data VMResult- = VMFailure EvmError -- ^ An operation failed- | VMSuccess (Expr Buf) -- ^ Reached STOP, RETURN, or end-of-code- | HandleEffect Effect -- ^ An effect must be handled for execution to continue- | Unfinished PartialExec -- ^ Execution could not continue further+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 -deriving instance Show VMResult+deriving instance Show (VMResult s) -- VM State ---------------------------------------------------------------------------------------- -- | The state of a stepwise EVM execution-data VM = VM- { result :: Maybe VMResult- , state :: FrameState- , frames :: [Frame]+data VM s = VM+ { result :: Maybe (VMResult s)+ , state :: FrameState s+ , frames :: [Frame s] , env :: Env , block :: Block , tx :: TxState@@ -612,118 +596,132 @@ , traces :: Zipper.TreePos Zipper.Empty Trace , cache :: Cache , burned :: {-# UNPACK #-} !Word64- , 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+ , 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]- , allowFFI :: Bool- , overrideCaller :: Maybe Addr+ , config :: RuntimeConfig } deriving (Show, Generic) -- | Alias for the type of e.g. @exec1@.-type EVM a = State VM a+type EVM s a = StateT (VM s) (ST s) a +-- | The VM base state (i.e. should new contracts be created with abstract balance / storage?)+data BaseState+ = EmptyBase+ | AbstractBase+ deriving (Show)++-- | Configuration options that need to be consulted at runtime+data RuntimeConfig = RuntimeConfig+ { allowFFI :: Bool+ , overrideCaller :: Maybe (Expr EAddr)+ , baseState :: BaseState+ }+ 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 = Frame+data Frame s = Frame { context :: FrameContext- , state :: FrameState+ , state :: FrameState s } deriving (Show) -- | Call/create info data FrameContext = CreationContext- { address :: Addr+ { address :: Expr EAddr , codehash :: Expr EWord- , createreversion :: Map Addr Contract+ , createreversion :: Map (Expr EAddr) Contract , substate :: SubState } | CallContext- { target :: Addr- , context :: Addr+ { target :: Expr EAddr+ , context :: Expr EAddr , offset :: W256 , size :: W256 , codehash :: Expr EWord , abi :: Maybe W256 , calldata :: Expr Buf- , callreversion :: (Map Addr Contract, Expr Storage)+ , callreversion :: Map (Expr EAddr) Contract , subState :: SubState } deriving (Eq, Ord, Show, Generic) -- | The "accrued substate" across a transaction data SubState = SubState- { selfdestructs :: [Addr]- , touchedAccounts :: [Addr]- , accessedAddresses :: Set Addr- , accessedStorageKeys :: Set (Addr, W256)- , refunds :: [(Addr, Word64)]+ { selfdestructs :: [Expr EAddr]+ , touchedAccounts :: [Expr EAddr]+ , accessedAddresses :: Set (Expr EAddr)+ , accessedStorageKeys :: Set (Expr EAddr, W256)+ , refunds :: [(Expr EAddr, Word64)] -- in principle we should include logs here, but do not for now } deriving (Eq, Ord, Show) -- | The "registers" of the VM along with memory and data stack-data FrameState = FrameState- { contract :: Addr- , codeContract :: Addr+data FrameState s = FrameState+ { contract :: Expr EAddr+ , codeContract :: Expr EAddr , code :: ContractCode , pc :: {-# UNPACK #-} !Int , stack :: [Expr EWord]- , memory :: Expr Buf+ , memory :: Memory s , memorySize :: Word64 , calldata :: Expr Buf , callvalue :: Expr EWord- , caller :: Expr EWord+ , caller :: Expr EAddr , gas :: {-# UNPACK #-} !Word64 , returndata :: Expr Buf , static :: Bool } deriving (Show, Generic) +data Memory s+ = ConcreteMemory (MutableMemory s)+ | SymbolicMemory !(Expr Buf)++instance Show (Memory s) where+ show (ConcreteMemory _) = "<can't show mutable memory>"+ show (SymbolicMemory m) = show m++type MutableMemory s = STVector s Word8+ -- | The state that spans a whole transaction data TxState = TxState { gasprice :: W256 , gaslimit :: Word64 , priorityFee :: W256- , origin :: Addr- , toAddr :: Addr+ , origin :: Expr EAddr+ , toAddr :: Expr EAddr , value :: Expr EWord , substate :: SubState , isCreate :: Bool- , txReversion :: Map Addr Contract+ , txReversion :: Map (Expr EAddr) Contract } deriving (Show) --- | When doing symbolic execution, we have three different--- ways to model the storage of contracts. This determines--- not only the initial contract storage model but also how--- RPC or state fetched contracts will be modeled.-data StorageModel- = ConcreteS -- ^ Uses `Concrete` Storage. Reading / Writing from abstract- -- locations causes a runtime failure. Can be nicely combined with RPC.-- | SymbolicS -- ^ Uses `Symbolic` Storage. Reading / Writing never reaches RPC,- -- but always done using an SMT array with no default value.-- | InitialS -- ^ Uses `Symbolic` Storage. Reading / Writing never reaches RPC,- -- but always done using an SMT array with 0 as the default value.-- deriving (Read, Show)--instance ParseField StorageModel- -- | Various environmental data data Env = Env- { contracts :: Map Addr Contract- , chainId :: W256- , storage :: Expr Storage- , origStorage :: Map W256 (Map W256 W256)+ { contracts :: Map (Expr EAddr) Contract+ , chainId :: W256+ , freshAddresses :: Int } deriving (Show, Generic) -- | Data about the block data Block = Block- { coinbase :: Addr+ { coinbase :: Expr EAddr , timestamp :: Expr EWord , number :: W256 , prevRandao :: W256@@ -733,61 +731,53 @@ , schedule :: FeeSchedule Word64 } deriving (Show, Generic) --- | The state of a contract+-- | Full contract state data Contract = Contract- { contractcode :: ContractCode- , balance :: W256- , nonce :: W256- , codehash :: Expr EWord- , opIxMap :: SV.Vector Int- , codeOps :: V.Vector (Int, Op)- , external :: Bool+ { code :: ContractCode+ , storage :: Expr Storage+ , origStorage :: Expr Storage+ , balance :: Expr EWord+ , nonce :: Maybe W64+ , codehash :: Expr EWord+ , opIxMap :: SV.Vector Int+ , codeOps :: V.Vector (Int, Op)+ , external :: Bool }- deriving (Eq, Ord, Show)+ deriving (Show, Eq, Ord) -- Bytecode Representations ------------------------------------------------------------------------ -- | A unique id for a given pc-type CodeLocation = (Addr, Int)+type CodeLocation = (Expr EAddr, Int) -- | The cache is data that can be persisted for efficiency: -- any expensive query that is constant at least within a block. data Cache = Cache- { fetchedContracts :: Map Addr Contract- , fetchedStorage :: Map W256 (Map W256 W256)- , path :: Map (CodeLocation, Int) Bool+ { fetched :: Map Addr Contract+ , path :: Map (CodeLocation, Int) Bool } deriving (Show, Generic) instance Semigroup Cache where a <> b = Cache- { fetchedContracts = Map.unionWith unifyCachedContract a.fetchedContracts b.fetchedContracts- , fetchedStorage = Map.unionWith unifyCachedStorage a.fetchedStorage b.fetchedStorage+ { fetched = Map.unionWith unifyCachedContract a.fetched b.fetched , path = mappend a.path b.path } instance Monoid Cache where- mempty = Cache { fetchedContracts = mempty- , fetchedStorage = mempty+ mempty = Cache { fetched = mempty , path = mempty } -unifyCachedStorage :: Map W256 W256 -> Map W256 W256 -> Map W256 W256-unifyCachedStorage _ _ = undefined- -- only intended for use in Cache merges, where we expect -- everything to be Concrete unifyCachedContract :: Contract -> Contract -> Contract-unifyCachedContract _ _ = undefined- {--unifyCachedContract a b = a & set storage merged- where merged = case (view storage a, view storage b) of+unifyCachedContract a b = a { storage = merged }+ where merged = case (a.storage, b.storage) of (ConcreteStore sa, ConcreteStore sb) -> ConcreteStore (mappend sa sb)- _ ->- view storage a- -}+ _ -> a.storage -- Bytecode Representations ------------------------------------------------------------------------@@ -809,9 +799,10 @@ hopefully we do not have to deal with dynamic immutable before we get a real data section... -} data ContractCode- = InitCode ByteString (Expr Buf) -- ^ "Constructor" code, during contract creation- | RuntimeCode RuntimeCode -- ^ "Instance" code, after contract creation- deriving (Show, Ord)+ = UnknownCode (Expr EAddr) -- ^ Fully abstract code, keyed on an address to give consistent results for e.g. extcodehash+ | InitCode ByteString (Expr Buf) -- ^ "Constructor" code, during contract creation+ | RuntimeCode RuntimeCode -- ^ "Instance" code, after contract creation+ deriving (Show, Eq, Ord) -- | We have two variants here to optimize the fully concrete case. -- ConcreteRuntimeCode just wraps a ByteString@@ -821,13 +812,6 @@ | SymbolicRuntimeCode (V.Vector (Expr Byte)) deriving (Show, Eq, Ord) --- runtime err when used for symbolic code-instance Eq ContractCode where- InitCode a b == InitCode c d = a == c && b == d- RuntimeCode x == RuntimeCode y = x == y- _ == _ = False-- -- Execution Traces -------------------------------------------------------------------------------- @@ -849,7 +833,7 @@ -- | Wrapper type containing vm traces and the context needed to pretty print them properly data Traces = Traces { traces :: Forest Trace- , contracts :: Map Addr Contract+ , contracts :: Map (Expr EAddr) Contract } deriving (Eq, Ord, Show, Generic) @@ -865,18 +849,19 @@ -- | A specification for an initial VM state data VMOpts = VMOpts { contract :: Contract+ , otherContracts :: [(Expr EAddr, Contract)] , calldata :: (Expr Buf, [Prop])- , initialStorage :: Expr Storage+ , baseState :: BaseState , value :: Expr EWord , priorityFee :: W256- , address :: Addr- , caller :: Expr EWord- , origin :: Addr+ , address :: Expr EAddr+ , caller :: Expr EAddr+ , origin :: Expr EAddr , gas :: Word64 , gaslimit :: Word64 , number :: W256 , timestamp :: Expr EWord- , coinbase :: Addr+ , coinbase :: Expr EAddr , prevRandao :: W256 , maxCodeSize :: W256 , blockGaslimit :: Word64@@ -885,7 +870,7 @@ , schedule :: FeeSchedule Word64 , chainId :: W256 , create :: Bool- , txAccessList :: Map Addr [W256]+ , txAccessList :: Map (Expr EAddr) [W256] , allowFFI :: Bool } deriving Show @@ -1063,7 +1048,6 @@ ( Num, Integral, Real, Ord, Generic , Bits , FiniteBits, Enum, Eq , Bounded )-instance JSON.FromJSON W64 instance Read W64 where readsPrec _ "0x" = [(0, "")]@@ -1075,6 +1059,14 @@ instance JSON.ToJSON W64 where toJSON x = JSON.String $ T.pack $ show x +instance FromJSON W64 where+ parseJSON v = do+ s <- T.unpack <$> parseJSON v+ case reads s of+ [(x, "")] -> return x+ _ -> fail $ "invalid hex word (" ++ s ++ ")"++ word64Field :: JSON.Object -> Key -> JSON.Parser Word64 word64Field x f = ((readNull 0) . T.unpack) <$> (x .: f)@@ -1168,8 +1160,18 @@ maybeLitWord :: Expr EWord -> Maybe W256 maybeLitWord (Lit w) = Just w+maybeLitWord (WAddr (LitAddr w)) = Just (into w) maybeLitWord _ = Nothing +maybeLitAddr :: Expr EAddr -> Maybe Addr+maybeLitAddr (LitAddr a) = Just a+maybeLitAddr _ = Nothing++maybeConcreteStore :: Expr Storage -> Maybe (Map W256 W256)+maybeConcreteStore (ConcreteStore s) = Just s+maybeConcreteStore _ = Nothing++ word256 :: ByteString -> Word256 word256 xs | BS.length xs == 1 = -- optimize one byte pushes@@ -1269,8 +1271,9 @@ -- Utils ------------------------------------------------------------------------------------------- +{- HLINT ignore internalError -} internalError:: HasCallStack => [Char] -> a-internalError m = error $ "Internal error: " ++ m ++ " -- " ++ (prettyCallStack callStack)+internalError m = error $ "Internal Error: " ++ m ++ " -- " ++ (prettyCallStack callStack) concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b] concatMapM f xs = fmap concat (mapM f xs)@@ -1308,6 +1311,15 @@ Right s -> "\"" <> T.unpack s <> "\"" Left _ -> "❮utf8 decode failed❯: " <> (show $ ByteStringS bs) +-- |'paddedShowHex' displays a number in hexidecimal 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+ where+ str = showHex n ""+ pad = replicate (w - length str) '0'++ -- Optics ------------------------------------------------------------------------------------------ @@ -1323,3 +1335,4 @@ makeFieldLabelsNoPrefix ''Contract makeFieldLabelsNoPrefix ''Env makeFieldLabelsNoPrefix ''Block+makeFieldLabelsNoPrefix ''RuntimeConfig
src/EVM/UnitTest.hs view
@@ -3,99 +3,72 @@ module EVM.UnitTest where -import Prelude hiding (Word)- import EVM import EVM.ABI-import EVM.Concrete import EVM.SMT import EVM.Solvers import EVM.Dapp-import EVM.Debug (srcMapCodePos) import EVM.Exec-import EVM.Expr (litAddr, readStorage', simplify)+import EVM.Expr (readStorage') import EVM.Expr qualified as Expr-import EVM.Facts qualified as Facts-import EVM.Facts.Git qualified as Git-import EVM.FeeSchedule qualified as FeeSchedule+import EVM.FeeSchedule (feeSchedule) import EVM.Fetch qualified as Fetch import EVM.Format import EVM.Solidity-import EVM.SymExec (defaultVeriOpts, symCalldata, verify, isQed, extractCex, runExpr, subModel, VeriOpts(..))+import EVM.SymExec (defaultVeriOpts, symCalldata, verify, isQed, extractCex, runExpr, prettyCalldata, panicMsg, VeriOpts(..), flattenExpr) import EVM.Types import EVM.Transaction (initTx)-import EVM.RLP-import EVM.Stepper (Stepper, interpret)+import EVM.Stepper (Stepper) import EVM.Stepper qualified as Stepper -import Control.Monad.Operational qualified as Operational+import Control.Monad.ST (RealWorld, ST, stToIO) import Optics.Core hiding (elements) import Optics.State import Optics.State.Operators-import Optics.Zoom-import Control.Monad.Par.Class (spawn_)-import Control.Monad.Par.Class qualified as Par-import Control.Monad.Par.IO (runParIO) import Control.Monad.State.Strict hiding (state)-import Control.Monad.State.Strict qualified as State import Data.ByteString.Lazy qualified as BSLazy import Data.Binary.Get (runGet) import Data.ByteString (ByteString)-import Data.ByteString qualified as BS import Data.Decimal (DecimalRaw(..))-import Data.Either (isRight)+import Data.Either (isRight, rights, lefts) import Data.Foldable (toList) import Data.Map (Map) import Data.Map qualified as Map-import Data.Maybe (fromMaybe, catMaybes, fromJust, isJust, fromMaybe, mapMaybe, isNothing)-import Data.MultiSet (MultiSet)-import Data.MultiSet qualified as MultiSet-import Data.Set (Set)-import Data.Set qualified as Set+import Data.Maybe import Data.Text (isPrefixOf, stripSuffix, intercalate, Text, pack, unpack) import Data.Text qualified as Text import Data.Text.Encoding (encodeUtf8) import Data.Text.IO qualified as Text-import Data.Vector (Vector)-import Data.Vector qualified as Vector-import Data.Word (Word32, Word64)+import Data.Word (Word64) import GHC.Natural-import System.Environment (lookupEnv) import System.IO (hFlush, stdout)-import Test.QuickCheck hiding (verbose, Success, Failure)-import qualified Test.QuickCheck as QC import Witch (unsafeInto, into) -data UnitTestOptions = UnitTestOptions+data UnitTestOptions s = UnitTestOptions { rpcInfo :: Fetch.RpcInfo , solvers :: SolverGroup , verbose :: Maybe Int , maxIter :: Maybe Integer , askSmtIters :: Integer , smtDebug :: Bool- , maxDepth :: Maybe Int , smtTimeout :: Maybe Natural , solver :: Maybe Text- , covMatch :: Maybe Text , match :: Text- , fuzzRuns :: Int- , replay :: Maybe (Text, BSLazy.ByteString)- , vmModifier :: VM -> VM , dapp :: DappInfo , testParams :: TestVMParams , ffiAllowed :: Bool } data TestVMParams = TestVMParams- { address :: Addr- , caller :: Addr- , origin :: Addr+ { address :: Expr EAddr+ , caller :: Expr EAddr+ , origin :: Expr EAddr , gasCreate :: Word64 , gasCall :: Word64 , baseFee :: W256 , priorityFee :: W256 , balanceCreate :: W256- , coinbase :: Addr+ , coinbase :: Expr EAddr , number :: W256 , timestamp :: W256 , gaslimit :: Word64@@ -121,7 +94,7 @@ -- | Generate VeriOpts from UnitTestOptions-makeVeriOpts :: UnitTestOptions -> VeriOpts+makeVeriOpts :: UnitTestOptions s -> VeriOpts makeVeriOpts opts = defaultVeriOpts { debug = opts.smtDebug , maxIter = opts.maxIter@@ -130,34 +103,20 @@ } -- | Top level CLI endpoint for hevm test-unitTest :: UnitTestOptions -> Contracts -> Maybe String -> IO Bool-unitTest opts (Contracts cs) cache' = do+unitTest :: UnitTestOptions RealWorld -> Contracts -> IO Bool+unitTest opts (Contracts cs) = do let unitTests = findUnitTests opts.match $ Map.elems cs results <- concatMapM (runUnitTestContract opts cs) unitTests- let (passing, vms) = unzip results- case cache' of- Nothing ->- pure ()- Just path ->- -- merge all of the post-vm caches and save into the state- let- evmcache = mconcat [vm.cache | vm <- vms]- in- liftIO $ Git.saveFacts (Git.RepoAt path) (Facts.cacheFacts evmcache)-- pure $ and passing-+ pure $ and results -- | 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 -> SolcContract -> Stepper ()+initializeUnitTest :: UnitTestOptions s -> SolcContract -> Stepper s () initializeUnitTest opts theContract = do let addr = opts.testParams.address Stepper.evm $ do- -- Maybe modify the initial VM, e.g. to load library code- modify opts.vmModifier -- Make a trace entry for running the constructor pushTrace (EntryTrace "constructor") @@ -166,7 +125,7 @@ Stepper.evm $ do -- Give a balance to the test target- #env % #contracts % ix addr % #balance %= (+ opts.testParams.balanceCreate)+ #env % #contracts % ix addr % #balance %= (`Expr.add` (Lit opts.testParams.balanceCreate)) -- call setUp(), if it exists, to initialize the test contract let theAbi = theContract.abiMap@@ -183,244 +142,16 @@ Left e -> pushTrace (ErrorTrace e) _ -> popTrace --- | Assuming a test contract is loaded and initialized, this stepper--- will run the specified test method and return whether it succeeded.-runUnitTest :: UnitTestOptions -> ABIMethod -> AbiValue -> Stepper Bool-runUnitTest a method args = do- x <- execTestStepper a method args- checkFailures a method x--execTestStepper :: UnitTestOptions -> ABIMethod -> AbiValue -> Stepper Bool-execTestStepper UnitTestOptions { .. } methodName' method = do- -- Set up the call to the test method- Stepper.evm $ do- abiCall testParams (Left (methodName', method))- pushTrace (EntryTrace methodName')- -- Try running the test method- Stepper.execFully >>= \case- -- If we failed, put the error in the trace.- Left e -> Stepper.evm (pushTrace (ErrorTrace e) >> popTrace) >> pure True- _ -> pure False--exploreStep :: UnitTestOptions -> ByteString -> Stepper Bool-exploreStep UnitTestOptions{..} bs = do- Stepper.evm $ do- cs <- use (#env % #contracts)- abiCall testParams (Right bs)- let Method _ inputs sig _ _ = fromMaybe (internalError "unknown abi call") $ Map.lookup (unsafeInto $ word $ BS.take 4 bs) dapp.abiMap- types = snd <$> inputs- let ?context = DappContext dapp cs- this <- fromMaybe (internalError "unknown target") <$> (use (#env % #contracts % at testParams.address))- let name = maybe "" (contractNamePart . (.contractName)) $ lookupCode this.contractcode dapp- pushTrace (EntryTrace (name <> "." <> sig <> "(" <> intercalate "," ((pack . show) <$> types) <> ")" <> showCall types (ConcreteBuf bs)))- -- Try running the test method- Stepper.execFully >>= \case- -- If we failed, put the error in the trace.- Left e -> Stepper.evm (pushTrace (ErrorTrace e) >> popTrace) >> pure True- _ -> pure False--checkFailures :: UnitTestOptions -> ABIMethod -> Bool -> Stepper Bool-checkFailures UnitTestOptions { .. } method bailed = do- -- Decide whether the test is supposed to fail or succeed- let shouldFail = "testFail" `isPrefixOf` method- if bailed then- pure shouldFail- else do- -- Ask whether any assertions failed- Stepper.evm $ do- popTrace- abiCall testParams $ Left ("failed()", emptyAbi)- res <- Stepper.execFully- case res of- Right (ConcreteBuf r) ->- let failed = case decodeAbiValue AbiBoolType (BSLazy.fromStrict r) of- AbiBool f -> f- _ -> internalError "fix me with better types"- in pure (shouldFail == failed)- c -> internalError $ "unexpected failure code: " <> show c---- | Randomly generates the calldata arguments and runs the test-fuzzTest :: UnitTestOptions -> Text -> [AbiType] -> VM -> Property-fuzzTest opts@UnitTestOptions{..} sig types vm = forAllShow (genAbiValue (AbiTupleType $ Vector.fromList types)) (show . ByteStringS . encodeAbiValue)- $ \args -> ioProperty $- EVM.Stepper.interpret (Fetch.oracle solvers rpcInfo) vm (runUnitTest opts sig args)--tick :: Text -> IO ()-tick x = Text.putStr x >> hFlush stdout---- | This is like an unresolved source mapping.-data OpLocation = OpLocation- { srcContract :: Contract- , srcOpIx :: Int- } deriving (Show)--instance Eq OpLocation where- (==) (OpLocation a b) (OpLocation a' b') = b == b' && a.contractcode == a'.contractcode--instance Ord OpLocation where- compare (OpLocation a b) (OpLocation a' b') = compare (a.contractcode, b) (a'.contractcode, b')--srcMapForOpLocation :: DappInfo -> OpLocation -> Maybe SrcMap-srcMapForOpLocation dapp (OpLocation contr opIx) = srcMap dapp contr opIx--type CoverageState = (VM, MultiSet OpLocation)--currentOpLocation :: VM -> OpLocation-currentOpLocation vm =- case currentContract vm of- Nothing ->- internalError "why no contract?"- Just c ->- OpLocation- c- (fromMaybe (internalError "op ix") (vmOpIx vm))--execWithCoverage :: StateT CoverageState IO VMResult-execWithCoverage = do _ <- runWithCoverage- fromJust <$> use (_1 % #result)--runWithCoverage :: StateT CoverageState IO VM-runWithCoverage = do- -- This is just like `exec` except for every instruction evaluated,- -- we also increment a counter indexed by the current code location.- vm0 <- use _1- case vm0.result of- Nothing -> do- vm1 <- zoom _1 (State.state (runState exec1) >> get)- zoom _2 (modify (MultiSet.insert (currentOpLocation vm1)))- runWithCoverage- Just _ -> pure vm0---interpretWithCoverage- :: UnitTestOptions- -> Stepper a- -> StateT CoverageState IO a-interpretWithCoverage opts@UnitTestOptions{..} =- eval . Operational.view-- where- eval- :: Operational.ProgramView Stepper.Action a- -> StateT CoverageState IO a-- eval (Operational.Return x) =- pure x-- eval (action Operational.:>>= k) =- case action of- Stepper.Exec ->- execWithCoverage >>= interpretWithCoverage opts . k- Stepper.Run ->- runWithCoverage >>= interpretWithCoverage opts . k- Stepper.Wait (PleaseAskSMT (Lit c) _ continue) ->- interpretWithCoverage opts (Stepper.evm (continue (Case (c > 0))) >>= k)- Stepper.Wait q ->- do m <- liftIO ((Fetch.oracle solvers rpcInfo) q)- zoom _1 (State.state (runState m)) >> interpretWithCoverage opts (k ())- Stepper.Ask _ ->- internalError "cannot make choice in this interpreter"- Stepper.IOAct q ->- zoom _1 (StateT (runStateT q)) >>= interpretWithCoverage opts . k- Stepper.EVM m ->- zoom _1 (State.state (runState m)) >>= interpretWithCoverage opts . k--coverageReport- :: DappInfo- -> MultiSet SrcMap- -> Map FilePath (Vector (Int, ByteString))-coverageReport dapp cov =- let- sources :: SourceCache- sources = dapp.sources-- allPositions :: Set (FilePath, Int)- allPositions =- ( Set.fromList- . mapMaybe (srcMapCodePos sources)- . toList- $ mconcat- ( dapp.solcByName- & Map.elems- & map (\x -> x.runtimeSrcmap <> x.creationSrcmap)- )- )-- srcMapCov :: MultiSet (FilePath, Int)- srcMapCov = MultiSet.mapMaybe (srcMapCodePos sources) cov-- linesByName :: Map FilePath (Vector ByteString)- linesByName =- Map.fromList $ zipWith- (\(name, _) lines' -> (name, lines'))- (Map.elems sources.files)- (Map.elems sources.lines)-- f :: FilePath -> Vector ByteString -> Vector (Int, ByteString)- f name =- Vector.imap- (\i bs ->- let- n =- if Set.member (name, i + 1) allPositions- then MultiSet.occur (name, i + 1) srcMapCov- else -1- in (n, bs))- in- Map.mapWithKey f linesByName--coverageForUnitTestContract- :: UnitTestOptions- -> Map Text SolcContract- -> SourceCache- -> (Text, [(Test, [AbiType])])- -> IO (MultiSet SrcMap)-coverageForUnitTestContract- opts@(UnitTestOptions {..}) contractMap _ (name, testNames) = do-- -- Look for the wanted contract by name from the Solidity info- case Map.lookup name contractMap of- Nothing ->- -- Fail if there's no such contract- internalError $ "Contract " ++ unpack name ++ " not found"-- Just theContract -> do- -- Construct the initial VM and begin the contract's constructor- let vm0 = initialUnitTestVm opts theContract- (vm1, cov1) <-- execStateT- (interpretWithCoverage opts- (Stepper.enter name >> initializeUnitTest opts theContract))- (vm0, mempty)-- -- Define the thread spawner for test cases- let- runOne' (test, _) = spawn_ . liftIO $ do- (_, (_, cov)) <-- runStateT- (interpretWithCoverage opts (runUnitTest opts (extractSig test) emptyAbi))- (vm1, mempty)- pure cov- -- Run all the test cases in parallel and gather their coverages- covs <-- runParIO (mapM runOne' testNames >>= mapM Par.get)-- -- Sum up all the coverage counts- let cov2 = MultiSet.unions (cov1 : covs)-- pure (MultiSet.mapMaybe (srcMapForOpLocation dapp) cov2)- runUnitTestContract- :: UnitTestOptions+ :: UnitTestOptions RealWorld -> Map Text SolcContract- -> (Text, [(Test, [AbiType])])- -> IO [(Bool, VM)]+ -> (Text, [Sig])+ -> IO [Bool] runUnitTestContract opts@(UnitTestOptions {..}) contractMap (name, testSigs) = do -- Print a header- liftIO $ putStrLn $ "Running " ++ show (length testSigs) ++ " tests for "- ++ unpack name+ 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@@ -430,8 +161,8 @@ Just theContract -> do -- Construct the initial VM and begin the contract's constructor- let vm0 = initialUnitTestVm opts theContract- vm1 <- liftIO $ EVM.Stepper.interpret (Fetch.oracle solvers rpcInfo) vm0 $ do+ vm0 <- stToIO $ initialUnitTestVm opts theContract+ vm1 <- Stepper.interpret (Fetch.oracle solvers rpcInfo) vm0 $ do Stepper.enter name initializeUnitTest opts theContract Stepper.evm get@@ -440,320 +171,86 @@ Just (VMFailure _) -> liftIO $ do Text.putStrLn "\x1b[31m[BAIL]\x1b[0m setUp() " tick "\n"- tick (Data.Text.pack $ show $ failOutput vm1 opts "setUp()")- pure [(False, vm1)]+ tick $ failOutput vm1 opts "setUp()"+ pure [False] Just (VMSuccess _) -> do let - runCache :: ([(Either Text Text, VM)], VM) -> (Test, [AbiType])- -> IO ([(Either Text Text, VM)], VM)- runCache (results, vm) (test, types) = do- (t, r, vm') <- runTest opts vm (test, types)- liftIO $ Text.putStrLn t- let vmCached = vm { cache = vm'.cache }- pure (((r, vm'): results), vmCached)-- -- Run all the test cases and print their status updates,- -- accumulating the vm cache throughout- (details, _) <- foldM runCache ([], vm1) testSigs+ -- Run all the test cases and print their status+ details <- forM testSigs $ \s -> do+ (result, detail) <- symRun opts vm1 s+ Text.putStrLn result+ pure detail - let running = [x | (Right x, _) <- details]- let bailing = [x | (Left x, _) <- details]+ let running = rights details+ bailing = lefts details - liftIO $ do- tick "\n"- tick (Text.unlines (filter (not . Text.null) running))- tick (Text.unlines bailing)+ tick "\n"+ tick (Text.unlines (filter (not . Text.null) running))+ tick (Text.unlines bailing) - pure [(isRight r, vm) | (r, vm) <- details]+ pure $ fmap isRight details _ -> internalError "setUp() did not end with a result" -runTest :: UnitTestOptions -> VM -> (Test, [AbiType]) -> IO (Text, Either Text Text, VM)-runTest opts@UnitTestOptions{} vm (ConcreteTest testName, []) = liftIO $ runOne opts vm testName emptyAbi-runTest opts@UnitTestOptions{..} vm (ConcreteTest testName, types) = liftIO $ case replay of- Nothing ->- fuzzRun opts vm testName types- Just (sig, callData) ->- if sig == testName- then runOne opts vm testName $- decodeAbiValue (AbiTupleType (Vector.fromList types)) callData- else fuzzRun opts vm testName types-runTest opts@UnitTestOptions{..} vm (InvariantTest testName, []) = liftIO $ case replay of- Nothing -> exploreRun opts vm testName []- Just (sig, cds) ->- if sig == testName- then exploreRun opts vm testName (decodeCalls cds)- else exploreRun opts vm testName []-runTest _ _ (InvariantTest _, types) = internalError $ "invariant testing with arguments: " <> show types <> " is not implemented (yet!)"-runTest opts vm (SymbolicTest testName, types) = symRun opts vm testName types--type ExploreTx = (Addr, Addr, ByteString, W256)--decodeCalls :: BSLazy.ByteString -> [ExploreTx]-decodeCalls b = fromMaybe (internalError "could not decode replay data") $ do- List v <- rlpdecode $ BSLazy.toStrict b- pure $ unList <$> v- where- unList (List [BS caller', BS target, BS cd, BS ts]) =- (unsafeInto (word caller'), unsafeInto (word target), cd, word ts)- unList _ = internalError "fix me with better types"---- | Runs an invariant test, calls the invariant before execution begins-initialExplorationStepper :: UnitTestOptions -> ABIMethod -> [ExploreTx] -> [Addr] -> Int -> Stepper (Bool, RLP)-initialExplorationStepper opts'' testName replayData targets i = do- let history = List []- x <- runUnitTest opts'' testName emptyAbi- if x- then explorationStepper opts'' testName replayData targets history i- else pure (False, history)--explorationStepper :: UnitTestOptions -> ABIMethod -> [ExploreTx] -> [Addr] -> RLP -> Int -> Stepper (Bool, RLP)-explorationStepper _ _ _ _ history 0 = pure (True, history)-explorationStepper opts@UnitTestOptions{..} testName replayData targets (List history) i = do- (caller', target, cd, timestamp') <-- case preview (ix (i - 1)) replayData of- Just v -> pure v- Nothing ->- Stepper.evmIO $ do- vm <- get- let cs = vm.env.contracts- noCode c = case c.contractcode of- RuntimeCode (ConcreteRuntimeCode "") -> True- RuntimeCode (SymbolicRuntimeCode c') -> null c'- _ -> False- mutable m = m.mutability `elem` [NonPayable, Payable]- knownAbis :: Map Addr SolcContract- knownAbis =- -- exclude contracts without code- Map.filter (not . BS.null . (.runtimeCode)) $- -- exclude contracts without state changing functions- Map.filter (not . null . Map.filter mutable . (.abiMap)) $- -- exclude testing abis- Map.filter (isNothing . preview (ix unitTestMarkerAbi) . (.abiMap)) $- -- pick all contracts with known compiler artifacts- fmap fromJust (Map.filter isJust $ Map.fromList [(addr, lookupCode c.contractcode dapp) | (addr, c) <- Map.toList cs])- selected = [(addr,- fromMaybe (internalError $ "no src found for: " <> show addr) $- lookupCode (fromMaybe (internalError $ "contract not found: " <> show addr) $- Map.lookup addr cs).contractcode dapp)- | addr <- targets]- -- go to IO and generate a random valid call to any known contract- liftIO $ do- -- select random contract- (target, solcInfo) <- generate $ elements (if null targets then Map.toList knownAbis else selected)- -- choose a random mutable method- (_, (Method _ inputs sig _ _)) <- generate (elements $ Map.toList $ Map.filter mutable solcInfo.abiMap)- let types = snd <$> inputs- -- set the caller to a random address with 90% probability, 10% known EOA address- let knownEOAs = Map.keys $ Map.filter noCode cs- AbiAddress caller' <-- if null knownEOAs- then generate $ genAbiValue AbiAddressType- else generate $ frequency- [ (90, genAbiValue AbiAddressType)- , (10, AbiAddress <$> elements knownEOAs)- ]- -- make a call with random valid data to the function- args <- generate $ genAbiValue (AbiTupleType $ Vector.fromList types)- let cd = abiMethod (sig <> "(" <> intercalate "," ((pack . show) <$> types) <> ")") args- -- increment timestamp with random amount- timepassed <- into <$> generate (arbitrarySizedNatural :: Gen Word32)- let ts = fromMaybe (internalError "symbolic timestamp not supported here") $ maybeLitWord vm.block.timestamp- pure (caller', target, cd, into ts + timepassed)- let opts' = opts { testParams = testParams {address = target, caller = caller', timestamp = timestamp'}}- thisCallRLP = List [BS $ word160Bytes caller', BS $ word160Bytes target, BS cd, BS $ word256Bytes timestamp']- -- set the timestamp- Stepper.evm $ assign (#block % #timestamp) (Lit timestamp')- -- perform the call- bailed <- exploreStep opts' cd- Stepper.evm popTrace- let newHistory = if bailed then List history else List (thisCallRLP:history)- opts'' = opts {testParams = testParams {timestamp = timestamp'}}- carryOn = explorationStepper opts'' testName replayData targets newHistory (i - 1)- -- if we didn't revert, run the test function- if bailed- then carryOn- else- do x <- runUnitTest opts'' testName emptyAbi- if x- then carryOn- else pure (False, List (thisCallRLP:history))-explorationStepper _ _ _ _ _ _ = internalError "malformed rlp"--getTargetContracts :: UnitTestOptions -> Stepper [Addr]-getTargetContracts UnitTestOptions{..} = do- vm <- Stepper.evm get- let contract' = fromJust $ currentContract vm- theAbi = (fromJust $ lookupCode contract'.contractcode dapp).abiMap- setUp = abiKeccak (encodeUtf8 "targetContracts()")- case Map.lookup setUp theAbi of- Nothing -> pure []- Just _ -> do- Stepper.evm $ abiCall testParams (Left ("targetContracts()", emptyAbi))- res <- Stepper.execFully- case res of- Right (ConcreteBuf r) ->- let vs = case decodeAbiValue (AbiTupleType (Vector.fromList [AbiArrayDynamicType AbiAddressType])) (BSLazy.fromStrict r) of- AbiTuple v -> v- _ -> internalError "fix me with better types"- targets = case Vector.toList vs of- [AbiArrayDynamic AbiAddressType ts] ->- let unAbiAddress (AbiAddress a) = a- unAbiAddress _ = internalError "fix me with better types"- in unAbiAddress <$> Vector.toList ts- _ -> internalError "fix me with better types"- in pure targets- _ -> internalError "internal error: unexpected failure code"--exploreRun :: UnitTestOptions -> VM -> ABIMethod -> [ExploreTx] -> IO (Text, Either Text Text, VM)-exploreRun opts@UnitTestOptions{..} initialVm testName replayTxs = do- let oracle = Fetch.oracle solvers rpcInfo- targets <- EVM.Stepper.interpret oracle initialVm (getTargetContracts opts)- let depth = fromMaybe 20 maxDepth- ((x, counterex), vm') <-- if null replayTxs then- foldM (\a@((success, _),_) _ ->- if success then- EVM.Stepper.interpret oracle initialVm $- (,) <$> initialExplorationStepper opts testName [] targets depth- <*> Stepper.evm get- else pure a)- ((True, (List [])), initialVm) -- no canonical "post vm"- [0..fuzzRuns]- else EVM.Stepper.interpret oracle initialVm $- (,) <$> initialExplorationStepper opts testName replayTxs targets (length replayTxs)- <*> Stepper.evm get- if x- then pure ("\x1b[32m[PASS]\x1b[0m " <> testName <> " (runs: " <> (pack $ show fuzzRuns) <>", depth: " <> pack (show depth) <> ")",- Right (passOutput vm' opts testName), vm') -- no canonical "post vm"- else let replayText = if null replayTxs- then "\nReplay data: '(" <> pack (show testName) <> "," <> pack (show (show (ByteStringS $ rlpencode counterex))) <> ")'"- else " (replayed)"- in pure ("\x1b[31m[FAIL]\x1b[0m " <> testName <> replayText, Left (failOutput vm' opts testName), vm')--execTest :: UnitTestOptions -> VM -> ABIMethod -> AbiValue -> IO (Bool, VM)-execTest opts@UnitTestOptions{..} vm testName args =- EVM.Stepper.interpret (Fetch.oracle solvers rpcInfo) vm $ do- (,) <$> execTestStepper opts testName args- <*> Stepper.evm get---- | Define the thread spawner for normal test cases-runOne :: UnitTestOptions -> VM -> ABIMethod -> AbiValue -> IO (Text, Either Text Text, VM)-runOne opts@UnitTestOptions{..} vm testName args = do- let argInfo = pack (if args == emptyAbi then "" else " with arguments: " <> show args)- (bailed, vm') <- execTest opts vm testName args- (success, vm'') <- EVM.Stepper.interpret (Fetch.oracle solvers rpcInfo) vm' $ do- (,) <$> (checkFailures opts testName bailed)- <*> Stepper.evm get- if success- then- let gasSpent = testParams.gasCall - vm'.state.gas- gasText = pack $ show (into gasSpent :: Integer)- in- pure- ("\x1b[32m[PASS]\x1b[0m "- <> testName <> argInfo <> " (gas: " <> gasText <> ")"- , Right (passOutput vm'' opts testName)- , vm''- )- else if bailed then- pure- ("\x1b[31m[BAIL]\x1b[0m "- <> testName <> argInfo- , Left (failOutput vm'' opts testName)- , vm''- )- else- pure- ("\x1b[31m[FAIL]\x1b[0m "- <> testName <> argInfo- , Left (failOutput vm'' opts testName)- , vm''- )---- | Define the thread spawner for property based tests-fuzzRun :: UnitTestOptions -> VM -> Text -> [AbiType] -> IO (Text, Either Text Text, VM)-fuzzRun opts@UnitTestOptions{..} vm testName types = do- let args = Args{ replay = Nothing- , maxSuccess = fuzzRuns- , maxDiscardRatio = 10- , maxSize = 100- , chatty = isJust verbose- , maxShrinks = maxBound- }- quickCheckWithResult args (fuzzTest opts testName types vm) >>= \case- QC.Success numTests _ _ _ _ _ ->- pure ("\x1b[32m[PASS]\x1b[0m "- <> testName <> " (runs: " <> (pack $ show numTests) <> ")"- -- this isn't the post vm we actually want, as we- -- can't retrieve the correct vm from quickcheck- , Right (passOutput vm opts testName)- , vm- )- QC.Failure _ _ _ _ _ _ _ _ _ _ failCase _ _ ->- let abiValue = decodeAbiValue (AbiTupleType (Vector.fromList types)) $ BSLazy.fromStrict $ hexText (pack $ concat failCase)- ppOutput = pack $ show abiValue- in do- -- Run the failing test again to get a proper trace- vm' <- EVM.Stepper.interpret (Fetch.oracle solvers rpcInfo) vm $- runUnitTest opts testName abiValue >> Stepper.evm get- pure ("\x1b[31m[FAIL]\x1b[0m "- <> testName <> ". Counterexample: " <> ppOutput- <> "\nRun:\n dapp test --replay '(\"" <> testName <> "\",\""- <> (pack (concat failCase)) <> "\")'\nto test this case again, or \n dapp debug --replay '(\""- <> testName <> "\",\"" <> (pack (concat failCase)) <> "\")'\nto debug it."- , Left (failOutput vm' opts testName)- , vm'- )- _ -> pure ("\x1b[31m[OOPS]\x1b[0m "- <> testName- , Left (failOutput vm opts testName)- , vm- )- -- | Define the thread spawner for symbolic tests-symRun :: UnitTestOptions -> VM -> Text -> [AbiType] -> IO (Text, Either Text Text, VM)-symRun opts@UnitTestOptions{..} vm testName types = do+symRun :: UnitTestOptions RealWorld -> VM RealWorld -> Sig -> IO (Text, Either Text Text)+symRun opts@UnitTestOptions{..} vm (Sig testName types) = do let cd = symCalldata testName types [] (AbstractBuf "txdata") shouldFail = "proveFail" `isPrefixOf` testName- testContract = vm.state.contract+ testContract store = fromMaybe (internalError "test contract not found in state") (Map.lookup vm.state.contract store) -- define postcondition depending on `shouldFail` -- We directly encode the failure conditions from failed() in ds-test since this is easier to encode than a call into failed() -- 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 = (And (readStorage' (litAddr testContract) (Lit 0) store) (Lit 2) .== Lit 2)- .|| (readStorage' (litAddr cheatCode) (Lit 0x6661696c65640000000000000000000000000000000000000000000000000000) store .== Lit 1)+ 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)+ Nothing -> And (readStorage' (Lit 0) (testContract store).storage) (Lit 2) .== Lit 2 postcondition = curry $ case shouldFail of True -> \(_, post) -> case post of Success _ _ _ store -> failed store _ -> PBool True False -> \(_, post) -> case post of Success _ _ _ store -> PNeg (failed store)- Failure _ _ _ -> PBool False+ Failure _ _ (Revert msg) -> case msg of+ ConcreteBuf b -> PBool $ b /= panicMsg 0x01+ b -> b ./= ConcreteBuf (panicMsg 0x01)+ Failure _ _ _ -> PBool True Partial _ _ _ -> PBool True _ -> internalError "Invalid leaf node" - vm' <- EVM.Stepper.interpret (Fetch.oracle solvers rpcInfo) vm $+ vm' <- Stepper.interpret (Fetch.oracle solvers rpcInfo) vm $ Stepper.evm $ do pushTrace (EntryTrace testName) makeTxCall testParams cd get -- check postconditions against vm- (_, results) <- verify solvers (makeVeriOpts opts) vm' (Just postcondition)+ (e, results) <- verify solvers (makeVeriOpts opts) vm' (Just postcondition)+ let allReverts = not . (any Expr.isSuccess) . flattenExpr $ e -- display results if all isQed results- then do- pure ("\x1b[32m[PASS]\x1b[0m " <> testName, Right "", vm)+ then if allReverts && (not shouldFail)+ then pure ("\x1b[31m[FAIL]\x1b[0m " <> testName, Left $ allBranchRev testName)+ else pure ("\x1b[32m[PASS]\x1b[0m " <> testName, Right "") else do let x = mapMaybe extractCex results let y = symFailure opts testName (fst cd) types x- pure ("\x1b[31m[FAIL]\x1b[0m " <> testName, Left y, vm)+ pure ("\x1b[31m[FAIL]\x1b[0m " <> testName, Left y) -symFailure :: UnitTestOptions -> Text -> Expr Buf -> [AbiType] -> [(Expr End, SMTCex)] -> Text+allBranchRev :: Text -> Text+allBranchRev testName = Text.unlines+ [ "Failure: " <> testName+ , ""+ , indentLines 2 $ Text.unlines+ [ "No reachable assertion violations, but all branches reverted"+ , "Prefix this testname with `proveFail` if this is expected"+ ]+ ]+symFailure :: UnitTestOptions RealWorld -> Text -> Expr Buf -> [AbiType] -> [(Expr End, SMTCex)] -> Text symFailure UnitTestOptions {..} testName cd types failures' = mconcat [ "Failure: "@@ -764,8 +261,8 @@ where showRes = \case Success _ _ _ _ -> if "proveFail" `isPrefixOf` testName- then "Successful execution"- else "Failed: DSTest Assertion Violation"+ then "Successful execution"+ else "Failed: DSTest Assertion Violation" res -> let ?context = DappContext { info = dapp, env = traceContext res} in Text.pack $ prettyvmresult res@@ -783,23 +280,7 @@ _ -> "" ] -prettyCalldata :: (?context :: DappContext) => SMTCex -> Expr Buf -> Text -> [AbiType] -> Text-prettyCalldata cex buf sig types = head (Text.splitOn "(" sig) <> showCalldata cex types buf--showCalldata :: (?context :: DappContext) => SMTCex -> [AbiType] -> Expr Buf -> Text-showCalldata cex tps buf = "(" <> intercalate "," (fmap showVal vals) <> ")"- where- argdata = Expr.drop 4 $ simplify $ subModel cex buf- vals = case decodeBuf tps argdata of- CAbi v -> v- _ -> internalError $ "unable to abi decode function arguments:\n" <> (Text.unpack $ formatExpr argdata)--showVal :: AbiValue -> Text-showVal (AbiBytes _ bs) = formatBytes bs-showVal (AbiAddress addr) = Text.pack . show $ addr-showVal v = Text.pack . show $ v--execSymTest :: UnitTestOptions -> ABIMethod -> (Expr Buf, [Prop]) -> Stepper (Expr End)+execSymTest :: UnitTestOptions RealWorld -> ABIMethod -> (Expr Buf, [Prop]) -> Stepper RealWorld (Expr End) execSymTest UnitTestOptions{ .. } method cd = do -- Set up the call to the test method Stepper.evm $ do@@ -808,7 +289,7 @@ -- Try running the test method runExpr -checkSymFailures :: UnitTestOptions -> Stepper VM+checkSymFailures :: UnitTestOptions RealWorld -> Stepper RealWorld (VM RealWorld) checkSymFailures UnitTestOptions { .. } = do -- Ask whether any assertions failed Stepper.evm $ do@@ -821,7 +302,7 @@ let p = Text.replicate n " " in Text.unlines (map (p <>) (Text.lines s)) -passOutput :: VM -> UnitTestOptions -> Text -> Text+passOutput :: VM s -> UnitTestOptions s -> Text -> Text passOutput vm UnitTestOptions { .. } testName = let ?context = DappContext { info = dapp, env = vm.env.contracts } in let v = fromMaybe 0 verbose@@ -836,8 +317,7 @@ ] else "" --- TODO-failOutput :: VM -> UnitTestOptions -> Text -> Text+failOutput :: VM s -> UnitTestOptions s -> Text -> Text failOutput vm UnitTestOptions { .. } testName = let ?context = DappContext { info = dapp, env = vm.env.contracts } in mconcat@@ -922,37 +402,37 @@ _ -> Nothing _ -> Just "<symbolic decimal>" -abiCall :: TestVMParams -> Either (Text, AbiValue) ByteString -> EVM ()+abiCall :: TestVMParams -> Either (Text, AbiValue) ByteString -> EVM 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 ()+makeTxCall :: TestVMParams -> (Expr Buf, [Prop]) -> EVM s () makeTxCall params (cd, cdProps) = do resetState assign (#tx % #isCreate) False- loadContract params.address+ execState (loadContract params.address) <$> get >>= put assign (#state % #calldata) cd #constraints %= (<> cdProps)- assign (#state % #caller) (litAddr params.caller)+ assign (#state % #caller) params.caller assign (#state % #gas) params.gasCall- origin' <- fromMaybe (initialContract (RuntimeCode (ConcreteRuntimeCode ""))) <$> use (#env % #contracts % at params.origin)- let originBal = origin'.balance- when (originBal < params.gasprice * (into params.gasCall)) $ internalError "insufficient balance for gas cost"+ 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 -> SolcContract -> VM-initialUnitTestVm (UnitTestOptions {..}) theContract =- let- vm = makeVm $ VMOpts+initialUnitTestVm :: UnitTestOptions s -> SolcContract -> ST s (VM s)+initialUnitTestVm (UnitTestOptions {..}) theContract = do+ vm <- makeVm $ VMOpts { contract = initialContract (InitCode theContract.creationCode mempty)+ , otherContracts = [] , calldata = mempty , value = Lit 0 , address = testParams.address- , caller = litAddr testParams.caller+ , caller = testParams.caller , origin = testParams.origin , gas = testParams.gasCreate , gaslimit = testParams.gasCreate@@ -965,56 +445,53 @@ , priorityFee = testParams.priorityFee , maxCodeSize = testParams.maxCodeSize , prevRandao = testParams.prevrandao- , schedule = FeeSchedule.berlin+ , schedule = feeSchedule , chainId = testParams.chainId , create = True- , initialStorage = EmptyStore+ , baseState = EmptyBase , txAccessList = mempty -- TODO: support unit test access lists??? , allowFFI = ffiAllowed }- creator =- initialContract (RuntimeCode (ConcreteRuntimeCode ""))- & set #nonce 1- & set #balance testParams.balanceCreate- in vm- & set (#env % #contracts % at ethrunAddress) (Just creator)---getParametersFromEnvironmentVariables :: Maybe Text -> IO TestVMParams-getParametersFromEnvironmentVariables rpc = do- block' <- maybe Fetch.Latest (Fetch.BlockNumber . read) <$> (lookupEnv "DAPP_TEST_NUMBER")+ let creator =+ initialContract (RuntimeCode (ConcreteRuntimeCode ""))+ & set #nonce (Just 1)+ & set #balance (Lit testParams.balanceCreate)+ pure $ vm & set (#env % #contracts % at (LitAddr ethrunAddress)) (Just creator) - (miner,ts,blockNum,ran,limit,base) <-- case rpc of- Nothing -> pure (0,Lit 0,0,0,0,0)- Just url -> Fetch.fetchBlockFrom block' url >>= \case- Nothing -> internalError "Could not fetch block"- Just Block{..} -> pure ( coinbase- , timestamp- , number- , prevRandao- , gaslimit- , baseFee- )- let- getWord s def = maybe def read <$> lookupEnv s- getAddr s def = maybe def read <$> lookupEnv s- ts' = fromMaybe (internalError "received unexpected symbolic timestamp via rpc") (maybeLitWord ts)+paramsFromRpc :: Fetch.RpcInfo -> IO TestVMParams+paramsFromRpc rpcinfo = do+ (miner,ts,blockNum,ran,limit,base) <- case rpcinfo of+ Nothing -> pure (SymAddr "miner", Lit 0, 0, 0, 0, 0)+ Just (block, url) -> Fetch.fetchBlockFrom block url >>= \case+ Nothing -> internalError "Could not fetch block"+ Just Block{..} -> pure ( coinbase+ , timestamp+ , number+ , prevRandao+ , gaslimit+ , baseFee+ )+ let ts' = fromMaybe (internalError "received unexpected symbolic timestamp via rpc") (maybeLitWord ts)+ pure $ TestVMParams+ -- TODO: make this symbolic! It needs some tweaking to the way that our+ -- symbolic interpreters work to allow us to symbolically exec constructor initialization+ { address = LitAddr 0xacab+ , caller = SymAddr "caller"+ , origin = SymAddr "origin"+ , gasCreate = defaultGasForCreating+ , gasCall = defaultGasForInvoking+ , baseFee = base+ , priorityFee = 0+ , balanceCreate = defaultBalanceForTestContract+ , coinbase = miner+ , number = blockNum+ , timestamp = ts'+ , gaslimit = limit+ , gasprice = 0+ , maxCodeSize = defaultMaxCodeSize+ , prevrandao = ran+ , chainId = 99+ } - TestVMParams- <$> getAddr "DAPP_TEST_ADDRESS" (createAddress ethrunAddress 1)- <*> getAddr "DAPP_TEST_CALLER" ethrunAddress- <*> getAddr "DAPP_TEST_ORIGIN" ethrunAddress- <*> getWord "DAPP_TEST_GAS_CREATE" defaultGasForCreating- <*> getWord "DAPP_TEST_GAS_CALL" defaultGasForInvoking- <*> getWord "DAPP_TEST_BASEFEE" base- <*> getWord "DAPP_TEST_PRIORITYFEE" 0- <*> getWord "DAPP_TEST_BALANCE" defaultBalanceForTestContract- <*> getAddr "DAPP_TEST_COINBASE" miner- <*> getWord "DAPP_TEST_NUMBER" blockNum- <*> getWord "DAPP_TEST_TIMESTAMP" ts'- <*> getWord "DAPP_TEST_GAS_LIMIT" limit- <*> getWord "DAPP_TEST_GAS_PRICE" 0- <*> getWord "DAPP_TEST_MAXCODESIZE" defaultMaxCodeSize- <*> getWord "DAPP_TEST_PREVRANDAO" ran- <*> getWord "DAPP_TEST_CHAINID" 99+tick :: Text -> IO ()+tick x = Text.putStr x >> hFlush stdout
test/EVM/Test/BlockchainTests.hs view
@@ -2,20 +2,19 @@ import EVM (initialContract, makeVm) import EVM.Concrete qualified as EVM-import EVM.Dapp (emptyDapp)-import EVM.Expr (litAddr)-import EVM.FeeSchedule qualified+import EVM.FeeSchedule (feeSchedule) import EVM.Fetch qualified import EVM.Format (hexText) import EVM.Stepper qualified-import EVM.Solvers (withSolvers, Solver(Z3)) import EVM.Transaction-import EVM.TTY qualified as TTY import EVM.Types hiding (Block, Case)+import EVM.Test.Tracing (interpretWithTrace, VMTrace, compareTraces, EVMToolTraceOutput(..)) -import Control.Arrow ((***), (&&&)) import Optics.Core+import Control.Arrow ((***), (&&&)) import Control.Monad+import Control.Monad.ST (RealWorld, stToIO)+import Control.Monad.State.Strict import Data.Aeson ((.:), (.:?), FromJSON (..)) import Data.Aeson qualified as JSON import Data.Aeson.Types qualified as JSON@@ -30,15 +29,13 @@ import System.Environment (lookupEnv, getEnv) import System.FilePath.Find qualified as Find import System.FilePath.Posix (makeRelative, (</>))+import Witch (into, unsafeInto) import Witherable (Filterable, catMaybes) import Test.Tasty import Test.Tasty.ExpectedFailure import Test.Tasty.HUnit-import Witch (into, unsafeInto) -type Storage = Map W256 W256- data Which = Pre | Post data Block = Block@@ -54,14 +51,14 @@ data Case = Case { vmOpts :: VMOpts- , checkContracts :: Map Addr (Contract, Storage)- , testExpectation :: Map Addr (Contract, Storage)+ , checkContracts :: Map Addr Contract+ , testExpectation :: Map Addr Contract } deriving Show data BlockchainCase = BlockchainCase { blocks :: [Block]- , pre :: Map Addr (Contract, Storage)- , post :: Map Addr (Contract, Storage)+ , pre :: Map Addr Contract+ , post :: Map Addr Contract , network :: String } deriving Show @@ -89,7 +86,7 @@ parsed <- parseBCSuite <$> LazyByteString.readFile file case parsed of Left "No cases to check." -> pure [] -- error "no-cases ok"- Left _err -> pure [] -- error err+ Left _err -> pure [] -- error _err Right allTests -> pure $ (\(name, x) -> testCase' name $ runVMTest True (name, x)) <$> Map.toList allTests where@@ -131,41 +128,55 @@ runVMTest :: Bool -> (String, Case) -> IO () runVMTest diffmode (_name, x) = do- let vm0 = vmForCase x+ vm0 <- vmForCase x result <- EVM.Stepper.interpret (EVM.Fetch.zero 0 (Just 0)) vm0 EVM.Stepper.runFully maybeReason <- checkExpectation diffmode x result forM_ maybeReason assertFailure --- | Example usage:--- $ cabal new-repl ethereum-tests--- ghci> debugVMTest "BlockchainTests/GeneralStateTests/VMTests/vmArithmeticTest/twoOps.json" "twoOps_d0g0v0_London"-debugVMTest :: String -> String -> IO ()-debugVMTest file test = do+-- | Run a vm test and output a geth style per opcode trace+traceVMTest :: String -> String -> IO [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 [(_, x')] -> x' _ -> internalError "test not found"- let vm0 = vmForCase x- result <- withSolvers Z3 0 Nothing $ \solvers ->- TTY.runFromVM solvers Nothing Nothing emptyDapp vm0- void $ checkExpectation True x result+ vm0 <- vmForCase x+ (_, (_, ts)) <- runStateT (interpretWithTrace (EVM.Fetch.zero 0 (Just 0)) EVM.Stepper.runFully) (vm0, [])+ pure ts +-- | Read a geth trace from disk+readTrace :: FilePath -> IO (Either String EVMToolTraceOutput)+readTrace = JSON.eitherDecodeFileStrict++-- | 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 file test gethTrace = do+ hevm <- traceVMTest file test+ EVMToolTraceOutput ts _ <- fromJust <$> (JSON.decodeFileStrict gethTrace :: IO (Maybe EVMToolTraceOutput))+ _ <- compareTraces hevm ts+ pure ()+ splitEithers :: (Filterable f) => f (Either a b) -> (f a, f b) splitEithers = (catMaybes *** catMaybes) . (fmap fst &&& fmap snd) . (fmap (preview _Left &&& preview _Right)) -checkStateFail :: Bool -> Case -> VM -> (Bool, Bool, Bool, Bool) -> IO String+fromConcrete :: Expr Storage -> Map W256 W256+fromConcrete (ConcreteStore s) = s+fromConcrete s = internalError $ "unexpected abstract store: " <> show s++checkStateFail :: Bool -> Case -> VM RealWorld -> (Bool, Bool, Bool, Bool) -> IO String checkStateFail diff x vm (okMoney, okNonce, okData, okCode) = do let- printContracts :: Map Addr (Contract, Storage) -> IO ()- printContracts cs = putStrLn $ Map.foldrWithKey (\k (c, s) acc ->+ printContracts :: Map Addr Contract -> IO ()+ printContracts cs = putStrLn $ Map.foldrWithKey (\k c acc -> acc ++ show k ++ " : "- ++ (show . toInteger $ (view #nonce c)) ++ " "- ++ (show . toInteger $ (view #balance c)) ++ " "- ++ (printStorage s)+ ++ (show $ fromJust $ c.nonce) ++ " "+ ++ (show $ fromJust $ maybeLitWord c.balance) ++ " "+ ++ (show $ fromConcrete $ c.storage) ++ "\n") "" cs reason = map fst (filter (not . snd)@@ -177,8 +188,7 @@ ]) check = x.checkContracts expected = x.testExpectation- actual = Map.map (,mempty) $ view (#env % #contracts) vm -- . to (fmap (clearZeroStorage.clearOrigStorage))) vm- printStorage = show -- TODO: fixme+ actual = fmap (clearZeroStorage . clearOrigStorage) $ forceConcreteAddrs vm.env.contracts when diff $ do putStr (unwords reason)@@ -190,7 +200,7 @@ printContracts actual pure (unwords reason) -checkExpectation :: Bool -> Case -> VM -> IO (Maybe String)+checkExpectation :: Bool -> Case -> VM RealWorld -> IO (Maybe String) checkExpectation diff x vm = do let expectation = x.testExpectation (okState, b2, b3, b4, b5) = checkExpectedContracts vm expectation@@ -200,67 +210,69 @@ Just <$> checkStateFail diff x vm (b2, b3, b4, b5) -- quotient account state by nullness-(~=) :: Map Addr (Contract, Storage) -> Map Addr (Contract, Storage) -> Bool+(~=) :: Map Addr Contract -> Map Addr Contract -> Bool (~=) cs1 cs2 = let nullAccount = EVM.initialContract (RuntimeCode (ConcreteRuntimeCode ""))- padNewAccounts cs ks = Map.union cs $ Map.fromList [(k, (nullAccount, mempty)) | k <- ks]+ padNewAccounts cs ks = Map.union cs $ Map.fromList [(k, nullAccount) | k <- ks] padded_cs1 = padNewAccounts cs1 (Map.keys cs2) padded_cs2 = padNewAccounts cs2 (Map.keys cs1) in and $ zipWith (===) (Map.elems padded_cs1) (Map.elems padded_cs2) -(===) :: (Contract, Storage) -> (Contract, Storage) -> Bool-(c1, s1) === (c2, s2) =+(===) :: Contract -> Contract -> Bool+c1 === c2 = codeEqual && storageEqual && (c1 ^. #balance == c2 ^. #balance) && (c1 ^. #nonce == c2 ^. #nonce) where- storageEqual = s1 == s2- codeEqual = case (c1 ^. #contractcode, c2 ^. #contractcode) of+ storageEqual = c1.storage == c2.storage+ codeEqual = case (c1 ^. #code, c2 ^. #code) of (RuntimeCode a', RuntimeCode b') -> a' == b' _ -> internalError "unexpected code" -checkExpectedContracts :: VM -> Map Addr (Contract, Storage) -> (Bool, Bool, Bool, Bool, Bool)+checkExpectedContracts :: VM RealWorld -> Map Addr Contract -> (Bool, Bool, Bool, Bool, Bool) checkExpectedContracts vm expected =- let cs = zipWithStorages $ vm ^. #env % #contracts -- . to (fmap (clearZeroStorage.clearOrigStorage))- expectedCs = clearStorage <$> expected- in ( (expectedCs ~= cs)- , (clearBalance <$> expectedCs) ~= (clearBalance <$> cs)- , (clearNonce <$> expectedCs) ~= (clearNonce <$> cs)- , (clearStorage <$> expectedCs) ~= (clearStorage <$> cs)- , (clearCode <$> expectedCs) ~= (clearCode <$> cs)+ let cs = fmap (clearZeroStorage . clearOrigStorage) $ forceConcreteAddrs vm.env.contracts+ in ( (expected ~= cs)+ , (clearBalance <$> expected) ~= (clearBalance <$> cs)+ , (clearNonce <$> expected) ~= (clearNonce <$> cs)+ , (clearStorage <$> expected) ~= (clearStorage <$> cs)+ , (clearCode <$> expected) ~= (clearCode <$> cs) )- where- zipWithStorages = Map.mapWithKey (\addr c -> (c, lookupStorage addr))- lookupStorage _ =- case vm ^. #env % #storage of- ConcreteStore _ -> mempty -- clearZeroStorage $ fromMaybe mempty $ Map.lookup (num addr) s- EmptyStore -> mempty- AbstractStore -> mempty -- error "AbstractStore, should this be handled?"- SStore {} -> mempty -- error "SStore, should this be handled?"- GVar _ -> internalError "unexpected global variable" -clearStorage :: (Contract, Storage) -> (Contract, Storage)-clearStorage (c, _) = (c, mempty)+clearOrigStorage :: Contract -> Contract+clearOrigStorage = set #origStorage (ConcreteStore mempty) -clearBalance :: (Contract, Storage) -> (Contract, Storage)-clearBalance (c, s) = (set #balance 0 c, s)+clearZeroStorage :: Contract -> Contract+clearZeroStorage c = case c.storage of+ ConcreteStore m -> let store = Map.filter (/= 0) m+ in set #storage (ConcreteStore store) c+ _ -> internalError "Internal Error: unexpected abstract store" -clearNonce :: (Contract, Storage) -> (Contract, Storage)-clearNonce (c, s) = (set #nonce 0 c, s)+clearStorage :: Contract -> Contract+clearStorage c = c { storage = clear c.storage }+ where+ clear :: Expr Storage -> Expr Storage+ clear (ConcreteStore _) = ConcreteStore mempty+ clear _ = internalError "Internal Error: unexpected abstract store" -clearCode :: (Contract, Storage) -> (Contract, Storage)-clearCode (c, s) = (set #contractcode (RuntimeCode (ConcreteRuntimeCode "")) c, s)+clearBalance :: Contract -> Contract+clearBalance c = set #balance (Lit 0) c -newtype ContractWithStorage = ContractWithStorage (Contract, Storage)+clearNonce :: Contract -> Contract+clearNonce c = set #nonce (Just 0) c -instance FromJSON ContractWithStorage where+clearCode :: Contract -> Contract+clearCode c = set #code (RuntimeCode (ConcreteRuntimeCode "")) c++instance FromJSON Contract where parseJSON (JSON.Object v) = do code <- (RuntimeCode . ConcreteRuntimeCode <$> (hexText <$> v .: "code"))- storage' <- v .: "storage"- balance' <- v .: "balance"- nonce' <- v .: "nonce"- let c = EVM.initialContract code- & #balance .~ balance'- & #nonce .~ nonce'- return $ ContractWithStorage (c, storage')+ storage <- v .: "storage"+ balance <- v .: "balance"+ nonce <- v .: "nonce"+ pure $ EVM.initialContract code+ & #balance .~ (Lit balance)+ & #nonce ?~ nonce+ & #storage .~ (ConcreteStore storage)+ & #origStorage .~ (ConcreteStore storage) parseJSON invalid = JSON.typeMismatch "Contract" invalid@@ -285,17 +297,15 @@ baseFee <- fmap read <$> v' .:? "baseFeePerGas" timestamp <- wordField v' "timestamp" mixHash <- wordField v' "mixHash"- return $ Block coinbase difficulty mixHash gasLimit (fromMaybe 0 baseFee) number timestamp txs+ pure $ Block coinbase difficulty mixHash gasLimit (fromMaybe 0 baseFee) number timestamp txs parseJSON invalid = JSON.typeMismatch "Block" invalid -parseContracts :: Which -> JSON.Object -> JSON.Parser (Map Addr (Contract, Storage))-parseContracts w v =- (Map.map unwrap) <$> (v .: which >>= parseJSON)+parseContracts :: Which -> JSON.Object -> JSON.Parser (Map Addr Contract)+parseContracts w v = v .: which >>= parseJSON where which = case w of Pre -> "pre" Post -> "postState"- unwrap (ContractWithStorage x) = x parseBCSuite :: Lazy.ByteString -> Either String (Map String Case) parseBCSuite x = case (JSON.eitherDecode' x) :: Either String (Map String BlockchainCase) of@@ -343,7 +353,7 @@ maxCodeSize = 24576 fromBlockchainCase' :: Block -> Transaction- -> Map Addr (Contract, Storage) -> Map Addr (Contract, Storage)+ -> Map Addr Contract -> Map Addr Contract -> Either BlockchainError Case fromBlockchainCase' block tx preState postState = let isCreate = isNothing tx.toAddr in@@ -352,40 +362,40 @@ (_, Nothing) -> Left (if isCreate then FailedCreate else InvalidTx) (Just origin, Just checkState) -> Right $ Case (VMOpts- { contract = EVM.initialContract theCode- , calldata = (cd, [])- , value = Lit tx.value- , address = toAddr- , caller = litAddr origin- , initialStorage = EmptyStore- , origin = origin- , gas = tx.gasLimit - txGasCost feeSchedule tx- , baseFee = block.baseFee- , priorityFee = priorityFee tx block.baseFee- , gaslimit = tx.gasLimit- , number = block.number- , timestamp = Lit block.timestamp- , coinbase = block.coinbase- , prevRandao = block.mixHash- , maxCodeSize = maxCodeSize- , blockGaslimit = block.gasLimit- , gasprice = effectiveGasPrice- , schedule = feeSchedule- , chainId = 1- , create = isCreate- , txAccessList = txAccessMap tx- , allowFFI = False+ { contract = EVM.initialContract theCode+ , otherContracts = []+ , calldata = (cd, [])+ , value = Lit tx.value+ , address = toAddr+ , caller = LitAddr origin+ , baseState = EmptyBase+ , origin = LitAddr origin+ , gas = tx.gasLimit - txGasCost feeSchedule tx+ , baseFee = block.baseFee+ , priorityFee = priorityFee tx block.baseFee+ , gaslimit = tx.gasLimit+ , number = block.number+ , timestamp = Lit block.timestamp+ , coinbase = LitAddr block.coinbase+ , prevRandao = block.mixHash+ , maxCodeSize = maxCodeSize+ , blockGaslimit = block.gasLimit+ , gasprice = effectiveGasPrice+ , schedule = feeSchedule+ , chainId = 1+ , create = isCreate+ , txAccessList = Map.mapKeys LitAddr (txAccessMap tx)+ , allowFFI = False }) checkState postState where- toAddr = fromMaybe (EVM.createAddress origin senderNonce) tx.toAddr- senderNonce = view (accountAt origin % #nonce) (Map.map fst preState)- feeSchedule = EVM.FeeSchedule.berlin- toCode = Map.lookup toAddr preState+ toAddr = maybe (EVM.createAddress origin (fromJust senderNonce)) LitAddr (tx.toAddr)+ senderNonce = view (accountAt (LitAddr origin) % #nonce) (Map.mapKeys LitAddr preState)+ toCode = Map.lookup toAddr (Map.mapKeys LitAddr preState) theCode = if isCreate then InitCode tx.txdata mempty- else maybe (RuntimeCode (ConcreteRuntimeCode "")) (view #contractcode . fst) toCode+ else maybe (RuntimeCode (ConcreteRuntimeCode "")) (view #code) toCode effectiveGasPrice = effectiveprice tx block.baseFee cd = if isCreate then mempty@@ -412,47 +422,45 @@ EIP1559Transaction -> fromJust tx.maxFeePerGas _ -> fromJust tx.gasPrice -validateTx :: Transaction -> Block -> Map Addr (Contract, Storage) -> Maybe ()+validateTx :: Transaction -> Block -> Map Addr Contract -> Maybe () validateTx tx block cs = do- let cs' = Map.map fst cs origin <- sender tx- originBalance <- (view #balance) <$> view (at origin) cs'- originNonce <- (view #nonce) <$> view (at origin) cs'+ (Lit originBalance) <- (view #balance) <$> view (at origin) cs+ originNonce <- (view #nonce) <$> view (at origin) cs let gasDeposit = (effectiveprice tx block.baseFee) * (into tx.gasLimit) if gasDeposit + tx.value <= originBalance- && tx.nonce == originNonce && block.baseFee <= maxBaseFee tx+ && (Just (unsafeInto tx.nonce) == originNonce) && block.baseFee <= maxBaseFee tx then Just () else Nothing -checkTx :: Transaction -> Block -> Map Addr (Contract, Storage) -> Maybe (Map Addr (Contract, Storage))+checkTx :: Transaction -> Block -> Map Addr Contract -> Maybe (Map Addr Contract) checkTx tx block prestate = do origin <- sender tx validateTx tx block prestate- let isCreate = isNothing tx.toAddr- senderNonce = view (accountAt origin % #nonce) (Map.map fst prestate)- toAddr = fromMaybe (EVM.createAddress origin senderNonce) tx.toAddr- prevCode = view (accountAt toAddr % #contractcode) (Map.map fst prestate)- prevNonce = view (accountAt toAddr % #nonce) (Map.map fst prestate)+ let isCreate = isNothing tx.toAddr+ cs = Map.mapKeys LitAddr prestate+ senderNonce = view (accountAt (LitAddr origin) % #nonce) cs+ toAddr = maybe (EVM.createAddress origin (fromJust senderNonce)) LitAddr (tx.toAddr)+ prevCode = view (accountAt toAddr % #code) cs+ prevNonce = view (accountAt toAddr % #nonce) cs nonEmptyAccount = case prevCode of RuntimeCode (ConcreteRuntimeCode b) -> not (BS.null b) _ -> True- badNonce = prevNonce /= 0+ badNonce = prevNonce /= Just 0 initCodeSizeExceeded = BS.length tx.txdata > (unsafeInto maxCodeSize * 2) if isCreate && (badNonce || nonEmptyAccount || initCodeSizeExceeded) then mzero else- return prestate+ pure prestate -vmForCase :: Case -> VM-vmForCase x =- let- a = x.checkContracts- cs = Map.map fst a- st = Map.mapKeys into $ Map.map snd a- vm = makeVm x.vmOpts- & set (#env % #contracts) cs- & set (#env % #storage) (ConcreteStore st)- & set (#env % #origStorage) st- in- initTx vm+vmForCase :: Case -> IO (VM RealWorld)+vmForCase x = do+ vm <- stToIO $ makeVm x.vmOpts+ <&> set (#env % #contracts) (Map.mapKeys LitAddr x.checkContracts)+ pure $ initTx vm++forceConcreteAddrs :: Map (Expr EAddr) Contract -> Map Addr Contract+forceConcreteAddrs cs = Map.mapKeys+ (fromMaybe (internalError "Internal Error: unexpected symbolic address") . maybeLitAddr)+ cs
test/EVM/Test/Tracing.hs view
@@ -13,7 +13,8 @@ import Control.Monad (when) import Control.Monad.Operational qualified as Operational-import Control.Monad.State.Strict (StateT(..), liftIO, runState)+import Control.Monad.ST (RealWorld, ST, stToIO)+import Control.Monad.State.Strict (StateT(..), liftIO) import Control.Monad.State.Strict qualified as State import Data.Aeson ((.:), (.:?)) import Data.Aeson qualified as JSON@@ -36,22 +37,21 @@ import Test.QuickCheck.Instances.Natural() import Test.QuickCheck.Instances.ByteString() import Test.Tasty (testGroup, TestTree)-import Test.Tasty.HUnit (assertEqual)+import Test.Tasty.HUnit (assertEqual, testCase) import Test.Tasty.QuickCheck hiding (Failure, Success)-import Witch (into)+import Witch (into, unsafeInto) import Optics.Core hiding (pre) import Optics.State-import Optics.Zoom import EVM (makeVm, initialContract, exec1) import EVM.Assembler (assemble) import EVM.Expr qualified as Expr+import EVM.Concrete qualified as Concrete import EVM.Exec (ethrunAddress) import EVM.Fetch qualified as Fetch import EVM.Format (bsToHex, formatBinary)-import EVM.Concrete (createAddress)-import EVM.FeeSchedule qualified as FeeSchedule+import EVM.FeeSchedule import EVM.Op (intToOpName) import EVM.Sign (deriveAddr) import EVM.Solvers@@ -65,10 +65,10 @@ VMTrace { tracePc :: Int , traceOp :: Int- , traceStack :: [W256]- , traceMemSize :: Word64+ , traceGas :: Data.Word.Word64+ , traceMemSize :: Data.Word.Word64 , traceDepth :: Int- , traceGas :: Word64+ , traceStack :: [W256] , traceError :: Maybe String } deriving (Generic, Show) @@ -96,6 +96,7 @@ , opName :: String , stack :: [W256] , error :: Maybe String+ , gasCost :: Maybe W256 } deriving (Generic, Show) instance JSON.FromJSON EVMToolTrace where@@ -109,6 +110,7 @@ <*> v .: "opName" <*> v .: "stack" <*> v .:? "error"+ <*> v .:? "gasCost" mkBlockHash:: Int -> Expr 'EWord mkBlockHash x = (into x :: Integer) & show & Char8.pack & EVM.Types.keccak' & Lit@@ -120,6 +122,8 @@ EVMToolOutput { output :: ByteStringS , gasUsed :: W256+ , time :: Maybe Integer+ , error :: Maybe String } deriving (Generic, Show) instance JSON.FromJSON EVMToolOutput@@ -140,14 +144,14 @@ , gasLimit :: Data.Word.Word64 , baseFee :: W256 , maxCodeSize :: W256- , schedule :: FeeSchedule.FeeSchedule Data.Word.Word64+ , schedule :: FeeSchedule Data.Word.Word64 , blockHashes :: Map.Map Int W256 } deriving (Show, Generic) instance JSON.ToJSON EVMToolEnv where toJSON b = JSON.object [ ("currentCoinBase" , (JSON.toJSON $ b.coinbase)) , ("currentDifficulty", (JSON.toJSON $ b.prevRandao))- , ("currentGasLimit" , (JSON.toJSON ("0x" ++ showHex (toInteger $ b.gasLimit) "")))+ , ("currentGasLimit" , (JSON.toJSON ("0x" ++ showHex (into @Integer b.gasLimit) ""))) , ("currentNumber" , (JSON.toJSON $ b.number)) , ("currentTimestamp" , (JSON.toJSON tstamp)) , ("currentBaseFee" , (JSON.toJSON $ b.baseFee))@@ -167,7 +171,7 @@ , gasLimit = 0xffffffffffffffff , baseFee = 0 , maxCodeSize= 0xffffffff- , schedule = FeeSchedule.berlin+ , schedule = feeSchedule , blockHashes = mempty } @@ -263,7 +267,7 @@ } txn = EVM.Transaction.Transaction { txdata = txData- , gasLimit = fromIntegral gaslimitExec+ , gasLimit = unsafeInto gaslimitExec , gasPrice = Just 1 , nonce = 172 , toAddr = Just 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192@@ -281,23 +285,20 @@ , timestamp = Lit 0x3e8 , number = 0x0 , prevRandao = 0x0- , gasLimit = fromIntegral gaslimitExec+ , gasLimit = unsafeInto gaslimitExec , baseFee = 0x0 , maxCodeSize = 0xfffff- , schedule = FeeSchedule.berlin+ , schedule = feeSchedule , blockHashes = blockHashesDefault } sk = 0xDC38EE117CAE37750EB1ECC5CFD3DE8E85963B481B93E732C5D0CB66EE6B0C9D- fromAddress :: Addr fromAddress = fromJust $ deriveAddr sk- toAddress :: Addr toAddress = 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 getHEVMRet :: OpContract -> ByteString -> Int -> IO (Either (EvmError, [VMTrace]) (Expr 'End, [VMTrace], VMTraceResult)) getHEVMRet contr txData gaslimitExec = do let (txn, evmEnv, contrAlloc, fromAddress, toAddress, _) = evmSetup contr txData gaslimitExec- hevmRun <- runCodeWithTrace Nothing evmEnv contrAlloc txn (fromAddress, toAddress)- return hevmRun+ runCodeWithTrace Nothing evmEnv contrAlloc txn (LitAddr fromAddress) (LitAddr toAddress) getEVMToolRet :: OpContract -> ByteString -> Int -> IO (Maybe EVMToolResult) getEVMToolRet contr txData gaslimitExec = do@@ -325,8 +326,7 @@ putStrLn $ "evmtool exited with code " <> show exitCode putStrLn $ "evmtool stderr output:" <> show evmtoolStderr putStrLn $ "evmtool stdout output:" <> show evmtoolStdout- evmtoolResult <- JSON.decodeFileStrict "result.json" :: IO (Maybe EVMToolResult)- return evmtoolResult+ JSON.decodeFileStrict "result.json" :: IO (Maybe EVMToolResult) -- Compares traces of evmtool (from go-ethereum) and HEVM compareTraces :: [VMTrace] -> [EVMToolTrace] -> IO (Bool)@@ -341,21 +341,19 @@ bPc = b.pc aStack = a.traceStack bStack = b.stack- aGas = fromIntegral a.traceGas+ aGas = into a.traceGas bGas = b.gas- if aGas /= bGas then do- putStrLn "GAS doesn't match:"- putStrLn $ "HEVM's gas : " <> (show aGas)- putStrLn $ "evmtool's gas: " <> (show bGas)- else- -- putStrLn $ "Gas match : " <> (show aGas)- return ()- if aOp /= bOp || aPc /= bPc then- putStrLn $ "HEVM: " <> (intToOpName aOp) <> " (pc " <> (show aPc) <> ") --- evmtool " <> (intToOpName bOp) <> " (pc " <> (show bPc) <> ")"- else- -- putStrLn $ "trace element match. " <> (intToOpName aOp) <> " pc: " <> (show aPc)- return ()+ -- putStrLn $ "hevm: " <> intToOpName aOp <> " pc: " <> show aPc <> " gas: " <> show aGas <> " stack: " <> show aStack+ -- putStrLn $ "geth: " <> intToOpName bOp <> " pc: " <> show bPc <> " gas: " <> show bGas <> " stack: " <> show bStack + when (aGas /= bGas) $ do+ putStrLn "GAS doesn't match:"+ putStrLn $ "HEVM's gas : " <> (show aGas)+ putStrLn $ "evmtool's gas: " <> (show bGas)+ putStrLn $ "executing opcode: " <> (intToOpName aOp)+ when (aOp /= bOp || aPc /= bPc) $ do+ putStrLn $ "HEVM: " <> (intToOpName aOp) <> " (pc " <> (show aPc) <> ") --- evmtool " <> (intToOpName bOp) <> " (pc " <> (show bPc) <> ")"+ when (isJust b.error) $ do putStrLn $ "Error by evmtool: " <> (show b.error) putStrLn $ "Error by HEVM : " <> (show a.traceError)@@ -403,25 +401,25 @@ deleteTraceOutputFiles :: Maybe EVMToolResult -> IO () deleteTraceOutputFiles evmtoolResult = case evmtoolResult of- Nothing -> return ()+ Nothing -> pure () Just res -> do let traceFileName = getTraceFileName res System.Directory.removeFile traceFileName System.Directory.removeFile (traceFileName ++ ".json") -- Create symbolic VM from concrete VM-symbolify :: VM -> 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 -> (Addr, Addr) -> IO (Either (EvmError, [VMTrace]) ((Expr 'End, [VMTrace], VMTraceResult)))-runCodeWithTrace rpcinfo evmEnv alloc txn (fromAddr, toAddress) = withSolvers Z3 0 Nothing $ \solvers -> do- let origVM = vmForRuntimeCode code' calldata' evmEnv alloc txn (fromAddr, toAddress)- calldata' = ConcreteBuf txn.txdata+runCodeWithTrace :: Fetch.RpcInfo -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction -> Expr EAddr -> Expr EAddr -> IO (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 -> IO (Expr End)+ 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 expr <- buildExpr solvers $ symbolify origVM (res, (vm, trace)) <- runStateT (interpretWithTrace (Fetch.oracle solvers rpcinfo) Stepper.execFully) (origVM, [])@@ -429,24 +427,24 @@ Left x -> pure $ Left (x, trace) Right _ -> pure $ Right (expr, trace, vmres vm) -vmForRuntimeCode :: ByteString -> Expr Buf -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction -> (Addr, Addr) -> VM-vmForRuntimeCode runtimecode calldata' evmToolEnv alloc txn (fromAddr, toAddress) =- let contr = initialContract (RuntimeCode (ConcreteRuntimeCode runtimecode))- contrWithBal = (contr :: Contract) { balance = alloc.balance }- in- (makeVm $ VMOpts- { contract = contrWithBal+vmForRuntimeCode :: ByteString -> Expr Buf -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction -> Expr EAddr -> Expr EAddr -> ST s (VM s)+vmForRuntimeCode runtimecode calldata' evmToolEnv alloc txn fromAddr toAddress =+ let contract = initialContract (RuntimeCode (ConcreteRuntimeCode runtimecode))+ & set #balance (Lit alloc.balance)+ in (makeVm $ VMOpts+ { contract = contract+ , otherContracts = [] , calldata = (calldata', []) , value = Lit txn.value- , initialStorage = EmptyStore+ , baseState = EmptyBase , address = toAddress- , caller = Expr.litAddr fromAddr+ , caller = fromAddr , origin = fromAddr- , coinbase = evmToolEnv.coinbase+ , coinbase = LitAddr evmToolEnv.coinbase , number = evmToolEnv.number , timestamp = evmToolEnv.timestamp , gasprice = fromJust txn.gasPrice- , gas = txn.gasLimit - fromIntegral (EVM.Transaction.txGasCost evmToolEnv.schedule txn)+ , gas = txn.gasLimit - (EVM.Transaction.txGasCost evmToolEnv.schedule txn) , gaslimit = txn.gasLimit , blockGaslimit = evmToolEnv.gasLimit , prevRandao = evmToolEnv.prevRandao@@ -458,19 +456,26 @@ , create = False , txAccessList = mempty , allowFFI = False- }) & set (#env % #contracts % at ethrunAddress)+ }) <&> set (#env % #contracts % at (LitAddr ethrunAddress)) (Just (initialContract (RuntimeCode (ConcreteRuntimeCode BS.empty))))- & set (#state % #calldata) calldata'+ <&> set (#state % #calldata) calldata' runCode :: Fetch.RpcInfo -> ByteString -> Expr Buf -> IO (Maybe (Expr Buf)) runCode rpcinfo code' calldata' = withSolvers Z3 0 Nothing $ \solvers -> do- let origVM = vmForRuntimeCode code' calldata' emptyEvmToolEnv emptyEVMToolAlloc EVM.Transaction.emptyTransaction (ethrunAddress, createAddress ethrunAddress 1)+ origVM <- stToIO $ vmForRuntimeCode+ code'+ calldata'+ emptyEvmToolEnv+ emptyEVMToolAlloc+ EVM.Transaction.emptyTransaction+ (LitAddr ethrunAddress)+ (Concrete.createAddress ethrunAddress 1) res <- Stepper.interpret (Fetch.oracle solvers rpcinfo) origVM Stepper.execFully pure $ case res of Left _ -> Nothing Right b -> Just b -vmtrace :: VM -> VMTrace+vmtrace :: VM s -> VMTrace vmtrace vm = let memsize = vm.state.memorySize@@ -485,7 +490,7 @@ , traceError = readoutError vm.result } where- readoutError :: Maybe VMResult -> Maybe String+ readoutError :: Maybe (VMResult 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"@@ -508,7 +513,7 @@ err -> Just $ "HEVM error: " <> show err readoutError _ = Nothing -vmres :: VM -> VMTraceResult+vmres :: VM s -> VMTraceResult vmres vm = let gasUsed' = vm.tx.gaslimit - vm.state.gas@@ -523,22 +528,23 @@ , gasUsed = gasUsed' } -type TraceState = (VM, [VMTrace])+type TraceState s = (VM s, [VMTrace]) -execWithTrace :: StateT TraceState IO VMResult+execWithTrace :: StateT (TraceState RealWorld) IO (VMResult RealWorld) execWithTrace = do _ <- runWithTrace fromJust <$> use (_1 % #result) -runWithTrace :: StateT TraceState IO VM+runWithTrace :: StateT (TraceState RealWorld) IO (VM RealWorld) runWithTrace = do -- This is just like `exec` except for every instruction evaluated, -- we also increment a counter indexed by the current code location. vm0 <- use _1 case vm0.result of Nothing -> do- State.modify (\(a, b) -> (a, b ++ [vmtrace vm0]))- zoom _1 (State.state (runState exec1))+ State.modify' (\(a, b) -> (a, b ++ [vmtrace vm0]))+ vm' <- liftIO $ stToIO $ State.execStateT exec1 vm0+ assign _1 vm' runWithTrace Just (VMFailure _) -> do -- Update error text for last trace element@@ -550,16 +556,16 @@ Just _ -> pure vm0 interpretWithTrace- :: Fetch.Fetcher- -> Stepper.Stepper a- -> StateT TraceState IO a+ :: Fetch.Fetcher RealWorld+ -> Stepper.Stepper RealWorld a+ -> StateT (TraceState RealWorld) IO a interpretWithTrace fetcher = eval . Operational.view where eval- :: Operational.ProgramView Stepper.Action a- -> StateT TraceState IO a+ :: Operational.ProgramView (Stepper.Action RealWorld) a+ -> StateT (TraceState RealWorld) IO a eval (Operational.Return x) = pure x@@ -568,22 +574,26 @@ case action of Stepper.Exec -> execWithTrace >>= interpretWithTrace fetcher . k- Stepper.Run ->- runWithTrace >>= 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)- zoom _1 (State.state (runState m)) >> interpretWithTrace fetcher (k ())+ 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.IOAct q ->- zoom _1 (StateT (runStateT q)) >>= interpretWithTrace fetcher . k- Stepper.EVM m ->- zoom _1 (State.state (runState m)) >>= interpretWithTrace fetcher . k+ liftIO q >>= interpretWithTrace fetcher . k+ Stepper.EVM m -> do+ vm <- use _1+ (r, vm') <- liftIO $ stToIO $ State.runStateT m vm+ assign _1 vm'+ interpretWithTrace fetcher (k r) -data OpContract = OpContract [Op]+newtype OpContract = OpContract [Op] instance Show OpContract where show (OpContract a) = "OpContract " ++ (show a) @@ -646,11 +656,11 @@ OpJumpi -> do let filtDests = (filter (> pos) jumpDests) rndPos <- randItem filtDests- fixup ax (pos+34) (ret++[(OpPush (Lit (fromInteger (fromIntegral rndPos)))), (OpJumpi)])+ fixup ax (pos+34) (ret++[(OpPush (Lit (unsafeInto rndPos))), (OpJumpi)]) OpJump -> do let filtDests = (filter (> pos) jumpDests) rndPos <- randItem filtDests- fixup ax (pos+34) (ret++[(OpPush (Lit (fromInteger (fromIntegral rndPos)))), (OpJump)])+ fixup ax (pos+34) (ret++[(OpPush (Lit (unsafeInto rndPos))), (OpJump)]) myop@(OpPush _) -> fixup ax (pos+33) (ret++[myop]) myop -> fixup ax (pos+1) (ret++[myop]) fmap OpContract ops2@@ -661,37 +671,40 @@ onePush :: Gen Op onePush = do p <- chooseInt (1, 10)- pure $ OpPush (Lit (fromIntegral p))+ pure $ OpPush (Lit (unsafeInto p)) genContract :: Int -> Gen [Op] genContract n = do y <- chooseInt (3, 6) pushes <- genPush y normalOps <- vectorOf (3*n+40) genOne+ large :: Bool <- chooseAny+ extra <- if large then vectorOf (100) genOne+ else pure [] addReturn <- chooseInt (0, 10)- let contr = pushes ++ normalOps+ let contr = pushes ++ normalOps ++ extra if addReturn < 10 then pure $ contr++[OpPush (Lit 0x40), OpPush (Lit 0x0), OpReturn] else pure contr where genOne :: Gen Op genOne = frequency [ -- math ops- (200, frequency [- (1, pure OpAdd)- , (1, pure OpMul)+ (20, frequency [+ (2, pure OpAdd)+ , (2, pure OpMul) , (1, pure OpSub)- , (1, pure OpDiv)+ , (2, pure OpDiv) , (1, pure OpSdiv)- , (1, pure OpMod)+ , (2, pure OpMod) , (1, pure OpSmod) , (1, pure OpAddmod)- , (1, pure OpMulmod)+ , (2, pure OpMulmod) , (1, pure OpExp) , (1, pure OpSignextend)- , (1, pure OpLt)- , (1, pure OpGt)- , (1, pure OpSlt)- , (1, pure OpSgt)+ , (2, pure OpLt)+ , (2, pure OpGt)+ , (2, pure OpSlt)+ , (2, pure OpSgt) , (1, pure OpSha3) ]) -- Comparison & binary ops@@ -708,8 +721,8 @@ , (1, pure OpSar) ]) -- calldata- , (800, pure OpCalldataload)- , (200, pure OpCalldatacopy)+ , (200, pure OpCalldataload)+ , (800, pure OpCalldatacopy) -- Get some info , (100, frequency [ (10, pure OpAddress)@@ -742,8 +755,8 @@ -- memory manip , (1200, frequency [ (50, pure OpMload)- , (50, pure OpMstore)- , (1, pure OpMstore8)+ , (1, pure OpMstore)+ , (300, pure OpMstore8) ]) -- storage manip , (100, frequency [@@ -751,7 +764,7 @@ , (1, pure OpSstore) ]) -- Jumping around- , (20, frequency [+ , (50, frequency [ (1, pure OpJump) , (10, pure OpJumpi) ])@@ -770,16 +783,16 @@ (1, pure OpPop) , (400, do -- x <- arbitrary- large <- chooseInt (0, 100)+ large <- chooseInt (0, 2000) x <- if large == 0 then chooseBoundedIntegral (0::W256, (2::W256)^(256::W256)-1) else chooseBoundedIntegral (0, 10)- pure $ OpPush (Lit (fromIntegral x)))+ pure $ OpPush (Lit x)) , (10, do x <- chooseInt (1, 10)- pure $ OpDup (fromIntegral x))+ pure $ OpDup (unsafeInto x)) , (10, do x <- chooseInt (1, 10)- pure $ OpSwap (fromIntegral x))+ pure $ OpSwap (unsafeInto x)) ])] -- End states -- , (1, frequency [@@ -795,12 +808,13 @@ randItem :: [a] -> IO a randItem = generate . Test.QuickCheck.elements -getOp :: VM -> Word8+getOp :: VM s -> Word8 getOp vm = let pcpos = vm ^. #state % #pc code' = vm ^. #state % #code xs = case code' of- InitCode _ _ -> internalError "InitCode instead of RuntimeCode"+ UnknownCode _ -> internalError "UnknownCode instead of RuntimeCode"+ InitCode bs _ -> BS.drop pcpos bs RuntimeCode (ConcreteRuntimeCode xs') -> BS.drop pcpos xs' RuntimeCode (SymbolicRuntimeCode _) -> internalError "RuntimeCode is symbolic" in if xs == BS.empty then 0@@ -808,68 +822,128 @@ tests :: TestTree tests = testGroup "contract-quickcheck-run"- [ testProperty "random-contract-concrete-call" $ \(contr :: OpContract) -> ioProperty $ do- txDataRaw <- generate $ sized $ \n -> vectorOf (10*n+5) $ chooseInt (0,255)- gaslimitExec <- generate $ chooseInt (40000, 0xffff)+ [ testProperty "random-contract-concrete-call" $ \(contr :: OpContract, GasLimitInt gasLimit, TxDataRaw txDataRaw) -> ioProperty $ 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- evmtoolResult <- getEVMToolRet contrFixed txData gaslimitExec- hevmRun <- getHEVMRet contrFixed txData gaslimitExec- (Just evmtoolTraceOutput) <- getTraceOutput evmtoolResult- case hevmRun of- (Right (expr, hevmTrace, hevmTraceResult)) -> do- let- concretize :: Expr a -> Expr Buf -> Expr a- concretize a c = mapExpr go a- where- go :: Expr a -> Expr a- go = \case- AbstractBuf "calldata" -> c- y -> y- concretizedExpr = concretize expr (ConcreteBuf txData)- simplConcExpr = Expr.simplify concretizedExpr- getReturnVal :: Expr End -> Maybe ByteString- getReturnVal (Success _ _ (ConcreteBuf bs) _) = Just bs- getReturnVal _ = Nothing- simplConcrExprRetval = getReturnVal simplConcExpr- traceOK <- compareTraces hevmTrace (evmtoolTraceOutput.trace)- -- putStrLn $ "HEVM trace : " <> show hevmTrace- -- putStrLn $ "evmtool trace: " <> show (evmtoolTraceOutput.toTrace)- assertEqual "Traces and gas must match" traceOK True- let resultOK = evmtoolTraceOutput.output.output == hevmTraceResult.out- if resultOK then do- putStrLn $ "HEVM & evmtool's outputs match: '" <> (bsToHex $ bssToBs evmtoolTraceOutput.output.output) <> "'"- if isNothing simplConcrExprRetval || (fromJust simplConcrExprRetval) == (bssToBs hevmTraceResult.out)- then do- putStr "OK, symbolic interpretation -> concrete calldata -> Expr.simplify gives the same answer."- if isNothing simplConcrExprRetval then putStrLn ", but it was a Nothing, so not strong equivalence"- else putStrLn ""- else do- putStrLn $ "concretized expr : " <> (show concretizedExpr)- putStrLn $ "simplified concretized expr: " <> (show simplConcExpr)- putStrLn $ "return value computed : " <> (show simplConcrExprRetval)- putStrLn $ "evmtool's return value : " <> (show hevmTraceResult.out)- assertEqual "Simplified, concretized expression must match evmtool's output." True False- else do- putStrLn $ "Name of trace file: " <> (getTraceFileName $ fromJust evmtoolResult)- putStrLn $ "HEVM result :" <> (show hevmTraceResult)- T.putStrLn $ "HEVM result: " <> (formatBinary $ bssToBs hevmTraceResult.out)- T.putStrLn $ "evm result : " <> (formatBinary $ bssToBs evmtoolTraceOutput.output.output)- 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- 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+ checkTraceAndOutputs contrFixed gasLimit txData+ , testCase "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+ 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+ -- 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+ , [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-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+ let contract = OpContract $ concat+ [ [OpPush (Lit 0xff),OpPush (Lit 8),OpMstore8] -- value, offs+ , [OpPush (Lit 0xff),OpPush (Lit 1),OpMstore8] -- value, offs+ , [OpPush (Lit 10),OpPush (Lit 4),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]) ]++checkTraceAndOutputs :: OpContract -> Int -> ByteString -> IO ()+checkTraceAndOutputs contract gasLimit txData = do+ evmtoolResult <- getEVMToolRet contract txData gasLimit+ hevmRun <- getHEVMRet contract txData gasLimit+ (Just evmtoolTraceOutput) <- getTraceOutput evmtoolResult+ case hevmRun of+ (Right (expr, hevmTrace, hevmTraceResult)) -> do+ let+ concretize :: Expr a -> Expr Buf -> Expr a+ concretize a c = mapExpr go a+ where+ go :: Expr a -> Expr a+ go = \case+ AbstractBuf "calldata" -> c+ y -> y+ concretizedExpr = concretize expr (ConcreteBuf txData)+ simplConcExpr = Expr.simplify concretizedExpr+ getReturnVal :: Expr End -> Maybe ByteString+ getReturnVal (Success _ _ (ConcreteBuf bs) _) = Just bs+ getReturnVal _ = Nothing+ simplConcrExprRetval = getReturnVal simplConcExpr+ traceOK <- compareTraces hevmTrace (evmtoolTraceOutput.trace)+ -- putStrLn $ "HEVM trace : " <> show hevmTrace+ -- 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+ putStrLn $ "HEVM & evmtool's outputs match: '" <> (bsToHex $ bssToBs evmtoolTraceOutput.output.output) <> "'"+ if isNothing simplConcrExprRetval || (fromJust simplConcrExprRetval) == (bssToBs hevmTraceResult.out)+ then do+ putStr "OK, symbolic interpretation -> concrete calldata -> Expr.simplify gives the same answer."+ if isNothing simplConcrExprRetval then putStrLn ", but it was a Nothing, so not strong equivalence"+ else putStrLn ""+ else do+ putStrLn $ "original expr : " <> (show expr)+ putStrLn $ "concretized expr : " <> (show concretizedExpr)+ putStrLn $ "simplified concretized expr : " <> (show simplConcExpr)+ putStrLn $ "evmtoolTraceOutput.output.output : " <> (show (evmtoolTraceOutput.output.output))+ putStrLn $ "HEVM trace result output : " <> (bsToHex (bssToBs hevmTraceResult.out))+ putStrLn $ "ret value computed via symb+conc : " <> (bsToHex (fromJust simplConcrExprRetval))+ assertEqual "Simplified, concretized expression must match evmtool's output." True False+ else do+ putStrLn $ "Name of trace file: " <> (getTraceFileName $ fromJust evmtoolResult)+ putStrLn $ "HEVM result :" <> (show hevmTraceResult)+ T.putStrLn $ "HEVM result: " <> (formatBinary $ bssToBs hevmTraceResult.out)+ T.putStrLn $ "evm result : " <> (formatBinary $ bssToBs evmtoolTraceOutput.output.output)+ 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+ 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++-- GasLimitInt+newtype GasLimitInt = GasLimitInt (Int)+ deriving (Show, Eq)++instance Arbitrary GasLimitInt where+ arbitrary = do+ let mkLimit = chooseInt (50000, 0xfffff)+ fmap GasLimitInt mkLimit++-- GenTxDataRaw+newtype TxDataRaw = TxDataRaw ([Int])+ deriving (Show, Eq)++instance Arbitrary TxDataRaw where+ arbitrary = do+ let+ txDataRaw = sized $ \n -> vectorOf (10*n+5) $ chooseInt (0,255)+ fmap TxDataRaw txDataRaw
test/EVM/Test/Utils.hs view
@@ -7,6 +7,7 @@ import Data.Text qualified as T import Data.Text.IO qualified as T import GHC.IO.Handle (hClose)+import GHC.Natural import Paths_hevm qualified as Paths import System.Directory import System.IO.Temp@@ -17,37 +18,27 @@ import EVM.Fetch (RpcInfo) import EVM.Solidity import EVM.Solvers-import EVM.TTY qualified as TTY import EVM.UnitTest+import Control.Monad.ST (RealWorld) -runSolidityTestCustom :: FilePath -> Text -> Maybe Integer -> Bool -> RpcInfo -> ProjectType -> IO Bool-runSolidityTestCustom testFile match maxIter ffiAllowed rpcinfo projectType = do+runSolidityTestCustom :: FilePath -> Text -> Maybe Natural -> Maybe Integer -> Bool -> RpcInfo -> ProjectType -> IO Bool+runSolidityTestCustom testFile match timeout maxIter ffiAllowed rpcinfo projectType = do withSystemTempDirectory "dapp-test" $ \root -> do compile projectType root testFile >>= \case Left e -> error e Right bo@(BuildOutput contracts _) -> do- withSolvers Z3 1 Nothing $ \solvers -> do+ withSolvers Z3 1 timeout $ \solvers -> do opts <- testOpts solvers root (Just bo) match maxIter ffiAllowed rpcinfo- unitTest opts contracts Nothing+ unitTest opts contracts runSolidityTest :: FilePath -> Text -> IO Bool-runSolidityTest testFile match = runSolidityTestCustom testFile match Nothing True Nothing Foundry--debugSolidityTest :: FilePath -> RpcInfo -> IO ()-debugSolidityTest testFile rpcinfo = do- withSystemTempDirectory "dapp-test" $ \root -> do- compile DappTools root testFile >>= \case- Left e -> error e- Right bo -> do- withSolvers Z3 1 Nothing $ \solvers -> do- opts <- testOpts solvers root (Just bo) ".*" Nothing True rpcinfo- TTY.main opts root (Just bo)+runSolidityTest testFile match = runSolidityTestCustom testFile match Nothing Nothing True Nothing Foundry -testOpts :: SolverGroup -> FilePath -> Maybe BuildOutput -> Text -> Maybe Integer -> Bool -> RpcInfo -> IO UnitTestOptions+testOpts :: SolverGroup -> FilePath -> Maybe BuildOutput -> Text -> Maybe Integer -> Bool -> RpcInfo -> IO (UnitTestOptions RealWorld) testOpts solvers root buildOutput match maxIter allowFFI rpcinfo = do let srcInfo = maybe emptyDapp (dappInfo root) buildOutput - params <- getParametersFromEnvironmentVariables Nothing+ params <- paramsFromRpc rpcinfo pure UnitTestOptions { solvers = solvers@@ -57,13 +48,8 @@ , smtDebug = False , smtTimeout = Nothing , solver = Nothing- , covMatch = Nothing , verbose = Just 1 , match = match- , maxDepth = Nothing- , fuzzRuns = 100- , replay = Nothing- , vmModifier = id , testParams = params , dapp = srcInfo , ffiAllowed = allowFFI
− test/contracts/fail/cheatCodes.sol
@@ -1,39 +0,0 @@-import "ds-test/test.sol";--interface Hevm {- function warp(uint256) external;- function roll(uint256) external;- function load(address,bytes32) external returns (bytes32);- function store(address,bytes32,bytes32) external;- function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32);- function addr(uint256) external returns (address);- function ffi(string[] calldata) external returns (bytes memory);- function prank(address) external;-}--contract Payable {- function hi() public payable {}-}--contract TestFailCheatCodes is DSTest {- Hevm hevm = Hevm(HEVM_ADDRESS);-- function testBadFFI() public {- string[] memory inputs = new string[](2);- inputs[0] = "echo";- inputs[1] = "acab";-- // should revert if --ffi hasn't been passed to hevm...- hevm.ffi(inputs);- }-- function test_prank_underflow() public {- address from = address(0x1312);- uint amt = 10;-- Payable target = new Payable();-- hevm.prank(from);- target.hi{value : amt}();- }-}
− test/contracts/fail/dsProveFail.sol
@@ -1,66 +0,0 @@-import "ds-test/test.sol";-import "tokens/erc20.sol";--contract SolidityTest is DSTest {- ERC20 token;-- function setUp() public {- token = new ERC20("TOKEN", "TKN", 18);- }-- function prove_trivial() public {- assert(false);- }-- function prove_add(uint x, uint y) public {- unchecked {- assertTrue(x + y >= x);- }- }-- //function proveFail_shouldFail(address usr) public {- //usr.call("");- //}-- function prove_smtTimeout(uint x, uint y, uint z) public {- if ((x * y / z) * (x / y) / (x * y) == (x * x * x * y * z / x * z * y)) {- assertTrue(false);- } else {- assertTrue(true);- }- }-- function prove_multi(uint x) public {- if (x == 3) {- assertTrue(false);- } else if (x == 9) {- assertTrue(false);- } else if (x == 1023423194871904872390487213) {- assertTrue(false);- } else {- assertTrue(true);- }- }-- function prove_mul(uint136 x, uint128 y) public {- x * y;- }-- function prove_distributivity(uint120 x, uint120 y, uint120 z) public {- assertEq(x + (y * z), (x + y) * (x + z));- }-- function prove_transfer(uint supply, address usr, uint amt) public {- token.mint(address(this), supply);-- uint prebal = token.balanceOf(usr);- token.transfer(usr, amt);- uint postbal = token.balanceOf(usr);-- uint expected = usr == address(this)- ? 0 // self transfer is a noop- : amt; // otherwise `amt` has been transfered to `usr`- assertEq(expected, postbal - prebal);- }-}-
− test/contracts/fail/invariantFail.sol
@@ -1,50 +0,0 @@-import "ds-test/test.sol";--contract Testdapp {- uint public x;- function f() public {- x++;- }- function g(uint y) public {- if (y % 2 == 0) x*=2;- }-}---contract TestdappTest is DSTest {- Testdapp testdapp;-- function setUp() public {- testdapp = new Testdapp();- }-- function invariantFirst() public {- assertLt(testdapp.x(), 100);- }-}--contract InvariantCount is DSTest {- BrokenAtStart count;- address[] targetContracts_;-- function targetContracts() public returns (address[] memory) {- return targetContracts_;- }- function setUp() public {- count = new BrokenAtStart();- targetContracts_.push(address(count));- }-- // this can only fail if we call the invariant method before calling any other method in the target contracts- function invariantCount() public {- assertGt(count.count(), 0);- }-}--contract BrokenAtStart {- uint public count;-- function inc() public {- count++;- }-}
− test/contracts/fail/trivial.sol
@@ -1,9 +0,0 @@-import {DSTest} from "ds-test/test.sol";--// should run and pass-contract Trivial is DSTest {- function testFalse() public {- assertTrue(false);- }-}-
− test/contracts/lib/erc20.sol
@@ -1,201 +0,0 @@-// modified from solmate erc20-// https://github.com/transmissions11/solmate/blob/c2594bf4635ad773a8f4763e20b7e79582e41535/src/tokens/ERC20.sol--/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.-/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)-/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)-/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.-contract ERC20 {-- // --- events ----- event Transfer(address indexed from, address indexed to, uint256 amount);- event Approval(address indexed owner, address indexed spender, uint256 amount);-- // --- metadata ----- string public name;- string public symbol;- uint8 public immutable decimals;-- // --- erc20 data ----- uint256 public totalSupply;- mapping(address => uint256) public balanceOf;- mapping(address => mapping(address => uint256)) public allowance;-- // --- eip2612 data ----- uint256 internal immutable INITIAL_CHAIN_ID;- bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;- mapping(address => uint256) public nonces;-- // --- admin ----- address public owner;- modifier auth { require(owner == msg.sender, "not-authorized"); _; }-- // --- init ----- constructor(- string memory _name,- string memory _symbol,- uint8 _decimals- ) {- name = _name;- symbol = _symbol;- decimals = _decimals;-- INITIAL_CHAIN_ID = block.chainid;- INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();-- owner = msg.sender;- }-- // --- erc20 logic ----- function approve(address spender, uint256 amount) public returns (bool) {- allowance[msg.sender][spender] = amount;-- emit Approval(msg.sender, spender, amount);-- return true;- }-- function transfer(address to, uint256 amount) public returns (bool) {- balanceOf[msg.sender] -= amount;-- // Cannot overflow because the sum of all user- // balances can't exceed the max uint256 value.- unchecked {- balanceOf[to] += amount;- }-- emit Transfer(msg.sender, to, amount);-- return true;- }-- function transferFrom(- address from,- address to,- uint256 amount- ) public returns (bool) {- uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.-- if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;-- balanceOf[from] -= amount;-- // Cannot overflow because the sum of all user- // balances can't exceed the max uint256 value.- unchecked {- balanceOf[to] += amount;- }-- emit Transfer(from, to, amount);-- return true;- }-- // --- mint / burn logic ----- function mint(address to, uint256 amount) public auth {- _mint(to, amount);- }-- function burn(address from, uint256 amount) public auth {- _burn(from, amount);- }-- // --- eip2612 logic ----- function permit(- address owner,- address spender,- uint256 value,- uint256 deadline,- uint8 v,- bytes32 r,- bytes32 s- ) public {- require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");-- // Unchecked because the only math done is incrementing- // the owner's nonce which cannot realistically overflow.- unchecked {- address recoveredAddress = ecrecover(- keccak256(- abi.encodePacked(- "\x19\x01",- DOMAIN_SEPARATOR(),- keccak256(- abi.encode(- keccak256(- "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"- ),- owner,- spender,- value,- nonces[owner]++,- deadline- )- )- )- ),- v,- r,- s- );-- require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");-- allowance[recoveredAddress][spender] = value;- }-- emit Approval(owner, spender, value);- }-- function DOMAIN_SEPARATOR() public view returns (bytes32) {- return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();- }-- function computeDomainSeparator() internal view returns (bytes32) {- return- keccak256(- abi.encode(- keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),- keccak256(bytes(name)),- keccak256("1"),- block.chainid,- address(this)- )- );- }-- // --- internal mint / burn logic ----- function _mint(address to, uint256 amount) internal {- totalSupply += amount;-- // Cannot overflow because the sum of all user- // balances can't exceed the max uint256 value.- unchecked {- balanceOf[to] += amount;- }-- emit Transfer(address(0), to, amount);- }-- function _burn(address from, uint256 amount) internal {- balanceOf[from] -= amount;-- // Cannot underflow because a user's balance- // will never be larger than the total supply.- unchecked {- totalSupply -= amount;- }-- emit Transfer(from, address(0), amount);- }-}
− test/contracts/lib/test.sol
@@ -1,469 +0,0 @@-// SPDX-License-Identifier: GPL-3.0-or-later--// This program is free software: you can redistribute it and/or modify-// it under the terms of the GNU General Public License as published by-// the Free Software Foundation, either version 3 of the License, or-// (at your option) any later version.--// This program is distributed in the hope that it will be useful,-// but WITHOUT ANY WARRANTY; without even the implied warranty of-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-// GNU General Public License for more details.--// You should have received a copy of the GNU General Public License-// along with this program. If not, see <http://www.gnu.org/licenses/>.--pragma solidity >=0.5.0;--contract DSTest {- event log (string);- event logs (bytes);-- event log_address (address);- event log_bytes32 (bytes32);- event log_int (int);- event log_uint (uint);- event log_bytes (bytes);- event log_string (string);-- event log_named_address (string key, address val);- event log_named_bytes32 (string key, bytes32 val);- event log_named_decimal_int (string key, int val, uint decimals);- event log_named_decimal_uint (string key, uint val, uint decimals);- event log_named_int (string key, int val);- event log_named_uint (string key, uint val);- event log_named_bytes (string key, bytes val);- event log_named_string (string key, string val);-- bool public IS_TEST = true;- bool private _failed;-- address constant HEVM_ADDRESS =- address(bytes20(uint160(uint256(keccak256('hevm cheat code')))));-- modifier mayRevert() { _; }- modifier testopts(string memory) { _; }-- function failed() public returns (bool) {- if (_failed) {- return _failed;- } else {- bool globalFailed = false;- if (hasHEVMContext()) {- (, bytes memory retdata) = HEVM_ADDRESS.call(- abi.encodePacked(- bytes4(keccak256("load(address,bytes32)")),- abi.encode(HEVM_ADDRESS, bytes32("failed"))- )- );- globalFailed = abi.decode(retdata, (bool));- }- return globalFailed;- }- } -- function fail() internal {- if (hasHEVMContext()) {- (bool status, ) = HEVM_ADDRESS.call(- abi.encodePacked(- bytes4(keccak256("store(address,bytes32,bytes32)")),- abi.encode(HEVM_ADDRESS, bytes32("failed"), bytes32(uint256(0x01)))- )- );- status; // Silence compiler warnings- }- _failed = true;- }-- function hasHEVMContext() internal view returns (bool) {- uint256 hevmCodeSize = 0;- assembly {- hevmCodeSize := extcodesize(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)- }- return hevmCodeSize > 0;- }-- modifier logs_gas() {- uint startGas = gasleft();- _;- uint endGas = gasleft();- emit log_named_uint("gas", startGas - endGas);- }-- function assertTrue(bool condition) internal {- if (!condition) {- emit log("Error: Assertion Failed");- fail();- }- }-- function assertTrue(bool condition, string memory err) internal {- if (!condition) {- emit log_named_string("Error", err);- assertTrue(condition);- }- }-- function assertEq(address a, address b) internal {- if (a != b) {- emit log("Error: a == b not satisfied [address]");- emit log_named_address(" Expected", b);- emit log_named_address(" Actual", a);- fail();- }- }- function assertEq(address a, address b, string memory err) internal {- if (a != b) {- emit log_named_string ("Error", err);- assertEq(a, b);- }- }-- function assertEq(bytes32 a, bytes32 b) internal {- if (a != b) {- emit log("Error: a == b not satisfied [bytes32]");- emit log_named_bytes32(" Expected", b);- emit log_named_bytes32(" Actual", a);- fail();- }- }- function assertEq(bytes32 a, bytes32 b, string memory err) internal {- if (a != b) {- emit log_named_string ("Error", err);- assertEq(a, b);- }- }- function assertEq32(bytes32 a, bytes32 b) internal {- assertEq(a, b);- }- function assertEq32(bytes32 a, bytes32 b, string memory err) internal {- assertEq(a, b, err);- }-- function assertEq(int a, int b) internal {- if (a != b) {- emit log("Error: a == b not satisfied [int]");- emit log_named_int(" Expected", b);- emit log_named_int(" Actual", a);- fail();- }- }- function assertEq(int a, int b, string memory err) internal {- if (a != b) {- emit log_named_string("Error", err);- assertEq(a, b);- }- }- function assertEq(uint a, uint b) internal {- if (a != b) {- emit log("Error: a == b not satisfied [uint]");- emit log_named_uint(" Expected", b);- emit log_named_uint(" Actual", a);- fail();- }- }- function assertEq(uint a, uint b, string memory err) internal {- if (a != b) {- emit log_named_string("Error", err);- assertEq(a, b);- }- }- function assertEqDecimal(int a, int b, uint decimals) internal {- if (a != b) {- emit log("Error: a == b not satisfied [decimal int]");- emit log_named_decimal_int(" Expected", b, decimals);- emit log_named_decimal_int(" Actual", a, decimals);- fail();- }- }- function assertEqDecimal(int a, int b, uint decimals, string memory err) internal {- if (a != b) {- emit log_named_string("Error", err);- assertEqDecimal(a, b, decimals);- }- }- function assertEqDecimal(uint a, uint b, uint decimals) internal {- if (a != b) {- emit log("Error: a == b not satisfied [decimal uint]");- emit log_named_decimal_uint(" Expected", b, decimals);- emit log_named_decimal_uint(" Actual", a, decimals);- fail();- }- }- function assertEqDecimal(uint a, uint b, uint decimals, string memory err) internal {- if (a != b) {- emit log_named_string("Error", err);- assertEqDecimal(a, b, decimals);- }- }-- function assertGt(uint a, uint b) internal {- if (a <= b) {- emit log("Error: a > b not satisfied [uint]");- emit log_named_uint(" Value a", a);- emit log_named_uint(" Value b", b);- fail();- }- }- function assertGt(uint a, uint b, string memory err) internal {- if (a <= b) {- emit log_named_string("Error", err);- assertGt(a, b);- }- }- function assertGt(int a, int b) internal {- if (a <= b) {- emit log("Error: a > b not satisfied [int]");- emit log_named_int(" Value a", a);- emit log_named_int(" Value b", b);- fail();- }- }- function assertGt(int a, int b, string memory err) internal {- if (a <= b) {- emit log_named_string("Error", err);- assertGt(a, b);- }- }- function assertGtDecimal(int a, int b, uint decimals) internal {- if (a <= b) {- emit log("Error: a > b not satisfied [decimal int]");- emit log_named_decimal_int(" Value a", a, decimals);- emit log_named_decimal_int(" Value b", b, decimals);- fail();- }- }- function assertGtDecimal(int a, int b, uint decimals, string memory err) internal {- if (a <= b) {- emit log_named_string("Error", err);- assertGtDecimal(a, b, decimals);- }- }- function assertGtDecimal(uint a, uint b, uint decimals) internal {- if (a <= b) {- emit log("Error: a > b not satisfied [decimal uint]");- emit log_named_decimal_uint(" Value a", a, decimals);- emit log_named_decimal_uint(" Value b", b, decimals);- fail();- }- }- function assertGtDecimal(uint a, uint b, uint decimals, string memory err) internal {- if (a <= b) {- emit log_named_string("Error", err);- assertGtDecimal(a, b, decimals);- }- }-- function assertGe(uint a, uint b) internal {- if (a < b) {- emit log("Error: a >= b not satisfied [uint]");- emit log_named_uint(" Value a", a);- emit log_named_uint(" Value b", b);- fail();- }- }- function assertGe(uint a, uint b, string memory err) internal {- if (a < b) {- emit log_named_string("Error", err);- assertGe(a, b);- }- }- function assertGe(int a, int b) internal {- if (a < b) {- emit log("Error: a >= b not satisfied [int]");- emit log_named_int(" Value a", a);- emit log_named_int(" Value b", b);- fail();- }- }- function assertGe(int a, int b, string memory err) internal {- if (a < b) {- emit log_named_string("Error", err);- assertGe(a, b);- }- }- function assertGeDecimal(int a, int b, uint decimals) internal {- if (a < b) {- emit log("Error: a >= b not satisfied [decimal int]");- emit log_named_decimal_int(" Value a", a, decimals);- emit log_named_decimal_int(" Value b", b, decimals);- fail();- }- }- function assertGeDecimal(int a, int b, uint decimals, string memory err) internal {- if (a < b) {- emit log_named_string("Error", err);- assertGeDecimal(a, b, decimals);- }- }- function assertGeDecimal(uint a, uint b, uint decimals) internal {- if (a < b) {- emit log("Error: a >= b not satisfied [decimal uint]");- emit log_named_decimal_uint(" Value a", a, decimals);- emit log_named_decimal_uint(" Value b", b, decimals);- fail();- }- }- function assertGeDecimal(uint a, uint b, uint decimals, string memory err) internal {- if (a < b) {- emit log_named_string("Error", err);- assertGeDecimal(a, b, decimals);- }- }-- function assertLt(uint a, uint b) internal {- if (a >= b) {- emit log("Error: a < b not satisfied [uint]");- emit log_named_uint(" Value a", a);- emit log_named_uint(" Value b", b);- fail();- }- }- function assertLt(uint a, uint b, string memory err) internal {- if (a >= b) {- emit log_named_string("Error", err);- assertLt(a, b);- }- }- function assertLt(int a, int b) internal {- if (a >= b) {- emit log("Error: a < b not satisfied [int]");- emit log_named_int(" Value a", a);- emit log_named_int(" Value b", b);- fail();- }- }- function assertLt(int a, int b, string memory err) internal {- if (a >= b) {- emit log_named_string("Error", err);- assertLt(a, b);- }- }- function assertLtDecimal(int a, int b, uint decimals) internal {- if (a >= b) {- emit log("Error: a < b not satisfied [decimal int]");- emit log_named_decimal_int(" Value a", a, decimals);- emit log_named_decimal_int(" Value b", b, decimals);- fail();- }- }- function assertLtDecimal(int a, int b, uint decimals, string memory err) internal {- if (a >= b) {- emit log_named_string("Error", err);- assertLtDecimal(a, b, decimals);- }- }- function assertLtDecimal(uint a, uint b, uint decimals) internal {- if (a >= b) {- emit log("Error: a < b not satisfied [decimal uint]");- emit log_named_decimal_uint(" Value a", a, decimals);- emit log_named_decimal_uint(" Value b", b, decimals);- fail();- }- }- function assertLtDecimal(uint a, uint b, uint decimals, string memory err) internal {- if (a >= b) {- emit log_named_string("Error", err);- assertLtDecimal(a, b, decimals);- }- }-- function assertLe(uint a, uint b) internal {- if (a > b) {- emit log("Error: a <= b not satisfied [uint]");- emit log_named_uint(" Value a", a);- emit log_named_uint(" Value b", b);- fail();- }- }- function assertLe(uint a, uint b, string memory err) internal {- if (a > b) {- emit log_named_string("Error", err);- assertLe(a, b);- }- }- function assertLe(int a, int b) internal {- if (a > b) {- emit log("Error: a <= b not satisfied [int]");- emit log_named_int(" Value a", a);- emit log_named_int(" Value b", b);- fail();- }- }- function assertLe(int a, int b, string memory err) internal {- if (a > b) {- emit log_named_string("Error", err);- assertLe(a, b);- }- }- function assertLeDecimal(int a, int b, uint decimals) internal {- if (a > b) {- emit log("Error: a <= b not satisfied [decimal int]");- emit log_named_decimal_int(" Value a", a, decimals);- emit log_named_decimal_int(" Value b", b, decimals);- fail();- }- }- function assertLeDecimal(int a, int b, uint decimals, string memory err) internal {- if (a > b) {- emit log_named_string("Error", err);- assertLeDecimal(a, b, decimals);- }- }- function assertLeDecimal(uint a, uint b, uint decimals) internal {- if (a > b) {- emit log("Error: a <= b not satisfied [decimal uint]");- emit log_named_decimal_uint(" Value a", a, decimals);- emit log_named_decimal_uint(" Value b", b, decimals);- fail();- }- }- function assertLeDecimal(uint a, uint b, uint decimals, string memory err) internal {- if (a > b) {- emit log_named_string("Error", err);- assertGeDecimal(a, b, decimals);- }- }-- function assertEq(string memory a, string memory b) internal {- if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) {- emit log("Error: a == b not satisfied [string]");- emit log_named_string(" Value a", a);- emit log_named_string(" Value b", b);- fail();- }- }- function assertEq(string memory a, string memory b, string memory err) internal {- if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) {- emit log_named_string("Error", err);- assertEq(a, b);- }- }-- function checkEq0(bytes memory a, bytes memory b) internal pure returns (bool ok) {- ok = true;- if (a.length == b.length) {- for (uint i = 0; i < a.length; i++) {- if (a[i] != b[i]) {- ok = false;- }- }- } else {- ok = false;- }- }- function assertEq0(bytes memory a, bytes memory b) internal {- if (!checkEq0(a, b)) {- emit log("Error: a == b not satisfied [bytes]");- emit log_named_bytes(" Expected", a);- emit log_named_bytes(" Actual", b);- fail();- }- }- function assertEq0(bytes memory a, bytes memory b, string memory err) internal {- if (!checkEq0(a, b)) {- emit log_named_string("Error", err);- assertEq0(a, b);- }- }-}
− test/contracts/pass/abstract.sol
@@ -1,15 +0,0 @@-import {DSTest} from "ds-test/test.sol";--// should not be run (no code)-abstract contract MyTest is DSTest {- function testAbstract() public {- assertTrue(true);- }-}--// should run and pass-contract TestMy is MyTest {- function testTrue() public {- assertTrue(true);- }-}
− test/contracts/pass/cheatCodes.sol
@@ -1,125 +0,0 @@-pragma experimental ABIEncoderV2;--import "ds-test/test.sol";--interface Hevm {- function warp(uint256) external;- function roll(uint256) external;- function load(address,bytes32) external returns (bytes32);- function store(address,bytes32,bytes32) external;- function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32);- function addr(uint256) external returns (address);- function ffi(string[] calldata) external returns (bytes memory);- function prank(address) external;-}--contract HasStorage {- uint slot0 = 10;-}--contract Prankster {- function prankme() public returns (address) {- return msg.sender;- }-}--contract Payable {- function hi() public payable {}-}--contract CheatCodes is DSTest {- address store = address(new HasStorage());- Hevm hevm = Hevm(HEVM_ADDRESS);-- function test_warp_concrete(uint128 jump) public {- uint pre = block.timestamp;- hevm.warp(block.timestamp + jump);- assertEq(block.timestamp, pre + jump);- }-- function prove_warp_symbolic(uint128 jump) public {- test_warp_concrete(jump);- }-- function test_roll_concrete(uint64 jump) public {- uint pre = block.number;- hevm.roll(block.number + jump);- assertEq(block.number, pre + jump);- }-- function test_store_load_concrete(uint x) public {- uint ten = uint(hevm.load(store, bytes32(0)));- assertEq(ten, 10);-- hevm.store(store, bytes32(0), bytes32(x));- uint val = uint(hevm.load(store, bytes32(0)));- assertEq(val, x);- }-- function prove_store_load_symbolic(uint x) public {- test_store_load_concrete(x);- }-- function test_sign_addr_digest(uint sk, bytes32 digest) public {- if (sk == 0) return; // invalid key-- (uint8 v, bytes32 r, bytes32 s) = hevm.sign(sk, digest);- address expected = hevm.addr(sk);- address actual = ecrecover(digest, v, r, s);-- assertEq(actual, expected);- }-- function test_sign_addr_message(uint sk, bytes memory message) public {- test_sign_addr_digest(sk, keccak256(message));- }-- function testFail_sign_addr(uint sk, bytes32 digest) public {- uint badKey = sk + 1;-- (uint8 v, bytes32 r, bytes32 s) = hevm.sign(badKey, digest);- address expected = hevm.addr(sk);- address actual = ecrecover(digest, v, r, s);-- assertEq(actual, expected);- }-- function testFail_addr_zero_sk() public {- hevm.addr(0);- }-- function testFFI() public {- string[] memory inputs = new string[](3);- inputs[0] = "echo";- inputs[1] = "-n";- inputs[2] = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046163616200000000000000000000000000000000000000000000000000000000";-- (string memory output) = abi.decode(hevm.ffi(inputs), (string));- assertEq(output, "acab");- }-- function test_prank() public {- Prankster prankster = new Prankster();- assertEq(prankster.prankme(), address(this));- hevm.prank(address(0xdeadbeef));- assertEq(prankster.prankme(), address(0xdeadbeef));- assertEq(prankster.prankme(), address(this));- }-- function test_prank_val() public {- address from = address(0x1312);- uint amt = 10;-- Payable target = new Payable();- from.call{value: amt}("");-- uint preBal = from.balance;-- hevm.prank(from);- target.hi{value : amt}();-- uint postBal = from.balance;-- assertEq(preBal - postBal, amt);- }-}
− test/contracts/pass/constantinople.sol
@@ -1,331 +0,0 @@-import "ds-test/test.sol";--contract DeadCode{- function dummy() external returns (uint256) {}-}--contract ConstantinopleTests is DSTest {- DeadCode notmuch;- function setUp() public {- notmuch = new DeadCode();- }-- // this 5 byte-long initcode simply returns nothing- // PUSH1 00 PUSH1 00 RETURN- // 60 00 60 00 f3- bytes32 zerocode = 0x60006000f3000000000000000000000000000000000000000000000000000000;- // this 13 byte-long initcode simply returns 0xdeadbeef:- // PUSH4 de ad be ef PUSH1 00 MSTORE PUSH1 32 PUSH1 00 RETURN- // 63 de ad be ef 60 00 52 60 20 60 00 f3- bytes32 deadcode = 0x63deadbeef60005260206000f300000000000000000000000000000000000000;- // this 25 byte-long initcode returns deadcode (but without the padding)- // PUSH1 0d PUSH1 0c PUSH1 00 CODECO PUSH1 0d PUSH1 00 RETURN deadcode- // 60 0d 60 0c 60 00 39 60 0d 60 00 f3- bytes32 deploysdeadcode = 0x600d600c600039600d6000f363deadbeef60005260206000f300000000000000;-- // EXTCODEHASH of non-existent account is 0- function test_extcodehash_1() public {- uint256 h;- assembly {- h := extcodehash(0x10)- }- assertEq(h, 0);- }- // EXTCODEHASH of account with no code is keccak256("")- function test_extcodehash_2() public {- address a;- uint256 h;- assembly {- let top := mload(0x40)- mstore(top, sload(zerocode.slot))- a := create(0, top, 5)- h := extcodehash(a)- }- assertEq(h, 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470);- }- // EXTCODEHASH of account with code 0xdeadbeef is keccak256(0xdeadbeef)- function test_extcodehash_3() public {- address a;- uint256 h;-- assembly {- let top := mload(0x40)- mstore(top, sload(deadcode.slot))- a := create(0, top, 13)- h := extcodehash(a)- }-- uint256 expected_h;- assembly {- let top := mload(0x40)- mstore(top, 0xdeadbeef)- expected_h := keccak256(top, 32)- }- assertEq(h, expected_h);- }- // EXTCODEHASH of the notmuch contract should be same as- // doing EXTCODECOPY and then keccak256- function test_extcodehash_4() public {- address a = address(notmuch);- uint256 h;-- assembly {- h := extcodehash(a)- }-- uint256 expected_h;- assembly {- let top := mload(0x40)- let size := extcodesize(a)- extcodecopy(a, top, 0, size)- expected_h := keccak256(top, size)- }- assertEq(h, expected_h);- }-- // address of account created by CREATE2 is- // keccak256(0xff + address + salt + keccak256(init_code))[12:]- function test_create2_1() public {- address a;- uint256 salt = 0xfacefeed;- assembly {- let top := mload(0x40)- mstore(top, sload(deadcode.slot))- a := create2(0, top, 13, salt)- }-- address expected_a;-- assembly {- let top := mload(0x40)- mstore(top, sload(deadcode.slot))- let inithash := keccak256(top, 13)- mstore(sub(top, 11), address())- mstore8(top, 0xff)- mstore(add(top, 21), salt)- mstore(add(top, 53), inithash)- expected_a := and(keccak256(top, 85), 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff)- }-- assertEq(a, expected_a);- }- // calling a CREATE2 contract works as expected- function test_create2_2() public {- address a;- uint256 salt = 0xfacefeed;- assembly {- let top := mload(0x40)- mstore(top, sload(deploysdeadcode.slot))- a := create2(0, top, 25, salt)- }-- assertEq(DeadCode(a).dummy(), 0xdeadbeef);- }- // TODO: test some SELFDESTRUCT properties of CREATE2- // TODO: test EXTCODEHASH on self-destructed contract- // TODO: test EXTCODEHASH on precompiled contracts-- // SHL, SHR and SAR tests taken from- // https://github.com/ethereum/EIPs/blob/fde32dfd6b24bac7bfabf6c1ebe3f5a603d5ff4c/EIPS/eip-145.md- function test_shl() public {- uint z;-- assembly {- z := shl(0x00, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000001);-- assembly {- z := shl(0x01, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000002);-- assembly {- z := shl(0xff, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(z, 0x8000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shl(0x0100, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shl(0x0101, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shl(0x00, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);-- assembly {- z := shl(0x01, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe);-- assembly {- z := shl(0xff, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0x8000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shl(0x0100, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shl(0x01, 0x0000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shl(0x01, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe);- }-- function test_shr() public {- uint z;-- assembly {- z := shr(0x00, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000001);-- assembly {- z := shr(0x01, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shr(0x01, 0x8000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(z, 0x4000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shr(0xff, 0x8000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000001);-- assembly {- z := shr(0x0100, 0x8000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shr(0x0101, 0x8000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shr(0x00, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);-- assembly {- z := shr(0x01, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);-- assembly {- z := shr(0xff, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000001);-- assembly {- z := shr(0x0100, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);-- assembly {- z := shr(0x01, 0x0000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(z, 0x0000000000000000000000000000000000000000000000000000000000000000);- }-- function test_sar() public {- uint z;-- assembly {- z := sar(0x00, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(bytes32(z), bytes32(0x0000000000000000000000000000000000000000000000000000000000000001));-- assembly {- z := sar(0x01, 0x0000000000000000000000000000000000000000000000000000000000000001)- }- assertEq(bytes32(z), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000));-- assembly {- z := sar(0x01, 0x8000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(bytes32(z), bytes32(0xc000000000000000000000000000000000000000000000000000000000000000));-- assembly {- z := sar(0xff, 0x8000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(bytes32(z), bytes32(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));-- assembly {- z := sar(0x0100, 0x8000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(bytes32(z), bytes32(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));-- assembly {- z := sar(0x0101, 0x8000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(bytes32(z), bytes32(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));-- assembly {- z := sar(0x00, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(bytes32(z), bytes32(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));-- assembly {- z := sar(0x01, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(bytes32(z), bytes32(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));-- assembly {- z := sar(0xff, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(bytes32(z), bytes32(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));-- assembly {- z := sar(0x0100, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(bytes32(z), bytes32(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));-- assembly {- z := sar(0x01, 0x0000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(bytes32(z), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000));-- assembly {- z := sar(0xfe, 0x4000000000000000000000000000000000000000000000000000000000000000)- }- assertEq(bytes32(z), bytes32(0x0000000000000000000000000000000000000000000000000000000000000001));-- assembly {- z := sar(0xf8, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(bytes32(z), bytes32(0x000000000000000000000000000000000000000000000000000000000000007f));-- assembly {- z := sar(0xfe, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(bytes32(z), bytes32(0x0000000000000000000000000000000000000000000000000000000000000001));-- assembly {- z := sar(0xff, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(bytes32(z), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000));-- assembly {- z := sar(0x0100, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)- }- assertEq(bytes32(z), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000));- }-}
− test/contracts/pass/dsProvePass.sol
@@ -1,83 +0,0 @@-import "ds-test/test.sol";-import "tokens/erc20.sol";--contract ConstructorArg {- address immutable public a;- constructor(address _a) public {- a = _a;- }-}--contract SolidityTest is DSTest {- ERC20 token;-- function setUp() public {- token = new ERC20("Token", "TKN", 18);- }-- function prove_trivial() public {- assertTrue(true);- }-- function prove_easy(uint v) public {- if (v != 100) return;- assertEq(v, 100);- }-- function prove_add(uint x, uint y) public {- unchecked {- if (x + y < x) return; // no overflow- assertTrue(x + y >= x);- }- }-- function prove_balance(address usr, uint amt) public {- assertEq(0, token.balanceOf(usr));- token.mint(usr, amt);- assertEq(amt, token.balanceOf(usr));- }-- function prove_supply(uint supply) public {- token.mint(address(this), supply);- uint actual = token.totalSupply();- assertEq(supply, actual);- }-- function prove_constructorArgs(address b) public {- ConstructorArg c = new ConstructorArg(b);- assertEq(b, c.a());- }-- function proveFail_revertSmoke() public {- require(false);- }-- function proveFail_assertSmoke() public {- assertTrue(false);- }-- // Takes too long, disabling here, moving to benchmarks- // function prove_transfer(uint supply, address usr, uint amt) public {- // if (amt > supply) return; // no underflow- //- // token.mint(address(this), supply);- //- // uint prebal = token.balanceOf(usr);- // token.transfer(usr, amt);- // uint postbal = token.balanceOf(usr);- //- // uint expected = usr == address(this)- // ? 0 // self transfer is a noop- // : amt; // otherwise `amt` has been transfered to `usr`- // assertEq(expected, postbal - prebal);- // }-- function prove_burn(uint supply, uint amt) public {- if (amt > supply) return; // no undeflow-- token.mint(address(this), supply);- token.burn(address(this), amt);-- assertEq(supply - amt, token.totalSupply());- }-}
− test/contracts/pass/invariants.sol
@@ -1,41 +0,0 @@-import "ds-test/test.sol";-import "tokens/erc20.sol";--contract InvariantTest is DSTest {- ERC20 token;- User user;- address[] targetContracts_;-- function targetContracts() public returns (address[] memory) {- return targetContracts_;- }-- function setUp() public {- token = new ERC20("TOKEN", "TKN", 18);- user = new User(token);- token.mint(address(user), type(uint).max);- targetContracts_.push(address(user));- }-- function invariantTestThisBal() public {- assertLe(token.balanceOf(address(user)), type(uint).max);- }- function invariantTotSupply() public {- assertEq(token.totalSupply(), type(uint).max);- }-}--contract User {- ERC20 token;- constructor(ERC20 token_) public {- token = token_;- }-- function doTransfer(address to, uint amount) public {- token.transfer(to, amount);- }-- function doSelfTransfer(uint amount) public {- token.transfer(address(this), amount);- }-}
− test/contracts/pass/libraries.sol
@@ -1,18 +0,0 @@-import "ds-test/test.sol";--library A {- function f(uint128 x) public returns (uint256) {- return uint(x) * 2;- }-}--contract B is DSTest {- using A for uint128;- function test_f(uint128 x) public {- assertEq(uint(x) * 2, x.f());- }-- function testFail_f() public {- assertEq(1, uint128(1).f());- }-}
− test/contracts/pass/loops.sol
@@ -1,12 +0,0 @@-import "ds-test/test.sol";--contract Loops is DSTest {-- function prove_loop(uint n) public {- uint counter = 0;- for (uint i = 0; i < n; i++) {- counter++;- }- assertTrue(counter < 100);- }-}
− test/contracts/pass/rpc.sol
@@ -1,16 +0,0 @@-import {DSTest} from "ds-test/test.sol";-import {ERC20} from "tokens/erc20.sol";--contract C is DSTest {- // BAL: https://etherscan.io/address/0xba100000625a3754423978a60c9317c58a424e3D#code- ERC20 bal = ERC20(0xba100000625a3754423978a60c9317c58a424e3D);-- function test_trivial() public {- uint ub = bal.balanceOf(0xBA12222222228d8Ba445958a75a0704d566BF2C8);- assertEq(ub, 27099516537379438397130892);- }-- function prove_trivial() public {- test_trivial();- }-}
− test/contracts/pass/trivial.sol
@@ -1,9 +0,0 @@-import {DSTest} from "ds-test/test.sol";--// should run and pass-contract Trivial is DSTest {- function testTrue() public {- assertTrue(true);- }-}-
test/rpc.hs view
@@ -10,6 +10,7 @@ import Data.Text (Text) import Data.Vector qualified as V +import Optics.Core import EVM (makeVm) import EVM.ABI import EVM.Fetch@@ -20,6 +21,7 @@ import EVM.Test.Utils import EVM.Solidity (ProjectType(..)) import EVM.Types hiding (BlockNumber)+import Control.Monad.ST (stToIO, RealWorld) main :: IO () main = defaultMain tests@@ -37,7 +39,7 @@ , prevRandao ) - assertEqual "coinbase" (Addr 0xea674fdde714fd979de3edf0f56aa9716b898ec8) cb+ assertEqual "coinbase" (LitAddr 0xea674fdde714fd979de3edf0f56aa9716b898ec8) cb assertEqual "number" (BlockNumber numb) block assertEqual "basefee" 38572377838 basefee assertEqual "prevRan" 11049842297455506 prevRan@@ -51,7 +53,7 @@ , prevRandao ) - assertEqual "coinbase" (Addr 0x690b9a9e9aa1c9db991c7721a92d351db4fac990) cb+ assertEqual "coinbase" (LitAddr 0x690b9a9e9aa1c9db991c7721a92d351db4fac990) cb assertEqual "number" (BlockNumber numb) block assertEqual "basefee" 22163046690 basefee assertEqual "prevRan" 0x2267531ab030ed32fd5f2ef51f81427332d0becbd74fe7f4cd5684ddf4b287e0 prevRan@@ -60,7 +62,7 @@ -- execute against remote state from a ds-test harness [ testCase "dapp-test" $ do let testFile = "test/contracts/pass/rpc.sol"- runSolidityTestCustom testFile ".*" Nothing False testRpcInfo Foundry >>= assertEqual "test result" True+ runSolidityTestCustom testFile ".*" Nothing Nothing False testRpcInfo Foundry >>= assertEqual "test result" True -- concretely exec "transfer" on WETH9 using remote rpc -- https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code@@ -73,11 +75,11 @@ postVm <- withSolvers Z3 1 Nothing $ \solvers -> Stepper.interpret (oracle solvers (Just (BlockNumber blockNum, testRpc))) vm Stepper.runFully let- postStore = case postVm.env.storage of+ wethStore = (fromJust $ Map.lookup (LitAddr 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) postVm.env.contracts).storage+ wethStore' = case wethStore of ConcreteStore s -> s- _ -> internalError "ConcreteStore expected"- wethStore = fromJust $ Map.lookup 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 postStore- receiverBal = fromJust $ Map.lookup (keccak' (word256Bytes 0xdead <> word256Bytes 0x3)) wethStore+ _ -> internalError "Expecting concrete store"+ receiverBal = fromJust $ Map.lookup (keccak' (word256Bytes 0xdead <> word256Bytes 0x3)) wethStore' msg = case postVm.result of Just (VMSuccess m) -> m _ -> internalError "VMSuccess expected"@@ -100,49 +102,50 @@ ] -- call into WETH9 from 0xf04a... (a large holder)-weth9VM :: W256 -> (Expr Buf, [Prop]) -> IO VM+weth9VM :: W256 -> (Expr Buf, [Prop]) -> IO (VM RealWorld) weth9VM blockNum calldata' = do let- caller' = Lit 0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e+ caller' = LitAddr 0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e weth9 = Addr 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 callvalue' = Lit 0 vmFromRpc blockNum calldata' callvalue' caller' weth9 -vmFromRpc :: W256 -> (Expr Buf, [Prop]) -> Expr EWord -> Expr EWord -> Addr -> IO VM-vmFromRpc blockNum calldata' callvalue' caller' address' = do- ctrct <- fetchContractFrom (BlockNumber blockNum) testRpc address' >>= \case- Nothing -> internalError $ "contract not found: " <> show address'+vmFromRpc :: W256 -> (Expr Buf, [Prop]) -> Expr EWord -> Expr EAddr -> Addr -> IO (VM RealWorld)+vmFromRpc blockNum calldata callvalue caller address = do+ ctrct <- fetchContractFrom (BlockNumber blockNum) testRpc address >>= \case+ Nothing -> internalError $ "contract not found: " <> show address Just contract' -> return contract' blk <- fetchBlockFrom (BlockNumber blockNum) testRpc >>= \case Nothing -> internalError "could not fetch block" Just b -> pure b - pure $ makeVm $ VMOpts- { contract = ctrct- , calldata = calldata'- , value = callvalue'- , address = address'- , caller = caller'- , origin = 0xacab- , gas = 0xffffffffffffffff- , gaslimit = 0xffffffffffffffff- , baseFee = blk.baseFee- , priorityFee = 0- , coinbase = blk.coinbase- , number = blk.number- , timestamp = blk.timestamp- , blockGaslimit = blk.gaslimit- , gasprice = 0- , maxCodeSize = blk.maxCodeSize- , prevRandao = blk.prevRandao- , schedule = blk.schedule- , chainId = 1- , create = False- , initialStorage = EmptyStore- , txAccessList = mempty- , allowFFI = False- }+ stToIO $ (makeVm $ VMOpts+ { contract = ctrct+ , otherContracts = []+ , calldata = calldata+ , value = callvalue+ , address = LitAddr address+ , caller = caller+ , origin = LitAddr 0xacab+ , gas = 0xffffffffffffffff+ , gaslimit = 0xffffffffffffffff+ , baseFee = blk.baseFee+ , priorityFee = 0+ , coinbase = blk.coinbase+ , number = blk.number+ , timestamp = blk.timestamp+ , blockGaslimit = blk.gaslimit+ , gasprice = 0+ , maxCodeSize = blk.maxCodeSize+ , prevRandao = blk.prevRandao+ , schedule = blk.schedule+ , chainId = 1+ , create = False+ , baseState = EmptyBase+ , txAccessList = mempty+ , allowFFI = False+ }) <&> set (#cache % #fetched % at address) (Just ctrct) testRpc :: Text testRpc = "https://eth-mainnet.alchemyapi.io/v2/vpeKFsEF6PHifHzdtcwXSDbhV3ym5Ro4"
test/test.hs view
@@ -7,2766 +7,3816 @@ import GHC.TypeLits import Data.Proxy-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.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.Time (diffUTCTime, getCurrentTime)-import Data.Typeable-import Data.Vector qualified as Vector-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 (into, unsafeInto)--import Optics.Core hiding (pre, re)-import Optics.State-import Optics.Operators.Unsafe--import EVM hiding (choose)-import EVM.ABI-import EVM.Concrete (createAddress)-import EVM.Exec-import EVM.Expr qualified as Expr-import EVM.Fetch qualified as Fetch-import EVM.Format (hexText)-import EVM.Patricia qualified as Patricia-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 "StorageTests"- [ testCase "read-from-sstore" $ assertEqual ""- (Lit 0xab)- (Expr.readStorage' (Lit 0x0) (Lit 0x0) (SStore (Lit 0x0) (Lit 0x0) (Lit 0xab) AbstractStore))- , testCase "read-from-concrete" $ assertEqual ""- (Lit 0xab)- (Expr.readStorage' (Lit 0x0) (Lit 0x0) (ConcreteStore $ Map.fromList [(0x0, Map.fromList [(0x0, 0xab)])]))- , testCase "read-past-abstract-writes-to-different-address" $ assertEqual ""- (Lit 0xab)- (Expr.readStorage' (Lit 0x0) (Lit 0x0) (SStore (Lit 0x1) (Var "a") (Var "b") (ConcreteStore $ Map.fromList [(0x0, Map.fromList [(0x0, 0xab)])])))- , testCase "abstract-slots-block-reads-for-same-address" $ assertEqual ""- (SLoad (Lit 0x0) (Lit 0x0) (SStore (Lit 0x0) (Var "b") (Var "c") (ConcreteStore $ Map.fromList [(0x0, Map.fromList [(0x0, 0xab)])])))- (Expr.readStorage' (Lit 0x0) (Lit 0x0)- (SStore (Lit 0x1) (Var "1312") (Var "acab") (SStore (Lit 0x0) (Var "b") (Var "c") (ConcreteStore $ Map.fromList [(0x0, Map.fromList [(0x0, 0xab)])]))))- , testCase "abstract-addrs-block-reads" $ assertEqual ""- (SLoad (Lit 0x0) (Lit 0x0) (SStore (Var "1312") (Lit 0x0) (Lit 0x0) (ConcreteStore $ Map.fromList [(0x0, Map.fromList [(0x0, 0xab)])])))- (Expr.readStorage' (Lit 0x0) (Lit 0x0)- (SStore (Lit 0xacab) (Lit 0xdead) (Lit 0x0) (SStore (Var "1312") (Lit 0x0) (Lit 0x0) (ConcreteStore $ Map.fromList [(0x0, Map.fromList [(0x0, 0xab)])]))))-- , testCase "accessStorage uses fetchedStorage" $ do- let dummyContract =- (initialContract (RuntimeCode (ConcreteRuntimeCode mempty)))- { external = True }- vm = vmForEthrunCreation ""- -- perform the initial access- vm1 = execState (EVM.accessStorage 0 (Lit 0) (pure . pure ())) vm- -- it should fetch the contract first- vm2 = case vm1.result of- Just (HandleEffect (Query (PleaseFetchContract _addr continue))) ->- execState (continue dummyContract) vm1- _ -> internalError "unexpected result"- -- then it should fetch the slow- vm3 = case vm2.result of- Just (HandleEffect (Query (PleaseFetchSlot _addr _slot continue))) ->- execState (continue 1337) vm2- _ -> internalError "unexpected result"- -- perform the same access as for vm1- vm4 = execState (EVM.accessStorage 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 "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- -- https://github.com/ethereum/hevm/issues/311- , ignoreTest $ testProperty "word-simplification" $ \(ZeroDepthWord expr) -> ioProperty $ do- let simplified = Expr.simplify expr- checkEquiv expr simplified- , testProperty "readStorage-equivalance" $ \(store, addr, slot) -> ioProperty $ do- let simplified = Expr.readStorage' addr slot store- full = SLoad addr slot store- checkEquiv simplified full- , testProperty "writeStorage-equivalance" $ \(val, GenWriteStorageExpr (addr, slot, store)) -> ioProperty $ do- let simplified = Expr.writeStorage addr slot val store- full = SStore addr 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- ]- , 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" $- let- w :: Int -> W256- w x = W256 $ EVM.Types.word256 $ BS.pack [fromIntegral x]- in- assertEqual ""- (EVM.SMT.encodeConcreteStore $- Map.fromList [(w 1, (Map.fromList [(w 2, w 99), (w 2, w 100)]))])- "(sstore (_ bv1 256) (_ bv2 256) (_ bv100 256) emptyStore)"- , 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 $ Vector.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of- AbiTuple (Vector.toList -> [e]) -> e- _ -> internalError "AbiTuple expected"- let hevmEncoded = encodeAbiValue (AbiTuple $ Vector.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 $ Vector.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of- AbiTuple (Vector.toList -> [e]) -> e- _ -> internalError "AbiTuple expected"- let hevmEncoded = encodeAbiValue (AbiTuple $ Vector.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 (Vector.singleton y)))- let solidityEncoded = case decodeAbiValue (AbiTupleType $ Vector.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of- AbiTuple (Vector.toList -> [e]) -> e- _ -> internalError "AbiTuple expected"- let hevmEncoded = encodeAbiValue (AbiTuple $ Vector.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;- }- }- |]-- (json, path') <- solidity' srccode- let (Contracts solc', _, _) = fromJust $ readStdJSON json- initCode = (solc' ^?! ix (path' <> ":A")).creationCode- -- add constructor arguments- 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) ->--- withMaxSuccess 100000 $- rlpdecode (rlpencode (BS bs)) == Just (BS bs)- , testProperty "rlp encode is a partial inverse (bytes)" $ \(Bytes bs) ->--- withMaxSuccess 100000 $- case rlpdecode bs of- Just r -> rlpencode r == bs- Nothing -> True- , testProperty "rlp decode is a retraction (RLP)" $ \(RLPData r) ->--- withMaxSuccess 100000 $- rlpdecode (rlpencode r) == Just r- ]- , testGroup "Merkle Patricia Trie"- [ testProperty "update followed by delete is id" $ \(Bytes r, Bytes s, Bytes t) ->- whenFail- (putStrLn ("r:" <> (show (ByteStringS r))) >>- putStrLn ("s:" <> (show (ByteStringS s))) >>- putStrLn ("t:" <> (show (ByteStringS t)))) $--- withMaxSuccess 100000 $- Patricia.insertValues [(r, BS.pack[1]), (s, BS.pack[2]), (t, BS.pack[3]),- (r, mempty), (s, mempty), (t, mempty)]- === (Just $ Patricia.Literal Patricia.Empty)- ]- , testGroup "Remote State Tests"- [- ]- , 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"- ,- -- TODO the system currently does not allow for symbolic JUMP- 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 _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x51] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts- putStrLn "expected counterexample found"- ]-- , 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/invariants.sol", "invariantTestThisBal", True)- , ("test/contracts/pass/dsProvePass.sol", "proveEasy", True)- , ("test/contracts/fail/trivial.sol", ".*", False)- , ("test/contracts/fail/invariantFail.sol", "invariantCount", False)- , ("test/contracts/fail/dsProveFail.sol", "prove_add", False)- ]- results <- forM cases $ \(testFile, match, expected) -> do- actual <- runSolidityTestCustom testFile match 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 "testFalse" >>= 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 "Prove-Tests-Fail" $ do- let testFile = "test/contracts/fail/dsProveFail.sol"- runSolidityTest testFile "prove_trivial" >>= assertEqual "test result" False- runSolidityTest testFile "prove_add" >>= assertEqual "test result" False- --runSolidityTest testFile "prove_smtTimeout" >>= 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" (Just 10) False Nothing Foundry >>= assertEqual "test result" True- runSolidityTestCustom testFile "prove_loop" (Just 100) False Nothing Foundry >>= assertEqual "test result" False- , testCase "Invariant-Tests-Pass" $ do- let testFile = "test/contracts/pass/invariants.sol"- runSolidityTest testFile ".*" >>= assertEqual "test result" True- , testCase "Invariant-Tests-Fail" $ do- let testFile = "test/contracts/fail/invariantFail.sol"- runSolidityTest testFile "invariantFirst" >>= assertEqual "test result" False- runSolidityTest testFile "invariantCount" >>= assertEqual "test result" False- , testCase "Cheat-Codes-Pass" $ do- let testFile = "test/contracts/pass/cheatCodes.sol"- runSolidityTest testFile ".*" >>= assertEqual "test result" True- , testCase "Cheat-Codes-Fail" $ do- let testFile = "test/contracts/fail/cheatCodes.sol"- runSolidityTestCustom testFile "testBadFFI" Nothing False Nothing Foundry >>= assertEqual "test result" False- runSolidityTestCustom testFile "test_prank_underflow" Nothing False Nothing Foundry >>= assertEqual "test result" False- , 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 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 Z3 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(uint160 a) external {- int256 j = int256(uint256(a)) + 1;- assert(false);- }- }- |]- (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint160)" [AbiUIntType 160])) [] 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-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- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s safeAdd (Just (Sig "add(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts AbstractStore (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 AbstractStore (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'- _ -> internalError "expected 1 arg"- this = Expr.litAddr $ prestate.state.codeContract- prex = Expr.readStorage' this (Lit 0) prestate.env.storage- in case leaf of- Success _ _ _ postStore -> Expr.add prex (Expr.mul (Lit 2) y) .== (Expr.readStorage' this (Lit 0) postStore)- _ -> PBool True- (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts AbstractStore (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')- _ -> internalError "expected 2 args"- this = Expr.litAddr $ prestate.state.codeContract- prestore = prestate.env.storage- prex = Expr.readStorage' this x prestore- prey = Expr.readStorage' this y prestore- in case poststate of- Success _ _ _ poststore -> let- postx = Expr.readStorage' this x poststore- posty = Expr.readStorage' this y poststore- in Expr.add prex prey .== Expr.add postx posty- _ -> PBool True- (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c (Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts AbstractStore (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 poststate =- let (x,y) = case getStaticAbiArgs 2 prestate of- [x',y'] -> (x',y')- _ -> internalError "expected 2 args"- this = Expr.litAddr $ prestate.state.codeContract- prestore = prestate.env.storage- prex = Expr.readStorage' this x prestore- prey = Expr.readStorage' this y prestore- in case poststate of- Success _ _ _ poststore -> let- postx = Expr.readStorage' this x poststore- posty = Expr.readStorage' this y poststore- in Expr.add prex prey .== Expr.add postx posty- _ -> PBool True- (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c (Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts AbstractStore (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 (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 a.x() can be anything- assert(a.x() == x);- }- }- contract A {- uint public x;- }- |]- aAddr = Addr 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B- Just c <- solcRuntime "C" code'- Just a <- solcRuntime "A" code'- (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $ \s -> do- let vm0 = abstractVM (mkCalldata (Just (Sig "call_A()" [])) []) c Nothing AbstractStore- let vm = vm0- & 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- addrC = into $ createAddress ethrunAddress 1- addrA = W256 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B- testCex = Map.size storeCex == 2 &&- case (Map.lookup addrC storeCex, Map.lookup addrA storeCex) of- (Just sC, Just sA) -> Map.size sC == 1 && Map.size sA == 1 &&- case (Map.lookup 0 sC, Map.lookup 0 sA) of- (Just x, Just y) -> x /= y- _ -> 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"- ,- ignoreTest $ testCase "safemath distributivity (yul)" $ do- let yulsafeDistributivity = hex "6355a79a6260003560e01c14156016576015601f565b5b60006000fd60a1565b603d602d604435600435607c565b6039602435600435607c565b605d565b6052604b604435602435605d565b600435607c565b141515605a57fe5b5b565b6000828201821115151560705760006000fd5b82820190505b92915050565b6000818384048302146000841417151560955760006000fd5b82820290505b92915050565b"- let vm = abstractVM (mkCalldata (Just (Sig "distributivity(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) []) yulsafeDistributivity Nothing AbstractStore- (_, [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 Z3 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 = into $ createAddress ethrunAddress 1- 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 = into $ createAddress ethrunAddress 1- 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);- }- }- |]- (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts EmptyStore Nothing (Just $ checkAssertions [0x01])- let testCex = Map.null cex.store- assertBool "Did not find expected storage cex" testCex- putStrLn "Expected counterexample found"- ]- , 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- return ()- ,- testCase "eq-sol-exp-cex" $ do- -- These yul programs are not equivalent: (try --calldata $(seth --to-uint256 2) for example)- 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- return ()- , 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---checkEquiv :: (Typeable a) => Expr a -> Expr a -> IO Bool-checkEquiv l r = withSolvers Z3 1 (Just 10) $ \solvers -> do- if l == r- then do- putStrLn "skip"- pure True- else do- let smt = assertProps [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)-loadVM x = do- vm1 <- Stepper.interpret (Fetch.zero 0 Nothing) (vmForEthrunCreation x) 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- loadContract target--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 $ Vector.fromList args))--getStaticAbiArgs :: Int -> VM -> [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 $ Vector.fromList types) (BS.fromStrict (BS.drop 4 bs)) of- AbiTuple xy' -> xy'- _ -> internalError "AbiTuple expected"- in Vector.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 Word256 where- arbitrary = liftM2 fromHiAndLo arbitrary arbitrary--instance Arbitrary W256 where- arbitrary = fmap W256 arbitrary--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---- 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---- 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 EWord, Expr Storage)- deriving (Show, Eq)--instance Arbitrary GenWriteStorageExpr where- arbitrary = do- addr <- arbitrary- slot <- arbitrary- let mkStore = oneof- [ pure EmptyStore- , 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 matchAddr = liftM2 (SStore addr) arbitrary arbitrary- matchBoth = fmap (SStore addr slot) arbitrary- addWrites :: Expr Storage -> Int -> Gen (Expr Storage)- addWrites b 0 = pure b- addWrites b n = liftM4 SStore arbitrary arbitrary arbitrary (addWrites b (n - 1))- s <- arbitrary- addMatch <- oneof [ matchAddr, matchBoth ]- let withMatch = addMatch s- newWrites <- oneof [ pure 0, pure 1, fmap (`mod` 5) arbitrary ]- addWrites withMatch newWrites- , arbitrary- ]- store <- mkStore- pure $ GenWriteStorageExpr (addr, 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 (fromIntegral (1_000_000 :: Int))) , (1, fmap Lit arbitrary) ]- fmap GenWriteByteIdx mkIdx--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 fromIntegral (arbitrary :: Gen Natural)--genName :: Gen Text--- In order not to generate SMT reserved words, we prepend with "esc_"-genName = fmap (T.pack . ("esc_" <> )) $ 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- [ fmap (Failure mempty mempty . Revert) subBuf- , liftM4 Success (return mempty) (return mempty) subBuf subStore- , liftM3 ITE subWord subEnd subEnd- ]- where- subBuf = defaultBuf (sz `div` 2)- subStore = genStorage (sz `div` 2)- subWord = defaultWord (sz `div` 2)- subEnd = genEnd (sz `div` 2)--genWord :: Int -> Int -> Gen (Expr EWord)-genWord litFreq 0 = frequency- [ (litFreq, do- val <- frequency- [ (10, fmap (`mod` 100) arbitrary)- , (1, arbitrary)- ]- pure $ Lit val- )- , (1, oneof- [ pure Origin- , pure Coinbase- , pure Timestamp- , pure BlockNumber- , pure PrevRandao- , pure GasLimit- , pure ChainId- , pure BaseFee- , fmap CallValue genNat- , fmap Caller genNat- , fmap Address genNat- --, liftM2 SelfBalance arbitrary arbitrary- --, liftM2 Gas arbitrary arbitrary- , fmap Lit arbitrary- , fmap Var genName- ]- )- ]-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- , liftM3 SLoad subWord 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)--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- , 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- [ pure AbstractStore- , pure EmptyStore- --, fmap ConcreteStore arbitrary- ]-genStorage sz = liftM4 SStore subWord 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))+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))+checkPost post c sig = do+ (e, res) <- withSolvers Z3 1 Nothing $ \s ->+ verifyContract s c sig [] defaultVeriOpts Nothing post+ let cexs = snd <$> mapMaybe getCex res+ case cexs of+ [] -> pure $ Right e+ cs -> pure $ Left cs++successGen :: [Prop] -> Expr End+successGen props = Success props mempty (ConcreteBuf "") mempty