Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cheatcodes as per new std-forge library #567

Merged
merged 29 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a16d3c0
Making assert... into reverts, as per std-forge
msooseth Oct 24, 2024
29279b0
Fixing some issues with tests
msooseth Oct 29, 2024
2cb92a9
Temporary
msooseth Oct 30, 2024
92a7048
Making progress
msooseth Oct 31, 2024
0905738
Moving to forge-std
msooseth Oct 31, 2024
da695e3
Fixing up
msooseth Oct 31, 2024
fed5b2a
Better error reporting
msooseth Oct 31, 2024
b00abc9
Update to bubble hevm errors back up
msooseth Nov 8, 2024
0f7eb14
Temporarily ignoring test while we figure out what to do with it
msooseth Nov 25, 2024
ad1e91a
Fixing interpretFailure
msooseth Nov 25, 2024
e155fb7
Ripping out interpretFailure idea
msooseth Nov 25, 2024
c8bd6b7
More debug printing, enabling tests
msooseth Nov 25, 2024
ed08948
Adding notes
msooseth Nov 25, 2024
3797dc0
Much easier to review and more compact thanks to @d-xo
msooseth Nov 26, 2024
65a8103
One more test on mem pairs
msooseth Nov 26, 2024
a6124bb
Fixing up call signature generation
msooseth Nov 26, 2024
a6151dd
Removing warnings
msooseth Nov 26, 2024
753a8f0
Fixing up rpc tests
msooseth Nov 26, 2024
66dbc56
Updating changelog
msooseth Nov 26, 2024
a01a83e
Removing DSTest, replacing with forge-std
msooseth Nov 26, 2024
dab414a
Update changelog
msooseth Nov 26, 2024
a4c6a95
Cleanup of changelog
msooseth Nov 27, 2024
1d55496
This is not needed anymore, it's not encoded there
msooseth Nov 27, 2024
4de8f70
Removing space
msooseth Nov 27, 2024
be2a67c
Merge branch 'main' into implement_new_cheatcodes
msooseth Nov 29, 2024
88ccb41
Allowing backwards compatibility with ds-test
msooseth Dec 2, 2024
8e4dce0
Merge branch 'main' into implement_new_cheatcodes
msooseth Dec 2, 2024
0d07497
Separate option for DSTest-type assertion type
msooseth Dec 2, 2024
f7219b9
Updating changelog
msooseth Dec 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- More complete and precise array/mapping slot rewrite, along with a copySlice improvement
- Use a let expression in copySlice to decrease expression size
- The `--debug` flag now dumps the internal expressions as well
- hevm now uses the std-forge library's way of detecting failures, i.e. through
reverting with a specific error code

## Added
- More POr and PAnd rules
Expand Down
8 changes: 4 additions & 4 deletions cli/cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ data Command w

