File size: 30,145 Bytes
1295969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.13;

import {VmSafe} from "./Vm.sol";
import {Variable, Type, TypeKind, LibVariable} from "./LibVariable.sol";

/// @notice  A contract that parses a toml configuration file and loads its
///          variables into storage, automatically casting them, on deployment.
///
/// @dev     This contract assumes a toml structure where top-level keys
///          represent chain ids or aliases. Under each chain key, variables are
///          organized by type in separate sub-tables like `[<chain>.<type>]`, where
///          type must be: `bool`, `address`, `bytes32`, `uint`, `int`, `string`, or `bytes`.
///
///          Supported format:
///          ```
///          [mainnet]
///          endpoint_url = "${MAINNET_RPC}"
///
///          [mainnet.bool]
///          is_live = true
///
///          [mainnet.address]
///          weth = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
///          whitelisted_admins = [
///             "${MAINNET_ADMIN}",
///             "0x00000000000000000000000000000000deadbeef",
///             "0x000000000000000000000000000000c0ffeebabe"
///          ]
///
///          [mainnet.uint]
///          important_number = 123
///          ```
contract StdConfig {
    using LibVariable for Type;
    using LibVariable for TypeKind;

    VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));

    /// @dev Types: `bool`, `address`, `bytes32`, `uint`, `int`, `string`, `bytes`.
    uint8 private constant NUM_TYPES = 7;

    // -- ERRORS ---------------------------------------------------------------

    error AlreadyInitialized(string key);
    error InvalidChainKey(string aliasOrId);
    error ChainNotInitialized(uint256 chainId);
    error UnableToParseVariable(string key);
    error WriteToFileInForbiddenCtxt();

    // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------

    /// @dev Path to the loaded TOML configuration file.
    string private _filePath;

    /// @dev List of top-level keys found in the TOML file, assumed to be chain names/aliases.
    string[] private _chainKeys;

    /// @dev Storage for the configured RPC URL for each chain.
    mapping(uint256 => string) private _rpcOf;

    /// @dev Storage for values, organized by chain ID and variable key.
    mapping(uint256 => mapping(string => bytes)) private _dataOf;

    /// @dev Type cache for runtime checking when casting.
    mapping(uint256 => mapping(string => Type)) private _typeOf;

    /// @dev When enabled, `set` will always write updates back to the configuration file.
    ///      Can only be enabled in a scripting context to prevent file corruption from
    ///      concurrent I/O access, as tests run in parallel.
    bool private _writeToFile;

    // -- CONSTRUCTOR ----------------------------------------------------------

    /// @notice Reads the TOML file and iterates through each top-level key, which is
    ///         assumed to be a chain name or ID. For each chain, it caches its RPC
    ///         endpoint and all variables defined in typed sub-tables like `[<chain>.<type>]`,
    ///         where type must be: `bool`, `address`, `bytes32`, `uint`, `int`, `string`, or `bytes`.
    ///
    ///         The constructor attempts to parse each variable first as a single value,
    ///         and if that fails, as an array of that type. If a variable cannot be
    ///         parsed as either, the constructor will revert with an error.
    ///
    /// @param  configFilePath: The local path to the TOML configuration file.
    /// @param  writeToFile: Whether to write updates back to the TOML file. Only for scripts.
    constructor(string memory configFilePath, bool writeToFile) {
        if (writeToFile && !vm.isContext(VmSafe.ForgeContext.ScriptGroup)) {
            revert WriteToFileInForbiddenCtxt();
        }

        _filePath = configFilePath;
        _writeToFile = writeToFile;
        string memory content = vm.resolveEnv(vm.readFile(configFilePath));
        string[] memory chain_keys = vm.parseTomlKeys(content, "$");

        // Cache the entire configuration to storage
        for (uint256 i = 0; i < chain_keys.length; i++) {
            string memory chain_key = chain_keys[i];
            // Ignore top-level keys that are not tables
            if (vm.parseTomlKeys(content, string.concat("$.", chain_key)).length == 0) {
                continue;
            }
            uint256 chainId = resolveChainId(chain_key);
            _chainKeys.push(chain_key);

            // Cache the configured RPC endpoint for that chain.
            // Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured.
            try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (
                string memory url
            ) {
                _rpcOf[chainId] = vm.resolveEnv(url);
            } catch {
                _rpcOf[chainId] = vm.resolveEnv(vm.rpcUrl(chain_key));
            }

            // Iterate through all the available `TypeKind`s (except `None`) to create the sub-section paths
            for (uint8 t = 1; t <= NUM_TYPES; t++) {
                TypeKind ty = TypeKind(t);
                string memory typePath = string.concat("$.", chain_key, ".", ty.toTomlKey());

                try vm.parseTomlKeys(content, typePath) returns (string[] memory keys) {
                    for (uint256 j = 0; j < keys.length; j++) {
                        string memory key = keys[j];
                        if (_typeOf[chainId][key].kind == TypeKind.None) {
                            _loadAndCacheValue(content, string.concat(typePath, ".", key), chainId, key, ty);
                        } else {
                            revert AlreadyInitialized(key);
                        }
                    }
                } catch {}
            }
        }
    }

    function _loadAndCacheValue(
        string memory content,
        string memory path,
        uint256 chainId,
        string memory key,
        TypeKind ty
    ) private {
        bool success = false;
        if (ty == TypeKind.Bool) {
            try vm.parseTomlBool(content, path) returns (bool val) {
                _dataOf[chainId][key] = abi.encode(val);
                _typeOf[chainId][key] = Type(TypeKind.Bool, false);
                success = true;
            } catch {
                try vm.parseTomlBoolArray(content, path) returns (bool[] memory val) {
                    _dataOf[chainId][key] = abi.encode(val);
                    _typeOf[chainId][key] = Type(TypeKind.Bool, true);
                    success = true;
                } catch {}
            }
        } else if (ty == TypeKind.Address) {
            try vm.parseTomlAddress(content, path) returns (address val) {
                _dataOf[chainId][key] = abi.encode(val);
                _typeOf[chainId][key] = Type(TypeKind.Address, false);
                success = true;
            } catch {
                try vm.parseTomlAddressArray(content, path) returns (address[] memory val) {
                    _dataOf[chainId][key] = abi.encode(val);
                    _typeOf[chainId][key] = Type(TypeKind.Address, true);
                    success = true;
                } catch {}
            }
        } else if (ty == TypeKind.Bytes32) {
            try vm.parseTomlBytes32(content, path) returns (bytes32 val) {
                _dataOf[chainId][key] = abi.encode(val);
                _typeOf[chainId][key] = Type(TypeKind.Bytes32, false);
                success = true;
            } catch {
                try vm.parseTomlBytes32Array(content, path) returns (bytes32[] memory val) {
                    _dataOf[chainId][key] = abi.encode(val);
                    _typeOf[chainId][key] = Type(TypeKind.Bytes32, true);
                    success = true;
                } catch {}
            }
        } else if (ty == TypeKind.Uint256) {
            try vm.parseTomlUint(content, path) returns (uint256 val) {
                _dataOf[chainId][key] = abi.encode(val);
                _typeOf[chainId][key] = Type(TypeKind.Uint256, false);
                success = true;
            } catch {
                try vm.parseTomlUintArray(content, path) returns (uint256[] memory val) {
                    _dataOf[chainId][key] = abi.encode(val);
                    _typeOf[chainId][key] = Type(TypeKind.Uint256, true);
                    success = true;
                } catch {}
            }
        } else if (ty == TypeKind.Int256) {
            try vm.parseTomlInt(content, path) returns (int256 val) {
                _dataOf[chainId][key] = abi.encode(val);
                _typeOf[chainId][key] = Type(TypeKind.Int256, false);
                success = true;
            } catch {
                try vm.parseTomlIntArray(content, path) returns (int256[] memory val) {
                    _dataOf[chainId][key] = abi.encode(val);
                    _typeOf[chainId][key] = Type(TypeKind.Int256, true);
                    success = true;
                } catch {}
            }
        } else if (ty == TypeKind.Bytes) {
            try vm.parseTomlBytes(content, path) returns (bytes memory val) {
                _dataOf[chainId][key] = abi.encode(val);
                _typeOf[chainId][key] = Type(TypeKind.Bytes, false);
                success = true;
            } catch {
                try vm.parseTomlBytesArray(content, path) returns (bytes[] memory val) {
                    _dataOf[chainId][key] = abi.encode(val);
                    _typeOf[chainId][key] = Type(TypeKind.Bytes, true);
                    success = true;
                } catch {}
            }
        } else if (ty == TypeKind.String) {
            try vm.parseTomlString(content, path) returns (string memory val) {
                _dataOf[chainId][key] = abi.encode(val);
                _typeOf[chainId][key] = Type(TypeKind.String, false);
                success = true;
            } catch {
                try vm.parseTomlStringArray(content, path) returns (string[] memory val) {
                    _dataOf[chainId][key] = abi.encode(val);
                    _typeOf[chainId][key] = Type(TypeKind.String, true);
                    success = true;
                } catch {}
            }
        }

        if (!success) {
            revert UnableToParseVariable(key);
        }
    }

    // -- HELPER FUNCTIONS -----------------------------------------------------

    /// @notice Enable or disable automatic writing to the TOML file on `set`.
    ///         Can only be enabled when scripting.
    function writeUpdatesBackToFile(bool enabled) public {
        if (enabled && !vm.isContext(VmSafe.ForgeContext.ScriptGroup)) {
            revert WriteToFileInForbiddenCtxt();
        }

        _writeToFile = enabled;
    }

    /// @notice Resolves a chain alias or a chain id string to its numerical chain id.
    /// @param aliasOrId The string representing the chain alias (i.e. "mainnet") or a numerical ID (i.e. "1").
    /// @return The numerical chain ID.
    /// @dev It first attempts to parse the input as a number. If that fails, it uses `vm.getChain` to resolve a named alias.
    ///      Reverts if the alias is not valid or not a number.
    function resolveChainId(string memory aliasOrId) public view returns (uint256) {
        try vm.parseUint(aliasOrId) returns (uint256 chainId) {
            return chainId;
        } catch {
            try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) {
                return chainInfo.chainId;
            } catch {
                revert InvalidChainKey(aliasOrId);
            }
        }
    }

    /// @dev Retrieves the chain key/alias from the configuration based on the chain ID.
    function _getChainKeyFromId(uint256 chainId) private view returns (string memory) {
        for (uint256 i = 0; i < _chainKeys.length; i++) {
            if (resolveChainId(_chainKeys[i]) == chainId) {
                return _chainKeys[i];
            }
        }
        revert ChainNotInitialized(chainId);
    }

    /// @dev Ensures type consistency when setting a value - prevents changing types unless uninitialized.
    ///      Updates type only when the previous type was `None`.
    function _ensureTypeConsistency(uint256 chainId, string memory key, Type memory ty) private {
        Type memory current = _typeOf[chainId][key];

        if (current.kind == TypeKind.None) {
            _typeOf[chainId][key] = ty;
        } else {
            current.assertEq(ty);
        }
    }

    /// @dev Wraps a string in double quotes for JSON compatibility.
    function _quote(string memory s) private pure returns (string memory) {
        return string.concat('"', s, '"');
    }

    /// @dev Writes a JSON-formatted value to a specific key in the TOML file.
    /// @param chainId The chain id to write under.
    /// @param ty The type category ('bool', 'address', 'uint', 'bytes32', 'string', or 'bytes').
    /// @param key The variable key name.
    /// @param jsonValue The JSON-formatted value to write.
    function _writeToToml(uint256 chainId, string memory ty, string memory key, string memory jsonValue) private {
        string memory chainKey = _getChainKeyFromId(chainId);
        string memory valueKey = string.concat("$.", chainKey, ".", ty, ".", key);
        vm.writeToml(jsonValue, _filePath, valueKey);
    }

    // -- GETTER FUNCTIONS -----------------------------------------------------

    /// @dev    Reads a variable for a given chain id and key, and returns it in a generic container.
    ///         The caller should use `LibVariable` to safely coerce the type.
    ///         Example: `uint256 myVar = config.get("my_key").toUint256();`
    ///
    /// @param  chain_id The chain ID to read from.
    /// @param  key The key of the variable to retrieve.
    /// @return `Variable` struct containing the type and the ABI-encoded value.
    function get(uint256 chain_id, string memory key) public view returns (Variable memory) {
        return Variable(_typeOf[chain_id][key], _dataOf[chain_id][key]);
    }

    /// @dev    Reads a variable for the current chain and a given key, and returns it in a generic container.
    ///         The caller should use `LibVariable` to safely coerce the type.
    ///         Example: `uint256 myVar = config.get("my_key").toUint256();`
    ///
    /// @param  key The key of the variable to retrieve.
    /// @return `Variable` struct containing the type and the ABI-encoded value.
    function get(string memory key) public view returns (Variable memory) {
        return get(vm.getChainId(), key);
    }

    /// @dev    Checks the existence of a variable for a given chain ID and key, and returns a boolean.
    ///         Example: `bool hasKey = config.exists(1, "my_key");`
    ///
    /// @param chain_id The chain ID to check.
    /// @param key The variable key name.
    /// @return `bool` indicating whether a variable with the given key exists.
    function exists(uint256 chain_id, string memory key) public view returns (bool) {
        return _dataOf[chain_id][key].length > 0;
    }

    /// @dev    Checks the existence of a variable for the current chain id and a given key, and returns a boolean.
    ///         Example: `bool hasKey = config.exists("my_key");`
    ///
    /// @param key The variable key name.
    /// @return `bool` indicating whether a variable with the given key exists.
    function exists(string memory key) public view returns (bool) {
        return exists(vm.getChainId(), key);
    }

    /// @notice Returns the numerical chain ids for all configured chains.
    function getChainIds() public view returns (uint256[] memory) {
        string[] memory keys = _chainKeys;

        uint256[] memory ids = new uint256[](keys.length);
        for (uint256 i = 0; i < keys.length; i++) {
            ids[i] = resolveChainId(keys[i]);
        }

        return ids;
    }

    /// @notice Reads the RPC URL for a specific chain id.
    function getRpcUrl(uint256 chainId) public view returns (string memory) {
        return _rpcOf[chainId];
    }

    /// @notice Reads the RPC URL for the current chain.
    function getRpcUrl() public view returns (string memory) {
        return _rpcOf[vm.getChainId()];
    }

    // -- SETTER FUNCTIONS (SINGLE VALUES) -------------------------------------

    /// @notice Sets a boolean value for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, bool value) public {
        Type memory ty = Type(TypeKind.Bool, false);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value));
    }

    /// @notice Sets a boolean value for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, bool value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets an address value for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, address value) public {
        Type memory ty = Type(TypeKind.Address, false);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value)));
    }

    /// @notice Sets an address value for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, address value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a bytes32 value for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, bytes32 value) public {
        Type memory ty = Type(TypeKind.Bytes32, false);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value)));
    }

    /// @notice Sets a bytes32 value for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, bytes32 value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a uint256 value for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, uint256 value) public {
        Type memory ty = Type(TypeKind.Uint256, false);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value));
    }

    /// @notice Sets a uint256 value for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, uint256 value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets an int256 value for a given key and chain ID.
    function set(uint256 chainId, string memory key, int256 value) public {
        Type memory ty = Type(TypeKind.Int256, false);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value));
    }

    /// @notice Sets an int256 value for a given key on the current chain.
    function set(string memory key, int256 value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a string value for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, string memory value) public {
        Type memory ty = Type(TypeKind.String, false);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(value));
    }

    /// @notice Sets a string value for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, string memory value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a bytes value for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, bytes memory value) public {
        Type memory ty = Type(TypeKind.Bytes, false);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value)));
    }

    /// @notice Sets a bytes value for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, bytes memory value) public {
        set(vm.getChainId(), key, value);
    }

    // -- SETTER FUNCTIONS (ARRAYS) --------------------------------------------

    /// @notice Sets a boolean array for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, bool[] memory value) public {
        Type memory ty = Type(TypeKind.Bool, true);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) {
            string memory json = "[";
            for (uint256 i = 0; i < value.length; i++) {
                json = string.concat(json, vm.toString(value[i]));
                if (i < value.length - 1) json = string.concat(json, ",");
            }
            json = string.concat(json, "]");
            _writeToToml(chainId, ty.kind.toTomlKey(), key, json);
        }
    }

    /// @notice Sets a boolean array for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, bool[] memory value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets an address array for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, address[] memory value) public {
        Type memory ty = Type(TypeKind.Address, true);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) {
            string memory json = "[";
            for (uint256 i = 0; i < value.length; i++) {
                json = string.concat(json, _quote(vm.toString(value[i])));
                if (i < value.length - 1) json = string.concat(json, ",");
            }
            json = string.concat(json, "]");
            _writeToToml(chainId, ty.kind.toTomlKey(), key, json);
        }
    }

    /// @notice Sets an address array for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, address[] memory value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a bytes32 array for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, bytes32[] memory value) public {
        Type memory ty = Type(TypeKind.Bytes32, true);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) {
            string memory json = "[";
            for (uint256 i = 0; i < value.length; i++) {
                json = string.concat(json, _quote(vm.toString(value[i])));
                if (i < value.length - 1) json = string.concat(json, ",");
            }
            json = string.concat(json, "]");
            _writeToToml(chainId, ty.kind.toTomlKey(), key, json);
        }
    }

    /// @notice Sets a bytes32 array for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, bytes32[] memory value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a uint256 array for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, uint256[] memory value) public {
        Type memory ty = Type(TypeKind.Uint256, true);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) {
            string memory json = "[";
            for (uint256 i = 0; i < value.length; i++) {
                json = string.concat(json, vm.toString(value[i]));
                if (i < value.length - 1) json = string.concat(json, ",");
            }
            json = string.concat(json, "]");
            _writeToToml(chainId, ty.kind.toTomlKey(), key, json);
        }
    }

    /// @notice Sets a uint256 array for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, uint256[] memory value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a int256 array for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, int256[] memory value) public {
        Type memory ty = Type(TypeKind.Int256, true);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) {
            string memory json = "[";
            for (uint256 i = 0; i < value.length; i++) {
                json = string.concat(json, vm.toString(value[i]));
                if (i < value.length - 1) json = string.concat(json, ",");
            }
            json = string.concat(json, "]");
            _writeToToml(chainId, ty.kind.toTomlKey(), key, json);
        }
    }

    /// @notice Sets a int256 array for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, int256[] memory value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a string array for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, string[] memory value) public {
        Type memory ty = Type(TypeKind.String, true);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) {
            string memory json = "[";
            for (uint256 i = 0; i < value.length; i++) {
                json = string.concat(json, _quote(value[i]));
                if (i < value.length - 1) json = string.concat(json, ",");
            }
            json = string.concat(json, "]");
            _writeToToml(chainId, ty.kind.toTomlKey(), key, json);
        }
    }

    /// @notice Sets a string array for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, string[] memory value) public {
        set(vm.getChainId(), key, value);
    }

    /// @notice Sets a bytes array for a given key and chain ID.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(uint256 chainId, string memory key, bytes[] memory value) public {
        Type memory ty = Type(TypeKind.Bytes, true);
        _ensureTypeConsistency(chainId, key, ty);
        _dataOf[chainId][key] = abi.encode(value);
        if (_writeToFile) {
            string memory json = "[";
            for (uint256 i = 0; i < value.length; i++) {
                json = string.concat(json, _quote(vm.toString(value[i])));
                if (i < value.length - 1) json = string.concat(json, ",");
            }
            json = string.concat(json, "]");
            _writeToToml(chainId, ty.kind.toTomlKey(), key, json);
        }
    }

    /// @notice Sets a bytes array for a given key on the current chain.
    /// @dev    Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled.
    function set(string memory key, bytes[] memory value) public {
        set(vm.getChainId(), key, value);
    }
}