diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/hevm-cli/hevm-cli.hs b/hevm-cli/hevm-cli.hs
--- a/hevm-cli/hevm-cli.hs
+++ b/hevm-cli/hevm-cli.hs
@@ -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
diff --git a/hevm.cabal b/hevm.cabal
--- a/hevm.cabal
+++ b/hevm.cabal
@@ -2,7 +2,7 @@
 name:
   hevm
 version:
-  0.51.0
+  0.51.1
 synopsis:
   Ethereum virtual machine evaluator
 description:
diff --git a/src/EVM.hs b/src/EVM.hs
--- a/src/EVM.hs
+++ b/src/EVM.hs
@@ -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
diff --git a/src/EVM/ABI.hs b/src/EVM/ABI.hs
--- a/src/EVM/ABI.hs
+++ b/src/EVM/ABI.hs
@@ -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
diff --git a/src/EVM/Assembler.hs b/src/EVM/Assembler.hs
--- a/src/EVM/Assembler.hs
+++ b/src/EVM/Assembler.hs
@@ -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]
diff --git a/src/EVM/FeeSchedule.hs b/src/EVM/FeeSchedule.hs
--- a/src/EVM/FeeSchedule.hs
+++ b/src/EVM/FeeSchedule.hs
@@ -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
diff --git a/src/EVM/Format.hs b/src/EVM/Format.hs
--- a/src/EVM/Format.hs
+++ b/src/EVM/Format.hs
@@ -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"
diff --git a/src/EVM/Op.hs b/src/EVM/Op.hs
--- a/src/EVM/Op.hs
+++ b/src/EVM/Op.hs
@@ -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
diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs
--- a/src/EVM/SymExec.hs
+++ b/src/EVM/SymExec.hs
@@ -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
diff --git a/src/EVM/Transaction.hs b/src/EVM/Transaction.hs
--- a/src/EVM/Transaction.hs
+++ b/src/EVM/Transaction.hs
@@ -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
diff --git a/src/EVM/Types.hs b/src/EVM/Types.hs
--- a/src/EVM/Types.hs
+++ b/src/EVM/Types.hs
@@ -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)
diff --git a/test/EVM/Test/BlockchainTests.hs b/test/EVM/Test/BlockchainTests.hs
--- a/test/EVM/Test/BlockchainTests.hs
+++ b/test/EVM/Test/BlockchainTests.hs
@@ -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
