Merge pull request #346 from crytic/expand-tx

rework the first field of Tx
pull/349/head
Will Song 5 years ago committed by GitHub
commit cb25bbdbcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      lib/Echidna/Campaign.hs
  2. 13
      lib/Echidna/Exec.hs
  3. 16
      lib/Echidna/RPC.hs
  4. 6
      lib/Echidna/Solidity.hs
  5. 2
      lib/Echidna/Test.hs
  6. 72
      lib/Echidna/Transaction.hs
  7. 3
      lib/Echidna/UI/Report.hs
  8. 4
      src/test/Spec.hs

@ -22,7 +22,6 @@ import Control.Monad.Trans.Random.Strict (liftCatch)
import Data.Aeson (ToJSON(..), object)
import Data.Binary.Get (runGetOrFail)
import Data.Bool (bool)
import Data.Either (lefts)
import Data.Foldable (toList)
import Data.Map (Map, mapKeys, unionWith, (\\), keys)
import Data.Maybe (fromMaybe, isJust, mapMaybe, maybeToList)
@ -175,7 +174,7 @@ execTxOptC t = do
res <- execTxWith vmExcept (usingCoverage $ pointCoverage (hasLens . coverage)) t
hasLens . coverage %= unionWith union og
grew <- (== LT) . comparing coveragePoints og <$> use (hasLens . coverage)
when grew $ hasLens . genDict %= gaddCalls (lefts [t ^. call])
when grew $ hasLens . genDict %= gaddCalls ([t ^. call] ^.. traverse . _SolCall)
return res
-- | Given an initial 'VM' and 'World' state and a number of calls to generate, generate that many calls,
@ -213,7 +212,7 @@ callseq v w ql = do
-- 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
-- type. It returns a 'GenDict' style HashMap.
parse l rt = H.fromList . flip mapMaybe l $ \(x, r) -> case (rt =<< x ^? call . _Left . _1, r) of
parse l rt = H.fromList . flip mapMaybe l $ \(x, r) -> case (rt =<< x ^? call . _SolCall . _1, r) of
(Just ty, VMSuccess b) -> (ty, ) . S.fromList . pure <$> runGetOrFail (getAbi ty) (b ^. lazy) ^? _Right . _3
_ -> Nothing

@ -10,7 +10,6 @@ module Echidna.Exec where
import Control.Lens
import Control.Monad.Catch (Exception, MonadThrow(..))
import Control.Monad.State.Strict (MonadState, execState)
import Data.Either (isRight)
import Data.Has (Has(..))
import Data.Map.Strict (Map)
import Data.Maybe (fromMaybe)
@ -70,12 +69,12 @@ execTxWith h m t = do (og :: VM) <- use hasLens
setupTx t
res <- m
cd <- use $ hasLens . state . calldata
case (res, isRight $ t ^. call) of
(f@Reversion, _) -> do hasLens .= og
hasLens . state . calldata .= cd
hasLens . result ?= f
(VMFailure x, _) -> h x
(VMSuccess bc, True) -> (hasLens %=) . execState $ do
case (res, t ^. call) of
(f@Reversion, _) -> do hasLens .= og
hasLens . state . calldata .= cd
hasLens . result ?= f
(VMFailure x, _) -> h x
(VMSuccess bc, SolCreate _) -> (hasLens %=) . execState $ do
env . contracts . at (t ^. dst) . _Just . contractcode .= InitCode ""
replaceCodeOfSelf (RuntimeCode bc)
loadContract (t ^. dst)

