Skip to content
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(forge): add forge selectors collision cmd #5116

Merged
merged 10 commits into from
Jun 17, 2023

Conversation

lmanini
Copy link
Contributor

@lmanini lmanini commented Jun 7, 2023

Motivation

Follow up PR for #5012.

Solution

Adds a subcommand to forge selectors to check for methods with colliding selectors between two contracts.
The output format is similar to that of forge inspect <contract> mi but it could still be improved if you guys dislike the current format.

To test this I've created a forge repo in the project, added Proxy.sol and Token.sol : gist.
Running forge selectors collision Token Proxy prints:

"42966c68": ("burn(uint256)", "collate_propagate_storage(bytes16)")
"9ce110d7": ("initializer()", "initializer()")
"06fdde03": ("name()", "name()")
"95d89b41": ("symbol()", "symbol()")
Error: 
At least one collision was found

and running forge selectors collision Proxy Counter prints:

No colliding method selectors between the two contracts.

Copy link
Member

@Evalir Evalir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This overall looks good! Just a few comments, mainly bikeshedding on the way we handle the output:

let second_method_map = second_artifact.method_identifiers.as_ref().unwrap();

let mut colliding_methods = Vec::new();
for (k1, v1) in first_method_map {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is ok, but maybe we could do this in a more rusty/functional way? something like filtering whichever method map for items contained in the second and collecting this into another vector.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good! will rewrite this with a functional construct 👍

Comment on lines 167 to 171
colliding_methods.iter().for_each(|t| {
println!("\"{}\": {:?}", first_method_map.get(t.0).unwrap(), t)
});
return Err(eyre::eyre!("At least one collision was found"))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I was thinking, maybe this command doesn't need to error on here? We can color the output with Paint::red for saying we found collisions, and then maybe pretty print on a table instead. wdyt @mds1 / @mattsse ?

Copy link
Collaborator

@mds1 mds1 Jun 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a common use of this command is to run it in CI to make sure you don't merge anything with selector collisions, so my suggestion would be to either:

  1. Have this command always exit with an error (i.e. std::process::exit(1)) if a collision is found, OR
  2. Only exit with an error if a --check flag is added

Currently we do both depending on the command,e.g. forge build --sizes exits with an error if anything is over the size limit, but forge fmt needs a --check flag. I can see an argument that the former should also have a --check flag, so I'm indifferent here and maybe we start requiring the --check flag as a standard for commands like this where it makes sense?

Also, I'm now realizing this may all be tangential to returning an Err from this function 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more lenient towards adding the --check flag so that we can support both of your guys' visions.
Regarding the pretty print on a table, yesterday I was looking at prettytable-rs but idk what you guys think about adding it as a dep.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other parts of the code use comfy-table to pretty-print stuff, that's what forge build --sizes uses, I think forge inspect too


/// Support build args
#[clap(flatten)]
build: Box<CoreBuildArgs>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this box necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust won't complain without it, but clippy does as without the two elements of the SelectorsSubcommands enum differ quite a bit in size

@lmanini
Copy link
Contributor Author

lmanini commented Jun 9, 2023

@Evalir I refactored the colliding_methods vector population and improved the output formatting - standing by for comments and tips on how to move regarding throwing an error or not on finding a collision :)

This is what the table looks like when a collision is found:

+----------+---------------+------------------------------------+
| Selector | Token         | Proxy                              |
+===============================================================+
| 42966c68 | burn(uint256) | collate_propagate_storage(bytes16) |
|----------+---------------+------------------------------------|
| 9ce110d7 | initializer() | initializer()                      |
|----------+---------------+------------------------------------|
| 06fdde03 | name()        | name()                             |
|----------+---------------+------------------------------------|
| 95d89b41 | symbol()      | symbol()                           |
+----------+---------------+------------------------------------+
Error: 
At least one collision was found

Copy link
Member

@Evalir Evalir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this lgtm pending the comment! honestly if we find collisions i would just not return the Err, just print the table along with a "Collisions found" on top

});
println!("{table}");

return Err(eyre::eyre!("At least one collision was found"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so what I'd do for this command is just not return an error at all—printing the table if we find collisions preceded by x collisions found is good enough for me. I'd only return an error if the command failed internally, as in building the project or trying to extract the selectors

Copy link
Member

@Evalir Evalir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aight! just pushed the last change—looks good! cc @mattsse for final approval/merge

@Evalir Evalir requested a review from mattsse June 17, 2023 02:10
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice job

lgtm

@mattsse mattsse merged commit cf104a5 into foundry-rs:master Jun 17, 2023
@lmanini lmanini deleted the lmanini/selectors-collision branch June 29, 2023 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants