1+ {-# LANGUAGE FlexibleContexts #-}
12{-# LANGUAGE FlexibleInstances #-}
23{-# LANGUAGE LambdaCase #-}
34{-# LANGUAGE MultiParamTypeClasses #-}
@@ -12,12 +13,17 @@ import Control.Monad (liftM2, liftM5)
1213import Control.Monad.Catch (MonadThrow )
1314import Control.Monad.IO.Class (MonadIO (.. ))
1415import Control.Monad.Reader (Reader , ReaderT (.. ), runReader )
16+ import Control.Monad.State (StateT (.. ), runStateT )
17+ import Control.Monad.Trans (lift )
1518import Data.ByteString.Lazy.Char8 (unpack )
16- import Data.Has (Has (.. ))
1719import Data.Aeson
1820import Data.Aeson.Lens
1921import Data.Functor ((<&>) )
20- import Data.Text (isPrefixOf )
22+ import Data.Has (Has (.. ))
23+ import Data.HashMap.Strict (keys )
24+ import Data.HashSet (HashSet , fromList , insert , difference )
25+ import Data.Maybe (fromMaybe )
26+ import Data.Text (Text , isPrefixOf )
2127import EVM (result )
2228import EVM.Concrete (Word (.. ), Whiff (.. ))
2329
@@ -42,6 +48,15 @@ data EConfig = EConfig { _cConf :: CampaignConf
4248 }
4349makeLenses ''EConfig
4450
51+ data EConfigWithUsage = EConfigWithUsage { _econfig :: EConfig
52+ , _badkeys :: HashSet Text
53+ , _unsetkeys :: HashSet Text
54+ }
55+ makeLenses ''EConfigWithUsage
56+
57+ instance Has EConfig EConfigWithUsage where
58+ hasLens = econfig
59+
4560instance Has CampaignConf EConfig where
4661 hasLens = cConf
4762
@@ -61,63 +76,85 @@ instance Has UIConf EConfig where
6176 hasLens = uConf
6277
6378instance FromJSON EConfig where
64- parseJSON (Object v) =
65- let tc = do psender <- v .:? " psender" .!= 0x00a329c0648769a73afac7f9381e08fb43dbea70
66- fprefix <- v .:? " prefix" .!= " echidna_"
67- let goal fname = if (fprefix <> " revert_" ) `isPrefixOf` fname then ResRevert else ResTrue
68- return $ TestConf (\ fname -> (== goal fname) . maybe ResOther classifyRes . view result)
69- (const psender)
70- getWord s d = C Dull . fromIntegral <$> v .:? s .!= (d :: Integer )
71- xc = liftM5 TxConf (getWord " propMaxGas" 8000030 ) (getWord " testMaxGas" 0xffffffff )
72- (getWord " maxGasprice" 100000000000 )
73- (getWord " maxTimeDelay" 604800 ) (getWord " maxBlockDelay" 60480 )
74- cov = v .:? " coverage" <&> \ case Just True -> Just mempty
75- _ -> Nothing
76- cc = CampaignConf <$> v .:? " testLimit" .!= 50000
77- <*> v .:? " stopOnFail" .!= False
78- <*> v .:? " seqLen" .!= 100
79- <*> v .:? " shrinkLimit" .!= 5000
80- <*> cov
81- <*> v .:? " seed"
82- <*> v .:? " dictFreq" .!= 0.40
83-
84- names :: Names
85- names Sender = (" from: " ++ ) . show
86- names _ = const " "
87- ppc :: Y. Parser (Campaign -> Int -> String )
88- ppc = liftM2 (\ cf xf c g -> runReader (ppCampaign c) (cf, xf, names) ++ " \n Seed: " ++ show g) cc xc
89- style :: Y. Parser (Campaign -> Int -> String )
90- style = v .:? " format" .!= (" text" :: String ) >>=
91- \ case " text" -> ppc
92- " json" -> pure . flip $ \ g ->
93- unpack . encode . set (_Object . at " seed" ) (Just . toJSON $ g) . toJSON;
94- " none" -> pure $ \ _ _ -> " "
95- _ -> pure $ \ _ _ -> M. fail
96- " unrecognized ui type (should be text, json, or none)" in
97- EConfig <$> cc
98- <*> pure names
99- <*> (SolConf <$> v .:? " contractAddr" .!= 0x00a329c0648769a73afac7f9381e08fb43dbea72
100- <*> v .:? " deployer" .!= 0x00a329c0648769a73afac7f9381e08fb43dbea70
101- <*> v .:? " sender" .!= NE. fromList [0x10000 , 0x20000 , 0x00a329c0648769a73afac7f9381e08fb43dbea70 ]
102- <*> v .:? " balanceAddr" .!= 0xffffffff
103- <*> v .:? " balanceContract" .!= 0
104- <*> v .:? " prefix" .!= " echidna_"
105- <*> v .:? " cryticArgs" .!= []
106- <*> v .:? " solcArgs" .!= " "
107- <*> v .:? " solcLibs" .!= []
108- <*> v .:? " quiet" .!= False
109- <*> v .:? " checkAsserts" .!= False )
110- <*> tc
111- <*> xc
112- <*> (UIConf <$> v .:? " dashboard" .!= True <*> v .:? " timeout" <*> style)
113- parseJSON _ = parseJSON (Object mempty )
79+ -- retrieve the config from the key usage annotated parse
80+ parseJSON = fmap _econfig . parseJSON
81+
82+ instance FromJSON EConfigWithUsage where
83+ -- this runs the parser in a StateT monad which keeps track of the keys
84+ -- utilized by the config parser
85+ -- we can then compare the set difference between the keys found in the config
86+ -- file and the keys used by the parser to comopute which keys were set in the
87+ -- config and not used and which keys were unset in the config and defaulted
88+ parseJSON o = do
89+ let v' = case o of
90+ Object v -> v
91+ _ -> mempty
92+ (c, ks) <- runStateT (parser v') $ fromList []
93+ let found = fromList (keys v')
94+ return $ EConfigWithUsage c (found `difference` ks) (ks `difference` found)
95+ -- this parser runs in StateT and comes equipped with the following
96+ -- equivalent unary operators:
97+ -- x .:? k (Parser) <==> x ..:? k (StateT)
98+ -- x .!= v (Parser) <==> x ..!= v (StateT)
99+ -- tl;dr use an extra initial . to lift into the StateT parser
100+ where parser v =
101+ let useKey k = hasLens %= insert k
102+ x ..:? k = useKey k >> lift (x .:? k)
103+ x ..!= y = fromMaybe y <$> x
104+ tc = do psender <- v ..:? " psender" ..!= 0x00a329c0648769a73afac7f9381e08fb43dbea70
105+ fprefix <- v ..:? " prefix" ..!= " echidna_"
106+ let goal fname = if (fprefix <> " revert_" ) `isPrefixOf` fname then ResRevert else ResTrue
107+ return $ TestConf (\ fname -> (== goal fname) . maybe ResOther classifyRes . view result)
108+ (const psender)
109+ getWord s d = C Dull . fromIntegral <$> v ..:? s ..!= (d :: Integer )
110+ xc = liftM5 TxConf (getWord " propMaxGas" 8000030 ) (getWord " testMaxGas" 0xffffffff )
111+ (getWord " maxGasprice" 100000000000 )
112+ (getWord " maxTimeDelay" 604800 ) (getWord " maxBlockDelay" 60480 )
113+ cov = v ..:? " coverage" <&> \ case Just True -> Just mempty
114+ _ -> Nothing
115+ cc = CampaignConf <$> v ..:? " testLimit" ..!= 50000
116+ <*> v ..:? " stopOnFail" ..!= False
117+ <*> v ..:? " seqLen" ..!= 100
118+ <*> v ..:? " shrinkLimit" ..!= 5000
119+ <*> cov
120+ <*> v ..:? " seed"
121+ <*> v ..:? " dictFreq" ..!= 0.40
122+ names :: Names
123+ names Sender = (" from: " ++ ) . show
124+ names _ = const " "
125+ -- ppc :: Has (HashSet Text) s => StateT s Y.Parser (Campaign -> Int -> String)
126+ ppc = liftM2 (\ cf xf c g -> runReader (ppCampaign c) (cf, xf, names) ++ " \n Seed: " ++ show g) cc xc
127+ -- style :: Has (HashSet Text) s => StateT s Y.Parser (Campaign -> Int -> String)
128+ style = v ..:? " format" ..!= (" text" :: String ) >>=
129+ \ case " text" -> ppc
130+ " json" -> pure . flip $ \ g ->
131+ unpack . encode . set (_Object . at " seed" ) (Just . toJSON $ g) . toJSON
132+ " none" -> pure $ \ _ _ -> " "
133+ _ -> pure $ \ _ _ -> M. fail
134+ " unrecognized ui type (should be text, json, or none)" in
135+ EConfig <$> cc
136+ <*> pure names
137+ <*> (SolConf <$> v ..:? " contractAddr" ..!= 0x00a329c0648769a73afac7f9381e08fb43dbea72
138+ <*> v ..:? " deployer" ..!= 0x00a329c0648769a73afac7f9381e08fb43dbea70
139+ <*> v ..:? " sender" ..!= (0x10000 NE. :| [0x20000 , 0x00a329c0648769a73afac7f9381e08fb43dbea70 ])
140+ <*> v ..:? " balanceAddr" ..!= 0xffffffff
141+ <*> v ..:? " balanceContract" ..!= 0
142+ <*> v ..:? " prefix" ..!= " echidna_"
143+ <*> v ..:? " cryticArgs" ..!= []
144+ <*> v ..:? " solcArgs" ..!= " "
145+ <*> v ..:? " solcLibs" ..!= []
146+ <*> v ..:? " quiet" ..!= False
147+ <*> v ..:? " checkAsserts" ..!= False )
148+ <*> tc
149+ <*> xc
150+ <*> (UIConf <$> v ..:? " dashboard" ..!= True <*> v ..:? " timeout" <*> style)
114151
115152-- | The default config used by Echidna (see the 'FromJSON' instance for values used).
116153defaultConfig :: EConfig
117154defaultConfig = either (error " Config parser got messed up :(" ) id $ Y. decodeEither' " "
118155
119156-- | Try to parse an Echidna config file, throw an error if we can't.
120- parseConfig :: (MonadThrow m , MonadIO m ) => FilePath -> m EConfig
157+ parseConfig :: (MonadThrow m , MonadIO m ) => FilePath -> m EConfigWithUsage
121158parseConfig f = liftIO (BS. readFile f) >>= Y. decodeThrow
122159
123160-- | Run some action with the default configuration, useful in the REPL.
0 commit comments