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

GenDict without lens #946

Merged
merged 1 commit into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 19 additions & 21 deletions lib/Echidna/ABI.hs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE TemplateHaskell #-}

module Echidna.ABI where

import Control.Lens
import Control.Monad (join, liftM2, liftM3, foldM, replicateM)
import Control.Monad.Random.Strict (MonadRandom, getRandom, getRandoms, getRandomR)
import Control.Monad.Random.Strict qualified as R
Expand Down Expand Up @@ -101,27 +99,27 @@ hashSig :: Text -> FunctionHash
hashSig = abiKeccak . TE.encodeUtf8

-- | Configuration necessary for generating new 'SolCalls'. Don't construct this by hand! Use 'mkConf'.
data GenDict = GenDict { _pSynthA :: Float
-- ^ Fraction of time to use dictionary vs. synthesize
, _constants :: HashMap AbiType (Set AbiValue)
-- ^ Constants to use, sorted by type
, _wholeCalls :: HashMap SolSignature (Set SolCall)
-- ^ Whole calls to use, sorted by type
, _defSeed :: Int
-- ^ Default seed to use if one is not provided in EConfig
, _rTypes :: Text -> Maybe AbiType
-- ^ Return types of any methods we scrape return values from
, _dictValues :: Set W256
-- ^ A set of int/uint constants for better performance
}

makeLenses 'GenDict
data GenDict = GenDict
{ pSynthA :: Float
-- ^ Fraction of time to use dictionary vs. synthesize
, constants :: HashMap AbiType (Set AbiValue)
-- ^ Constants to use, sorted by type
, wholeCalls :: HashMap SolSignature (Set SolCall)
-- ^ Whole calls to use, sorted by type
, defSeed :: Int
-- ^ Default seed to use if one is not provided in EConfig
, rTypes :: Text -> Maybe AbiType
-- ^ Return types of any methods we scrape return values from
, dictValues :: Set W256
-- ^ A set of int/uint constants for better performance
}

hashMapBy :: (Hashable k, Hashable a, Eq k, Ord a) => (a -> k) -> Set a -> HashMap k (Set a)
hashMapBy f = M.fromListWith Set.union . fmap (\v -> (f v, Set.singleton v)) . Set.toList

gaddCalls :: Set SolCall -> GenDict -> GenDict
gaddCalls c = wholeCalls <>~ hashMapBy (fmap $ fmap abiValueType) c
gaddCalls calls dict =
dict { wholeCalls = dict.wholeCalls <> hashMapBy (fmap $ fmap abiValueType) calls }

defaultDict :: GenDict
defaultDict = mkGenDict 0 Set.empty Set.empty 0 (const Nothing)
Expand Down Expand Up @@ -307,15 +305,15 @@ genWithDict :: (Eq a, Hashable a, MonadRandom m)
=> GenDict -> HashMap a (Set b) -> (a -> m b) -> a -> m b
genWithDict genDict m g t = do
r <- getRandom
let maybeValM = if genDict._pSynthA >= r then fromDict else pure Nothing
let maybeValM = if genDict.pSynthA >= r then fromDict else pure Nothing
fromDict = case M.lookup t m of
Nothing -> pure Nothing
Just cs -> Just <$> rElem' cs
fromMaybe <$> g t <*> maybeValM

