Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/cvl/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ expr.md
statements.md
imports.md
using.md
linking.md
methods.md
rules.md
builtin.md
Expand Down
348 changes: 348 additions & 0 deletions docs/cvl/linking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
(linking)=
`Links` Block
===========

The `links` block allows you to declare contract linking directly in your CVL
specification, replacing the {ref}`--link` and {ref}`--struct_link` conf file
attributes. Each entry in the `links` block tells the Prover that a particular
storage location holding an address should be resolved to a specific contract
instance in the {term}`scene`.

The `links` block supports linking simple scalar fields, struct fields, array
elements, mapping entries, immutable variables, and arbitrary nesting of these.
Entries can target a single contract or a list of possible contracts, and can
use wildcard indices to cover all keys of a mapping or all elements of an array.

```{contents}
```

Syntax
------

The syntax for the `links` block is given by the following [EBNF grammar](ebnf-syntax):

```
links_block ::= "links" "{" { link_entry } "}"

link_entry ::= link_path "=>" id ";"
| link_path "=>" "[" id { "," id } "]" ";"

link_path ::= id "." id { link_segment }

link_segment ::= "." id
| "[" index_expr "]"

index_expr ::= number
| "to_bytes" number "(" number ")"
| id
| "_"
```

See {ref}`identifiers` for the `id` production, and {doc}`expr` for the
`number` production.

The first `id` in a `link_path` is the contract alias (introduced via a
{ref}`using statement <using-stmt>`), and the second `id` is the name of a
storage variable in that contract.


(basic-linking)=
Basic Linking
-------------

The simplest form of linking maps a storage variable that holds an address to a
contract instance in the scene. This is equivalent to using the {ref}`--link`
conf file attribute.

Given a contract with a storage variable `token` of type `address` (or a
contract type like `IERC20`):

```solidity
contract Pool {
IERC20 public token;
}
```

You can link it in your spec:

```cvl
using Pool as pool;
using TokenImpl as tokenImpl;

links {
pool.token => tokenImpl;
}
```

This is equivalent to passing `--link Pool:token=TokenImpl` on the command line
or in the conf file.


(multi-target-linking)=
Multi-Target Dispatch
---------------------

When a storage location could resolve to one of several contracts, you can
specify a list of possible targets using square brackets:

```cvl
links {
pool.token => [tokenA, tokenB];
}
```

This tells the Prover that `pool.token` could be the address of either
`tokenA` or `tokenB`, and it will consider both possibilities when resolving
calls through that field.

Multi-target entries are particularly useful when combined with array or mapping
linking. For example, to verify a contract that holds two tokens which may or
may not be the same:

```cvl
links {
main.tokens[0] => tokenA;
main.tokens[1] => [tokenA, tokenB];
}
```

This allows the Prover to explore both the case where both elements point to
the same contract and the case where they point to different contracts.
Comment on lines +109 to +110
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we want to state here that this is similar to a dispatch list? Should we recommend one over the other?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think that might just be more confusing...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You might be right, let's keep it simple.



(struct-linking)=
Struct Field Linking
--------------------

To link an address field inside a struct, use dot notation to navigate into the
struct. This replaces the {ref}`--struct_link` CLI flag.

```solidity
contract Main {
struct TokenHolder {
IERC20 token;
}
TokenHolder public holder;
}
```

```cvl
links {
main.holder.token => tokenImpl;
}
```

This works with arbitrarily nested structs:

```cvl
links {
main.wrapper.inner.token => tokenImpl;
}
```

Unlike `--struct_link`, the `links` block targets a specific storage variable
and struct path, so there is no risk of accidentally linking the same field name
across unrelated struct types.

```{note}
The `--struct_link` flag matches a field name across all structs in the contract,
including struct values inside mappings and arrays. For example, if a contract
has `mapping(uint => MyStruct) myMapping` where `MyStruct` has an `address`
field called `token`, then `--struct_link C:token=TokenA` would link the `token`
field in every `MyStruct` value in the mapping. The equivalent in the `links`
block would be `c.myMapping[_].token => tokenA;`.
```


(array-linking)=
Array Linking
-------------

You can link elements of both static and dynamic arrays using index notation.

### Static arrays

```solidity
contract Main {
IERC20[3] public fixedTokens;
}
```

```cvl
links {
main.fixedTokens[0] => tokenA;
main.fixedTokens[1] => tokenB;
main.fixedTokens[2] => tokenC;
}
```

### Dynamic arrays

```solidity
contract Main {
IERC20[] public tokens;
}
```

```cvl
links {
main.tokens[0] => tokenA;
main.tokens[1] => tokenB;
}
```

```{note}
For dynamic arrays with concrete index links, the linking is conditional on the
array being long enough. For example, linking `main.tokens[1] => tokenA`
causes the Prover to assume `tokens.length > 1 => main.tokens[1] == tokenA`.
If the array is shorter than the linked index, no assumption is made about that
element.
```


(mapping-linking)=
Mapping Linking
---------------

You can link entries of mappings by specifying the key in square brackets.

