Skip to content

Commit 2e68e26

Browse files
merge with master
2 parents 763e705 + 1b444b6 commit 2e68e26

24 files changed

Lines changed: 278 additions & 60 deletions

.github/scripts/install-libff.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@ cd libff
1818
git checkout v0.2.1
1919
git submodule init && git submodule update
2020

21+
# Fix libff header installation with CMake >= 4.3
22+
# CMake 4.3 commit 4e7e6928cb ("install: Fix bugs around empty
23+
# directories") changed the behavior of install(DIRECTORY "" ...).
24+
# Previously, an empty string was silently expanded to the current source
25+
# directory. CMake 4.3 now treats it as a no-op that creates the
26+
# destination directory but installs nothing into it.
27+
# Replace the empty string with "./" to explicitly reference the current
28+
# source directory, restoring header installation. The trailing slash
29+
# ensures the directory *contents* are installed rather than the directory
30+
# itself.
31+
# See: https://gitlab.kitware.com/cmake/cmake/-/issues/27568
32+
if [ "$HOST_OS" = "macOS" ]; then
33+
sed -i '' 's#DIRECTORY "" DESTINATION "include/libff"#DIRECTORY "./" DESTINATION "include/libff"#' libff/CMakeLists.txt
34+
else
35+
sed -i 's#DIRECTORY "" DESTINATION "include/libff"#DIRECTORY "./" DESTINATION "include/libff"#' libff/CMakeLists.txt
36+
fi
37+
2138
ARGS=("-DCMAKE_INSTALL_PREFIX=$PREFIX" "-DWITH_PROCPS=OFF" "-DCMAKE_POLICY_VERSION_MINIMUM=3.5" "-DCMAKE_CXX_STANDARD=11")
2239
CXXFLAGS=""
2340
if [ "$HOST_OS" = "macOS" ]; then

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ jobs:
258258
SOLC_VER: ${{ matrix.solc }}
259259

260260
- name: Install Foundry
261-
uses: foundry-rs/foundry-toolchain@8789b3e21e6c11b2697f5eb56eddae542f746c10 # v1
261+
uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1
262262

263263
- name: Download testsuite
264264
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8

.github/workflows/docker.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ jobs:
3232
PLATFORM: "${{ matrix.platform }}"
3333

3434
- name: Set up Docker Buildx
35-
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
35+
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
3636
id: buildx
3737
with:
3838
install: true
3939

4040
- name: Set Docker metadata (Ubuntu & NVM variant)
4141
id: meta-ubuntu
42-
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
42+
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
4343
with:
4444
images: |
4545
ghcr.io/${{ github.repository }}/echidna
@@ -58,21 +58,21 @@ jobs:
5858
} >> "$GITHUB_OUTPUT"
5959
6060
- name: GitHub Container Registry Login
61-
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
61+
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
6262
with:
6363
registry: ghcr.io
6464
username: ${{ github.actor }}
6565
password: ${{ secrets.GITHUB_TOKEN }}
6666

6767
- name: Docker Hub Login
68-
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
68+
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
6969
if: github.repository == 'crytic/echidna'
7070
with:
7171
username: ${{ secrets.DOCKERHUB_USERNAME }}
7272
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }}
7373

7474
- name: Docker Build and Push (Ubuntu & NVM variant)
75-
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
75+
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
7676
id: build-ubuntu
7777
with:
7878
platforms: ${{ matrix.platform }}
@@ -116,25 +116,25 @@ jobs:
116116
merge-multiple: true
117117

118118
- name: GitHub Container Registry Login
119-
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
119+
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
120120
with:
121121
registry: ghcr.io
122122
username: ${{ github.actor }}
123123
password: ${{ secrets.GITHUB_TOKEN }}
124124

125125
- name: Docker Hub Login
126-
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
126+
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
127127
if: github.repository == 'crytic/echidna'
128128
with:
129129
username: ${{ secrets.DOCKERHUB_USERNAME }}
130130
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }}
131131

132132
- name: Set up Docker Buildx
133-
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
133+
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
134134

135135
- name: Set Docker metadata (Ubuntu & NVM variant)
136136
id: meta-ubuntu
137-
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
137+
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
138138
with:
139139
images: |
140140
ghcr.io/${{ github.repository }}/echidna

.github/workflows/hlint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
persist-credentials: false
2222

2323
- name: Install Nix
24-
uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31
24+
uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31
2525
with:
2626
nix_path: nixpkgs=channel:nixos-unstable
2727

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
5252

5353
- name: Configure Cachix
54-
uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17
54+
uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
5555
with:
5656
name: trailofbits
5757
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## Unreleased
22