@ -13,7 +13,7 @@ import Control.Monad (foldM)
import Control.Monad.Catch (MonadThrow, throwM)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Reader.Class (MonadReader(..))
import Control.Monad.State.Strict (MonadState, execStateT, runStateT, get, put, runState)
import Control.Monad.State.Strict (MonadState, execStateT, runStateT, get, put)
import Data.Aeson (FromJSON(..), (.:), withObject, eitherDecodeFileStrict)
import Data.Binary.Get (runGetOrFail)
import Data.ByteString.Char8 (ByteString, empty)
@ -29,7 +29,6 @@ import EVM.Types (Addr, W256)
import Text.Read (readMaybe)
import qualified Control.Monad.Fail as M (MonadFail(..))
import qualified Control.Monad.State.Strict as S (state)
import qualified Data.ByteString.Base16 as BS16 (decode)
import qualified Data.Text as T (Text, drop, unpack)
import qualified Data.Vector as V (fromList)
@ -117,7 +116,7 @@ execEthenoTxs ts addr et = do
-- found the tests, so just return the contract
Just m -> return $ Just m
-- try to see if this is the contract we wish to test
Nothing -> let txs = ts <&> \t -> Tx (Left (t, [])) ca ca g 0 0 (0,0)
Nothing -> let txs = ts <&> \t -> Tx (SolCall (t, [])) ca ca g 0 0 (0,0)
-- every test was executed successfully
go [] = return (Just ca)
-- execute x and check if it returned something of the correct type
@ -141,12 +140,5 @@ execEthenoTxs ts addr et = do
-- | For an etheno txn, set up VM to execute txn
setupEthenoTx :: (MonadState x m, Has VM x) => Etheno -> m ()
setupEthenoTx (AccountCreated _) = pure ()
setupEthenoTx (ContractCreated f c _ _ d v) = S.state . runState . zoom hasLens . sequence_ $
[ result .= Nothing, state . pc .= 0, state . stack .= mempty, state . gas .= 0xffffffff
, tx . origin .= f, state . caller .= f, state . callvalue .= w256 v, setup]
where setup = assign (env . contracts . at c) (Just . initialContract . RuntimeCode $ d) >> loadContract c
setupEthenoTx (FunctionCall f t _ _ d v) = S.state . runState . zoom hasLens . sequence_ $
[ result .= Nothing, state . pc .= 0, state . stack .= mempty, state . gas .= 0xffffffff
, tx . origin .= f, state . caller .= f, state . callvalue .= w256 v, setup]
where setup = loadContract t >> state . calldata .= d
setupEthenoTx (ContractCreated f c _ _ d v) = setupTx $ Tx (SolCreate d) f c 0xffffffff 0 (w256 v) (0, 0)
setupEthenoTx (FunctionCall f t _ _ d v) = setupTx $ Tx (SolCalldata d) f t 0xffffffff 0 (w256 v) (0, 0)

@ -35,7 +35,7 @@ import System.Directory (findExecutable)
import Echidna.ABI (SolSignature)
import Echidna.Exec (execTx)
import Echidna.RPC (loadEthenoBatch)
import Echidna.Transaction (TxConf, Tx(..), World(..))
import Echidna.Transaction (TxConf, TxCall(SolCreate), Tx(..), World(..))
import EVM hiding (contracts)
import qualified EVM (contracts)
@ -142,7 +142,7 @@ loadLibraries :: (MonadIO m, MonadThrow m, MonadReader x m, Has SolConf x)
=> [SolcContract] -> Addr -> Addr -> VM -> m VM
loadLibraries [] _ _ vm = return vm
loadLibraries (l:ls) la d vm = loadLibraries ls (la + 1) d =<< loadRest
where loadRest = execStateT (execTx $ Tx (Right $ l ^. creationCode) d la 0xffffffff 0 0 (0,0)) vm
where loadRest = execStateT (execTx $ Tx (SolCreate $ l ^. creationCode) d la 0xffffffff 0 0 (0,0)) vm
-- | Generate a string to use as argument in solc to link libraries starting from addrLibrary
linkLibraries :: [String] -> String
@ -192,7 +192,7 @@ loadSpecified name cs = do
Just (t,_) -> throwM $ TestArgsFound t -- Test args check
Nothing -> do
vm <- loadLibraries ls addrLibrary d blank
let transaction = unless (isJust fp) $ void . execTx $ Tx (Right bc) d ca 0xffffffff 0 (w256 $ fromInteger balc) (0, 0)
let transaction = unless (isJust fp) $ void . execTx $ Tx (SolCreate bc) d ca 0xffffffff 0 (w256 $ fromInteger balc) (0, 0)
(, fallback NE.<| neFuns, fst <$> tests) <$> execStateT transaction vm
where choose [] _ = throwM NoContracts

@ -60,7 +60,7 @@ checkETest t = asks getter >>= \(TestConf p s) -> view (hasLens . propGas) >>= \
matchC sig = not . (BS.isPrefixOf . BS.take 4 $ abiCalldata (encodeSig sig) mempty)
res <- case t of
-- If our test is a regular user-defined test, we exec it and check the result
Left (f, a) -> execTx (Tx (Left (f, [])) (s a) a g 0 0 (0, 0)) >> gets (p f . getter)
Left (f, a) -> execTx (Tx (SolCall (f, [])) (s a) a g 0 0 (0, 0)) >> gets (p f . getter)
-- If our test is an auto-generated assertion test, we check if we failed an assert on that fn
Right sig -> (||) <$> fmap matchR (use $ hasLens . result)
<*> fmap (matchC sig) (use $ hasLens . state . calldata)

