|
| 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. |
0 commit comments