-- | Synthesize a random 'AbiValue' given its 'AbiType'. Requires a dictionary.
genAbiValueM :: MonadRandom m => GenDict -> AbiType -> m AbiValue
genAbiValueM genDict = genWithDict genDict genDict._constants $ \case
genAbiValueM genDict = genWithDict genDict genDict.constants $ \case
(AbiUIntType n) -> fixAbiUInt n . fromInteger <$> getRandomUint n
(AbiIntType n) -> fixAbiInt n . fromInteger <$> getRandomInt n
AbiAddressType -> AbiAddress . fromInteger <$> getRandomR (0, 2 ^ (160 :: Integer) - 1)
Expand All @@ -334,7 +332,7 @@ genAbiValueM genDict = genWithDict genDict genDict._constants $ \case
genAbiCallM :: MonadRandom m => GenDict -> SolSignature -> m SolCall
genAbiCallM genDict abi = do
solCall <- genWithDict genDict
genDict._wholeCalls
genDict.wholeCalls
(traverse $ traverse (genAbiValueM genDict))
abi
mutateAbiCall solCall
Expand Down
13 changes: 8 additions & 5 deletions lib/Echidna/Campaign.hs
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,16 @@ callseq ic v w ql = do
-- Keep track of the number of calls to `callseq`
ncallseqs += 1
-- Now we try to parse the return values as solidity constants, and add then to the 'GenDict'
types <- gets (._genDict._rTypes)
types <- gets (._genDict.rTypes)
let results = parse (map (\(t, (vr, _)) -> (t, vr)) res) types
-- union the return results with the new addresses
additions = H.unionWith Set.union diffs results
-- append to the constants dictionary
modifying (genDict . constants) . H.unionWith Set.union $ additions
modifying (genDict . dictValues) . Set.union $ mkDictValues $ Set.unions $ H.elems additions
let dict = camp._genDict
genDict .= dict
{ constants = H.unionWith Set.union additions dict.constants
, dictValues = Set.union (mkDictValues $ Set.unions $ H.elems additions) dict.dictValues
}
where
-- Given a list of transactions and a return typing rule, this checks whether we know the return
-- type for each function called, and if we do, tries to parse the return value as a value of that
Expand Down Expand Up @@ -258,8 +261,8 @@ campaign
campaign u vm w ts d txs = do
conf <- asks (.cfg.campaignConf)
let c = fromMaybe mempty conf.knownCoverage
let effectiveSeed = fromMaybe d'._defSeed conf.seed
effectiveGenDict = d' { _defSeed = effectiveSeed }
let effectiveSeed = fromMaybe d'.defSeed conf.seed
effectiveGenDict = d' { defSeed = effectiveSeed }
d' = fromMaybe defaultDict d
execStateT
(evalRandT runCampaign (mkStdGen effectiveSeed))
Expand Down
2 changes: 1 addition & 1 deletion lib/Echidna/Output/JSON.hs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ encodeCampaign C.Campaign{..} = encode
Campaign { _success = True
, _error = Nothing
, _tests = mapTest <$> _tests
, seed = _genDict._defSeed
, seed = _genDict.defSeed
, coverage = mapKeys (("0x" ++) . (`showHex` "") . keccak') $ DF.toList <$>_coverage
, gasInfo = toList _gasInfo
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Echidna/Transaction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ genTxM memo m = do
World ss hmm lmm ps _ <- asks fst
genDict <- gets (._genDict)
mm <- getSignatures hmm lmm
let ns = genDict._dictValues
let ns = genDict.dictValues
s' <- rElem' ss
r' <- rElem' $ Set.fromList (mapMaybe (toContractA mm) (toList m))
c' <- genInteractionsM genDict (snd r')
Expand Down
2 changes: 1 addition & 1 deletion lib/Echidna/UI/Report.hs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ ppCampaign c = do
gasInfoPrinted <- ppGasInfo c
let coveragePrinted = maybe "" ("\n" ++) . ppCoverage $ c._coverage
corpusPrinted = maybe "" ("\n" ++) . ppCorpus $ c._corpus
seedPrinted = "\nSeed: " ++ show c._genDict._defSeed
seedPrinted = "\nSeed: " ++ show c._genDict.defSeed
pure $
testsPrinted
++ gasInfoPrinted
Expand Down
2 changes: 1 addition & 1 deletion lib/Echidna/UI/Widgets.hs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ summaryWidget :: Campaign -> Widget Name
summaryWidget c =
padLeft (Pad 1) (
str ("Tests found: " ++ show (length c._tests)) <=>
str ("Seed: " ++ show c._genDict._defSeed)
str ("Seed: " ++ show c._genDict.defSeed)
<=>
maybe emptyWidget str (ppCoverage c._coverage)
<=>
Expand Down