Skip to content

Commit d78c321

Browse files
committed
Library auto-linking support through crytic-compile
1 parent 49a804e commit d78c321

File tree

5 files changed

+139
-2
lines changed

5 files changed

+139
-2
lines changed

lib/Echidna/Libraries.hs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module Echidna.Libraries where
2+
3+
import Control.Monad.Catch (MonadThrow)
4+
import Control.Monad.Reader (liftIO, MonadReader, MonadIO)
5+
import Control.Monad.ST (RealWorld)
6+
import Data.Aeson (FromJSON(..), withObject, (.:), eitherDecode)
7+
import Data.ByteString.Lazy qualified as LBS
8+
import Data.List (find, isSuffixOf)
9+
import Data.Map (Map)
10+
import Data.Map qualified as Map
11+
import Data.Maybe (mapMaybe)
12+
import Data.Text (Text)
13+
import Data.Text qualified as T
14+
import System.Directory (listDirectory)
15+
16+
import EVM.Solidity
17+
import EVM.Types hiding (Env)
18+
19+
import Echidna.Deploy (deployContracts)
20+
import Echidna.Types.Config (Env(..))
21+
22+
-- | Library linking information from crytic-compile --compile-autolink
23+
data LibraryLinking = LibraryLinking
24+
{ deploymentOrder :: [Text]
25+
, libraryAddresses :: Map Text Addr
26+
} deriving (Show)
27+
28+
instance FromJSON LibraryLinking where
29+
parseJSON = withObject "LibraryLinking" $ \o -> do
30+
deploymentOrder <- o .: "deployment_order"
31+
libraryAddresses <- o .: "library_addresses"
32+
pure LibraryLinking{deploymentOrder, libraryAddresses}
33+
34+
-- | Try to read library linking information
35+
readLibraryLinking :: FilePath -> IO (Maybe LibraryLinking)
36+
readLibraryLinking d = do
37+
fs <- filter (".link" `Data.List.isSuffixOf`) <$> listDirectory d
38+
case fs of
39+
[] -> pure Nothing
40+
[linkingFile] -> do
41+
content <- LBS.readFile linkingFile
42+
case eitherDecode content of
43+
Left err -> do
44+
putStrLn $ "Warning: Failed to parse " <> linkingFile <> ": " <> err
45+
pure Nothing
46+
Right linking -> pure (Just linking)
47+
_ -> error $ "Multiple link files found: " <> show fs <> "\n"
48+
49+
-- | Deploy libraries using autolink information if available
50+
deployAutolinkLibraries
51+
:: (MonadIO m, MonadReader Env m, MonadThrow m)
52+
=> [SolcContract]
53+
-> Addr
54+
-> VM Concrete RealWorld
55+
-> m (VM Concrete RealWorld)
56+
deployAutolinkLibraries cs deployer vm = do
57+
linking <- liftIO $ readLibraryLinking "crytic-export"
58+
case linking of
59+
Nothing -> pure vm
60+
Just (LibraryLinking deploymentOrder libraryAddresses) -> do
61+
-- Deploy libraries in the specified order at the specified addresses
62+
let orderedLibs = mapMaybe (\libName -> do
63+
addr <- Map.lookup libName libraryAddresses
64+
contract <- find (\c -> T.isSuffixOf (":" <> libName) c.contractName) cs
65+
pure (addr, contract)) deploymentOrder
66+
deployContracts orderedLibs deployer vm

lib/Echidna/Solidity.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import Echidna.Deploy (deployContracts, deployBytecodes)
4242
import Echidna.Etheno (loadEthenoBatch)
4343
import Echidna.Events (extractEvents)
4444
import Echidna.Exec (execTx, execTxWithCov, initialVM)
45+
import Echidna.Libraries (deployAutolinkLibraries)
4546
import Echidna.SourceAnalysis.Slither
4647
import Echidna.Test (createTests, isAssertionMode, isPropertyMode, isDapptestMode)
4748
import Echidna.Types.Campaign (CampaignConf(..))
@@ -191,12 +192,15 @@ loadSpecified env mainContract cs = do
191192
ls <- mapM (chooseContract cs . Just . T.pack) solConf.solcLibs
192193

