hevm 0.51.0 → 0.51.1
raw patch · 13 files changed
+103/−66 lines, 13 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ EVM.ABI: instance Data.Data.Data EVM.ABI.AbiType
+ EVM.FeeSchedule: [$sel:g_initcodeword:FeeSchedule] :: FeeSchedule n -> n
+ EVM.Op: OpPush0 :: GenericOp a
+ EVM.Types: MaxInitCodeSizeExceeded :: W256 -> W256 -> EvmError
+ EVM.Types: OpPush0 :: GenericOp a
- EVM: costOfCreate :: FeeSchedule Word64 -> Word64 -> W256 -> (Word64, Word64)
+ EVM: costOfCreate :: FeeSchedule Word64 -> Word64 -> W256 -> Bool -> (Word64, Word64)
- EVM: create :: (?op :: Word8) => Addr -> Contract -> Word64 -> W256 -> [Expr EWord] -> Addr -> Expr Buf -> EVM ()
+ EVM: create :: (?op :: Word8) => Addr -> Contract -> W256 -> Word64 -> W256 -> [Expr EWord] -> Addr -> Expr Buf -> EVM ()
- EVM.FeeSchedule: FeeSchedule :: n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> FeeSchedule n
+ EVM.FeeSchedule: FeeSchedule :: n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> FeeSchedule n
Files
- CHANGELOG.md +11/−0
- hevm-cli/hevm-cli.hs +10/−5
- hevm.cabal +1/−1
- src/EVM.hs +28/−29
- src/EVM/ABI.hs +2/−1
- src/EVM/Assembler.hs +1/−0
- src/EVM/FeeSchedule.hs +4/−2
- src/EVM/Format.hs +1/−0
- src/EVM/Op.hs +3/−0
- src/EVM/SymExec.hs +1/−1
- src/EVM/Transaction.hs +3/−2
- src/EVM/Types.hs +2/−0
- test/EVM/Test/BlockchainTests.hs +36/−25
CHANGELOG.md view
@@ -5,6 +5,17 @@ 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.51.1] - 2023-06-02++## Fixed++- hevm now gracefully handles missing `out` directories+- Constraints are correctly propogated to the final output expression during symbolic execution++## Changed++- HEVM is now fully compliant with the Shanghai hard fork+ ## [0.51.0] - 2023-04-27 ## Added
hevm-cli/hevm-cli.hs view
@@ -43,7 +43,8 @@ import Data.Version (showVersion) import Data.DoubleWord (Word256) import System.IO (stderr)-import System.Directory (withCurrentDirectory, getCurrentDirectory)+import System.Directory (withCurrentDirectory, getCurrentDirectory, doesDirectoryExist)+import System.FilePath ((</>)) import System.Exit (exitFailure, exitWith, ExitCode(..)) import qualified Data.ByteString as ByteString@@ -337,10 +338,14 @@ getSrcInfo cmd = do root <- getRoot cmd withCurrentDirectory root $ do- buildOutput <- readBuildOutput root (getProjectType cmd)- case buildOutput of- Left _ -> pure emptyDapp- Right o -> pure $ dappInfo root o+ 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
hevm.cabal view
@@ -2,7 +2,7 @@ name: hevm version:- 0.51.0+ 0.51.1 synopsis: Ethereum virtual machine evaluator description:
src/EVM.hs view
@@ -98,7 +98,7 @@ let txaccessList = o.txAccessList txorigin = o.origin txtoAddr = o.address- initialAccessedAddrs = fromList $ [txorigin, txtoAddr] ++ [1..9] ++ (Map.keys txaccessList)+ 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@@ -238,6 +238,12 @@ case getOp(?op) of + OpPush0 -> do+ limitStack 1 $+ burn g_base $ do+ next+ pushSym (Lit 0)+ OpPush n' -> do let n = fromIntegral n' !xs = case vm.state.code of@@ -621,9 +627,9 @@ refund (g_sreset + g_access_list_storage_key) else do when (original /= 0) $- if new' == 0- then refund (g_sreset + g_access_list_storage_key)- else unRefund (g_sreset + g_access_list_storage_key)+ 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)@@ -695,14 +701,11 @@ availableGas <- use (#state % #gas) let newAddr = createAddress self this.nonce- (cost, gas') = costOfCreate fees availableGas 0+ (cost, gas') = costOfCreate fees availableGas xSize False _ <- accessAccountForGas newAddr- burn (cost - gas') $ do- -- unfortunately we have to apply some (pretty hacky)- -- heuristics here to parse the unstructured buffer read- -- from memory into a code and data section+ burn cost $ do let initCode = readMemory xOffset' xSize' vm- create self this (num gas') xValue xs newAddr initCode+ create self this xSize (num gas') xValue xs newAddr initCode _ -> underrun OpCall ->@@ -794,9 +797,10 @@ \initCode -> do let newAddr = create2Address self xSalt initCode- (cost, gas') = costOfCreate fees availableGas xSize+ (cost, gas') = costOfCreate fees availableGas xSize True _ <- accessAccountForGas newAddr- burn (cost - gas') $ create self this gas' xValue xs newAddr (ConcreteBuf initCode)+ burn cost $+ create self this xSize gas' xValue xs newAddr (ConcreteBuf initCode) _ -> underrun OpStaticcall ->@@ -1254,7 +1258,6 @@ let sumRefunds = sum (snd <$> tx.substate.refunds)- blockReward = num (block.schedule.r_block) gasUsed = tx.gaslimit - gasRemaining cappedRefund = min (quot gasUsed 5) (num sumRefunds) originPay = (num $ gasRemaining + cappedRefund) * tx.gasprice@@ -1266,14 +1269,6 @@ (Map.adjust (over #balance (+ minerPay)) block.coinbase) touchAccount block.coinbase - -- pay out the block reward, recreating the miner if necessary- preuse (#env % #contracts % ix block.coinbase) >>= \case- Nothing -> modifying (#env % #contracts)- (Map.insert block.coinbase (initialContract (RuntimeCode (ConcreteRuntimeCode ""))))- Just _ -> noop- modifying (#env % #contracts)- (Map.adjust (over #balance (+ blockReward)) block.coinbase)- -- perform state trie clearing (EIP 161), of selfdestructs -- and touched accounts. addresses are cleared if they have -- a) selfdestructed, or@@ -1627,8 +1622,8 @@ create :: (?op :: Word8) => Addr -> Contract- -> Word64 -> W256 -> [Expr EWord] -> Addr -> Expr Buf -> EVM ()-create self this xGas' xValue xs newAddr initCode = do+ -> W256 -> Word64 -> W256 -> [Expr EWord] -> Addr -> Expr Buf -> EVM ()+create self this xSize xGas' xValue xs newAddr initCode = do vm0 <- get let xGas = num xGas' if this.nonce == num (maxBound :: Word64)@@ -1643,6 +1638,11 @@ 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)@@ -1662,7 +1662,6 @@ -- unfortunately we have to apply some (pretty hacky) -- heuristics here to parse the unstructured buffer read -- from memory into a code and data section- -- TODO: comment explaining whats going on here let contract' = do prefixLen <- Expr.concPrefix initCode prefix <- Expr.toList $ Expr.take (num prefixLen) initCode@@ -2207,12 +2206,12 @@ -- Gas cost of create, including hash cost if needed costOfCreate :: FeeSchedule Word64- -> Word64 -> W256 -> (Word64, Word64)-costOfCreate (FeeSchedule {..}) availableGas hashSize =- (createCost + initGas, initGas)+ -> Word64 -> W256 -> Bool -> (Word64, Word64)+costOfCreate (FeeSchedule {..}) availableGas size hashNeeded = (createCost, initGas) where- createCost = g_create + hashCost- hashCost = g_sha3word * ceilDiv (num hashSize) 32+ byteCost = if hashNeeded then g_sha3word + g_initcodeword else g_initcodeword+ createCost = g_create + codeCost+ codeCost = byteCost * (ceilDiv (num size) 32) initGas = allButOne64th (availableGas - createCost) concreteModexpGasFee :: ByteString -> Word64
src/EVM/ABI.hs view
@@ -66,6 +66,7 @@ import Data.Bits (shiftL, shiftR, (.&.)) import Data.ByteString (ByteString) import Data.Char (isHexDigit)+import Data.Data (Data) import Data.DoubleWord (Word256, Int256, signedWord) import Data.Functor (($>)) import Data.Text (Text)@@ -134,7 +135,7 @@ | AbiArrayType Int AbiType | AbiTupleType (Vector AbiType) | AbiFunctionType- deriving (Read, Eq, Ord, Generic)+ deriving (Read, Eq, Ord, Generic, Data) instance Show AbiType where show = Text.unpack . abiTypeSolidity
src/EVM/Assembler.hs view
@@ -106,4 +106,5 @@ else error $ "Internal Error: invalid argument to OpLog: " <> show n -- we just always assemble OpPush into PUSH32 OpPush wrd -> (LitByte 0x7f) : [Expr.indexWord (Lit i) wrd | i <- [0..31]]+ OpPush0 -> [LitByte 0x5f] OpUnknown o -> [LitByte o]
src/EVM/FeeSchedule.hs view
@@ -35,6 +35,7 @@ , g_logtopic :: n , g_sha3 :: n , g_sha3word :: n+ , g_initcodeword :: n , g_copy :: n , g_blockhash :: n , g_extcodehash :: n@@ -110,6 +111,7 @@ , g_logtopic = 375 , g_sha3 = 30 , g_sha3word = 6+ , g_initcodeword = 2 , g_copy = 3 , g_blockhash = 20 , g_extcodehash = 400@@ -173,8 +175,8 @@ -- <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_sload = 100+ , g_sreset = 5000 - 2100 , g_call = 2600 , g_balance = 2600 , g_extcode = 2600
src/EVM/Format.hs view
@@ -385,6 +385,7 @@ StateChangeWhileStatic -> "State change while static" CallDepthLimitReached -> "Call depth limit reached" MaxCodeSizeExceeded a b -> "Max code size exceeded: max: " <> show a <> " actual: " <> show b+ MaxInitCodeSizeExceeded a b -> "Max init code size exceeded: max: " <> show a <> " actual: " <> show b InvalidFormat -> "Invalid Format" PrecompileFailure -> "Precompile failure" ReturnDataOutOfBounds -> "Return data out of bounds"
src/EVM/Op.hs view
@@ -86,6 +86,7 @@ 0x5a -> "GAS" 0x5b -> "JUMPDEST" --+ 0x5f -> "PUSH0" 0x60 -> "PUSH1" 0x61 -> "PUSH2" 0x62 -> "PUSH3"@@ -250,6 +251,7 @@ OpDup x -> "DUP" ++ show x OpSwap x -> "SWAP" ++ show x OpLog x -> "LOG" ++ show x+ OpPush0 -> "PUSH0" OpPush x -> case x of Lit x' -> "PUSH 0x" ++ (showHex x' "") _ -> "PUSH " ++ show x@@ -332,6 +334,7 @@ 0x59 -> OpMsize 0x5a -> OpGas 0x5b -> OpJumpdest+ 0x5f -> OpPush0 0xf0 -> OpCreate 0xf1 -> OpCall 0xf2 -> OpCallcode
src/EVM/SymExec.hs view
@@ -442,7 +442,7 @@ runExpr :: Stepper.Stepper (Expr End) runExpr = do vm <- Stepper.runFully- let asserts = vm.keccakEqs+ let asserts = vm.keccakEqs <> vm.constraints pure $ case vm.result of Just (VMSuccess buf) -> Success asserts buf vm.env.storage Just (VMFailure e) -> Failure asserts e
src/EVM/Transaction.hs view
@@ -2,7 +2,7 @@ import Prelude hiding (Word) -import EVM (initialContract)+import EVM (initialContract, ceilDiv) import EVM.FeeSchedule import EVM.RLP import EVM.Types@@ -185,10 +185,11 @@ zeroBytes = BS.count 0 calldata nonZeroBytes = BS.length calldata - zeroBytes baseCost = fs.g_transaction- + (if isNothing tx.toAddr then fs.g_txcreate else 0)+ + (if isNothing tx.toAddr then fs.g_txcreate + initcodeCost else 0) + (accessListPrice fs tx.accessList ) zeroCost = fs.g_txdatazero nonZeroCost = fs.g_txdatanonzero+ initcodeCost = fs.g_initcodeword * num (ceilDiv (BS.length calldata) 32) in baseCost + zeroCost * (fromIntegral zeroBytes) + nonZeroCost * (fromIntegral nonZeroBytes) instance FromJSON AccessListEntry where
src/EVM/Types.hs view
@@ -502,6 +502,7 @@ | InvalidMemoryAccess | CallDepthLimitReached | MaxCodeSizeExceeded W256 W256+ | MaxInitCodeSizeExceeded W256 W256 | InvalidFormat | PrecompileFailure | ReturnDataOutOfBounds@@ -931,6 +932,7 @@ | OpDup !Word8 | OpSwap !Word8 | OpLog !Word8+ | OpPush0 | OpPush a | OpUnknown Word8 deriving (Show, Eq, Functor)
test/EVM/Test/BlockchainTests.hs view
@@ -45,6 +45,7 @@ data Block = Block { coinbase :: Addr , difficulty :: W256+ , mixHash :: W256 , gasLimit :: Word64 , baseFee :: W256 , number :: W256@@ -91,7 +92,7 @@ Left "No cases to check." -> pure [] -- error "no-cases ok" Left _err -> pure [] -- error err Right allTests -> pure $- (\(name, x) -> testCase' name $ runVMTest False (name, x)) <$> Map.toList allTests+ (\(name, x) -> testCase' name $ runVMTest True (name, x)) <$> Map.toList allTests where testCase' name assertion = case Map.lookup name problematicTests of@@ -104,29 +105,29 @@ commonProblematicTests :: Map String (TestTree -> TestTree) commonProblematicTests = Map.fromList- [ ("loopMul_d0g0v0_London", ignoreTestBecause "hevm is too slow")- , ("loopMul_d1g0v0_London", ignoreTestBecause "hevm is too slow")- , ("loopMul_d2g0v0_London", ignoreTestBecause "hevm is too slow")- , ("CALLBlake2f_MaxRounds_d0g0v0_London", ignoreTestBecause "very slow, bypasses timeout due time spent in FFI")+ [ ("loopMul_d0g0v0_Shanghai", ignoreTestBecause "hevm is too slow")+ , ("loopMul_d1g0v0_Shanghai", ignoreTestBecause "hevm is too slow")+ , ("loopMul_d2g0v0_Shanghai", ignoreTestBecause "hevm is too slow")+ , ("CALLBlake2f_MaxRounds_d0g0v0_Shanghai", ignoreTestBecause "very slow, bypasses timeout due time spent in FFI") ] ciProblematicTests :: Map String (TestTree -> TestTree) ciProblematicTests = Map.fromList- [ ("Return50000_d0g1v0_London", ignoreTest)- , ("Return50000_2_d0g1v0_London", ignoreTest)- , ("randomStatetest177_d0g0v0_London", ignoreTest)- , ("static_Call50000_d0g0v0_London", ignoreTest)- , ("static_Call50000_d1g0v0_London", ignoreTest)- , ("static_Call50000bytesContract50_1_d1g0v0_London", ignoreTest)- , ("static_Call50000bytesContract50_2_d1g0v0_London", ignoreTest)- , ("static_Return50000_2_d0g0v0_London", ignoreTest)- , ("loopExp_d10g0v0_London", ignoreTest)- , ("loopExp_d11g0v0_London", ignoreTest)- , ("loopExp_d12g0v0_London", ignoreTest)- , ("loopExp_d13g0v0_London", ignoreTest)- , ("loopExp_d14g0v0_London", ignoreTest)- , ("loopExp_d8g0v0_London", ignoreTest)- , ("loopExp_d9g0v0_London", ignoreTest)+ [ ("Return50000_d0g1v0_Shanghai", ignoreTest)+ , ("Return50000_2_d0g1v0_Shanghai", ignoreTest)+ , ("randomStatetest177_d0g0v0_Shanghai", ignoreTest)+ , ("static_Call50000_d0g0v0_Shanghai", ignoreTest)+ , ("static_Call50000_d1g0v0_Shanghai", ignoreTest)+ , ("static_Call50000bytesContract50_1_d1g0v0_Shanghai", ignoreTest)+ , ("static_Call50000bytesContract50_2_d1g0v0_Shanghai", ignoreTest)+ , ("static_Return50000_2_d0g0v0_Shanghai", ignoreTest)+ , ("loopExp_d10g0v0_Shanghai", ignoreTest)+ , ("loopExp_d11g0v0_Shanghai", ignoreTest)+ , ("loopExp_d12g0v0_Shanghai", ignoreTest)+ , ("loopExp_d13g0v0_Shanghai", ignoreTest)+ , ("loopExp_d14g0v0_Shanghai", ignoreTest)+ , ("loopExp_d8g0v0_Shanghai", ignoreTest)+ , ("loopExp_d9g0v0_Shanghai", ignoreTest) ] runVMTest :: Bool -> (String, Case) -> IO ()@@ -284,7 +285,8 @@ number <- wordField v' "number" baseFee <- fmap read <$> v' .:? "baseFeePerGas" timestamp <- wordField v' "timestamp"- return $ Block coinbase difficulty gasLimit (fromMaybe 0 baseFee) number timestamp txs+ mixHash <- wordField v' "mixHash"+ return $ Block coinbase difficulty mixHash gasLimit (fromMaybe 0 baseFee) number timestamp txs parseJSON invalid = JSON.typeMismatch "Block" invalid @@ -331,13 +333,16 @@ fromBlockchainCase :: BlockchainCase -> Either BlockchainError Case fromBlockchainCase (BlockchainCase blocks preState postState network) = case (blocks, network) of- ([block], "London") -> case block.txs of+ ([block], "Shanghai") -> case block.txs of [tx] -> fromBlockchainCase' block tx preState postState [] -> Left NoTxs _ -> Left TooManyTxs ([_], _) -> Left OldNetwork (_, _) -> Left TooManyBlocks +maxCodeSize :: W256+maxCodeSize = 24576+ fromBlockchainCase' :: Block -> Transaction -> Map Addr (Contract, Storage) -> Map Addr (Contract, Storage) -> Either BlockchainError Case@@ -362,8 +367,8 @@ , number = block.number , timestamp = Lit block.timestamp , coinbase = block.coinbase- , prevRandao = block.difficulty- , maxCodeSize = 24576+ , prevRandao = block.mixHash+ , maxCodeSize = maxCodeSize , blockGaslimit = block.gasLimit , gasprice = effectiveGasPrice , schedule = feeSchedule@@ -429,7 +434,13 @@ 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)- if isCreate && ((case prevCode of {RuntimeCode (ConcreteRuntimeCode b) -> not (BS.null b); _ -> True}) || (prevNonce /= 0))++ nonEmptyAccount = case prevCode of+ RuntimeCode (ConcreteRuntimeCode b) -> not (BS.null b)+ _ -> True+ badNonce = prevNonce /= 0+ initCodeSizeExceeded = BS.length tx.txdata > (num maxCodeSize * 2)+ if isCreate && (badNonce || nonEmptyAccount || initCodeSizeExceeded) then mzero else return prestate