Skip to content

Commit c463d61

Browse files
Merge remote-tracking branch 'origin' into fixed-sym-exec
2 parents 9fbe922 + 931765b commit c463d61

File tree

7 files changed

+73
-13
lines changed

7 files changed

+73
-13
lines changed

lib/Echidna/Campaign.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import EVM.Types hiding (Env, Frame(state), Gas)
3636
import EVM.Solidity (SolcContract(..), Method(..))
3737

3838
import Echidna.ABI
39+
import Echidna.Events (extractEventValues)
3940
import Echidna.Exec
4041
import Echidna.Mutator.Corpus
4142
import Echidna.Shrink (shrinkTest)
@@ -490,11 +491,13 @@ callseq vm txSeq = do
490491
diffs = Map.fromList [(AbiAddressType, Set.fromList $ AbiAddress . forceAddr <$> newAddrs)]
491492
-- Now we try to parse the return values as solidity constants, and add them to 'GenDict'
492493
resultMap = returnValues results workerState.genDict.rTypes
494+
-- compute the new events to be stored
495+
eventDiffs = extractEventValues env.dapp vm vm'
493496
-- union the return results with the new addresses
494-
additions = Map.unionWith Set.union diffs resultMap
497+
additions = Map.unionsWith Set.union [resultMap, eventDiffs, diffs]
495498
-- append to the constants dictionary
496499
updatedDict = workerState.genDict
497-
{ constants = Map.unionWith Set.union additions workerState.genDict.constants
500+
{ constants = Map.unionWith Set.union workerState.genDict.constants additions
498501
, dictValues = Set.union (mkDictValues $ Set.unions $ Map.elems additions)
499502
workerState.genDict.dictValues
500503
}

lib/Echidna/Events.hs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33

44
module Echidna.Events where
55

6+
import Data.Binary.Get
67
import Data.ByteString (ByteString)
78
import Data.ByteString qualified as BS
89
import Data.ByteString.Lazy (fromStrict)
10+
import Data.List (foldl')
911
import Data.Map (Map)
1012
import Data.Map qualified as Map
1113
import Data.Maybe (fromJust, catMaybes, maybeToList)
14+
import Data.Set (Set)
15+
import Data.Set qualified as Set
1216
import Data.Text (pack, Text)
1317
import Data.Tree (flatten)
1418
import Data.Tree.Zipper (fromForest, TreePos, Empty)
1519
import Data.Vector (fromList)
1620

1721
import EVM (traceForest)
18-
import EVM.ABI (Event(..), Indexed(..), decodeAbiValue, AbiType(..), AbiValue(..))
22+
import EVM.ABI (Event(..), Indexed(..), decodeAbiValue, getAbi, AbiType(..), AbiValue(..))
1923
import EVM.Dapp (DappContext(..), DappInfo(..))
2024
import EVM.Expr (maybeLitWordSimp)
2125
import EVM.Format (showValues, showError, contractNamePart)
@@ -72,6 +76,35 @@ extractEvents decodeErrors dappInfo vm =
7276
Just $ humanPanic $ decodePanic d
7377
_ -> Nothing
7478

79+
-- | Extract all non‑indexed event values emitted between two VM states.
80+
extractEventValues :: DappInfo -> VM Concrete s -> VM Concrete s -> Map AbiType (Set AbiValue)
81+
extractEventValues dappInfo vm vm' =
82+
let
83+
oldLogs = vm.logs
84+
newLogs = vm'.logs
85+
86+
-- only the newly emitted entries
87+
delta = filter (`notElem` oldLogs) newLogs
88+
89+
-- decode each Expr Log
90+
goLog = \case
91+
LogEntry _addr (ConcreteBuf bs) (sigHash : _) ->
92+
case Map.lookup (forceWord sigHash) dappInfo.eventMap of
93+
Just (Event _ _ params) ->
94+
[ (ty, val)
95+
| (_, ty, NotIndexed) <- params
96+
, Right (_, _, val) <- [ runGetOrFail (getAbi ty) (fromStrict bs) ]
97+
]
98+
Nothing -> []
99+
_ -> []
100+
101+
pairs = concatMap goLog delta
102+
in
103+
foldl'
104+
(\m (ty,v) -> Map.insertWith Set.union ty (Set.singleton v) m)
105+
Map.empty
106+
pairs
107+
75108
maybeContractNameFromCodeHash :: DappInfo -> W256 -> Maybe Text
76109
maybeContractNameFromCodeHash info codeHash = contractToName <$> maybeContract
77110
where maybeContract = snd <$> Map.lookup codeHash info.solcByHash

lib/Echidna/Output/Source.hs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ ppCoveredCode fileType sc cs s | null s = "Coverage map is empty"
8585
topHeader = case fileType of
8686
Lcov -> "TN:\n"
8787
Html -> "<style> code { white-space: pre-wrap; display: block; background-color: #eee; }" <>
88-
".executed { background-color: #afa; }" <>
89-
".reverted { background-color: #ffa; }" <>
90-
".unexecuted { background-color: #faa; }" <>
91-
".neutral { background-color: #eee; }" <>
88+
".e { background-color: #afa; }" <> -- executed
89+
".r { background-color: #ffa; }" <> -- reverted
90+
".u { background-color: #faa; }" <> -- unexecuted
91+
".n { background-color: #eee; }" <> -- neutral
9292
"</style>"
9393
Txt -> ""
9494
-- ^ Text to add to top of the file
@@ -123,7 +123,7 @@ markLines fileType codeLines runtimeLines resultMap =
123123
"</span>"
124124
_ -> line
125125
where
126-
cssClass = if n `elem` runtimeLines then getCSSClass markers else "neutral"
126+
cssClass = if n `elem` runtimeLines then getCSSClass markers else "n" -- fallback to 'neutral' class.
127127
result = case fileType of
128128
Lcov -> pack $ printf "DA:%d,%d" n (length results)
129129
_ -> pack $ printf " %*d | %-4s| %s" lineNrSpan n markers (wrapLine codeLine)
@@ -134,9 +134,9 @@ markLines fileType codeLines runtimeLines resultMap =
134134
getCSSClass :: String -> Text
135135
getCSSClass markers =
136136
case markers of
137-
[] -> "unexecuted"
138-
_ | '*' `elem` markers -> "executed"
139-
_ -> "reverted"
137+
[] -> "u" -- unexecuted
138+
_ | '*' `elem` markers -> "e" -- executed
139+
_ -> "r" -- reverted
140140

141141
-- | Select the proper marker, according to the result of the transaction
142142
getMarker :: TxResult -> Char

lib/Echidna/Transaction.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ setupTx tx@Tx{call} = fromEVM $ do
177177
, callvalue = Lit tx.value
178178
}
179179
, block = advanceBlock vm.block tx.delay
180-
, tx = vm.tx { gasprice = tx.gasprice, origin = LitAddr tx.src }
180+
, tx = vm.tx { gasprice = tx.gasprice, origin = LitAddr tx.src, subState = subState vm }
181181
}
182182
when isCreate $ do
183183
#env % #contracts % at (LitAddr tx.dst) .=
@@ -207,6 +207,12 @@ setupTx tx@Tx{call} = fromEVM $ do
207207
SolCreate bc -> bc
208208
SolCall cd -> encode cd
209209
SolCalldata cd -> cd
210+
subState vm = let
211+
initialAccessedAddrs = Set.fromList $
212+
[LitAddr tx.src, LitAddr tx.dst, vm.block.coinbase]
213+
++ fmap LitAddr [1..10] -- precompile addresses
214+
touched = if isCreate then [LitAddr tx.src] else [LitAddr tx.src, LitAddr tx.dst]
215+
in SubState mempty touched initialAccessedAddrs mempty mempty mempty
210216

