From 48215ccc63d56ec26dd02d211c9872f0bd18dc07 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Fri, 8 Nov 2024 05:56:46 +0100 Subject: [PATCH] Update to bubble hevm errors back up --- src/EVM.hs | 30 ++++++++++++++++-------------- src/EVM/Types.hs | 40 ++++++++++++++++++++++++++++++++++++++++ src/EVM/UnitTest.hs | 13 +++++++++---- test/EVM/Test/Tracing.hs | 21 +-------------------- test/test.hs | 6 +++--- 5 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/EVM.hs b/src/EVM.hs index 5fb013ae0..f5888c609 100644 --- a/src/EVM.hs +++ b/src/EVM.hs @@ -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 () @@ -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) @@ -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 @@ -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) @@ -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 diff --git a/src/EVM/Types.hs b/src/EVM/Types.hs index d03f2ee94..3b67fc557 100644 --- a/src/EVM/Types.hs +++ b/src/EVM/Types.hs @@ -550,6 +550,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] } diff --git a/src/EVM/UnitTest.hs b/src/EVM/UnitTest.hs index f66b1400b..8b4b7afeb 100644 --- a/src/EVM/UnitTest.hs +++ b/src/EVM/UnitTest.hs @@ -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: "; diff --git a/test/EVM/Test/Tracing.hs b/test/EVM/Test/Tracing.hs index a791c4d7b..7db641265 100644 --- a/test/EVM/Test/Tracing.hs +++ b/test/EVM/Test/Tracing.hs @@ -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 diff --git a/test/test.hs b/test/test.hs index c49d46cfc..1c5cbd777 100644 --- a/test/test.hs +++ b/test/test.hs @@ -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