Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1d8ca99
Add forge compilation with linked library support
Jun 4, 2025
2fc238c
Fix formatting
GiovanniTorrisi-ChainSecurity Jun 4, 2025
9aa3ba7
Fix formatting
GiovanniTorrisi-ChainSecurity Jun 5, 2025
8587c19
Add simple test
GiovanniTorrisi-ChainSecurity Jun 5, 2025
44c8ad1
Fix formatting
GiovanniTorrisi-ChainSecurity Jun 5, 2025
eac50af
Fix formatting
GiovanniTorrisi-ChainSecurity Jun 6, 2025
6ce3733
Add forge compilation with linked library support
Jun 4, 2025
6557017
Fix formatting
GiovanniTorrisi-ChainSecurity Jun 4, 2025
3d08c9e
Fix formatting
GiovanniTorrisi-ChainSecurity Jun 5, 2025
211e1a5
Add simple test
GiovanniTorrisi-ChainSecurity Jun 5, 2025
84fd553
Fix formatting
GiovanniTorrisi-ChainSecurity Jun 5, 2025
46adf9a
Fix formatting
GiovanniTorrisi-ChainSecurity Jun 6, 2025
940f9e5
Merge branch 'compilation_with_libraries' of https://github.com/Chain…
GiovanniTorrisi-ChainSecurity Jun 16, 2025
c9ad45a
Fix test
GiovanniTorrisi-ChainSecurity Jun 16, 2025
f9ca545
Fix formatting
GiovanniTorrisi-ChainSecurity Jul 3, 2025
8633e4d
Merge branch 'main' into compilation_with_libraries
GiovanniTorrisi-ChainSecurity Jul 3, 2025
58f23f4
Fix extra function argument
GiovanniTorrisi-ChainSecurity Jul 3, 2025
c9f5fc7
Fix missing libraries flag
GiovanniTorrisi-ChainSecurity Jul 3, 2025
c1a4303
Fix formatting
GiovanniTorrisi-ChainSecurity Jul 3, 2025
6d674e5
Merge branch 'main' into compilation_with_libraries
GiovanniTorrisi-ChainSecurity Jul 4, 2025
1e87d90
Fix test
GiovanniTorrisi-ChainSecurity Jul 4, 2025
15cd8a2
Remove unused import
GiovanniTorrisi-ChainSecurity Jul 4, 2025
e7ec1b9
Fix missing --libraries in generate-build-cache command
GiovanniTorrisi-ChainSecurity Jul 4, 2025
a596302
Merge branch 'main' into compilation_with_libraries
Jul 8, 2025
dbac172
updated readme
Jul 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Once a DVF is published, any user can choose to trust the signer of that DVF and
- [Registry](#registry)
- [Etherscan Verified Contracts](#etherscan-verified-contracts)
- [Initialization by event topics](#initialization-by-event-topics)
- [Public libraries](#public-libraries)

7. [Common Problems](#common-problems)
8. [Getting Help](#getting-help)
Expand Down Expand Up @@ -544,6 +545,18 @@ Alternatively, it is also possible to pass an empty list of event topics to sear
dv init --project <PROJECT_PATH> --address <ADDRESS> --contractname <NAME> --eventtopics "" new.dvf.json
```

### Public libraries

If your contracts use public libraries, they are not automatically linked during compilation. You have to explicitly pass the addresses of the libraries using the `--libraries` argument:

```
dv init --project <PROJECT_PATH> --address <ADDRESS> --contractname <NAME> --libraries <LIBRARIES> new.dvf.json
```

`<LIBRARIES>` can be a comma-separated list of libraries. Each item must have the following format: `<PATH>:<NAME>:<ADDRESS>`.

`<PATH>` is the path to the library contract file, relative to your project root. `<NAME>` is the name of the library contract. `<ADDRESS>` is the public address of the already deployed library.

## Common Problems

Sometimes, it is possible that the `init` command cannot find a deployment transaction. In this case, you have the following options:
Expand Down Expand Up @@ -571,7 +584,6 @@ This section will be updated soon.

## Known Limitations and Bugs

- Compilation with libraries is currently not supported. The best workaround is to compile using `forge build --libraries <LIBS> --build-info --build-info-path <TMPDIR>` and then use `<TMPDIR>` using the `--buildcache` argument.
- Currently only solidity is supported.
- Only projects with `solc` version starting from `0.5.13` are supported due to the lack of generated storage layout in older versions (see [solc release 0.5.13](https://github.com/ethereum/solidity/releases/tag/v0.5.13)).
- The RPC endpoints automatically parsed in `dv generate-config` are not guaranteed to be compatible.
Expand Down
36 changes: 29 additions & 7 deletions lib/bytecode_verification/parse_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,36 @@ impl ProjectInfo {
}

// build it
fn forge_build(project: &Path, build_info_path: &Path) -> Result<(), ValidationError> {
fn forge_build(
project: &Path,
build_info_path: &Path,
libraries: Option<Vec<String>>,
) -> Result<(), ValidationError> {
info!(
"Starting <forge build>. If you had previous builds, it is recommended to <forge clean>."
);
let build = Command::new("forge")

let mut command = Command::new("forge");
command
.current_dir(project)
.arg("build")
.arg("--build-info")
.arg("--build-info-path")
.arg(build_info_path.to_str().unwrap())
.output()
.expect("Could not build project");
.arg(build_info_path.to_str().unwrap());

if let Some(libs) = libraries {
for lib in libs {
command.arg("--libraries").arg(lib);
}
}

let program = command.get_program();
let args: Vec<_> = command.get_args().collect();

println!("Command: {:?}", program);
println!("Args: {:?}", args);

let build = command.output().expect("Could not build project");

if !build.status.success() {
println!(
Expand Down Expand Up @@ -1375,6 +1393,7 @@ impl ProjectInfo {
project: &Path,
env: Environment,
artifacts_path: &Path,
libraries: Option<Vec<String>>,
) -> Result<PathBuf, ValidationError> {
let build_info_path: PathBuf;
let build_info_dir: TempDir;
Expand All @@ -1385,7 +1404,7 @@ impl ProjectInfo {
build_info_dir = Builder::new().prefix("dvf_bi").tempdir().unwrap();
// Persist for now
build_info_path = build_info_dir.keep();
Self::forge_build(project, &build_info_path)?;
Self::forge_build(project, &build_info_path, libraries)?;
}
Environment::Hardhat => {
assert!(Self::check_hardhat(project));
Expand All @@ -1403,10 +1422,13 @@ impl ProjectInfo {
env: Environment,
artifacts_path: &Path,
build_cache: Option<&String>,
libraries: Option<Vec<String>>,
) -> Result<Self, ValidationError> {
println!("Libraries are {:?}", libraries);

let build_info_path: PathBuf = match build_cache {
Some(s) => PathBuf::from(s),
None => Self::compile(project, env, artifacts_path)?,
None => Self::compile(project, env, artifacts_path, libraries)?,
};

let command = match env {
Expand Down
54 changes: 49 additions & 5 deletions src/dvf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,12 @@ fn main() {
.action(clap::ArgAction::SetTrue),
)
.arg(arg!(--buildcache <PATH>).help("Folder containing build-info files"))
.arg(
arg!(--libraries <LIBRARY> ...)
.help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags")
.value_delimiter(',')
.action(clap::ArgAction::Append),
)
.arg(
arg!(--implementationbuildcache <PATH>)
.help("Folder containing the implementation contract's build-info files"),
Expand Down Expand Up @@ -571,6 +577,12 @@ fn main() {
arg!(--artifacts <PATH>)
.help("Folder containing the artifacts")
.default_value("artifacts"),
)
.arg(
arg!(--libraries <LIBRARY> ...)
.help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags")
.value_delimiter(',')
.action(clap::ArgAction::Append),
),
)
.subcommand(
Expand Down Expand Up @@ -620,6 +632,12 @@ fn main() {
.help("Folder containing the artifacts")
.default_value("artifacts"),
)
.arg(
arg!(--libraries <LIBRARY> ...)
.help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags")
.value_delimiter(',')
.action(clap::ArgAction::Append),
)
.arg(arg!(--buildcache <PATH>).help("Folder containing build-info files")),
)
.subcommand(
Expand Down Expand Up @@ -783,6 +801,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> {
let artifacts = sub_m.get_one::<String>("artifacts").unwrap();
let build_cache = sub_m.get_one::<String>("buildcache");
let artifacts_path = get_project_paths(project, artifacts);
let libraries = sub_m
.get_many::<String>("libraries")
.map(|vals| vals.cloned().collect());
let event_topics = sub_m
.get_many::<Vec<B256>>("eventtopics")
.map(|v| v.flat_map(|x| x.clone()).collect::<Vec<_>>());
Expand Down Expand Up @@ -869,6 +890,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> {
env,
&artifacts_path,
build_cache,
libraries.clone(),
)?;

print_progress("Comparing bytecode.", &mut pc, &progress_mode);
Expand Down Expand Up @@ -990,6 +1012,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> {
imp_env,
&imp_artifacts_path,
imp_build_cache,
libraries,
)?;

print_progress(
Expand Down Expand Up @@ -1564,14 +1587,17 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> {
let project = sub_m.get_one::<PathBuf>("project").unwrap();
let artifacts = sub_m.get_one::<String>("artifacts").unwrap();
let artifacts_path = get_project_paths(project, artifacts);
let libraries = sub_m
.get_many::<String>("libraries")
.map(|vals| vals.cloned().collect());

let mut pc = 1_u64;
let progress_mode: ProgressMode = ProgressMode::GenerateBuildCache;

// Bytecode and Immutable check
print_progress("Compiling local bytecode.", &mut pc, &progress_mode);

let build_cache_path = ProjectInfo::compile(project, env, &artifacts_path)?;
let build_cache_path = ProjectInfo::compile(project, env, &artifacts_path, libraries)?;

println!("Build Cache: {}", build_cache_path.display());
exit(0);
Expand All @@ -1583,6 +1609,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> {
let project = sub_m.get_one::<PathBuf>("project").unwrap();
let artifacts = sub_m.get_one::<String>("artifacts").unwrap();
let artifacts_path = get_project_paths(project, artifacts);
let libraries = sub_m
.get_many::<String>("libraries")
.map(|vals| vals.cloned().collect());

let contract_name = sub_m.get_one::<String>("contractname").unwrap().to_string();
let address = sub_m.get_one::<Address>("address").unwrap();
Expand All @@ -1607,8 +1636,14 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> {
// Bytecode and Immutable check
print_progress("Compiling local bytecode.", &mut pc, &progress_mode);

let mut project_info =
ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache)?;
let mut project_info = ProjectInfo::new(
&contract_name,
project,
env,
&artifacts_path,
build_cache,
libraries,
)?;

print_progress("Comparing bytecode.", &mut pc, &progress_mode);
let factory_mode = sub_m.get_flag("factory");
Expand Down Expand Up @@ -1647,6 +1682,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> {
let project = sub_m.get_one::<PathBuf>("project").unwrap();
let artifacts = sub_m.get_one::<String>("artifacts").unwrap();
let artifacts_path = get_project_paths(project, artifacts);
let libraries = sub_m
.get_many::<String>("libraries")
.map(|vals| vals.cloned().collect());

let contract_name = sub_m.get_one::<String>("contractname").unwrap().to_string();
let build_cache = sub_m.get_one::<String>("buildcache");
Expand All @@ -1655,8 +1693,14 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> {
let progress_mode: ProgressMode = ProgressMode::ListEvents;

print_progress("Compiling local bytecode.", &mut pc, &progress_mode);
let project_info =
ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache)?;
let project_info = ProjectInfo::new(
&contract_name,
project,
env,
&artifacts_path,
build_cache,
libraries,
)?;

let mut event_table = Table::new();
for event in project_info.events {
Expand Down
42 changes: 42 additions & 0 deletions tests/Contracts/script/Deploy_LinkedLibraries.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
# deploy.sh

set -e

ANVIL_DEF_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
RPC_URL=$1

LIB_ADDR1=$(forge create src/linked_libraries/SimpleMath.sol:SimpleMath \
--private-key $ANVIL_DEF_PRIVATE_KEY \
--rpc-url $RPC_URL \
--chain-id 31337 \
--broadcast \
--json | jq -r .deployedTo)

echo "Deployed SimpleMath to: $LIB_ADDR1"

LIB_ADDR2=$(forge create src/linked_libraries/SimpleNumber.sol:SimpleNumber \
--private-key $ANVIL_DEF_PRIVATE_KEY \
--rpc-url $RPC_URL \
--chain-id 31337 \
--broadcast \
--json | jq -r .deployedTo)

echo "Deployed SimpleNumber to: $LIB_ADDR2"

# Step 3: Run the Solidity script to deploy Calculator
CALCULATOR_ADDR=$(forge create src/linked_libraries/Calculator.sol:Calculator \
--private-key $ANVIL_DEF_PRIVATE_KEY \
--rpc-url $RPC_URL \
--chain-id 31337 \
--libraries src/linked_libraries/SimpleMath.sol:SimpleMath:$LIB_ADDR1 \
--libraries src/linked_libraries/SimpleNumber.sol:SimpleNumber:$LIB_ADDR2 \
--broadcast \
--json | jq -r .deployedTo)

echo "Deployed Calculator to: $CALCULATOR_ADDR"

# clean up because we want to test that the DV tool is able to generate the build files
forge clean


18 changes: 18 additions & 0 deletions tests/Contracts/src/linked_libraries/Calculator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./SimpleMath.sol";
import "./SimpleNumber.sol";

contract Calculator {
/**
* @dev Calculates the sum of two numbers using the SimpleMath library.
*/
function calculateSum(uint256 a, uint256 b) public pure returns (uint256) {
return SimpleMath.add(a, b);
}

function getSimpleNumber() public pure returns (uint256) {
return SimpleNumber.number();
}
}
11 changes: 11 additions & 0 deletions tests/Contracts/src/linked_libraries/SimpleMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

library SimpleMath {
/**
* @dev Returns the sum of two unsigned integers.
*/
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
}
11 changes: 11 additions & 0 deletions tests/Contracts/src/linked_libraries/SimpleNumber.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

library SimpleNumber {
/**
* @dev Returns simple number
*/
function number() public pure returns (uint256) {
return 42;
}
}
27 changes: 27 additions & 0 deletions tests/expected_dvfs/LinkedLibraries.dvf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"version": "0.9.1",
"id": "0x8cb6eba76dc9103ef653730f5c4431b0b8bcfa0f08166a677e38084ea6a23e2c",
"contract_name": "Calculator",
"address": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0",
"chain_id": 31337,
"deployment_block_num": 4,
"init_block_num": 4,
"deployment_tx": "0xfb42c08715bcd72f549378cb69613499ecabb8b9bce4054237aa82213ca0a15b",
"codehash": "0x1f193068755fd35acee5dd20113cc9f941ba09a7fd34e5f8e45163cfcacb97c5",
"insecure": false,
"immutables": [],
"constructor_args": [],
"critical_storage_variables": [],
"critical_events": [],
"unvalidated_metadata": {
"author_name": "Author",
"description": "System Description",
"hardfork": [
"paris",
"shanghai"
],
"audit_report": "https://example.org/report.pdf",
"source_url": "https://github.com/source/code",
"security_contact": "security@example.org"
}
}
1 change: 1 addition & 0 deletions tests/test_decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod tests {
Environment::Foundry,
PathBuf::from("").as_path(),
None,
None,
)
.unwrap();
let pretty_printer = PrettyPrinter::new(&empty_config, None);
Expand Down
Loading
Loading