Skip to content

Commit 690087f

Browse files
9547DaniPopes
andauthored
feat: add linking error (#9610)
* feat(linking): add a linking error code Signed-off-by: 9547 <[email protected]> * test linking failure Signed-off-by: 9547 <[email protected]> * track link error for all libs Signed-off-by: 9547 <[email protected]> * using the library F? Signed-off-by: 9547 <[email protected]> * fix * apply existing lib link * fix Signed-off-by: 9547 <[email protected]> * create2 check link fully Signed-off-by: 9547 <[email protected]> * clippy Signed-off-by: 9547 <[email protected]> * multi runner err check Signed-off-by: 9547 <[email protected]> * check linkable first Signed-off-by: 9547 <[email protected]> * fix Signed-off-by: 9547 <[email protected]> * rename Signed-off-by: 9547 <[email protected]> --------- Signed-off-by: 9547 <[email protected]> Co-authored-by: DaniPopes <[email protected]>
1 parent 5e3e557 commit 690087f

File tree

2 files changed

+60
-3
lines changed

2 files changed

+60
-3
lines changed

crates/forge/src/multi_runner.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,26 +506,28 @@ impl MultiContractRunnerBuilder {
506506
linker.contracts.keys(),
507507
)?;
508508

509-
let linked_contracts = linker.get_linked_artifacts(&libraries)?;
509+
let linked_contracts = linker.get_linked_artifacts_cow(&libraries)?;
510510

511511
// Create a mapping of name => (abi, deployment code, Vec<library deployment code>)
512512
let mut deployable_contracts = DeployableContracts::default();
513513

514514
for (id, contract) in linked_contracts.iter() {
515-
let Some(abi) = &contract.abi else { continue };
515+
let Some(abi) = contract.abi.as_ref() else { continue };
516516

517517
// if it's a test, link it and add to deployable contracts
518518
if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true)
519519
&& abi.functions().any(|func| func.name.is_any_test())
520520
{
521+
linker.ensure_linked(contract, id)?;
522+
521523
let Some(bytecode) =
522524
contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty())
523525
else {
524526
continue;
525527
};
526528

527529
deployable_contracts
528-
.insert(id.clone(), TestContract { abi: abi.clone(), bytecode });
530+
.insert(id.clone(), TestContract { abi: abi.clone().into_owned(), bytecode });
529531
}
530532
}
531533

crates/linking/src/lib.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ pub enum LinkerError {
2929
InvalidAddress(<Address as std::str::FromStr>::Err),
3030
#[error("cyclic dependency found, can't link libraries via CREATE2")]
3131
CyclicDependency,
32+
#[error("failed linking {artifact}")]
33+
LinkingFailed { artifact: String },
3234
}
3335

3436
pub struct Linker<'a> {
@@ -265,6 +267,30 @@ impl<'a> Linker<'a> {
265267
Ok(contract)
266268
}
267269

270+
/// Ensures that both initial and deployed bytecode are linked.
271+
pub fn ensure_linked(
272+
&self,
273+
contract: &CompactContractBytecodeCow<'a>,
274+
target: &ArtifactId,
275+
) -> Result<(), LinkerError> {
276+
if let Some(bytecode) = &contract.bytecode
277+
&& bytecode.object.is_unlinked()
278+
{
279+
return Err(LinkerError::LinkingFailed {
280+
artifact: target.source.to_string_lossy().into(),
281+
});
282+
}
283+
if let Some(deployed_bytecode) = &contract.deployed_bytecode
284+
&& let Some(deployed_bytecode_obj) = &deployed_bytecode.bytecode
285+
&& deployed_bytecode_obj.object.is_unlinked()
286+
{
287+
return Err(LinkerError::LinkingFailed {
288+
artifact: target.source.to_string_lossy().into(),
289+
});
290+
}
291+
Ok(())
292+
}
293+
268294
pub fn get_linked_artifacts(
269295
&self,
270296
libraries: &Libraries,
@@ -685,4 +711,33 @@ mod tests {
685711
);
686712
});
687713
}
714+
715+
#[test]
716+
fn linking_failure() {
717+
let linker = LinkerTest::new("../../testdata/default/linking/simple", true);
718+
let linker_instance =
719+
Linker::new(linker.project.root(), linker.output.artifact_ids().collect());
720+
721+
// Create a libraries object with an incorrect library name that won't match any references
722+
let mut libraries = Libraries::default();
723+
libraries.libs.entry("default/linking/simple/Simple.t.sol".into()).or_default().insert(
724+
"NonExistentLib".to_string(),
725+
"0x5a443704dd4b594b382c22a083e2bd3090a6fef3".to_string(),
726+
);
727+
728+
// Try to link the LibraryConsumer contract with incorrect library
729+
let artifact_id = linker_instance
730+
.contracts
731+
.keys()
732+
.find(|id| id.name == "LibraryConsumer")
733+
.expect("LibraryConsumer contract not found");
734+
735+
let contract = linker_instance.contracts.get(artifact_id).unwrap();
736+
737+
// Verify that the artifact has unlinked bytecode
738+
assert!(
739+
linker_instance.ensure_linked(contract, artifact_id).is_err(),
740+
"Expected artifact to have unlinked bytecode"
741+
);
742+
}
688743
}

0 commit comments

Comments
 (0)