Skip to content

Commit db96559

Browse files
authored
Merge pull request #485 from crytic/dev
prepare for 0.3.4 release
2 parents c4df3c5 + edb4e79 commit db96559

File tree

7 files changed

+112
-16
lines changed

7 files changed

+112
-16
lines changed

.github/workflows/doc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
- run: pip install -e ".[doc]"
3838
- run: pdoc -o html/ crytic_compile
3939
- name: Upload artifact
40-
uses: actions/upload-pages-artifact@v1
40+
uses: actions/upload-pages-artifact@v2
4141
with:
4242
# Upload the doc
4343
path: './html/'

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ jobs:
4545
path: dist/
4646

4747
- name: publish
48-
uses: pypa/[email protected].7
48+
uses: pypa/[email protected].8
4949

5050
- name: sign
51-
uses: sigstore/gh-action-sigstore-python@v1.2.3
51+
uses: sigstore/gh-action-sigstore-python@v2.0.0
5252
with:
5353
inputs: ./dist/*.tar.gz ./dist/*.whl
5454
release-signing-artifacts: true

README.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@
55

66
Library to help smart contract compilation. It includes support for:
77
- Direct solc compilation
8+
- [Foundry](https://github.com/foundry-rs/foundry/)
9+
- [Hardhat](https://github.com/nomiclabs/hardhat)
810
- [Brownie](https://github.com/iamdefinitelyahuman/brownie)
911
- [Buidler](https://github.com/nomiclabs/buidler)
1012
- [Dapp](https://dapp.tools/dapp/)
1113
- [Embark](https://embark.status.im/)
1214
- [Etherlime](https://github.com/LimeChain/etherlime)
1315
- [Etherscan](https://etherscan.io/) (including several alt-chain explorers and testnets)
14-
- [Foundry](https://github.com/foundry-rs/foundry/)
15-
- [Hardhat](https://github.com/nomiclabs/hardhat)
1616
- [Truffle](https://truffleframework.com/)
1717
- [Waffle](https://github.com/EthWorks/Waffle)
1818

19+
To force compilation with a specific framework, use the `--compile-force-framework` flag. For example, to force compilation with Hardhat:
20+
21+
```shell
22+
crytic-compile . --compile-force-framework hardhat
23+
```
24+
1925
See the [Configuration](https://github.com/crytic/crytic-compile/wiki/Configuration) documentation for advanced usages.
2026

2127
The plugin is used in Trail of Bits tools, including:
@@ -27,21 +33,33 @@ The plugin is used in Trail of Bits tools, including:
2733

2834
## Installation
2935

30-
```bash
36+
```shell
3137
pip3 install crytic-compile
3238
```
3339

3440
## Usage
3541

36-
### Standalone
37-
```bash
42+
In the root directory of your project e.g. same directory as `hardhat.config.js` or `foundry.toml`, run:
43+
44+
```shell
3845
crytic-compile .
3946
```
4047

4148
Crytic-compile will generate `crytic-export/contracts.json` containing the AST/ABI and bytecodes of the contracts.
4249

4350
Run `crytic-compile --help` for more options.
4451

52+
## Library Linking
53+
54+
If your project uses [libraries](https://docs.soliditylang.org/en/latest/contracts.html#libraries) with external functions, they can be linked to their deployed address with the `--compile-libraries` flag. For example, if you have a library `SafeMath` deployed at `0xff`, you can link it with:
55+
56+
57+
```shell
58+
crytic-compile . --compile-libraries "(SafeMath, 0xff)"
59+
```
60+
61+
If you are fuzzing with Echidna or Medusa, follow this [tutorial on linking libraries](https://secure-contracts.com/program-analysis/echidna/advanced/working-with-libraries.html?highlight=library#linking-libraries).
62+
4563
### As a library
4664

4765
See the [library documentation](https://github.com/crytic/crytic-compile/wiki/Library-Documentation).

crytic_compile/crytic_compile.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,14 @@ def _extract_libraries(libraries_str: Optional[str]) -> Optional[Dict[str, int]]
6363

6464
if not libraries_str:
6565
return None
66-
67-
pattern = r"\((?P<name>\w+),\s*(?P<value1>0x[0-9a-fA-F]{2})\),?"
66+
# Extract tuple like (libname1, 0x00)
67+
pattern = r"\((?P<name>\w+),\s*(?P<value1>0x[0-9a-fA-F]{2,40})\),?"
6868
matches = re.findall(pattern, libraries_str)
6969

7070
if not matches:
71-
logging.info(f"Libraries {libraries_str} could not be parsed")
72-
return None
71+
raise ValueError(
72+
f"Invalid library linking directive\nGot:\n{libraries_str}\nExpected format:\n(libname1, 0x00),(libname2, 0x02)"
73+
)
7374

7475
ret: Dict[str, int] = {}
7576
for key, value in matches:

crytic_compile/platform/solc.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@
3434
def _build_contract_data(compilation_unit: "CompilationUnit") -> Dict:
3535
contracts = {}
3636

37-
libraries = compilation_unit.crytic_compile.libraries
37+
libraries_to_update = compilation_unit.crytic_compile.libraries
3838

3939
for filename, source_unit in compilation_unit.source_units.items():
4040
for contract_name in source_unit.contracts_names:
41+
libraries = source_unit.libraries_names_and_patterns(contract_name)
4142
abi = str(source_unit.abi(contract_name))
4243
abi = abi.replace("'", '"')
4344
abi = abi.replace("True", "true")
@@ -48,10 +49,11 @@ def _build_contract_data(compilation_unit: "CompilationUnit") -> Dict:
4849
"srcmap": ";".join(source_unit.srcmap_init(contract_name)),
4950
"srcmap-runtime": ";".join(source_unit.srcmap_runtime(contract_name)),
5051
"abi": abi,
51-
"bin": source_unit.bytecode_init(contract_name, libraries),
52-
"bin-runtime": source_unit.bytecode_runtime(contract_name, libraries),
52+
"bin": source_unit.bytecode_init(contract_name, libraries_to_update),
53+
"bin-runtime": source_unit.bytecode_runtime(contract_name, libraries_to_update),
5354
"userdoc": source_unit.natspec[contract_name].userdoc.export(),
5455
"devdoc": source_unit.natspec[contract_name].devdoc.export(),
56+
"libraries": dict(libraries) if libraries else {},
5557
}
5658
return contracts
5759

@@ -519,16 +521,21 @@ def _run_solc(
519521

520522
additional_kwargs: Dict = {"cwd": working_dir} if working_dir else {}
521523
if not compiler_version.version in [f"0.4.{x}" for x in range(0, 11)]:
522-
# Add . as default allowed path
524+
# Add --allow-paths argument, if it isn't already specified
525+
# We allow the CWD as well as the directory that contains the file
523526
if "--allow-paths" not in cmd:
524527
file_dir_start = os.path.normpath(os.path.dirname(filename))
528+
# Paths in the --allow-paths arg can't contain commas, since this is the delimeter
529+
# Try using absolute path; if it contains a comma, try using relative path instead
525530
file_dir = os.path.abspath(file_dir_start)
526531
if "," in file_dir:
527532
try:
528533
file_dir = os.path.relpath(file_dir_start)
529534
except ValueError:
535+
# relpath can fail if, for example, we're on Windows and the directory is on a different drive than CWD
530536
pass
531537

538+
# Even the relative path might have a comma in it, so we want to make sure first
532539
if "," not in file_dir:
533540
cmd += ["--allow-paths", ".," + file_dir]
534541
else:

tests/library_linking.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
library NeedsLinkingA {
2+
function testA() external pure returns (uint) {
3+
return type(uint).max;
4+
}
5+
}
6+
library NeedsLinkingB {
7+
function testB() external pure returns (uint) {
8+
return type(uint).min;
9+
}
10+
}
11+
contract TestLibraryLinking {
12+
function test() external {
13+
NeedsLinkingA.testA();
14+
NeedsLinkingB.testB();
15+
}
16+
}

tests/test_library_linking.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""
2+
Test library linking
3+
"""
4+
import re
5+
from pathlib import Path
6+
import pytest
7+
from crytic_compile.crytic_compile import CryticCompile
8+
9+
TEST_DIR = Path(__file__).resolve().parent
10+
11+
LIBRARY_PLACEHOLDER_REGEX = r"__.{36}__"
12+
13+
14+
def test_library_linking() -> None:
15+
"""Test that the placeholder is not present in the bytecode when the libraries are provided"""
16+
cc = CryticCompile(
17+
Path(TEST_DIR / "library_linking.sol").as_posix(),
18+
compile_libraries="(NeedsLinkingA, 0xdead),(NeedsLinkingB, 0x000000000000000000000000000000000000beef)",
19+
)
20+
for compilation_unit in cc.compilation_units.values():
21+
for source_unit in compilation_unit.source_units.values():
22+
assert (
23+
len(re.findall(r"__.{36}__", source_unit.bytecode_init("TestLibraryLinking"))) == 2
24+
)
25+
assert (
26+
len(re.findall(r"__.{36}__", source_unit.bytecode_runtime("TestLibraryLinking")))
27+
== 2
28+
)
29+
libraries = compilation_unit.crytic_compile.libraries
30+
assert (
31+
len(
32+
re.findall(
33+
r"__.{36}__", source_unit.bytecode_init("TestLibraryLinking", libraries)
34+
)
35+
)
36+
== 0
37+
)
38+
assert (
39+
len(
40+
re.findall(
41+
r"__.{36}__", source_unit.bytecode_runtime("TestLibraryLinking", libraries)
42+
)
43+
)
44+
== 0
45+
)
46+
47+
48+
def test_library_linking_validation() -> None:
49+
"""Test that invalid compile libraries argument raises an error"""
50+
with pytest.raises(ValueError):
51+
CryticCompile(
52+
Path(TEST_DIR / "library_linking.sol").as_posix(),
53+
compile_libraries="(NeedsLinkingA, 0x)",
54+
)

0 commit comments

Comments
 (0)