-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Linking rewrite #7027
feat: Linking rewrite #7027
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new linling algorithm to be less complex and linking just one target artifact. If we need to link more artifacts, it can just be invoked several times as it's done in tests right now.
this approach is so much better
looks very promising already
let (new_libraries, predeploy_libraries): (Vec<_>, Vec<_>) = | ||
run_dependencies.into_iter().unzip(); | ||
pub fn link( | ||
&self, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need &self here or can this also be a standalone function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really need &self
here, however I believe that highlevel_known_contracts
and Libraries
logic is specific to scripts so we can make it a standalone function but still in scope of the scripts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wonder if we can give this a different name e.g link_script
for clarity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Evalir you mean separate it from ScriptArgs
and rename?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@klkvr personally don't mind either separating it or keeping it on ScriptArgs
—just think the fn name can be more specific 😄
If we can make this a standalone fn outside ScriptArgs
I think it would be nice, as it shows this is explictly a script-related helper.
crates/forge/bin/cmd/script/build.rs
Outdated
let mut new_libraries = Libraries { libs: BTreeMap::new() }; | ||
for (id, address) in libs_to_deploy.iter().chain(predeployed_libs.iter()) { | ||
new_libraries | ||
.libs | ||
.entry(id.source.clone()) | ||
.or_default() | ||
.insert(id.name.split('.').next().unwrap().to_owned(), address.to_string()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we need to deploy libraries, it might be nice to do it with create2 (if we don't already). Using create2 when a hardcoded create2 deployer is available on the chain seems preferable because then libs can be automatically reused across projects without needing to redeploy (see also: #4810)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great, some comments
let (new_libraries, predeploy_libraries): (Vec<_>, Vec<_>) = | ||
run_dependencies.into_iter().unzip(); | ||
pub fn link( | ||
&self, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wonder if we can give this a different name e.g link_script
for clarity?
Adds helper method to strip path prefixes from `Libraries` object, I'll need this for foundry-rs/foundry#7027
6cf6caa
to
f900581
Compare
Linking is a bit more complex than it has to be because of the way we handle libraries. Before passing sources into solc, we are stripping paths to start from project root (otherwise, keep global path) and once compiler finishes, we are joining all paths to start from the project root again. The issue here is that foundry/crates/forge/src/link.rs Lines 98 to 109 in 243eeee
|
UPD: I've updated linker logic to accept root path and strip all paths while matching link_references to artifacts, that way we now only assume that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the code is looking great to me bar mattsse's nits. I think what we want is probably manual testing with big projects (Maple, spark protocol) that historically have had problems with our linker. I can gather a few of these closed issues so we can test.
I also think that, for next steps, we should consider extracting the core linking code into a new crate, and maybe exposing it through a struct Linker
, instead of having multiple loose fns, some public, some private?
Yep, it makes sense, basically |
@Evalir @mattsse so I've refactored the code to have linking logic in I've tested it on Aave-like codebase with several external libraries and fould couple bugs with how we collect Regarding |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/forge/src/multi_runner.rs
Outdated
.deployed_bytecode | ||
.and_then(|d_bcode| d_bcode.bytecode) | ||
.and_then(|bcode| bcode.object.into_bytes()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be simplified with .get_deployed_bytecode_bytes().cloned()
we could also clean this up on foundry-compilers so that this function no longer returns Cow, not useful anyway
crates/forge/src/multi_runner.rs
Outdated
let Some(bytecode) = linked_contract | ||
.bytecode | ||
.and_then(|b| b.object.into_bytes()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is also linked_contract.get_bytecode_bytes()
Also closes #5014, added test for it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall looks better, although I wonder if we should get rid of eyre in the linker. imo eyre is more for CLI stuff, this should be more strongly typed. wdyt?
// user-provided paths to be able to match them correctly. | ||
let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path()); | ||
|
||
let mut needed_libraries = BTreeSet::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first I was wondering if this was safe, because you can have a library that itself is unlinked, so it's not deployable until all those unlinked references are resolved.
However at second thought, this is safe, because we know the addresses in such a scenario ahead of time, as long as it is deployed in the correct order.
I feel like we should add a note about that, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, it's safe because libraries doesn't use its dependencies during deployment
there is already a note about the deployment order, do you think it should be extended?
foundry/crates/forge/src/link.rs
Line 28 in 20aad6b
/// The order in which they appear in the vector is the order in which they should be deployed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no it's prob fine
Co-authored-by: Bjerg <onbjerg@users.noreply.github.com>
currently linker errors are more like panics and getting a error there probably means that the code invoking it didn't sanitize inputs normally, so it's not something we would need to catch but we might be adding more features to linker (like that one) and for them we might have broader errors spectrum, so yeah it probably makes sense to add errors enum |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lg tm
only rustfmt, missing then send it @klkvr 🙏 |
Motivation
The way current linking implementation works is by linking each individual artifact in an isolated way. For each artifact
post_link
hook is getting called with the following data (PostLinkInput
).id
of the linked artifactcontract
containing linked contract artifactknown_contracts
which seems to be just a mutable reference getting passed arounddependencies
- vector of libraries that needs to be deployed for the given artifactThe
dependencies
vector contents is explained here:foundry/crates/forge/src/link.rs
Lines 580 to 583 in 2cf84d9
The thing which highlights the downside of this approach is that for different artifact
dependencies
may include different libraries with different nonces. This is expected behavior but it results in verifiaction issues (#6506).Solution
Considering that current approach does not really make much sense, I've decided to implement new linling algorithm to be less complex and linking just one
target
artifact. If we need to link more artifacts, it can just be invoked several times as it's done in tests right now.I believe that one of the most sensitive places of the linker is conversion between
ArtifactId
and solc library format (src/Lib.sol:Lib
) as it's related to paths. Even though we strip paths everywhere I think there might be some edge cases here and would like to do some testing in that direction.Closes #6506
Closes #5014