Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into elopez/windows-buil…
Browse files Browse the repository at this point in the history
…d-refresh
  • Loading branch information
elopez committed Feb 22, 2023
2 parents 5dd55b1 + 046cbcc commit 130aec8
Show file tree
Hide file tree
Showing 26 changed files with 160 additions and 114 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }}

- name: Docker Build and Push (Ubuntu & NVM variant)
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
platforms: linux/amd64
target: final-ubuntu
Expand All @@ -74,7 +74,7 @@ jobs:
cache-to: type=gha,mode=max

- name: Docker Build and Push (Distroless variant)
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
if: ${{ env.WORKFLOW_BUILD_DISTROLESS == true }}
with:
platforms: linux/amd64
Expand Down
38 changes: 20 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
## TODO
## 2.1.0 (Unreleased)

* Rename multi-abi mode to allContracts

## 2.0.5

Expand All @@ -7,26 +9,26 @@
* Update hevm to 0.50 (#884, #894, #896, #897, #901)
* Added saving and loading of reproducers for every test (#858)
* Added events and revert reasons for any failure in the constructor (#871)
* Fixed uninitialized sender addresses from etheno transactions (#823)
* Fixed crash when minimizing inputs during optimization tests (#837)
* Fixed uninitialized sender addresses from etheno transactions (#823)
* Fixed crash when minimizing inputs during optimization tests (#837)
* Refactored code and removed useless dependencies (#856, #857, #874, #878, #895, #903)

## 2.0.4

* Added colored html for coverage output code (#816)
* Fixed crash when parsing solc versions (#835)
* Fixed crash when parsing solc versions (#835)
* Fixed long transactions and event lines in UI (#832)
* Added Homebrew installation instructions (#848)
* Added Homebrew installation instructions (#848)
* Moved all nix stuff to flake and use nix-bundle-exe for macOS release (#851)
* Updated codebase to GHC 9.0.2 (#846)
* Updated codebase to GHC 9.0.2 (#846)
* Refactored code and removed useless dependencies (#854, #853, #829, #827, #828)

## 2.0.3

* Clean up Docker containers (#706)
* Avoid resetting accounts if there is a deployed contract (#795)
* Fixed decoding non-utf8 strings from slither printer (#799)
* Fixed generation and mutation of extreme signed integers (#791)
* Fixed decoding non-utf8 strings from slither printer (#799)
* Fixed generation and mutation of extreme signed integers (#791)
* Removed fallback from signature map when it is not defined (#772)

## 2.0.2
Expand All @@ -37,13 +39,13 @@
* Fixed crash when the EVM execution triggers more than one query (#760)
* Added support for detection and handling of ancient solc versions (#675)
* Added explicit static flag and removed pthread one from ghc options (#768)

## 2.0.1

* Optimized stateless mutators (#747)
* Expanded and improved command-line help (#741)
* Added dapptest support: compatibility mode to run foundry and dapptool fuzz tests (#733, #745)
* Generate more values closer to the maximum (#736)
* Generate more values closer to the maximum (#736)
* Fix TERMINFO path for Nix release builds (#731)
* Mitigate large memory consumption when replaying corpus (#725)
* Fix --shrink-limit to change shrink limit instead of test limit (#728)
Expand All @@ -54,14 +56,14 @@

* Refactored test internal data structures and code
* Refactored unit test code and moved the related files to the `tests` directory
* Added support to show events and custom errors when a property/assertion fails
* Added support to show events and custom errors when a property/assertion fails
* Added support for catching assertion failure in Solidity 0.8.x
* Added two new testing mode: optimization and overflow (only in Solidity 0.8.x)
* Added optional checks for contract destruction
* Added `testMode` option and removed related flags
* Simplified contract deployer and property sender addresses to be easier to read
* Updated hevm to 0.49.0

## 1.7.3

* Removed old compilation artifacts before starting a new fuzzing campaign (#697)
Expand Down Expand Up @@ -92,17 +94,17 @@
* Refactor coverage types and added corpus size in UI (#627)
* Fixed link to macOS binary in binaries.soliditylang.org (#629)
* Fixed default.nix to use 1.7.0 as version (#623)
* Refactored Test type (#622)
* Refactored Test type (#622)

## 1.7.0

* Refactored and improved etheno support to be more useful (#615)
* Refactored and improved etheno support to be more useful (#615)
* Coverage filenames are not overwritten (#620)
* Refactored the mutator code (#618)
* More corpus and array mutations implemented (#372)
* Source coverage is printed after fuzzing campaign (#516)
* Nix improvements and fixes (#603, #604, #608, #612)
* Simplified slither information parsing (#543)
* Simplified slither information parsing (#543)
* Enabled use of coverage by default (#605)
* Run echidna tests in parallel (#571)

Expand All @@ -112,10 +114,10 @@
* Use metadata to detect deployed contracts (#593)
* Semver integration for improving testing with different solc versions (#594)
* Added some performance improvement in property execution (#576)
* Fixed wait bug when shrinking (#584)
* Fixed wait bug when shrinking (#584)
* Added funwithnumber example from Sabre (#565)
* Improved function filtering to be more precise (#570)
* Small fixes in the macOS CI (#597), the README (#590) and Nix (#581)
* Small fixes in the macOS CI (#597), the README (#590) and Nix (#581)

## 1.6.0
* Slither is now a required dependency.
Expand All @@ -134,7 +136,7 @@
* Fix negative address bug (#552)
* Various Github Actions improvements (#527, #554)
* Allow to bypass EIP-170 and set up a custom max code size (#544)

## 1.5.1

* Fix timestamp and block delays having the initial timestamp/block added to them (#460, #469)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Our tool signals each execution trace in the corpus with the following "line mar

Echidna can test contracts compiled with different smart contract build systems, including [Truffle](https://truffleframework.com/) or [hardhat](https://hardhat.org/) using [crytic-compile](https://github.com/crytic/crytic-compile). To invoke echidna with the current compilation framework, use `echidna .`.

On top of that, Echidna supports two modes of testing complex contracts. Firstly, one can [describe an initialization procedure with Truffle and Etheno](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/end-to-end-testing.md) and use that as the base state for Echidna. Secondly, echidna can call into any contract with a known ABI by passing in the corresponding solidity source in the CLI. Use `multi-abi: true` in your config to turn this on.
On top of that, Echidna supports two modes of testing complex contracts. Firstly, one can [describe an initialization procedure with Truffle and Etheno](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/end-to-end-testing.md) and use that as the base state for Echidna. Secondly, Echidna can call into any contract with a known ABI by passing in the corresponding Solidity source in the CLI. Use `allContracts: true` in your config to turn this on.

### Crash course on Echidna

Expand Down
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
23 changes: 15 additions & 8 deletions lib/Echidna/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Data.Text (isPrefixOf)
import Data.Yaml qualified as Y

import EVM (VM(..))
import EVM.Types (W256)

import Echidna.Test
import Echidna.Types.Campaign
Expand Down Expand Up @@ -51,15 +52,21 @@ instance FromJSON EConfigWithUsage where
let useKey k = modify' $ insert k
x ..:? k = useKey k >> lift (x .:? k)
x ..!= y = fromMaybe y <$> x
getWord s d = fromIntegral <$> v ..:? s ..!= (d :: Integer)
-- Parse as unbounded Integer and see if it fits into W256
getWord256 k def = do
value :: Integer <- fromMaybe (fromIntegral (def :: W256)) <$> v ..:? k
if value > fromIntegral (maxBound :: W256) then
fail $ show k <> ": value does not fit in 256 bits"
else
pure $ fromIntegral value

-- TxConf
xc = TxConf <$> getWord "propMaxGas" maxGasPerBlock
<*> getWord "testMaxGas" maxGasPerBlock
<*> getWord "maxGasprice" 0
<*> getWord "maxTimeDelay" defaultTimeDelay
<*> getWord "maxBlockDelay" defaultBlockDelay
<*> getWord "maxValue" 100000000000000000000 -- 100 eth
xc = TxConf <$> v ..:? "propMaxGas" ..!= maxGasPerBlock
<*> v ..:? "testMaxGas" ..!= maxGasPerBlock
<*> getWord256 "maxGasprice" 0
<*> getWord256 "maxTimeDelay" defaultTimeDelay
<*> getWord256 "maxBlockDelay" defaultBlockDelay
<*> getWord256 "maxValue" 100000000000000000000 -- 100 eth

-- TestConf
tc = do
Expand Down Expand Up @@ -103,7 +110,7 @@ instance FromJSON EConfigWithUsage where
<*> v ..:? "initialize" ..!= Nothing
<*> v ..:? "deployContracts" ..!= []
<*> v ..:? "deployBytecodes" ..!= []
<*> v ..:? "multi-abi" ..!= False
<*> v ..:? "allContracts" ..!= False
<*> mode
<*> v ..:? "testDestruction" ..!= False
<*> v ..:? "allowFFI" ..!= False
Expand Down
2 changes: 1 addition & 1 deletion lib/Echidna/Fetch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ deployBytecodes' di ((a, bc):cs) d vm =
where
zeros = pack $ replicate 320 0 -- This will initialize with zero a large number of possible constructor parameters
loadRest = do
vm' <- execStateT (execTx $ createTx (bc `append` zeros) d a (fromInteger unlimitedGasPerBlock) (0, 0)) vm
vm' <- execStateT (execTx $ createTx (bc `append` zeros) d a unlimitedGasPerBlock (0, 0)) vm
case vm'._result of
(Just (VMSuccess _)) -> return vm'
_ -> throwM $ DeploymentFailed a (Data.Text.unlines $ extractEvents True di vm')
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
4 changes: 2 additions & 2 deletions lib/Echidna/RPC.hs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,6 @@ execEthenoTxs et = do
-- | For an etheno txn, set up VM to execute txn
setupEthenoTx :: MonadState VM m => Etheno -> m ()
setupEthenoTx (AccountCreated f) = initAddress f -- TODO: improve etheno to include initial balance
setupEthenoTx (ContractCreated f c _ _ d v) = setupTx $ createTxWithValue d f c (fromInteger unlimitedGasPerBlock) v (1, 1)
setupEthenoTx (FunctionCall f t _ _ d v) = setupTx $ Tx (SolCalldata d) f t (fromInteger unlimitedGasPerBlock) 0 v (1, 1)
setupEthenoTx (ContractCreated f c _ _ d v) = setupTx $ createTxWithValue d f c unlimitedGasPerBlock v (1, 1)
setupEthenoTx (FunctionCall f t _ _ d v) = setupTx $ Tx (SolCalldata d) f t unlimitedGasPerBlock 0 v (1, 1)
setupEthenoTx (BlockMined n t) = setupTx $ Tx NoCall 0 0 0 0 0 (fromInteger t, fromInteger n)
6 changes: 3 additions & 3 deletions lib/Echidna/Solidity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ loadSpecified solConf name cs = do

-- Set up initial VM, either with chosen contract or Etheno initialization file
-- need to use snd to add to ABI dict
let vm = initialVM ffi & block . gaslimit .~ fromInteger unlimitedGasPerBlock
let vm = initialVM ffi & block . gaslimit .~ unlimitedGasPerBlock
& block . maxCodeSize .~ fromInteger mcs
blank' <- maybe (pure vm) (loadEthenoBatch ffi) fp
let blank = populateAddresses (Set.insert d ads) bala blank'
Expand Down Expand Up @@ -218,12 +218,12 @@ loadSpecified solConf name cs = do
vm2 <- deployBytecodes di dpb d vm1

-- main contract deployment
let deployment = execTx $ createTxWithValue bc d ca (fromInteger unlimitedGasPerBlock) (fromInteger balc) (0, 0)
let deployment = execTx $ createTxWithValue bc d ca unlimitedGasPerBlock (fromInteger balc) (0, 0)
vm3 <- execStateT deployment vm2
when (isNothing $ currentContract vm3) (throwM $ DeploymentFailed ca $ T.unlines $ extractEvents True di vm3)

-- Run
let transaction = execTx $ uncurry basicTx setUpFunction d ca (fromInteger unlimitedGasPerBlock) (0, 0)
let transaction = execTx $ uncurry basicTx setUpFunction d ca unlimitedGasPerBlock (0, 0)
vm4 <- if isDapptestMode tm && setUpFunction `elem` abi then execStateT transaction vm3 else return vm3

case vm4._result of
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/Types/Solidity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ data SolConf = SolConf
, initialize :: Maybe FilePath -- ^ Initialize world with Etheno txns
, deployContracts :: [(Addr, String)] -- ^ List of contracts to deploy in specific addresses
, deployBytecodes :: [(Addr, Text)] -- ^ List of contracts to deploy in specific addresses
, multiAbi :: Bool -- ^ Whether or not to use the multi-abi mode
, allContracts :: Bool -- ^ Whether or not to fuzz all contracts
, testMode :: String -- ^ Testing mode
, testDestruction :: Bool -- ^ Whether or not to add a property to detect contract destruction
, allowFFI :: Bool -- ^ Whether or not to allow FFI hevm cheatcode
Expand Down
Loading

0 comments on commit 130aec8

Please sign in to comment.