-- 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)"
, projectType :: w ::: Maybe ProjectType <?> "Is this a CombinedJSON or Foundry 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"
Expand Down Expand Up @@ -147,11 +147,11 @@ data Command w
, 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)"
, projectType :: w ::: Maybe ProjectType <?> "Is this a CombinedJSON or Foundry project (default: Foundry)"
}
| Test -- Run DSTest unit tests
| Test -- Run Foundry 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)"
, projectType :: w ::: Maybe ProjectType <?> "Is this a CombinedJSON or Foundry 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"
Expand Down
6 changes: 2 additions & 4 deletions doc/src/exec.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,11 @@ Available options:
--rpc TEXT Fetch state from a remote node
--block W256 Block state is be fetched from
--root STRING Path to project root directory (default: . )
--project-type PROJECTTYPE
Is this a Foundry or DappTools project (default:
Foundry)
--project-type PROJECTTYPE Foundry or CombinedJSON project
```

Minimum required flags: either you must provide `--code` or you must both pass
`--rpc` and `--address`.
`--rpc` and `--address`.

If the execution returns an output, it will be written
to stdout. Exit code indicates whether the execution was successful or
Expand Down
4 changes: 1 addition & 3 deletions doc/src/symbolic.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ Available options:
--rpc TEXT Fetch state from a remote node
--block W256 Block state is be fetched from
--root STRING Path to project root directory (default: . )
--project-type PROJECTTYPE
Is this a Foundry or DappTools project (default:
Foundry)
--project-type PROJECTTYPE Foundry or CombinedJSON project
--initial-storage INITIALSTORAGE
Starting state for storage: Empty, Abstract (default
Abstract)
Expand Down
4 changes: 1 addition & 3 deletions doc/src/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ Usage: hevm test [--root STRING] [--project-type PROJECTTYPE] [--rpc TEXT]
Available options:
-h,--help Show this help text
--root STRING Path to project root directory (default: . )
--project-type PROJECTTYPE
Is this a Foundry or DappTools project (default:
Foundry)
--project-type PROJECTTYPE Foundry or CombinedJSON project
--rpc TEXT Fetch state from a remote node
--number W256 Block: number
--verbose INT Append call trace: {1} failures {2} all
Expand Down
5 changes: 0 additions & 5 deletions funding.json

This file was deleted.

118 changes: 116 additions & 2 deletions src/EVM.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ 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.Char8 qualified as BS8
import Data.ByteString.Base16 qualified as BS16
import Data.ByteString.Lazy (fromStrict)
import Data.ByteString.Lazy qualified as LS
Expand Down Expand Up @@ -1903,6 +1904,48 @@ cheatActions = Map.fromList
, $(envReadMultipleCheat "envBytes32(string,string)" $ AbiBytesType 32) stringToBytes32
, $(envReadMultipleCheat "envString(string,string)" AbiStringType) stringToByteString
, $(envReadMultipleCheat "envBytes(bytes,bytes)" AbiBytesDynamicType) stringHexToByteString
, action "assertTrue(bool)" $ \sig input ->
case decodeBuf [AbiBoolType] input of
CAbi [AbiBool True] -> doStop
CAbi [AbiBool False] -> frameRevert "assertion failed"
SAbi [eword] -> case (Expr.simplify (Expr.iszero eword)) of
Lit 1 -> frameRevert "assertion failed"
Lit 0 -> doStop
ew -> branch ew $ \case
True -> frameRevert "assertion failed"
False -> doStop
k -> vmError $ BadCheatCode ("assertTrue(bool) parameter decoding failed: " <> show k) sig
, action "assertFalse(bool)" $ \sig input ->
case decodeBuf [AbiBoolType] input of
CAbi [AbiBool False] -> doStop
CAbi [AbiBool True] -> frameRevert "assertion failed"
SAbi [eword] -> case (Expr.simplify (Expr.iszero eword)) of
Lit 0 -> frameRevert "assertion failed"
Lit 1 -> doStop
ew -> branch ew $ \case
False -> frameRevert "assertion failed"
True -> doStop
k -> vmError $ BadCheatCode ("assertFalse(bool) parameter decoding failed: " <> show k) sig
, action "assertEq(bool,bool)" $ assertEq AbiBoolType
, action "assertEq(uint256,uint256)" $ assertEq (AbiUIntType 256)
, action "assertEq(int256,int256)" $ assertEq (AbiIntType 256)
, action "assertEq(address,address)" $ assertEq AbiAddressType
, action "assertEq(bytes32,bytes32)" $ assertEq (AbiBytesType 32)
--
, 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 "assertLt(uint256,uint256)" $ assertLt (AbiUIntType 256)
, action "assertLt(int256,int256)" $ assertLt (AbiIntType 256)
, action "assertLe(uint256,uint256)" $ assertLe (AbiUIntType 256)
, action "assertLe(int256,int256)" $ assertLe (AbiIntType 256)
, action "assertGt(uint256,uint256)" $ assertGt (AbiUIntType 256)
, action "assertGt(int256,int256)" $ assertGt (AbiIntType 256)
, action "assertGe(uint256,uint256)" $ assertGe (AbiUIntType 256)
, action "assertGe(int256,int256)" $ assertGe (AbiIntType 256)
]
where
action s f = (abiKeccak s, f (abiKeccak s))
Expand Down Expand Up @@ -1942,6 +1985,77 @@ cheatActions = Map.fromList
stringToByteString = Right . Char8.pack
stringHexToByteString :: String -> Either ByteString ByteString
stringHexToByteString s = either (const $ Left "invalid bytes value") Right $ BS16.decodeBase16Untyped . Char8.pack . strip0x $ s
paramDecodeErr abitype name abivals = name <> "(" <> (show abitype) <> "," <> (show abitype) <>
") parameter decoding failed. Error: " <> show abivals
revertErr a b comp = frameRevert $ "assertion failed: " <>
BS8.pack (show a) <> " " <> comp <> " " <> BS8.pack (show b)
assertEq abitype sig input = do
msooseth marked this conversation as resolved.
Show resolved Hide resolved
case decodeBuf [abitype, abitype] input of
CAbi [a, b] | a == b -> doStop
CAbi [a, b] -> revertErr a b "!="
SAbi [ew1, ew2] -> case (Expr.simplify (Expr.eq ew1 ew2)) of
Lit 0 -> revertErr ew1 ew2 "!="
Lit _ -> doStop
ew -> branch ew $ \case
False ->revertErr ew1 ew2 "!="
True -> doStop
abivals -> vmError (BadCheatCode (paramDecodeErr abitype "assertEq" abivals) sig)
assertNotEq abitype sig input = do
case decodeBuf [abitype, abitype] input of
CAbi [a, b] | a /= b -> doStop
CAbi [a, b] -> revertErr a b "=="
SAbi [ew1, ew2] -> case (Expr.simplify (Expr.eq ew1 ew2)) of
Lit 1 -> revertErr ew1 ew2 "=="
Lit _ -> doStop
ew -> branch ew $ \case
True ->revertErr ew1 ew2 "=="
False -> doStop
abivals -> vmError (BadCheatCode (paramDecodeErr abitype "assertNotEq" abivals) sig)
assertLt abitype sig input = do
case decodeBuf [abitype, abitype] input of
CAbi [a, b] | a < b -> doStop
CAbi [a, b] -> revertErr a b ">="
SAbi [ew1, ew2] -> case (Expr.simplify (Expr.lt ew1 ew2)) of
Lit 0 -> revertErr ew1 ew2 ">="
Lit _ -> doStop
ew -> branch ew $ \case
False ->revertErr ew1 ew2 ">="
True -> doStop
abivals -> vmError (BadCheatCode (paramDecodeErr abitype "assertLt" abivals) sig)
assertGt abitype sig input =
case decodeBuf [abitype, abitype] input of
CAbi [a, b] | a > b -> doStop
CAbi [a, b] -> revertErr a b "<="
SAbi [ew1, ew2] -> case (Expr.simplify (Expr.gt ew1 ew2)) of
Lit 0 -> revertErr ew1 ew2 "<="
Lit _ -> doStop
ew -> branch ew $ \case
False ->revertErr ew1 ew2 "<="
True -> doStop
abivals -> vmError (BadCheatCode (paramDecodeErr abitype "assertGt" abivals) sig)
assertLe abitype sig input =
case decodeBuf [abitype, abitype] input of
CAbi [a, b] | a <= b -> doStop
CAbi [a, b] -> revertErr a b ">"
SAbi [ew1, ew2] -> case (Expr.simplify (Expr.leq ew1 ew2)) of
Lit 0 -> revertErr ew1 ew2 ">"
Lit _ -> doStop
ew -> branch ew $ \case
False ->revertErr ew1 ew2 ">"
True -> doStop
abivals -> vmError (BadCheatCode (paramDecodeErr abitype "assertLe" abivals) sig)
assertGe abitype sig input =
case decodeBuf [abitype, abitype] input of
CAbi [a, b] | a >= b -> doStop
CAbi [a, b] -> revertErr a b "<"
SAbi [ew1, ew2] -> case (Expr.simplify (Expr.geq ew1 ew2)) of
Lit 0 -> revertErr ew1 ew2 "<"
Lit 1 -> doStop
ew -> branch ew $ \case
False ->revertErr ew1 ew2 "<"
True -> doStop
abivals -> vmError (BadCheatCode (paramDecodeErr abitype "assertGe" abivals) sig)


-- * General call implementation ("delegateCall")
-- note that the continuation is ignored in the precompile case
Expand Down Expand Up @@ -2267,10 +2381,10 @@ finishFrame how = do
push 0

-- Case 3: Error during a call?
FrameErrored _ -> do
FrameErrored e -> do
revertContracts
revertSubstate
assign (#state % #returndata) mempty
assign (#state % #returndata) (ConcreteBuf (BS8.pack $ show e))
msooseth marked this conversation as resolved.
Show resolved Hide resolved
push 0
-- Or were we creating?
CreationContext _ _ reversion subState' -> do
Expand Down
1 change: 0 additions & 1 deletion src/EVM/ABI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,6 @@ decodeBuf tps buf =
else case runGetOrFail (getAbiSeq (length tps) tps) (BSLazy.fromStrict asBS) of
Right ("", _, args) -> CAbi (toList args)
_ -> NoVals

where
isDynamic t = abiKind t == Dynamic

Expand Down
10 changes: 1 addition & 9 deletions src/EVM/Solidity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ instance Monoid BuildOutput where
mempty = BuildOutput mempty mempty

-- | The various project types understood by hevm
data ProjectType = DappTools | CombinedJSON | Foundry | FoundryStdLib
data ProjectType = CombinedJSON | Foundry
deriving (Eq, Show, Read, ParseField)

data SourceCache = SourceCache
Expand Down Expand Up @@ -285,13 +285,6 @@ makeSrcMaps = (\case (_, Fe, _) -> Nothing; x -> Just (done x))

-- | Reads all solc output json files found under the provided filepath and returns them merged into a BuildOutput
readBuildOutput :: App m => FilePath -> ProjectType -> m (Either String BuildOutput)
readBuildOutput root DappTools = do
let outDir = root </> "out"
jsons <- liftIO $ findJsonFiles outDir
case jsons of
[x] -> readSolc DappTools root (outDir </> x)
[] -> pure . Left $ "no json files found in: " <> outDir
_ -> pure . Left $ "multiple json files found in: " <> outDir
readBuildOutput root CombinedJSON = do
let outDir = root </> "out"
jsons <- liftIO $ findJsonFiles outDir
Expand Down Expand Up @@ -413,7 +406,6 @@ force :: String -> Maybe a -> a
force s = fromMaybe (internalError s)

readJSON :: ProjectType -> Text -> Text -> Maybe (Contracts, Asts, Sources)
readJSON DappTools _ json = readStdJSON json
readJSON CombinedJSON _ json = readCombinedJSON json
readJSON _ contractName json = readFoundryJSON contractName json

Expand Down
37 changes: 19 additions & 18 deletions src/EVM/UnitTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import EVM.Transaction (initTx)
import EVM.Stepper (Stepper)
import EVM.Stepper qualified as Stepper

import Control.Monad (void, when, forM, forM_)
import Control.Monad (void, when, forM, forM_, unless)
import Control.Monad.ST (RealWorld, ST, stToIO)
import Control.Monad.State.Strict (execState, get, put, liftIO)
import Optics.Core
import Optics.State
import Optics.State.Operators
import Data.Binary.Get (runGet)
import Data.ByteString (ByteString)
import Data.ByteString.Char8 qualified as BS
import Data.ByteString.Lazy qualified as BSLazy
import Data.Decimal (DecimalRaw(..))
import Data.Foldable (toList)
Expand Down Expand Up @@ -203,16 +204,18 @@ symRun opts@UnitTestOptions{..} vm (Sig testName types) = do
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
Success _ _ _ store -> failed store
_ -> PBool True
False -> \(_, post) -> case post of
Success _ _ _ store -> PNeg (failed store)
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"
Success _ _ _ store -> PNeg (failed store)
Failure _ _ (Revert msg) -> case msg of
ConcreteBuf b ->
if (BS.isPrefixOf (selector "Error(string)") b) || b == panicMsg 0x01 then PBool False
else PBool True
b -> b ./= ConcreteBuf (panicMsg 0x01)
Failure _ _ _ -> PBool True
Partial _ _ _ -> PBool True
_ -> internalError "Invalid leaf node"

vm' <- Stepper.interpret (Fetch.oracle solvers rpcInfo) vm $
Stepper.evm $ do
Expand All @@ -224,6 +227,12 @@ 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
putStrLn $ " \x1b[33mWARNING\x1b[0m: hevm was only able to partially explore the test " <> Text.unpack testName <> " due to: ";
msooseth marked this conversation as resolved.
Show resolved Hide resolved
forM_ (fails) $ \case
(Failure _ _ err) -> putStrLn $ " -> " <> show err
_ -> 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: ";
forM_ (groupIssues (filter isError results)) $ \(num, str) -> putStrLn $ " " <> show num <> "x -> " <> str
Expand Down Expand Up @@ -287,14 +296,6 @@ execSymTest UnitTestOptions{ .. } method cd = do
-- Try running the test method
runExpr

checkSymFailures :: VMOps t => UnitTestOptions RealWorld -> Stepper t RealWorld (VM t RealWorld)
checkSymFailures UnitTestOptions { .. } = do
-- Ask whether any assertions failed
Stepper.evm $ do
popTrace
abiCall testParams (Left ("failed()", emptyAbi))
Stepper.runFully

Comment on lines -290 to -297
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used anywhere, so I'm removing it

indentLines :: Int -> Text -> Text
indentLines n s =
let p = Text.replicate n " "
Expand Down
16 changes: 4 additions & 12 deletions test/EVM/Test/Utils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,16 @@ callProcessCwd cmd args cwd = do
exit_code <- withCreateProcess (proc cmd args) { cwd = Just cwd, delegate_ctlc = True } $ \_ _ _ p ->
waitForProcess p
case exit_code of
ExitSuccess -> return ()
ExitSuccess -> pure ()
ExitFailure r -> processFailedException "callProcess" cmd args r

compile :: App m => ProjectType -> FilePath -> FilePath -> m (Either String BuildOutput)
compile DappTools root src = do
json <- liftIO $ compileWithDSTest src
liftIO $ createDirectory (root </> "out")
liftIO $ T.writeFile (root </> "out" </> "dapp.sol.json") json
readBuildOutput root DappTools
compile CombinedJSON _root _src = error "unsupported"
compile foundryType root src = do
compile CombinedJSON _root _src = internalError "unsupported compile type: CombinedJSON"
compile Foundry root src = do
liftIO $ createDirectory (root </> "src")
liftIO $ writeFile (root </> "src" </> "unit-tests.t.sol") =<< readFile =<< Paths.getDataFileName src
liftIO $ initLib (root </> "lib" </> "ds-test") ("test" </> "contracts" </> "lib" </> "test.sol") "test.sol"
liftIO $ initLib (root </> "lib" </> "tokens") ("test" </> "contracts" </> "lib" </> "erc20.sol") "erc20.sol"
case foundryType of
FoundryStdLib -> liftIO $ initStdForgeDir (root </> "lib" </> "forge-std")
Foundry -> pure ()
liftIO $ initStdForgeDir (root </> "lib" </> "forge-std")
(res,out,err) <- liftIO $ readProcessWithExitCode "forge" ["build", "--ast", "--root", root] ""
case res of
ExitFailure _ -> pure . Left $ "compilation failed: " <> "exit code: " <> show res <> "\n\nstdout:\n" <> out <> "\n\nstderr:\n" <> err
Expand Down
4 changes: 2 additions & 2 deletions test/contracts/fail/check-prefix.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "ds-test/test.sol";
import "forge-std/Test.sol";

contract SolidityTest is DSTest {
contract SolidityTest is Test {

function setUp() public {
}
Expand Down
4 changes: 2 additions & 2 deletions test/contracts/fail/dsProveFail.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "ds-test/test.sol";
import "forge-std/Test.sol";
import "tokens/erc20.sol";

contract SolidityTest is DSTest {
contract SolidityTest is Test {
ERC20 token;

function setUp() public {
Expand Down
4 changes: 2 additions & 2 deletions test/contracts/fail/trivial.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {DSTest} from "ds-test/test.sol";
import {Test} from "forge-std/Test.sol";

// should run and pass
contract Trivial is DSTest {
contract Trivial is Test {
function prove_false() public {
assertTrue(false);
}
Expand Down
Loading
Loading