211217
advanceBlock :: Block -> (W256, W256) -> Block
212218
advanceBlock blk (t,b) =

src/test/Tests/Values.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ valuesTests = testGroup "Value extraction tests"
4646
, testContract' "values/contract.sol" (Just "Test") Nothing (Just "values/contract.yaml") False FuzzWorker
4747
[ ("verify_first passed", solved "verify_first")
4848
, ("verify_later passed", solved "verify_later") ]
49-
49+
, testContract' "values/events.sol" Nothing Nothing (Just "values/events.yaml") False FuzzWorker
50+
[ ("check passed", solved "check") ]
5051
]

tests/solidity/values/events.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
contract EventAssert {
2+
bytes32 private hash;
3+
event Secret(bytes32);
4+
function reset(uint seed) public {
5+
require(hash == 0);
6+
bytes32 secret = keccak256(abi.encodePacked(seed));
7+
emit Secret(secret);
8+
hash = keccak256(abi.encodePacked(secret));
9+
}
10+
function check(bytes32 seed) public {
11+
if (keccak256(abi.encodePacked(seed)) == hash) {
12+
assert(false);
13+
}
14+
}
15+
}

tests/solidity/values/events.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
testLimit: 5000
2+
testMode: assertion

0 commit comments

Comments
 (0)