@ -19,7 +19,6 @@ import Control.Monad.Reader.Class (MonadReader)
import Control.Monad.State.Strict (MonadState, State, evalStateT, runState)
import Data.Aeson (ToJSON(..), object)
import Data.ByteString (ByteString)
import Data.Either (either)
import Data.Has (Has(..))
import Data.List (intercalate)
import EVM hiding (value)
@ -28,21 +27,31 @@ import EVM.Concrete (Word(..), w256)
import EVM.Types (Addr)
import qualified Control.Monad.State.Strict as S (state)
import qualified Data.ByteString.Base16 as BS16
import qualified Data.ByteString.Char8 as BSC8
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T
import qualified Data.Vector as V
import Echidna.ABI
-- | A transaction call is either a @CREATE@, a fully instrumented 'SolCall', or
-- an abstract call consisting only of calldata.
data TxCall = SolCreate ByteString
| SolCall SolCall
| SolCalldata ByteString
deriving (Show, Ord, Eq)
makePrisms ''TxCall
-- | A transaction is either a @CREATE@ or a regular call with an origin, destination, and value.
-- Note: I currently don't model nonces or signatures here.
data Tx = Tx { _call :: Either SolCall ByteString -- | Either a call or code for a @CREATE@
, _src :: Addr -- | Origin
, _dst :: Addr -- | Destination
, _gas' :: Word -- | Gas
, _gasprice' :: Word -- | Gas price
, _value :: Word -- | Value
, _delay :: (Word, Word) -- | (Time, # of blocks since last call)
data Tx = Tx { _call :: TxCall -- | Call
, _src :: Addr -- | Origin
, _dst :: Addr -- | Destination
, _gas' :: Word -- | Gas
, _gasprice' :: Word -- | Gas price
, _value :: Word -- | Value
, _delay :: (Word, Word) -- | (Time, # of blocks since last call)
} deriving (Eq, Ord, Show)
makeLenses ''Tx
@ -65,17 +74,25 @@ makeLenses 'TxConf
ppSolCall :: SolCall -> String
ppSolCall (t, vs) = (if t == "" then T.unpack "*fallback*" else T.unpack t) ++ "(" ++ intercalate "," (ppAbiValue <$> vs) ++ ")"
-- | Pretty-print some 'TxCall'
ppTxCall :: TxCall -> String
ppTxCall (SolCreate _) = "<CREATE>"
ppTxCall (SolCall x) = ppSolCall x
ppTxCall (SolCalldata x) = BSC8.unpack $ "0x" <> BS16.encode x
instance ToJSON Tx where
toJSON (Tx c s d g gp v (t, b)) = object [ ("call", toJSON $ either ppSolCall (const "<CREATE>") c)
-- from/to are Strings, since JSON doesn't support hexadecimal notation
, ("from", toJSON $ show s)
, ("to", toJSON $ show d)
, ("value", toJSON $ show v)
, ("gas", toJSON $ show g)
, ("gasprice", toJSON $ show gp)
, ("time delay", toJSON $ show t)
, ("block delay", toJSON $ show b)
]
toJSON (Tx c s d g gp v (t, b)) =
object
[ ("call", toJSON $ ppTxCall c)
-- from/to are Strings, since JSON doesn't support hexadecimal notation
, ("from", toJSON $ show s)
, ("to", toJSON $ show d)
, ("value", toJSON $ show v)
, ("gas", toJSON $ show g)
, ("gasprice", toJSON $ show gp)
, ("time delay", toJSON $ show t)
, ("block delay", toJSON $ show b)
]
-- | If half a tuple is zero, make both halves zero. Useful for generating delays, since block number
-- only goes up with timestamp
@ -105,7 +122,7 @@ genTxWith :: (MonadRandom m, MonadState x m, Has World x, MonadThrow m)
-> m Tx
genTxWith s r c g gp v t = use hasLens >>= \(World ss rs) ->
let s' = s ss; r' = r rs; c' = join $ liftM2 c s' r' in
((liftM5 Tx (Left <$> c') s' (fst <$> r') g gp <*>) =<< liftM3 v s' r' c') <*> t
((liftM5 Tx (SolCall <$> c') s' (fst <$> r') g gp <*>) =<< liftM3 v s' r' c') <*> t
-- | Synthesize a random 'Transaction', not using a dictionary.
genTx :: forall m x y. (MonadRandom m, MonadReader x m, Has TxConf x, MonadState y m, Has World y, MonadThrow m) => m Tx
@ -122,15 +139,19 @@ genTxM = view hasLens >>= \(TxConf _ g maxGp t b) -> genTxWith
-- | Check if a 'Transaction' is as \"small\" (simple) as possible (using ad-hoc heuristics).
canShrinkTx :: Tx -> Bool
canShrinkTx (Tx (Right _) _ _ _ 0 0 (0, 0)) = False
canShrinkTx (Tx (Left (_,l)) _ _ _ 0 0 (0, 0)) = any canShrinkAbiValue l
canShrinkTx _ = True
canShrinkTx (Tx (SolCreate _) _ _ _ 0 0 (0, 0)) = False
canShrinkTx (Tx (SolCall (_,l)) _ _ _ 0 0 (0, 0)) = any canShrinkAbiValue l
canShrinkTx (Tx (SolCalldata _) _ _ _ 0 0 (0, 0)) = False
canShrinkTx _ = True
-- | Given a 'Transaction', generate a random \"smaller\" 'Transaction', preserving origin,
-- destination, value, and call signature.
shrinkTx :: MonadRandom m => Tx -> m Tx
shrinkTx tx'@(Tx c _ _ _ gp (C _ v) (C _ t, C _ b)) = let
c' = either (fmap Left . shrinkAbiCall) (fmap Right . pure) c
c' = case c of
SolCreate{} -> pure c
SolCall sc -> SolCall <$> shrinkAbiCall sc
SolCalldata{} -> pure c
lower 0 = pure $ w256 0
lower x = w256 . fromIntegral <$> getRandomR (0 :: Integer, fromIntegral x)
>>= \r -> uniform [0, r] -- try 0 quicker
@ -155,7 +176,8 @@ setupTx (Tx c s r g gp v (t, b)) = liftSH . sequence_ $
, tx . gasprice .= gp, tx . origin .= s, state . caller .= s, state . callvalue .= v
, block . timestamp += t, block . number += b, setup] where
setup = case c of
Left cd -> loadContract r >> state . calldata .= encode cd
Right bc -> assign (env . contracts . at r) (Just $ initialContract (RuntimeCode bc) & set balance v) >> loadContract r
SolCreate bc -> assign (env . contracts . at r) (Just $ initialContract (RuntimeCode bc) & set balance v) >> loadContract r
SolCall cd -> loadContract r >> state . calldata .= encode cd
SolCalldata cd -> loadContract r >> state . calldata .= cd
encode (n, vs) = abiCalldata
(encodeSig (n, abiValueType <$> vs)) $ V.fromList vs

@ -5,7 +5,6 @@ module Echidna.UI.Report where
import Control.Lens
import Control.Monad.Reader (MonadReader)
import Data.Either (either)
import Data.Has (Has(..))
import Data.List (nub)
import Data.Map (Map)
@ -31,7 +30,7 @@ progress n m = "(" ++ show n ++ "/" ++ show m ++ ")"
-- | Given rules for pretty-printing associated address, and whether to print them, pretty-print a 'Transaction'.
ppTx :: (MonadReader x m, Has Names x, Has TxConf x) => Bool -> Tx -> m String
ppTx pn (Tx c s r g gp v (t, b)) = let sOf = either ppSolCall (const "<CREATE>") in do
ppTx pn (Tx c s r g gp v (t, b)) = let sOf = ppTxCall in do
names <- view hasLens
tGas <- view $ hasLens . txGas
return $ sOf c ++ (if not pn then "" else names Sender s ++ names Receiver r)

@ -12,7 +12,7 @@ import Echidna.ABI (SolCall, mkGenDict)
import Echidna.Campaign (Campaign(..), CampaignConf(..), TestState(..), campaign, tests)
import Echidna.Config (EConfig, EConfigWithUsage(..), _econfig, defaultConfig, parseConfig, sConf, cConf)
import Echidna.Solidity
import Echidna.Transaction (Tx, call)
import Echidna.Transaction (TxCall(SolCall), Tx, call)
import Control.Lens
import Control.Monad (liftM2, void)
@ -242,4 +242,4 @@ solvedLen i t = (== Just i) . fmap length . solnFor t
-- NOTE: this just verifies a call was found in the solution. Doesn't care about ordering/seq length
solvedWith :: SolCall -> Text -> Campaign -> Bool
solvedWith c t = maybe False (any $ (== Left c) . view call) . solnFor t
solvedWith c t = maybe False (any $ (== SolCall c) . view call) . solnFor t

Loading…
Cancel
Save