Skip to content

Commit

Permalink
Binding rules to support contract inheritance (NomicFoundation#1075)
Browse files Browse the repository at this point in the history
This PR adds binding rules to resolve contract inheritance and virtual
method lookups (both direct and via `super`). It does that by
introducing a generalized strategy to deal with references that resolve
to multiple definitions. It ranks the definitions according to selector
tags found in the definitions. Currently implements two ranking
algorithms:

- A simple one, for aliasing definitions such as import aliases for
symbols. In this case, the aliased definition is marked down,
prioritizing the actual definitions the aliases point to.
- One based on C3 linearisation, that can resolve calls to virtual
methods in a hierarchy of contracts (either directly or via `super`).

For the second algorithm, a substantial amount of extra infrastructure
is added to the stack graph, in the form of extra attributes that the
bindings system tracks. The newly introduced graph attributes are:

- `tag` to mark definitions and references with a tag to change the
behaviour of the ranking algorithm.
- `parents` which is applicable to both definitions and references,
contain an ordered list of definitions and references to construct a
hierarchy. This is relevant in contract definitions, to enumerate the
base contracts through their references in the inheritance list, and in
function call expressions, to indicate from which contract they are
being done.
- `export_node` applies to contract definitions and provides a
connection point for parent contracts to access virtual members defined
in derived contracts.
- `imports_node` also applies to contract definitions and provide the
analogous connection point used for base contracts.

These last two attributes are used in conjunction with a new property of
the bindings module `context` which can be used to indicate the resolver
what is the current binding context (ie. what contract is being
compiled). With virtual methods, the actual definition of a reference
can only be correctly resolved knowing the full inheritance hierarchy of
the contract being compiled. When setting the context, the bindings
module with patch its internal stack graph and create new links between
the context contract and all the parents in its hierarchy. These are
"reverse" links, in the sense that allow a reference in a base contract
to resolve to a definition in a sub-contract down the hierarchy.

All this machinery (parent links, context and import/export nodes)
provide the necessary information for a reference to resolve to all
possible definitions in the compilation contract hierarchy. Then, the C3
linearisation ranking algorithm helps decide which one to choose.

In particular, for Solidity:
- Contract definitions are linked to their bases via the `parents`
attribute.
- All contract functions definitions are linked to the contract via the
`parents` attribute, and are also `tag`ged with the value `c3`.
- Function call references that occur in contract functions are also
linked to the enclosing contract via the `parents` attribute.
- Super call references are `tag`ged with the value `super`.
- All contracts have `export_node` set to its `@contract.members` scope
node.
- All contracts have `imports_node` set to both its
`@contract.lexical_scope` and a `super_import` (which is similar to
`@contract.super_scope`) scope nodes.
- Import alias definitions are `tag`ged with the value `alias`.

Issues remaining:
- [x] Generalize the detection of `super` calls. Currently that is done
crudely by inspecting the stack graph path from the reference to the
definition and searching for a push node with the symbol `super`.
- [x] Decide what to return when there are multiple definitions and the
ranking algorithm cannot disambiguate them. Currently we panic and crash
the program, which should be fine for well-formed Solidity programs.
Probably makes sense to return a `Result<Definition, ResolutionError>`
instead of an `Option<Definition>`.
  • Loading branch information
ggiraldez authored Sep 13, 2024
1 parent dd20a02 commit 2924b53
Show file tree
Hide file tree
Showing 49 changed files with 2,447 additions and 393 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/infra/utils/src/codegen/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn generate_header(file_path: &Path) -> String {
"ebnf" => format!("(* {warning_line} *)"),
"json" => String::new(),
"html" | "md" => format!("<!-- {warning_line} -->"),
"js" | "rs" | "ts" | "wit" => format!("// {warning_line}"),
"dot" | "js" | "rs" | "ts" | "wit" => format!("// {warning_line}"),
"yml" | "txt" => format!("# {warning_line}"),
"mmd" => format!("%% {warning_line}"),
ext => panic!("Unsupported extension to generate a header for: {ext}"),
Expand All @@ -53,7 +53,7 @@ fn run_formatter(file_path: &Path, contents: &str) -> Result<String> {
// We already generate formatted content for these, so no need to run expensive formatting.
Ok(contents.to_owned())
}
"ebnf" | "mmd" | "txt" | "wit" => {
"dot" | "ebnf" | "mmd" | "txt" | "wit" => {
// No formatters available for these (yet).
Ok(contents.to_owned())
}
Expand Down
170 changes: 78 additions & 92 deletions crates/metaslang/bindings/generated/public_api.txt

Large diffs are not rendered by default.

Loading

0 comments on commit 2924b53

Please sign in to comment.