3+
* The reason a test failed (e.g. revert, returned false) is now shown consistently in the text, JSON and UI outputs, including when no transactions are required to reproduce the failure (#1476)
4+
35
## 2.3.2
46

57
* Improved support for Foundry tests (#1499)

lib/Echidna.hs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import Data.Map.Strict qualified as Map
1212
import Data.Maybe (mapMaybe)
1313
import Data.Set qualified as Set
1414
import Data.Text qualified as T
15+
import System.Console.ANSI (hNowSupportsANSI)
1516
import System.FilePath ((</>))
16-
import System.IO (stderr, hPutStrLn)
17+
import System.IO (stderr, stdout, hPutStrLn)
1718

1819
import EVM (cheatCode)
1920
import EVM.ABI (AbiValue(AbiAddress))
@@ -132,10 +133,11 @@ mkEnv cfg buildOutput tests world slitherInfo = do
132133
testRefs <- traverse newIORef tests
133134
fetchSession <- EVM.Fetch.mkSession cfg.campaignConf.corpusDir (fromIntegral <$> cfg.rpcBlock)
134135
contractNameCache <- newIORef mempty
136+
useColor <- hNowSupportsANSI stdout
135137
-- TODO put in real path
136138
let dapp = dappInfo "/" buildOutput
137139
sourceCache = buildOutput.sources
138140
pure $ Env { cfg, dapp, sourceCache, codehashMap, fetchSession, contractNameCache
139141
, chainId, eventQueue, bus, coverageRefInit, coverageRefRuntime, corpusRef, testRefs, world
140-
, slitherInfo
142+
, slitherInfo, useColor
141143
}

lib/Echidna/Exec.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import Echidna.Types (ExecException(..), fromEVM, emptyAccount)
4040
import Echidna.Types.Config (Env(..), EConfig(..), UIConf(..), OperationMode(..), OutputFormat(Text))
4141
import Echidna.Types.Coverage (CoverageInfo)
4242
import Echidna.Types.Solidity (SolConf(..))
43-
import Echidna.Types.Tx (TxCall(..), Tx(call, dst), TxResult(..), initialTimestamp, initialBlockNumber, getResult)
43+
import Echidna.Types.Tx (TxCall(..), Tx(call, dst, delay), TxResult(..), initialTimestamp, initialBlockNumber, getResult)
4444
import Echidna.Utility (getTimestamp, timePrefix)
4545

4646
-- | Broad categories of execution failures: reversions, illegal operations, and ???.
@@ -184,6 +184,9 @@ execTxWith executeTx tx = do
184184
burnedGas <- gets (.burned)
185185
-- If a transaction reverts reset VM to state before the transaction.
186186
put vmBeforeTx
187+
-- Re-apply the time/block advancement from the transaction's delay.
188+
-- Time doesn't go backwards on revert, only EVM state does.
189+
#block %= \b -> advanceBlock b tx.delay
187190
-- Undo reset of some of the VM state.
188191
-- Otherwise we'd lose all information about the reverted transaction like
189192
-- contract address, calldata, result and traces.

lib/Echidna/Output/Foundry.hs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
module Echidna.Output.Foundry (foundryTest) where
66

77
import Data.Aeson (Value(..), object, (.=))
8-
import Data.Functor ((<&>))
9-
import Data.List (elemIndex, nub)
8+
import Data.List (elemIndex, isPrefixOf, nub)
109
import Data.Maybe (fromMaybe, mapMaybe)
1110
import Data.Text (Text, unpack)
1211
import Data.Text.Lazy (fromStrict)
@@ -33,33 +32,67 @@ foundryTest :: Maybe Text -> Addr -> EchidnaTest -> TL.Text
3332
foundryTest mContractName psender test =
3433
case test.testType of
3534
AssertionTest{} ->
36-
let testData = createTestData mContractName Nothing test
35+
let testData = createTestData mContractName Nothing Nothing test
3736
in fromStrict $ substituteValue template (toMustache testData)
3837
PropertyTest name _ ->
39-
let testData = createTestData mContractName (Just (name, psender)) test
38+
let testData = createTestData mContractName (Just (name, psender)) Nothing test
39+
in fromStrict $ substituteValue template (toMustache testData)
40+
CallTest name _ | "AssertionFailed" `isPrefixOf` unpack name ->
41+
-- Echidna detects assertion failures via events named AssertionFailed
42+
-- with any argument types (see checkAssertionEvent in Echidna.Test).
43+
-- We check all overloads defined in crytic's fuzzlib (LibLog.sol):
44+
-- AssertionFailed()
45+
-- AssertionFailed(string)
46+
-- AssertionFailed(string,string)
47+
-- AssertionFailed(string,bytes)
48+
-- AssertionFailed(string,uint256)
49+
-- AssertionFailed(string,int256)
50+
-- AssertionFailed(string,address)
51+
-- AssertionFailed(string,bool)
52+
-- AssertionFailed(string,bytes32)
53+
let eventAssert = Just $
54+
" // Check that an AssertionFailed event was emitted\n"
55+
++ " Vm.Log[] memory entries = vm.getRecordedLogs();\n"
56+
++ " bool found = false;\n"
57+
++ " for (uint i = 0; i < entries.length; i++) {\n"
58+
++ " if (entries[i].topics.length > 0 && _isAssertionFailed(entries[i].topics[0])) {\n"
59+
++ " found = true;\n"
60+
++ " break;\n"
61+
++ " }\n"
62+
++ " }\n"
63+
++ " assertTrue(found, \"Expected AssertionFailed event\");"
64+
testData = createTestData mContractName Nothing eventAssert test
4065
in fromStrict $ substituteValue template (toMustache testData)
4166
_ -> ""
4267

4368
-- | Create an Aeson Value from test data for the Mustache template.
4469
-- When a property name and psender are provided, a final assertion is added
4570
-- to call the property from psender and check it returns false.
46-
createTestData :: Maybe Text -> Maybe (Text, Addr) -> EchidnaTest -> Value
47-
createTestData mContractName mProperty test =
71+
-- When an event assertion is provided, vm.recordLogs() is added at the start
72+
-- and the event check is added at the end.
73+
createTestData :: Maybe Text -> Maybe (Text, Addr) -> Maybe String -> EchidnaTest -> Value
74+
createTestData mContractName mProperty mEventAssert test =
4875
let
4976
senders = nub $ map (.src) test.reproducer
5077
actors = zipWith actorObject senders [1..]
5178
repro = mapMaybe (foundryTx senders) test.reproducer
5279
cName = fromMaybe "YourContract" mContractName
53-
propAssertion = mProperty <&> \(name, addr) ->
54-
" vm.stopPrank();\n vm.prank(" ++ formatAddr addr ++ ");\n"
55-
++ " assertFalse(Target." ++ unpack name ++ "());"
80+
propAssertion = case mProperty of
81+
Just (name, addr) -> Just $
82+
" vm.stopPrank();\n vm.prank(" ++ formatAddr addr ++ ");\n"
83+
++ " assertFalse(Target." ++ unpack name ++ "());"
84+
Nothing -> mEventAssert
85+
preamble = case mEventAssert of
86+
Just _ -> Just (" vm.recordLogs();" :: String)
87+
Nothing -> Nothing
5688
in
5789
object
5890
[ "testName" .= ("FoundryTest" :: Text)
5991
, "contractName" .= cName
6092
, "actors" .= actors
6193
, "reproducer" .= repro
6294
, "propertyAssertion" .= propAssertion
95+
, "preamble" .= preamble
6396
]
6497

6598
-- | Create a JSON object for an actor.

lib/Echidna/Output/JSON.hs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import Echidna.Types.Config (Env(..))
2222
import Echidna.Types.Coverage (CoverageInfo, mergeCoverageMaps)
2323
import Echidna.Types.Test (EchidnaTest(..))
2424
import Echidna.Types.Test qualified as T
25-
import Echidna.Types.Tx (Tx(..), TxCall(..))
25+
import Echidna.Types.Tx (Tx(..), TxCall(..), TxResult)
2626

2727
data Campaign = Campaign
2828
{ _success :: Bool
@@ -46,6 +46,7 @@ data Test = Test
4646
, name :: Text
4747
, status :: TestStatus
4848
, _error :: Maybe String
49+
, reason :: Maybe TxResult
4950
, events :: Events
5051
, testType :: TestType
5152
, transactions :: Maybe [Transaction]
@@ -57,6 +58,7 @@ instance ToJSON Test where
5758
, "name" .= name
5859
, "status" .= status
5960
, "error" .= _error
61+
, "reason" .= reason
6062
, "events" .= events
6163
, "type" .= testType
6264
, "transactions" .= transactions
@@ -122,6 +124,7 @@ mapTest dappInfo test =
122124
, name = "name" -- TODO add a proper name here
123125
, status = status
124126
, _error = err
127+
, reason = mapReason test.state
125128
, events = maybe [] (extractEvents False dappInfo) test.vm
126129
, testType = Property
127130
, transactions = transactions
@@ -134,6 +137,11 @@ mapTest dappInfo test =
134137
mapTestState (T.Large _) txs = (Shrinking, Just $ mapTx <$> txs, Nothing)
135138
mapTestState (T.Failed e) _ = (Error, Nothing, Just $ Prelude.show e) -- TODO add (show e)
136139

140+
-- The reason a test failed is only meaningful for falsified tests.
141+
mapReason T.Solved = Just test.result
142+
mapReason (T.Large _) = Just test.result
143+
mapReason _ = Nothing
144+
137145
mapTx tx =
138146
let (function, args) = mapCall tx.call
139147
in Transaction

0 commit comments

Comments
 (0)