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

Simplify Echidna.ABI #545

Merged
merged 1 commit into from
Nov 10, 2020
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
65 changes: 33 additions & 32 deletions lib/Echidna/ABI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@

module Echidna.ABI where

import Control.Lens
import Control.Lens (makeLenses, (<>~))
import Control.Monad (join, liftM2, liftM3, foldM, replicateM)
import Control.Monad.Reader (runReaderT)
import Control.Monad.Reader.Class (MonadReader)
import Control.Monad.Random.Strict (MonadRandom, getRandom, getRandoms, getRandomR, uniformMay)
import Data.Bool (bool)
import Data.ByteString (ByteString)
import Data.Foldable (toList)
import Data.Has (Has(..))
import Data.Hashable (Hashable(..))
import Data.HashMap.Strict (HashMap)
import Data.HashSet (HashSet, fromList, union)
Expand All @@ -38,11 +35,11 @@ import qualified Data.ByteString as BS
import qualified Data.HashMap.Strict as M
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Encoding as TE
import qualified Data.Vector as V
import qualified Data.HashSet as H

import Echidna.Mutator (mutateLL, replaceAt)
import Echidna.Mutator (mutateLL, replaceAt)
import Echidna.Types.Random
import Echidna.Types.Signature

Expand All @@ -61,7 +58,7 @@ mkValidAbiUInt i x = if x <= 2 ^ i - 1 then Just $ AbiUInt i x else Nothing

makeNumAbiValues :: Integer -> [AbiValue]
makeNumAbiValues i = let l f = f <$> commonTypeSizes <*> fmap fromIntegral [i-1..i+1] in
catMaybes (l mkValidAbiInt ++ l mkValidAbiUInt)
catMaybes (l mkValidAbiInt ++ l mkValidAbiUInt)

makeArrayAbiValues :: BS.ByteString -> [AbiValue]
makeArrayAbiValues b = let size = BS.length b in [AbiString b, AbiBytesDynamic b] ++
Expand Down Expand Up @@ -96,7 +93,7 @@ encodeSig :: SolSignature -> Text
encodeSig (n, ts) = n <> "(" <> T.intercalate "," (abiTypeSolidity <$> ts) <> ")"

-- | Get the signature of a solidity method
hashSig :: Text -> FunctionHash
hashSig :: Text -> FunctionHash
hashSig = abiKeccak . TE.encodeUtf8

-- | Configuration necessary for generating new 'SolCalls'. Don't construct this by hand! Use 'mkConf'.
Expand Down Expand Up @@ -126,8 +123,8 @@ gaddCalls c = wholeCalls <>~ hashMapBy (fmap $ fmap abiValueType) c
defaultDict :: GenDict
defaultDict = mkGenDict 0 [] [] 0 (const Nothing)

dictValues :: GenDict -> [Integer]
dictValues g = catMaybes $ concatMap (\(_,h) -> map fromValue $ H.toList h) $ M.toList $ g ^. constants
dictValues :: GenDict -> [Integer]
dictValues g = catMaybes $ concatMap (\(_,h) -> map fromValue $ H.toList h) $ M.toList $ _constants g
Copy link
Contributor

Choose a reason for hiding this comment

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

i would try to use lens wherever possible to promote usage and keep things consistent

