Skip to content

Commit 20cf61d

Browse files
Basic support for testing contracts with libraries (#236)
1 parent 5855e1a commit 20cf61d

File tree

5 files changed

+57
-6
lines changed

5 files changed

+57
-6
lines changed

examples/solidity/basic/library.sol

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
library Test{
2+
struct Storage{
3+
bool flag;
4+
}
5+
6+
function set(Storage storage st) public{
7+
st.flag = true;
8+
}
9+
10+
}
11+
12+
contract Contract{
13+
using Test for Test.Storage;
14+
Test.Storage st;
15+
16+
function set() public{
17+
st.set();
18+
}
19+
20+
function echidna_library_call() external view returns (bool) {
21+
return (!st.flag);
22+
}
23+
}

examples/solidity/basic/library.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
solcLibs: ["basic/library.sol:Test"]
2+
quiet: true

lib/Echidna/Config.hs

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ instance FromJSON EConfig where
7777
<*> v .:? "initialBalance" .!= 0xffffffff
7878
<*> v .:? "prefix" .!= "echidna_"
7979
<*> v .:? "solcArgs" .!= ""
80+
<*> v .:? "solcLibs" .!= []
8081
<*> v .:? "quiet" .!= False)
8182
<*> tc
8283
<*> (UIConf <$> v .:? "dashboard" .!= True <*> style)

lib/Echidna/Solidity.hs

+28-6
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ data SolConf = SolConf { _contractAddr :: Addr -- ^ Contract address to us
7878
, _initialBalance :: Integer -- ^ Initial balance of deployer and senders
7979
, _prefix :: Text -- ^ Function name prefix used to denote tests
8080
, _solcArgs :: String -- ^ Args to pass to @solc@
81+
, _solcLibs :: [String] -- ^ List of libraries to load, in order.
8182
, _quiet :: Bool -- ^ Suppress @solc@ output, errors, and warnings
8283
}
8384
makeLenses ''SolConf
@@ -87,21 +88,38 @@ contracts :: (MonadIO m, MonadThrow m, MonadReader x m, Has SolConf x) => FilePa
8788
contracts fp = do
8889
a <- view (hasLens . solcArgs)
8990
q <- view (hasLens . quiet)
90-
pure (a, q) >>= liftIO . solc >>= (\case
91+
ls <- view (hasLens . solcLibs)
92+
pure (a, q, ls) >>= liftIO . solc >>= (\case
9193
Nothing -> throwM CompileFailure
9294
Just m -> pure . toList $ fst m) where
9395
usual = ["--combined-json=bin-runtime,bin,srcmap,srcmap-runtime,abi,ast", fp]
94-
solc (a, q) = do
96+
solc (a, q, ls) = do
9597
stderr <- if q then UseHandle <$> openFile "/dev/null" WriteMode
9698
else pure Inherit
9799
readSolc =<< writeSystemTempFile ""
98-
=<< readCreateProcess (proc "solc" $ usual <> words a) {std_err = stderr} ""
100+
=<< readCreateProcess (proc "solc" $ usual <> words (a ++ linkLibraries ls)) {std_err = stderr} ""
99101

100102
populateAddresses :: [Addr] -> Integer -> VM -> VM
101103
populateAddresses [] _ vm = vm
102104
populateAddresses (a:as) b vm = populateAddresses as b (vm & set (env . EVM.contracts . at a) (Just account))
103105
where account = initialContract (RuntimeCode mempty) & set nonce 1 & set balance (w256 $ fromInteger b)
104106

107+
-- | Address to load the first library
108+
addrLibrary :: Addr
109+
addrLibrary = 0xff
110+
111+
-- | Load a list of solidity contracts as libraries
112+
loadLibraries :: (MonadIO m, MonadThrow m, MonadReader x m, Has SolConf x)
113+
=> [SolcContract] -> Addr -> Addr -> VM -> m VM
114+
loadLibraries [] _ _ vm = return vm
115+
loadLibraries (l:ls) la d vm = loadLibraries ls (la + 1) d =<< loadRest
116+
where loadRest = execStateT (execTx $ Tx (Right $ l ^. creationCode) d la 0) vm
117+
118+
-- | Generate a string to use as argument in solc to link libraries starting from addrLibrary
119+
linkLibraries :: [String] -> String
120+
linkLibraries [] = ""
121+
linkLibraries ls = "--libraries " ++ concat (imap (\i x -> concat [x, ":", show $ addrLibrary + (toEnum i :: Addr) , ","]) ls)
122+
105123
-- | Given an optional contract name and a list of 'SolcContract's, try to load the specified
106124
-- contract, or, if not provided, the first contract in the list, into a 'VM' usable for Echidna
107125
-- testing and extract an ABI and list of tests. Throws exceptions if anything returned doesn't look
@@ -114,23 +132,27 @@ loadSpecified name cs = let ensure l e = if l == mempty then throwM e else pure
114132
c <- choose cs name
115133
q <- view (hasLens . quiet)
116134
liftIO $ do
117-
when (isNothing name && length cs > 1) $
135+
when (isNothing name && length cs > 1 && not q) $
118136
putStrLn "Multiple contracts found in file, only analyzing the first"
119137
unless q . putStrLn $ "Analyzing contract: " <> unpack (c ^. contractName)
120138

121139
-- Local variables
122-
(SolConf ca d ads b pref _ _) <- view hasLens
140+
(SolConf ca d ads b pref _ libs _) <- view hasLens
123141
let bc = c ^. creationCode
124142
blank = populateAddresses (ads |> d) b (vmForEthrunCreation bc)
125143
abi = liftM2 (,) (view methodName) (fmap snd . view methodInputs) <$> toList (c ^. abiMap)
126144
(tests, funs) = partition (isPrefixOf pref . fst) abi
127145

146+
-- Select libraries
147+
ls <- mapM (choose cs . Just . pack) libs
148+
128149
-- Make sure everything is ready to use, then ship it
129150
mapM_ (uncurry ensure) [(abi, NoFuncs), (tests, NoTests), (funs, OnlyTests)] -- ABI checks
130151
ensure bc (NoBytecode $ c ^. contractName) -- Bytecode check
131152
case find (not . null . snd) tests of
132153
Just (t,_) -> throwM $ TestArgsFound t -- Test args check
133-
Nothing -> (, funs, fst <$> tests) <$> execStateT (execTx $ Tx (Right bc) d ca 0) blank
154+
Nothing -> loadLibraries ls addrLibrary d blank >>=
155+
fmap (, funs, fst <$> tests) . execStateT (execTx $ Tx (Right bc) d ca 0)
134156

135157
where choose [] _ = throwM NoContracts
136158
choose (c:_) Nothing = return c

src/test/Spec.hs

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ integrationTests = testGroup "Solidity Integration Testing"
111111
[ ("echidna_state3 failed", solved "echidna_state3") ]
112112
, testContract "basic/balance.sol" (Just "basic/balance.yaml")
113113
[ ("echidna_balance failed", passed "echidna_balance") ]
114+
, testContract "basic/library.sol" (Just "basic/library.yaml")
115+
[ ("echidna_library_call failed", solved "echidna_library_call") ]
116+
114117
]
115118

116119
testContract :: FilePath -> Maybe FilePath -> [(String, Campaign -> Bool)] -> TestTree

0 commit comments

Comments
 (0)