Skip to content

Commit

Permalink
Update to bubble hevm errors back up
Browse files Browse the repository at this point in the history
  • Loading branch information
msooseth committed Nov 8, 2024
1 parent fed5b2a commit 2cee6f1
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 41 deletions.
30 changes: 16 additions & 14 deletions src/EVM.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1696,7 +1696,7 @@ cheat gas (inOffset, inSize) (outOffset, outSize) xs = do
Nothing -> partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) "symbolic cheatcode selector" (wrap [abi])
Just (unsafeInto -> abi') ->
case Map.lookup abi' cheatActions of
Nothing -> vmError (BadCheatCode "cannot understand cheatcode, maybe cheatcode not supported?" abi')
Nothing -> vmError (BadCheatCode "Cannot understand cheatcode. " abi')
Just action -> action input

type CheatAction t s = Expr Buf -> EVM t s ()
Expand Down Expand Up @@ -1931,12 +1931,14 @@ cheatActions = Map.fromList
, action "assertEq(int256,int256)" $ assertEq (AbiIntType 256)
, action "assertEq(address,address)" $ assertEq AbiAddressType
, action "assertEq(bytes32,bytes32)" $ assertEq (AbiBytesType 32)
, action "assertEq(string,string)" $ assertEq (AbiStringType)
--
, action "assertNotEq(bool,bool)" $ assertNotEq AbiBoolType
, action "assertNotEq(uint256,uint256)" $ assertNotEq (AbiUIntType 256)
, action "assertNotEq(int256,int256)" $ assertNotEq (AbiIntType 256)
, action "assertNotEq(address,address)" $ assertNotEq AbiAddressType
, action "assertNotEq(bytes32,bytes32)" $ assertNotEq (AbiBytesType 32)
, action "assertNotEq(string,string)" $ assertNotEq (AbiStringType)
--
, action "assertLt(uint256,uint256)" $ assertLt (AbiUIntType 256)
, action "assertLt(int256,int256)" $ assertLt (AbiIntType 256)
Expand Down Expand Up @@ -2326,14 +2328,10 @@ finishFrame how = do
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
insertTrace $ case how of
FrameReturned output -> ReturnTrace output nextFrame.context
FrameReverted e -> ErrorTrace (Revert e)
FrameErrored e -> ErrorTrace e
-- Pop to the previous level of the debug trace stack.
popTrace

Expand Down Expand Up @@ -2384,8 +2382,10 @@ finishFrame how = do
FrameErrored e -> do
revertContracts
revertSubstate
assign (#state % #returndata) (ConcreteBuf (BS8.pack $ show e))
push 0
if (isInterpretFailure e) then finishFrame (FrameErrored e)
else do
assign (#state % #returndata) mempty
push 0
-- Or were we creating?
CreationContext _ _ reversion subState' -> do
creator <- use (#state % #contract)
Expand Down Expand Up @@ -2428,11 +2428,13 @@ finishFrame how = do
push 0

-- Case 6: Error during a creation?
FrameErrored _ -> do
FrameErrored e -> do
revertContracts
revertSubstate
assign (#state % #returndata) mempty
push 0
if (isInterpretFailure e) then finishFrame (FrameErrored e)
else do
assign (#state % #returndata) mempty
push 0


-- * Memory helpers
Expand Down
41 changes: 41 additions & 0 deletions src/EVM/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import EVM.FeeSchedule (FeeSchedule (..))
import Text.Regex.TDFA qualified as Regex
import Text.Read qualified
import Witch
import Test.QuickCheck (Function(function))


-- Template Haskell --------------------------------------------------------------------------
Expand Down Expand Up @@ -550,6 +551,46 @@ data EvmError
| NonexistentFork Int
deriving (Show, Eq, Ord)

isInterpretFailure :: EvmError -> Bool
isInterpretFailure = \case
(BadCheatCode {}) -> True
(NonexistentFork {}) -> True
PrecompileFailure -> True
StateChangeWhileStatic -> True
UnrecognizedOpcode _ -> True
_ -> False

isInterpretFailEnd :: Expr a -> Bool
isInterpretFailEnd = \case
(Failure _ _ k) -> isInterpretFailure k
_ -> False

evmErrToString :: EvmError -> String
evmErrToString = \case
-- NOTE: error text made to closely match go-ethereum's errors.go file
OutOfGas {} -> "Out of gas"
-- TODO "contract creation code storage out of gas" not handled
CallDepthLimitReached -> "Max call depth exceeded"
BalanceTooLow {} -> "Insufficient balance for transfer"
-- TODO "contract address collision" not handled
Revert {} -> "Execution reverted"
-- TODO "max initcode size exceeded" not handled
MaxCodeSizeExceeded {} -> "Max code size exceeded"
BadJumpDestination -> "Invalid jump destination"
StateChangeWhileStatic -> "Attempting to modify state while in static context"
ReturnDataOutOfBounds -> "Return data out of bounds"
IllegalOverflow -> "Gas uint64 overflow"
UnrecognizedOpcode op -> "Invalid opcode: 0x" <> showHex op ""
NonceOverflow -> "Nonce uint64 overflow"
StackUnderrun -> "Stack underflow"
StackLimitExceeded -> "Stack limit reached"
InvalidMemoryAccess -> "Write protection"
(BadCheatCode err fun) -> err <> " Cheatcode function selector: " <> show fun
NonexistentFork fork -> "Nonexistent fork: " <> show fork
PrecompileFailure -> "Precompile failure"
err -> "hevm error: " <> show err


-- | Sometimes we can only partially execute a given program
data PartialExec
= UnexpectedSymbolicArg { pc :: Int, opcode :: String, msg :: String, args :: [SomeExpr] }
Expand Down
13 changes: 9 additions & 4 deletions src/EVM/UnitTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,16 @@ symRun opts@UnitTestOptions{..} vm (Sig testName types) = do
-- check postconditions against vm
(e, results) <- verify solvers (makeVeriOpts opts) (symbolify vm') (Just postcondition)
let allReverts = not . (any Expr.isSuccess) . flattenExpr $ e
let fails = filter (Expr.isFailure) $ flattenExpr e
unless (null fails) $ liftIO $ do
let interpFails = filter (isInterpretFailEnd) $ flattenExpr e

conf <- readConfig
when conf.debug $ liftIO $ forM_ (filter Expr.isFailure (flattenExpr e)) $ \case
(Failure _ _ a) -> putStrLn $ " -> debug of func: " <> Text.unpack testName <> " Failure at the end of expr: " <> show a;
_ -> internalError "cannot be, filtered for failure"
unless (null interpFails) $ liftIO $ do
putStrLn $ " \x1b[33mWARNING\x1b[0m: hevm was only able to partially explore the test " <> Text.unpack testName <> " due to: ";
forM_ (fails) $ \case
(Failure _ _ err) -> putStrLn $ " -> " <> show err
forM_ (interpFails) $ \case
(Failure _ _ f) -> putStrLn $ " -> " <> evmErrToString f
_ -> internalError "unexpected failure"
when (any isUnknown results || any isError results) $ liftIO $ do
putStrLn $ " \x1b[33mWARNING\x1b[0m: hevm was only able to partially explore the test " <> Text.unpack testName <> " due to: ";
Expand Down
21 changes: 1 addition & 20 deletions test/EVM/Test/Tracing.hs
Original file line number Diff line number Diff line change
Expand Up @@ -513,26 +513,7 @@ vmtrace vm =
}
where
readoutError :: Maybe (VMResult t s) -> Maybe String
readoutError (Just (VMFailure e)) = case e of
-- NOTE: error text made to closely match go-ethereum's errors.go file
OutOfGas {} -> Just "out of gas"
-- TODO "contract creation code storage out of gas" not handled
CallDepthLimitReached -> Just "max call depth exceeded"
BalanceTooLow {} -> Just "insufficient balance for transfer"
-- TODO "contract address collision" not handled
Revert {} -> Just "execution reverted"
-- TODO "max initcode size exceeded" not handled
MaxCodeSizeExceeded {} -> Just "max code size exceeded"
BadJumpDestination -> Just "invalid jump destination"
StateChangeWhileStatic -> Just "write protection"
ReturnDataOutOfBounds -> Just "return data out of bounds"
IllegalOverflow -> Just "gas uint64 overflow"
UnrecognizedOpcode op -> Just $ "invalid opcode: 0x" <> showHex op ""
NonceOverflow -> Just "nonce uint64 overflow"
StackUnderrun -> Just "stack underflow"
StackLimitExceeded -> Just "stack limit reached"
InvalidMemoryAccess -> Just "write protection"
err -> Just $ "HEVM error: " <> show err
readoutError (Just (VMFailure e)) = Just $ evmErrToString e
readoutError _ = Nothing

vmres :: VM Concrete s -> VMTraceResult
Expand Down
6 changes: 3 additions & 3 deletions test/test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ import EVM.UnitTest (writeTrace)

testEnv :: Env
testEnv = Env { config = defaultConfig {
dumpQueries = False
, dumpExprs = False
dumpQueries = True
, dumpExprs = True
, dumpEndStates = False
, debug = False
, debug = True
, abstRefineArith = False
, abstRefineMem = False
, dumpTrace = False
Expand Down

0 comments on commit 2cee6f1

Please sign in to comment.