Skip to content

Commit

Permalink
feat: Linking rewrite (#7027)
Browse files Browse the repository at this point in the history
* rewrite linking

* fix ci

* fix ci 2

* fix ci 3

* docs

* Refactor

* fix

* fix tests

* handle root path

* tests

* clippy

* tests

* Bump compilers

* review fixes

* fix Cargo.toml

* docs

* ok_or_eyre -> ok_or_else for dynamic errors

* refactor

* filter empty bytecode in scripts

* fix known_contracts for tests

* get_bytecode_bytes

* cycle lib deps

* add doc about cyclic dependencies

* add missed test file

* Update crates/forge/src/link.rs

Co-authored-by: Bjerg <onbjerg@users.noreply.github.com>

* LinkerError

* clippy

* small fix

* fmt

---------

Co-authored-by: Bjerg <onbjerg@users.noreply.github.com>
  • Loading branch information
klkvr and onbjerg authored Feb 12, 2024
1 parent 0f746d6 commit 446bba8
Show file tree
Hide file tree
Showing 8 changed files with 595 additions and 898 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ alloy-rlp = "0.3.3"
solang-parser = "=0.3.3"

## misc
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
chrono = { version = "0.4", default-features = false, features = [
"clock",
"std",
] }
color-eyre = "0.6"
derive_more = "0.99"
eyre = "0.6"
Expand Down
211 changes: 77 additions & 134 deletions crates/forge/bin/cmd/script/build.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use super::*;
use alloy_primitives::{Address, Bytes};
use eyre::{Context, ContextCompat, Result};
use forge::link::{link_with_nonce_or_address, PostLinkInput, ResolvedDependency};
use forge::link::{LinkOutput, Linker};
use foundry_cli::utils::get_cached_entry_by_name;
use foundry_common::{
compact_to_contract,
compile::{self, ContractSources, ProjectCompiler},
fs,
};
use foundry_compilers::{
artifacts::{CompactContractBytecode, ContractBytecode, ContractBytecodeSome, Libraries},
artifacts::{ContractBytecode, ContractBytecodeSome, Libraries},
cache::SolFilesCache,
contracts::ArtifactContracts,
info::ContractInfo,
ArtifactId, Project, ProjectCompileOutput,
};
use std::{collections::BTreeMap, str::FromStr};
use std::str::FromStr;

impl ScriptArgs {
/// Compiles the file or project and the verify metadata.
Expand Down Expand Up @@ -56,32 +56,39 @@ impl ScriptArgs {
})
.collect::<Result<ArtifactContracts>>()?;

let mut output = self.link(
project,
contracts,
script_config.config.parsed_libraries()?,
let target = self.find_target(&project, &contracts)?.clone();
script_config.target_contract = Some(target.clone());

let libraries = script_config.config.solc_settings()?.libraries;
let linker = Linker::new(project.root(), contracts);

let (highlevel_known_contracts, libraries, predeploy_libraries) = self.link_script_target(
&linker,
libraries,
script_config.evm_opts.sender,
script_config.sender_nonce,
target.clone(),
)?;

output.sources = sources;
script_config.target_contract = Some(output.target.clone());
let contract = highlevel_known_contracts.get(&target).unwrap();

Ok(output)
Ok(BuildOutput {
project,
linker,
contract: contract.clone(),
highlevel_known_contracts,
libraries,
predeploy_libraries,
sources,
})
}

pub fn link(
/// Tries to find artifact for the target script contract.
pub fn find_target<'a>(
&self,
project: Project,
contracts: ArtifactContracts,
libraries_addresses: Libraries,
sender: Address,
nonce: u64,
) -> Result<BuildOutput> {
let mut run_dependencies = vec![];
let mut contract = CompactContractBytecode::default();
let mut highlevel_known_contracts = BTreeMap::new();

project: &Project,
contracts: &'a ArtifactContracts,
) -> Result<&'a ArtifactId> {
let mut target_fname = dunce::canonicalize(&self.path)
.wrap_err("Couldn't convert contract path to absolute path.")?
.strip_prefix(project.root())
Expand All @@ -97,114 +104,60 @@ impl ScriptArgs {
true
};

let mut extra_info = ExtraLinkingInfo {
no_target_name,
target_fname: target_fname.clone(),
contract: &mut contract,
dependencies: &mut run_dependencies,
matched: false,
target_id: None,
};

// link_with_nonce_or_address expects absolute paths
let mut libs = libraries_addresses.clone();
for (file, libraries) in libraries_addresses.libs.iter() {
if file.is_relative() {
let mut absolute_path = project.root().clone();
absolute_path.push(file);
libs.libs.insert(absolute_path, libraries.clone());
}
}

link_with_nonce_or_address(
contracts.clone(),
&mut highlevel_known_contracts,
libs,
sender,
nonce,
&mut extra_info,
|post_link_input| {
let PostLinkInput {
contract,
known_contracts: highlevel_known_contracts,
id,
extra,
dependencies,
} = post_link_input;
let mut target = None;

fn unique_deps(deps: Vec<ResolvedDependency>) -> Vec<(String, Bytes)> {
let mut filtered = Vec::new();
let mut seen = HashSet::new();
for dep in deps {
if !seen.insert(dep.id.clone()) {
continue
}
filtered.push((dep.id, dep.bytecode));
for (id, contract) in contracts.iter() {
if no_target_name {
// Match artifact source, and ignore interfaces
if id.source == std::path::Path::new(&target_fname) &&
contract.bytecode.as_ref().map_or(false, |b| b.object.bytes_len() > 0)
{
if target.is_some() {
eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`")
}

filtered
target = Some(id);
}

// if it's the target contract, grab the info
if extra.no_target_name {
// Match artifact source, and ignore interfaces
if id.source == std::path::Path::new(&extra.target_fname) &&
contract.bytecode.as_ref().map_or(false, |b| b.object.bytes_len() > 0)
{
if extra.matched {
eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`")
}
*extra.dependencies = unique_deps(dependencies);
*extra.contract = contract.clone();
extra.matched = true;
extra.target_id = Some(id.clone());
}
} else {
let (path, name) = extra
.target_fname
.rsplit_once(':')
.expect("The target specifier is malformed.");
let path = std::path::Path::new(path);
if path == id.source && name == id.name {
*extra.dependencies = unique_deps(dependencies);
*extra.contract = contract.clone();
extra.matched = true;
extra.target_id = Some(id.clone());
}
} else {
let (path, name) =
target_fname.rsplit_once(':').expect("The target specifier is malformed.");
let path = std::path::Path::new(path);
if path == id.source && name == id.name {
target = Some(id);
}
}
}

if let Ok(tc) = ContractBytecode::from(contract).try_into() {
highlevel_known_contracts.insert(id, tc);
}

Ok(())
},
project.root(),
)?;

let target = extra_info
.target_id
.ok_or_else(|| eyre::eyre!("Could not find target contract: {}", target_fname))?;

let (new_libraries, predeploy_libraries): (Vec<_>, Vec<_>) =
run_dependencies.into_iter().unzip();
target.ok_or_else(|| eyre::eyre!("Could not find target contract: {}", target_fname))
}

// Merge with user provided libraries
let mut new_libraries = Libraries::parse(&new_libraries)?;
for (file, libraries) in libraries_addresses.libs.into_iter() {
new_libraries.libs.entry(file).or_default().extend(libraries)
}
/// Links script artifact with given libraries or library addresses computed from script sender
/// and nonce.
///
/// Populates [BuildOutput] with linked target contract, libraries, bytes of libs that need to
/// be predeployed and `highlevel_known_contracts` - set of known fully linked contracts
pub fn link_script_target(
&self,
linker: &Linker,
libraries: Libraries,
sender: Address,
nonce: u64,
target: ArtifactId,
) -> Result<(ArtifactContracts<ContractBytecodeSome>, Libraries, Vec<Bytes>)> {
let LinkOutput { libs_to_deploy, contracts, libraries } =
linker.link_with_nonce_or_address(libraries, sender, nonce, &target)?;

// Collect all linked contracts with non-empty bytecode
let highlevel_known_contracts = contracts
.iter()
.filter_map(|(id, contract)| {
ContractBytecodeSome::try_from(ContractBytecode::from(contract.clone()))
.ok()
.map(|tc| (id.clone(), tc))
})
.filter(|(_, tc)| tc.bytecode.object.is_non_empty_bytecode())
.collect();

Ok(BuildOutput {
target,
contract,
known_contracts: contracts,
highlevel_known_contracts: ArtifactContracts(highlevel_known_contracts),
predeploy_libraries,
sources: Default::default(),
project,
libraries: new_libraries,
})
Ok((highlevel_known_contracts, libraries, libs_to_deploy))
}

pub fn get_project_and_output(
Expand Down Expand Up @@ -263,20 +216,10 @@ impl ScriptArgs {
}
}

struct ExtraLinkingInfo<'a> {
no_target_name: bool,
target_fname: String,
contract: &'a mut CompactContractBytecode,
dependencies: &'a mut Vec<(String, Bytes)>,
matched: bool,
target_id: Option<ArtifactId>,
}

pub struct BuildOutput {
pub project: Project,
pub target: ArtifactId,
pub contract: CompactContractBytecode,
pub known_contracts: ArtifactContracts,
pub contract: ContractBytecodeSome,
pub linker: Linker,
pub highlevel_known_contracts: ArtifactContracts<ContractBytecodeSome>,
pub libraries: Libraries,
pub predeploy_libraries: Vec<Bytes>,
Expand Down
Loading

0 comments on commit 446bba8

Please sign in to comment.