### Numeric keys

```cvl
links {
main.tokenMap[0] => tokenA;
main.tokenMap[1] => tokenB;
}
```

### Byte cast keys

For mappings with `bytesN` keys, use the `to_bytesN(...)` cast:

```cvl
links {
main.bytes4Map[to_bytes4(0x12345678)] => tokenA;
}
```

### Contract alias keys

For mappings with `address` keys, you can use a contract alias as the key:

```cvl
links {
main.addrMap[tokenA] => tokenC;
}
```

Here, the address of the `tokenA` contract instance is used as the mapping key.


(wildcard-linking)=
Wildcard Indices
----------------

Use `_` as a wildcard index to link all elements of an array or all entries of a
mapping to the same target(s):

```cvl
links {
main.tokenMap[_] => tokenA;
}
```

This means that for any key, the value of `tokenMap` is linked to `tokenA`.

### Wildcard precedence

When both concrete and wildcard entries exist for the same path, concrete
entries take precedence. For example:

```cvl
links {
main.tokens[0] => tokenB;
main.tokens[_] => tokenA;
}
```

Here, `main.tokens[0]` resolves to `tokenB`, while all other indices resolve to
`tokenA`.

```{caution}
You cannot mix concrete and wildcard indices in the same entry when there are
multiple levels of indexing. For example, `main.nested[0][_]` is not allowed.
Each entry must use either all concrete indices or all wildcard indices.
```


(nested-linking)=
Nested Structures
-----------------

The `links` block supports arbitrary nesting of structs, arrays, and mappings:

```cvl
links {
// Array element within a struct
main.arrayHolder.tokens[0] => tokenC;

// Struct within an array
main.structItems[0].token => tokenA;

// Struct value within a mapping
main.structMap[0].token => tokenC;
}
```


(immutable-linking)=
Immutable Linking
-----------------

Immutable variables can also be linked:

```cvl
links {
main.immutableToken => tokenB;
main.immutableTokenMulti => [tokenA, tokenC];
}
```


(linking-requirements)=
Requirements and Limitations
----------------------------

```{important}
The `links` block requires storage layout information from the Solidity
compiler. This information is only available for contracts compiled with
**Solidity version 0.5.13 or later**.
```

- Library contracts cannot be linked.
- The `links` block and the {ref}`--link` / {ref}`--struct_link` CLI flags are
**mutually exclusive** for the same contract. You cannot use both in the
same verification run.


(linking-migration)=
Migrating from CLI Flags
-------------------------

If you are currently using `--link` or `--struct_link`, you can migrate to the
`links` block as follows:

| CLI Flag | `links` Block Equivalent |
| --- | --- |
| `--link Pool:token=TokenImpl` | `pool.token => tokenImpl;` |
| `--struct_link Pool:field=TokenImpl` | One entry per storage variable containing a struct with that field, using the full path. For example, `pool.holder.field => tokenImpl;` for a direct struct, or `pool.myMapping[_].field => tokenImpl;` for struct values inside a mapping or array. |

The `links` block provides several advantages over the CLI flags:

- **Type-checked:** The Prover validates that the paths and targets in the
`links` block are well-typed.
- **Precise struct linking:** Unlike `--struct_link`, which applies to all
structs with a matching field name, the `links` block targets a specific
storage path.
- **Richer linking:** Support for arrays, mappings, wildcards, and multi-target
dispatch that are not available through CLI flags.
3 changes: 3 additions & 0 deletions docs/cvl/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ A spec may contain any of the following:
- **[Using statements](using):** Using statements allow a specification to reference
multiple contracts.

- **{ref}`Links blocks <linking>`:** `links` blocks declare contract linking inline in the
spec, mapping storage variables holding addresses to specific contract instances.

- **[Methods blocks](methods):** `methods` blocks contain information on how methods
should be summarized by the Prover during verification.

Expand Down
14 changes: 14 additions & 0 deletions docs/prover/cli/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1939,6 +1939,13 @@ _Configuration file_
<contract>:<slot>=<address>
```

```{note}
The recommended approach is to declare linking inline in your spec using a
{ref}`links block <linking>`, which provides type checking, precise struct
paths, and support for arrays, mappings, and wildcards. The `links` block and
`--link` flag cannot be used together in the same Prover run.
```

**What does it do?**
Links a slot in a contract with another contract.

Expand Down Expand Up @@ -2158,6 +2165,13 @@ struct ReserveTokens {
... then the `struct_link` setting above would result in the same fixed address values for `tokenA` and `tokenB` in instances of this struct, which is likely an unintended constraint. Similarly, structs that are values in a mapping or array will _all_ get the same address linkage.
````

```{note}
Consider using a {ref}`links block <linking>` in your spec instead of
`--struct_link`. The `links` block targets specific storage paths and avoids the
ambiguity of matching all structs with the same field name. The `links` block and
`--struct_link` flag cannot be used together for the same contract.
```


Options for job metadata and dashboard filtering
================================================
Expand Down
Loading
Loading