193194
flip runReaderT env $ do
194-
-- library deployment
195+
-- library deployment (solcLibs)
195196
vm0 <- deployContracts (zip [addrLibrary ..] ls) solConf.deployer blank
196197

198+
-- library deployment (from autolink, if link file exists)
199+
vm0' <- deployAutolinkLibraries cs solConf.deployer vm0
200+
197201
-- additional contract deployment (by name)
198202
cs' <- mapM ((chooseContract cs . Just) . T.pack . snd) solConf.deployContracts
199-
vm1 <- deployContracts (zip (map fst solConf.deployContracts) cs') solConf.deployer vm0
203+
vm1 <- deployContracts (zip (map fst solConf.deployContracts) cs') solConf.deployer vm0'
200204

201205
-- additional contract deployment (bytecode)
202206
vm2 <- deployBytecodes solConf.deployBytecodes solConf.deployer vm1

src/test/Tests/Integration.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ integrationTests = testGroup "Solidity Integration Testing"
6060
[ ("echidna_library_call failed", solved "echidna_library_call")
6161
, ("echidna_valid_timestamp failed", passed "echidna_valid_timestamp")
6262
]
63+
, testContract' "basic/autolink.sol" (Just "TestExternalLibrary") Nothing (Just "basic/autolink.yaml") True FuzzWorker
64+
[ ("echidna_library_call_works failed", passed "echidna_library_call_works") ]
6365
, testContractV "basic/fallback.sol" (Just (< solcV (0,6,0))) Nothing
6466
[ ("echidna_fallback failed", solved "echidna_fallback") ]
6567
, testContract "basic/push_long.sol" (Just "basic/push_long.yaml")

tests/solidity/basic/autolink.sol

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
/**
5+
* @title Library1
6+
* @dev A basic library that demonstrates a simple public function returning a string.
7+
*/
8+
library Library1 {
9+
/**
10+
* @dev Returns a static string "Library"
11+
* @return A static string value
12+
*/
13+
function getLibrary() public pure returns(string memory) {
14+
return "Library";
15+
}
16+
}
17+
18+
/**
19+
* @title Library2
20+
* @dev A library that depends on Library1, demonstrating direct library dependencies.
21+
* This library calls Library1's function and returns its result.
22+
*/
23+
library Library2 {
24+
/**
25+
* @dev Calls and returns the result from Library1.getLibrary()
26+
* @return A string value from the dependent library
27+
*/
28+
function getLibrary() public pure returns(string memory) {
29+
return Library1.getLibrary();
30+
}
31+
}
32+
33+
/**
34+
* @title Library3
35+
* @dev A library with multiple dependencies, demonstrating complex dependency chains.
36+
* This library calls both Library1 and Library2, creating a transitive dependency.
37+
*/
38+
library Library3 {
39+
/**
40+
* @dev Calls Library2.getLibrary() and then returns the result from Library1.getLibrary()
41+
* @return A string value from Library1
42+
*/
43+
function getLibrary() public pure returns(string memory) {
44+
Library2.getLibrary();
45+
return Library1.getLibrary();
46+
}
47+
}
48+
49+
/**
50+
* @title TestExternalLibrary
51+
* @dev A contract that uses the external libraries defined above.
52+
* This contract serves as a test case for the Medusa fuzzer to verify proper
53+
* library resolution, deployment ordering, and ABI handling.
54+
*/
55+
contract TestExternalLibrary {
56+
/**
57+
* @dev Echidna property test to verify library functionality
58+
* Returns true if Library3 returns "Library", false otherwise
59+
* @return true if library calls work correctly
60+
*/
61+
function echidna_library_call_works() public view returns(bool){
62+
return keccak256(abi.encodePacked(Library3.getLibrary())) == keccak256(abi.encodePacked("Library"));
63+
}
64+
}

tests/solidity/basic/autolink.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cryticArgs: ["--compile-autolink"]

0 commit comments

Comments
 (0)