Skip to content

Commit 7e80fe2

Browse files
committed
Library auto-linking support through crytic-compile
1 parent 9870b1a commit 7e80fe2

File tree

5 files changed

+140
-2
lines changed

5 files changed

+140
-2
lines changed

lib/Echidna/Libraries.hs

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

lib/Echidna/Solidity.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import Echidna.ABI
4141
import Echidna.Deploy (deployContracts, deployBytecodes)
4242
import Echidna.Events (extractEvents)
4343
import Echidna.Exec (execTx, execTxWithCov, initialVM)
44+
import Echidna.Libraries (deployAutolinkLibraries)
4445
import Echidna.SourceAnalysis.Slither
4546
import Echidna.Test (createTests, isAssertionMode, isPropertyMode, isDapptestMode)
4647
import Echidna.Types.Campaign (CampaignConf(..))
@@ -189,12 +190,15 @@ loadSpecified env mainContract cs = do
189190
ls <- mapM (chooseContract cs . Just . T.pack) solConf.solcLibs
190191

191192
flip runReaderT env $ do
192-
-- library deployment
193+
-- library deployment (solcLibs)
193194
vm0 <- deployContracts (zip [addrLibrary ..] ls) solConf.deployer blank
194195

196+
-- library deployment (from autolink, if link file exists)
197+
vm0' <- deployAutolinkLibraries cs solConf.deployer vm0
198+
195199
-- additional contract deployment (by name)
196200
cs' <- mapM ((chooseContract cs . Just) . T.pack . snd) solConf.deployContracts
197-
vm1 <- deployContracts (zip (map fst solConf.deployContracts) cs') solConf.deployer vm0
201+
vm1 <- deployContracts (zip (map fst solConf.deployContracts) cs') solConf.deployer vm0'
198202

199203
-- additional contract deployment (bytecode)
200204
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)