Skip to content

Commit f47ea03

Browse files
committed
document links block
1 parent a0cf92f commit f47ea03

File tree

5 files changed

+381
-0
lines changed

5 files changed

+381
-0
lines changed

docs/cvl/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ expr.md
1616
statements.md
1717
imports.md
1818
using.md
19+
linking.md
1920
methods.md
2021
rules.md
2122
builtin.md

docs/cvl/linking.md

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
(linking)=
2+
`Links` Block
3+
===========
4+
5+
The `links` block allows you to declare contract linking directly in your CVL
6+
specification, replacing the {ref}`--link` and {ref}`--struct_link` CLI flags.
7+
Each entry in the `links` block tells the Prover that a particular storage
8+
location holding an address should be resolved to a specific contract instance
9+
in the {term}`scene`.
10+
11+
The `links` block supports linking simple scalar fields, struct fields, array
12+
elements, mapping entries, immutable variables, and arbitrary nesting of these.
13+
Entries can target a single contract or a list of possible contracts, and can
14+
use wildcard indices to cover all keys of a mapping or all elements of an array.
15+
16+
```{contents}
17+
```
18+
19+
Syntax
20+
------
21+
22+
The syntax for the `links` block is given by the following [EBNF grammar](ebnf-syntax):
23+
24+
```
25+
links_block ::= "links" "{" { link_entry } "}"
26+
27+
link_entry ::= link_path "=>" id ";"
28+
| link_path "=>" "[" id { "," id } "]" ";"
29+
30+
link_path ::= id "." id { link_segment }
31+
32+
link_segment ::= "." id
33+
| "[" index_expr "]"
34+
35+
index_expr ::= number
36+
| "to_bytes" number "(" number ")"
37+
| id
38+
| "_"
39+
```
40+
41+
See {ref}`identifiers` for the `id` production, and {doc}`expr` for the
42+
`number` production.
43+
44+
The first `id` in a `link_path` is the contract alias (introduced via a
45+
{ref}`using statement <using-stmt>`), and the second `id` is the name of a
46+
storage variable in that contract.
47+
48+
49+
(basic-linking)=
50+
Basic Linking
51+
-------------
52+
53+
The simplest form of linking maps a storage variable that holds an address to a
54+
contract instance in the scene. This is equivalent to using the {ref}`--link`
55+
CLI flag.
56+
57+
Given a contract with a storage variable `token` of type `address` (or a
58+
contract type like `IERC20`):
59+
60+
```solidity
61+
contract Pool {
62+
IERC20 public token;
63+
}
64+
```
65+
66+
You can link it in your spec:
67+
68+
```cvl
69+
using Pool as pool;
70+
using TokenImpl as tokenImpl;
71+
72+
links {
73+
pool.token => tokenImpl;
74+
}
75+
```
76+
77+
This is equivalent to passing `--link Pool:token=TokenImpl` on the command line
78+
or in the conf file.
79+
80+
81+
(multi-target-linking)=
82+
Multi-Target Dispatch
83+
---------------------
84+
85+
When a storage location could resolve to one of several contracts, you can
86+
specify a list of possible targets using square brackets:
87+
88+
```cvl
89+
links {
90+
pool.token => [tokenA, tokenB];
91+
}
92+
```
93+
94+
This tells the Prover that `pool.token` could be the address of either
95+
`tokenA` or `tokenB`, and it will consider both possibilities when resolving
96+
calls through that field.
97+
98+
Multi-target entries are particularly useful when combined with array or mapping
99+
linking. For example, to verify a contract that holds two tokens which may or
100+
may not be the same:
101+
102+
```cvl
103+
links {
104+
main.tokens[0] => tokenA;
105+
main.tokens[1] => [tokenA, tokenB];
106+
}
107+
```
108+
109+
This allows the Prover to explore both the case where both elements point to
110+
the same contract and the case where they point to different contracts.
111+
112+
113+
(struct-linking)=
114+
Struct Field Linking
115+
--------------------
116+
117+
To link an address field inside a struct, use dot notation to navigate into the
118+
struct. This replaces the {ref}`--struct_link` CLI flag.
119+
120+
```solidity
121+
contract Main {
122+
struct TokenHolder {
123+
IERC20 token;
124+
}
125+
TokenHolder public holder;
126+
}
127+
```
128+
129+
```cvl
130+
links {
131+
main.holder.token => tokenImpl;
132+
}
133+
```
134+
135+
This works with arbitrarily nested structs:
136+
137+
```cvl
138+
links {
139+
main.wrapper.inner.token => tokenImpl;
140+
}
141+
```
142+
143+
Unlike `--struct_link`, the `links` block targets a specific storage variable
144+
and struct path, so there is no risk of accidentally linking the same field name
145+
across unrelated struct types.
146+
147+
```{note}
148+
The `--struct_link` flag matches a field name across all structs in the contract,
149+
including struct values inside mappings and arrays. For example, if a contract
150+
has `mapping(uint => MyStruct) myMapping` where `MyStruct` has an `address`
151+
field called `token`, then `--struct_link C:token=TokenA` would link the `token`
152+
field in every `MyStruct` value in the mapping. The equivalent in the `links`
153+
block would be `c.myMapping[_].token => tokenA;`.
154+
```
155+
156+
157+
(array-linking)=
158+
Array Linking
159+
-------------
160+
161+
You can link elements of both static and dynamic arrays using index notation.
162+
163+
### Static arrays
164+
165+
```solidity
166+
contract Main {
167+
IERC20[3] public fixedTokens;
168+
}
169+
```
170+
171+
```cvl
172+
links {
173+
main.fixedTokens[0] => tokenA;
174+
main.fixedTokens[1] => tokenB;
175+
main.fixedTokens[2] => tokenC;
176+
}
177+
```
178+
179+
### Dynamic arrays
180+
181+
```solidity
182+
contract Main {
183+
IERC20[] public tokens;
184+
}
185+
```
186+
187+
```cvl
188+
links {
189+
main.tokens[0] => tokenA;
190+
main.tokens[1] => tokenB;
191+
}
192+
```
193+
194+
```{note}
195+
For dynamic arrays with concrete index links, the linking is conditional on the
196+
array being long enough. For example, linking `main.tokens[1] => tokenA`
197+
causes the Prover to assume `tokens.length > 1 => main.tokens[1] == tokenA`.
198+
If the array is shorter than the linked index, no assumption is made about that
199+
element.
200+
```
201+
202+
203+
(mapping-linking)=
204+
Mapping Linking
205+
---------------
206+
207+
You can link entries of mappings by specifying the key in square brackets.
208+
209+
### Numeric keys
210+
211+
```cvl
212+
links {
213+
main.tokenMap[0] => tokenA;
214+
main.tokenMap[1] => tokenB;
215+
}
216+
```
217+
218+
### Byte cast keys
219+
220+
For mappings with `bytesN` keys, use the `to_bytesN(...)` cast:
221+
222+
```cvl
223+
links {
224+
main.bytes4Map[to_bytes4(0x12345678)] => tokenA;
225+
}
226+
```
227+
228+
### Contract alias keys
229+
230+
For mappings with `address` keys, you can use a contract alias as the key:
231+
232+
```cvl
233+
links {
234+
main.addrMap[tokenA] => tokenC;
235+
}
236+
```
237+
238+
Here, the address of the `tokenA` contract instance is used as the mapping key.
239+
240+
241+
(wildcard-linking)=
242+
Wildcard Indices
243+
----------------
244+
245+
Use `_` as a wildcard index to link all elements of an array or all entries of a
246+
mapping to the same target(s):
247+
248+
```cvl
249+
links {
250+
main.tokenMap[_] => tokenA;
251+
}
252+
```
253+
254+
This means that for any key, the value of `tokenMap` is linked to `tokenA`.
255+
256+
### Wildcard precedence
257+
258+
When both concrete and wildcard entries exist for the same path, concrete
259+
entries take precedence. For example:
260+
261+
```cvl
262+
links {
263+
main.tokens[0] => tokenB;
264+
main.tokens[_] => tokenA;
265+
}
266+
```
267+
268+
Here, `main.tokens[0]` resolves to `tokenB`, while all other indices resolve to
269+
`tokenA`.
270+
271+
```{caution}
272+
You cannot mix concrete and wildcard indices in the same entry when there are
273+
multiple levels of indexing. For example, `main.nested[0][_]` is not allowed.
274+
Each entry must use either all concrete indices or all wildcard indices.
275+
```
276+
277+
278+
(nested-linking)=
279+
Nested Structures
280+
-----------------
281+
282+
The `links` block supports arbitrary nesting of structs, arrays, and mappings:
283+
284+
```cvl
285+
links {
286+
// Array element within a struct
287+
main.arrayHolder.tokens[0] => tokenC;
288+
289+
// Struct within an array
290+
main.structItems[0].token => tokenA;
291+
292+
// Struct value within a mapping
293+
main.structMap[0].token => tokenC;
294+
}
295+
```
296+
297+
298+
(immutable-linking)=
299+
Immutable Linking
300+
-----------------
301+
302+
Immutable variables can also be linked:
303+
304+
```cvl
305+
links {
306+
main.immutableToken => tokenB;
307+
main.immutableTokenMulti => [tokenA, tokenC];
308+
}
309+
```
310+
311+
312+
(linking-requirements)=
313+
Requirements and Limitations
314+
----------------------------
315+
316+
```{important}
317+
The `links` block requires storage layout information from the Solidity
318+
compiler. This information is only available for contracts compiled with
319+
**Solidity version 0.5.13 or later**.
320+
```
321+
322+
- Library contracts cannot be linked.
323+
- The `links` block and the {ref}`--link` / {ref}`--struct_link` CLI flags are
324+
**mutually exclusive** for the same contract. You cannot use both in the
325+
same verification run.
326+
327+
328+
(linking-migration)=
329+
Migrating from CLI Flags
330+
-------------------------
331+
332+
If you are currently using `--link` or `--struct_link`, you can migrate to the
333+
`links` block as follows:
334+
335+
| CLI Flag | `links` Block Equivalent |
336+
| --- | --- |
337+
| `--link Pool:token=TokenImpl` | `pool.token => tokenImpl;` |
338+
| `--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. |
339+
340+
The `links` block provides several advantages over the CLI flags:
341+
342+
- **Type-checked:** The Prover validates that the paths and targets in the
343+
`links` block are well-typed.
344+
- **Precise struct linking:** Unlike `--struct_link`, which applies to all
345+
structs with a matching field name, the `links` block targets a specific
346+
storage path.
347+
- **Richer linking:** Support for arrays, mappings, wildcards, and multi-target
348+
dispatch that are not available through CLI flags.

docs/cvl/overview.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ A spec may contain any of the following:
1616
- **[Using statements](using):** Using statements allow a specification to reference
1717
multiple contracts.
1818

19+
- **[Links blocks](linking):** `links` blocks declare contract linking inline in the
20+
spec, mapping storage variables holding addresses to specific contract instances.
21+
1922
- **[Methods blocks](methods):** `methods` blocks contain information on how methods
2023
should be summarized by the Prover during verification.
2124

docs/prover/cli/options.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,13 @@ _Configuration file_
19661966
]
19671967
```
19681968

1969+
```{note}
1970+
You can also declare linking inline in your spec using a {ref}`links block <linking>`,
1971+
which provides type checking, precise struct paths, and support for arrays,
1972+
mappings, and wildcards. The `links` block and `--link` flag cannot be used
1973+
together for the same contract.
1974+
```
1975+
19691976
(--optimistic_contract_recursion)=
19701977
## `optimistic_contract_recursion`
19711978

@@ -2158,6 +2165,13 @@ struct ReserveTokens {
21582165
... 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.
21592166
````
21602167

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

21622176
Options for job metadata and dashboard filtering
21632177
================================================

0 commit comments

Comments
 (0)