where fromValue (AbiUInt _ n) = Just (toInteger n)
fromValue (AbiInt _ n) = Just (toInteger n)
fromValue _ = Nothing
Expand Down Expand Up @@ -159,7 +156,7 @@ getRandomUint n = join $ R.fromList [(getRandomR (0, 1023), 1), (getRandomR (0,
-- Note that we define the dictionary case ('genAbiValueM') first (below), so recursive types can be
-- be generated using the same dictionary easily
genAbiValue :: MonadRandom m => AbiType -> m AbiValue
genAbiValue = flip runReaderT defaultDict . genAbiValueM
genAbiValue = genAbiValueM defaultDict
Copy link
Contributor

Choose a reason for hiding this comment

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

we could always just give explicit parameters here to avoid runReaderT

genAbiValue t = genAbiValueM t defaultDict


-- | Synthesize a random 'SolCall' given its 'SolSignature'. Doesn't use a dictionary.
genAbiCall :: MonadRandom m => SolSignature -> m SolCall
Expand Down Expand Up @@ -261,24 +258,24 @@ shrinkAbiCall = traverse $ traverse shrinkAbiValue
-- | Given an 'AbiValue', generate a random \"similar\" value of the same 'AbiType'.
mutateAbiValue :: MonadRandom m => AbiValue -> m AbiValue
mutateAbiValue (AbiUInt n x) = getRandomR (0, 9 :: Int) >>= -- 10% of chance of mutation
\case
\case
0 -> (AbiUInt n <$> mutateNum x)
_ -> return $ AbiUInt n x
mutateAbiValue (AbiInt n x) = getRandomR (0, 9 :: Int) >>= -- 10% of chance of mutation
\case
\case
0 -> (AbiInt n <$> mutateNum x)
_ -> return $ AbiInt n x
_ -> return $ AbiInt n x

mutateAbiValue (AbiAddress x) = return $ AbiAddress x
mutateAbiValue (AbiBool _) = genAbiValue AbiBoolType
mutateAbiValue (AbiBytes n b) = do fs <- replicateM n getRandom
xs <- mutateLL (Just n) (BS.pack fs) b
xs <- mutateLL (Just n) (BS.pack fs) b
return (AbiBytes n xs)

mutateAbiValue (AbiBytesDynamic b) = AbiBytesDynamic <$> mutateLL Nothing mempty b
mutateAbiValue (AbiString b) = AbiString <$> mutateLL Nothing mempty b
mutateAbiValue (AbiArray n t l) = do fs <- replicateM n $ genAbiValue t
xs <- mutateLL (Just n) (V.fromList fs) l
xs <- mutateLL (Just n) (V.fromList fs) l
return (AbiArray n t xs)

mutateAbiValue (AbiArrayDynamic t l) = AbiArrayDynamic t <$> mutateLL Nothing mempty l
Expand All @@ -297,16 +294,15 @@ mutateAbiCall = traverse f
-- | Given a generator taking an @a@ and returning a @b@ and a way to get @b@s associated with some
-- @a@ from a GenDict, return a generator that takes an @a@ and either synthesizes new @b@s with the
-- provided generator or uses the 'GenDict' dictionary (when available).
genWithDict :: (Eq a, Hashable a, MonadReader x m, Has GenDict x, MonadRandom m)
=> (GenDict -> HashMap a [b]) -> (a -> m b) -> a -> m b
genWithDict f g t = do
let fromDict = uniformMay . M.lookupDefault [] t . f
c <- view hasLens
fromMaybe <$> g t <*> (bool (pure Nothing) (fromDict c) . (c ^. pSynthA >=) =<< getRandom)
genWithDict :: (Eq a, Hashable a, MonadRandom m)
=> GenDict -> HashMap a [b] -> (a -> m b) -> a -> m b
genWithDict genDict f g t = do
Copy link
Contributor

Choose a reason for hiding this comment

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

we should rename the f parameter here because its meaning has changed and is no longer a function

let fromDict = uniformMay (M.lookupDefault [] t f)
fromMaybe <$> g t <*> (bool (pure Nothing) fromDict . (_pSynthA genDict >=) =<< getRandom)
Copy link
Contributor

Choose a reason for hiding this comment

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

if we want to target #545 this is a good line to target

do
  r <- getRandom
  fromMaybe <$> g t <*> bool (pure Nothing) fromDict (genDict ^. pSynthA >= r)


-- | Synthesize a random 'AbiValue' given its 'AbiType'. Requires a dictionary.
genAbiValueM :: (MonadReader x m, Has GenDict x, MonadRandom m) => AbiType -> m AbiValue
genAbiValueM = genWithDict (fmap toList . view constants) $ \case
genAbiValueM :: MonadRandom m => GenDict -> AbiType -> m AbiValue
genAbiValueM genDict = genWithDict genDict (toList <$> _constants genDict) $ \case
Copy link
Contributor

Choose a reason for hiding this comment

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

this is why i want to avoid removing MonadReader from things. we now have to pass this around everywhere

(AbiUIntType n) -> AbiUInt n . fromInteger <$> getRandomUint n
(AbiIntType n) -> AbiInt n . fromInteger <$> getRandomR (-1 * 2 ^ n, 2 ^ (n - 1))
AbiAddressType -> AbiAddress . fromInteger <$> getRandomR (0, 2 ^ (160 :: Integer) - 1)
Expand All @@ -317,15 +313,20 @@ genAbiValueM = genWithDict (fmap toList . view constants) $ \case
AbiStringType -> liftM2 (\n -> AbiString . BS.pack . take n)
(getRandomR (1, 32)) getRandoms
(AbiArrayDynamicType t) -> fmap (AbiArrayDynamic t) $ getRandomR (1, 32)
>>= flip V.replicateM (genAbiValueM t)
(AbiArrayType n t) -> AbiArray n t <$> V.replicateM n (genAbiValueM t)
(AbiTupleType v) -> AbiTuple <$> traverse genAbiValueM v
>>= flip V.replicateM (genAbiValueM genDict t)
Copy link
Contributor

Choose a reason for hiding this comment

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

like here

(AbiArrayType n t) -> AbiArray n t <$> V.replicateM n (genAbiValueM genDict t)
(AbiTupleType v) -> AbiTuple <$> traverse (genAbiValueM genDict) v

-- | Given a 'SolSignature', generate a random 'SolCalls' with that signature, possibly with a dictionary.
genAbiCallM :: (MonadReader x m, Has GenDict x, MonadRandom m) => SolSignature -> m SolCall
genAbiCallM abi = genWithDict (fmap toList . view wholeCalls) (traverse $ traverse genAbiValueM) abi >>= mutateAbiCall
genAbiCallM :: MonadRandom m => GenDict -> SolSignature -> m SolCall
genAbiCallM genDict abi =
genWithDict
Copy link
Contributor

Choose a reason for hiding this comment

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

if we are to make this multiline can we use do

genDict
(toList <$> _wholeCalls genDict)
(traverse $ traverse (genAbiValueM genDict))
abi
>>= mutateAbiCall

-- | Given a list of 'SolSignature's, generate a random 'SolCall' for one, possibly with a dictionary.
genInteractionsM :: (MonadReader x m, Has GenDict x, MonadRandom m)
=> NE.NonEmpty SolSignature -> m SolCall
genInteractionsM l = genAbiCallM =<< rElem l
genInteractionsM :: MonadRandom m => GenDict -> NE.NonEmpty SolSignature -> m SolCall
genInteractionsM genDict l = genAbiCallM genDict =<< rElem l
2 changes: 1 addition & 1 deletion lib/Echidna/Transaction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ genTxM m = do
let ns = dictValues genDict
s' <- rElem ss
r' <- rElem $ NE.fromList . catMaybes $ toContractA mm <$> toList m
c' <- genInteractionsM (snd r')
c' <- genInteractionsM genDict (snd r')
v' <- genValue mv ns ps c'
t' <- (,) <$> genDelay t ns <*> genDelay b ns
pure $ Tx (SolCall c') s' (fst r') g gp v' (level t')
Expand Down