Skip to content

Commit 58d6b28

Browse files
authored
feat: deploy script for SnapchainConfigRegistry (#495)
* feat: deploy script for SnapchainConfigRegistry Adds `DeploySnapchainConfigRegistry`, modeled on `DeployTierRegistry`: the same `Salts` / `DeploymentParams` / `Addresses` / `Contracts` structs and `runDeploy` / `runSetup` overload pairs, deploying through the ImmutableCreate2Factory so the script stays idempotent. `runSetup` seeds the validator-set history and peer lists, then hands ownership to the address that will author config changes from then on. Two instances are expected, one per Snapchain network: Mainnet on Ethereum L1, Testnet on Sepolia. Separate chains rather than two addresses on one chain, so a testnet mistake cannot touch mainnet state. Adds the `eth_sepolia` RPC endpoint that arrangement needs. Seed data moves out of the test suite into `SnapchainConfigRegistrySeed`, inherited by both the deploy script and the suite. This is the follow-up the test suite flagged in its own comment: the golden test now pins the literal bytes the deploy writes rather than a parallel copy that can drift. Seed selection is gated on `block.chainid` rather than passed in. Snapchain testnet runs a wholly separate validator set, so a script that silently fell back to mainnet's history on Sepolia would produce a registry that is wrong in the worst available way -- well-formed, renderable, and capable of panicking every testnet node on boot, since a malformed key is a startup panic rather than a warning. History is append-only, so that is not a recoverable mistake: the remedy is a redeploy at a fresh salt plus repointing every node. Sepolia therefore reverts with `NoSeedDataForChain` until the testnet data is transcribed and verified. The fork test asserts the script-seeded registry renders identically to a prank-seeded reference instance rather than against a second copy of the expected document. Both draw from the same seed and the suite's golden test pins the reference to a literal, so script output equals suite output equals the checked-in bytes, with one copy of that string in the repo. The seed-gate case needs its own test contract, because ImmutableCreate2Deployer accumulates `names` in storage across `register` calls: a second deploy from a contract that already deployed one in `setUp` finds the first entry's address on the repeat pass and `deploymentChanged()` then reports no change, so setup would skip rather than revert. Owner address and vanity salt are left unset in `.env.prod` on purpose. `vm.envAddress` reverts on an unset variable, which is the correct failure until the owner is chosen -- a placeholder would deploy a registry owned by the wrong address, and ownership is what governs the validator set. * chore: license deployment code as MIT The new script and deploy test carried UNLICENSED, matching the other files in `script/` and `test/Deploy/`. Relicense both as MIT so they match the contracts in `src/`, which are MIT throughout. Leaves the pre-existing `script/*.s.sol` and `test/Deploy/*` files on UNLICENSED; sweeping those is a separate change. * test: cover idempotent re-run and salt selection Two branches of the deploy script had no coverage. Every existing test hits `deploymentChanged() == true`, so the first-run path was well covered and the second-run path not at all; and `loadDeploymentParams` / `loadSalt` were never called by anything. `test_reRunIsIdempotent` runs the script a second time against an already-deployed registry and asserts nothing moves. The second run is the dangerous one: seeding again would append the ten-entry history a second time, and re-running `transferOwnership` after the owner had accepted would quietly hand the registry back to the deployer. Deployed from a fresh script instance, since ImmutableCreate2Deployer accumulates `names` in storage and a real re-run starts with that empty. `test_loadsParamsAndSelectsSaltByChain` covers env loading and the Sepolia salt override, including the fallback that puts both registries at the same address. The failure mode there is invisible until broadcast: a mistyped variable name reads as unset, and `vm.envOr` then supplies `bytes32(0)` -- a salt with no caller-lock, at an address anyone could have front-run. Written as one function in a deliberate order, because `vm.setEnv` writes the process environment with no way to unset afterwards, so the no-override case must be asserted first. Both verified by mutation: forcing setup to always run fails only the idempotency test, and dropping the Sepolia branch fails only the salt test. * refactor(script): make the CREATE2 factory overridable ImmutableCreate2Deployer hardcodes 0age's factory in three places: the address register() predicts against, the hasBeenDeployed query, and the safeCreate2 call. Isolate them behind virtual hooks so a script can route its deployments through a different factory. No behavior change. The default stays the ImmutableCreate2Factory, which every contract this repo has deployed went through -- an override moves the deployment address of everything the overriding script registers, so it is not a decision to inherit by accident. * feat(script): deploy the config registry through the canonical CREATE2 proxy The ImmutableCreate2Factory requires the salt's first 20 bytes to equal msg.sender, which binds a mined salt to the key that broadcasts. The canonical deterministic-deployment proxy at 0x4e59b448... checks nothing, so the whole 32-byte salt is searchable and the address the registry lands at is a property of the contract alone. What that gives up is exclusivity: anyone can submit the same salt and init code first. The deployment itself is harmless -- same creation code and same constructor argument, so the contract that lands is ours, owned by us, at their expense -- but runSetup gated on deploymentChanged(), which reports whether *this run* deployed it, and would have logged "no changes" over a registry left permanently unseeded. Gate on configVersion() instead, which asks the registry what it holds rather than what this process did, and also closes the same hole for a run interrupted between deploy and setup. Covered by a test that deploys the registry from a stranger through the proxy and asserts the script seeds it anyway. Also corrects the deployment shape doc, which still described two registries on Ethereum L1 rather than mainnet plus Sepolia. * chore(env): record the mined SnapchainConfigRegistry CREATE2 salt Puts the registry at 0x00000000fc51aD6eb74EAE89ba4b01b1776fBA85, matching the 0x00000000fc prefix every other contract in this repo carries. Mined against the canonical proxy, so it has no deployer prefix -- but it is still bound to DEPLOYER, which is the registry's constructor argument, and to this exact build of the creation code. Leaves the testnet override commented out rather than set to zeros. loadSalt reads it with vm.envOr, so unset falls back to the shared salt, which is what puts both registries at the same address; zeros would not have meant "no override" but "deploy Sepolia at the salt-zero address". * feat(script): seed the Sepolia registry with Snapchain testnet's history Sepolia reverted with NoSeedDataForChain because testnet's validator set is wholly separate from mainnet's and transcribing it was deferred. Fill it in: thirteen entries and seven keys, from the VALIDATOR_SETS string in snapchain-deployer/.stack/deploy.yml, which every testnet pod there carries byte-identically. Two of the seven keys are retired -- one ran from genesis to 24_811_937 and its replacement left at 28_681_000 -- and are kept because read nodes syncing from genesis verify old commit signatures against the historical entries. Verified against the live cluster rather than the file alone. The last thirty commits carry the five current keys (juno, iris, vega, merry, gloin) at shard heights 42_738_208 / 43_002_880 / 43_012_625, all far past the final entry at 37_197_000, so the transcribed history is complete and not merely current. The certificates show four of the five signers: quorum is 4 of 5 and gloin is network-distant, so its precommit lands after quorum forms -- its own heights track the cluster. Peers come from tau, the read node, because validators each omit themselves and tau omits nothing. The unresolved question about publishing 10.0.x.x VPC addresses in a registry external validators read applies here as it does to mainnet, and is noted in the same terms. The seed gate test moves to OP mainnet, which is now the nearest plausible chain with no data. Adds a test asserting no mainnet key reaches a testnet registry -- append-only history makes that unrecoverable short of a redeploy at a fresh salt.
1 parent 760be0e commit 58d6b28

12 files changed

Lines changed: 998 additions & 124 deletions

.env.example

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ TIER_REGISTRY_VAULT_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692
3535
TIER_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692
3636
TIER_REGISTRY_MIGRATOR_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692
3737

38+
# Snapchain config registry params
39+
SNAPCHAIN_CONFIG_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692
40+
3841
# RPC endpoints
3942
ETH_MAINNET_RPC_URL=
43+
ETH_SEPOLIA_RPC_URL=
4044
OP_MAINNET_RPC_URL=
4145
BASE_MAINNET_RPC_URL=
4246

@@ -48,6 +52,22 @@ SIGNED_KEY_REQUEST_VALIDATOR_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77c
4852
BUNDLER_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6e451fc0a34ec4c008c9a31fa
4953
RECOVERY_PROXY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6110eaaca06f77900dac1cad3
5054
TIER_REGISTRY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6110eaaca06f77900dac1cad3
55+
# Puts the registry at 0x00000000fc51aD6eb74EAE89ba4b01b1776fBA85. One salt covers both deployments:
56+
# identical creation code, constructor args, and salt put the mainnet and Sepolia registries at the
57+
# same address on their respective chains.
58+
#
59+
# Mined against the canonical CREATE2 proxy at 0x4e59b44847b379578588920cA78FbF26c0B4956C rather
60+
# than the ImmutableCreate2Factory the salts above target, so it carries no deployer prefix -- that
61+
# proxy does not check its caller. Still bound to the deployer, because the deployer is the
62+
# registry's constructor argument: DEPLOYER must be 0xb1b46D15902D7432eB5E313694420F7c08253Ff7 when
63+
# this script broadcasts, or the address will not reproduce.
64+
SNAPCHAIN_CONFIG_REGISTRY_CREATE2_SALT=0x0f92adea41b48685dcbd278b6c54943858e5bf280b17e325c7142e581d6d2d8e
65+
66+
# Left unset on purpose. `loadSalt` reads it with `vm.envOr`, so an unset variable falls back to the
67+
# salt above -- which is what puts both registries at the same address. Setting it to zeros would
68+
# not mean "no override"; it would deploy Sepolia at the salt-zero address instead. Fill it in only
69+
# if the address above turns out to be taken on Sepolia.
70+
# SNAPCHAIN_CONFIG_REGISTRY_TESTNET_CREATE2_SALT=
5171

5272
# Deployer address.
5373
DEPLOYER=0x6D2b70e39C6bc63763098e336323591eb77Cd0C6

.env.prod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,24 @@ TIER_REGISTRY_VAULT_ADDRESS=0x0BDcA19c9801bb484285362fD5dd0c94592c874C
3535
TIER_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692
3636
TIER_REGISTRY_MIGRATOR_ADDRESS=0x2d93c2f74b2c4697f9ea85d0450148aa45d4d5a2
3737

38+
# Snapchain config registry params
39+
#
40+
# Deliberately unset. The owner is the ops multisig that will author config changes, and it has not
41+
# been chosen; the vanity salt has not been mined. `vm.envAddress` reverts on an unset variable,
42+
# which is the correct failure until both are settled -- a placeholder here would deploy a registry
43+
# owned by the wrong address, and ownership is what governs the validator set.
44+
#
45+
# One salt covers both deployments: identical creation code, constructor args, and salt put the
46+
# mainnet and Sepolia registries at the same address on their respective chains. The TESTNET
47+
# override exists only in case that address turns out to be taken on Sepolia.
48+
#
49+
# SNAPCHAIN_CONFIG_REGISTRY_OWNER_ADDRESS=
50+
# SNAPCHAIN_CONFIG_REGISTRY_CREATE2_SALT=
51+
# SNAPCHAIN_CONFIG_REGISTRY_TESTNET_CREATE2_SALT=
52+
3853
# RPC endpoints
3954
ETH_MAINNET_RPC_URL=
55+
ETH_SEPOLIA_RPC_URL=
4056
OP_MAINNET_RPC_URL=
4157
BASE_MAINNET_RPC_URL=
4258

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This repository contains all the contracts deployed by the [Farcaster protocol](
1212
8. **[Recovery Proxy](./src/RecoveryProxy.sol)** - proxy for recovery service operators to initiate fid recovery.
1313
9. **[Fname Resolver](./src/FnameResolver.sol)** - validates Farcaster ENS names which were issued offchain.
1414
10. **[Tier Registry](./src/TierRegistry.sol)** - processes Farcaster Pro subscription payments.
15+
11. **[Snapchain Config Registry](./src/SnapchainConfigRegistry.sol)** - holds the validator sets and gossip peer lists for a Snapchain network.
1516

1617
Read the [docs](docs/docs.md) for more details on how the contracts work.
1718

@@ -40,9 +41,18 @@ The [v3.1 contracts](https://github.com/farcasterxyz/contracts/releases/tag/v3.1
4041

4142
### ETH Mainnet
4243

43-
| Contract | Address |
44-
| ------------- | ---------------- |
45-
| FnameResolver | Not yet deployed |
44+
| Contract | Address |
45+
| ----------------------- | ---------------- |
46+
| FnameResolver | Not yet deployed |
47+
| SnapchainConfigRegistry | Not yet deployed |
48+
49+
### ETH Sepolia
50+
51+
Serves the Snapchain **Testnet** network. A separate chain rather than a second address on mainnet, so a testnet config mistake cannot touch mainnet state and the rehearsal costs nothing real.
52+
53+
| Contract | Address |
54+
| ----------------------- | ---------------- |
55+
| SnapchainConfigRegistry | Not yet deployed |
4656

4757
## Audits
4858

docs/docs.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ graph TD
5757
9. [Tier Registry](#19-tier-registry)
5858
2. [L1 Contracts](#2-l1-contracts)
5959
1. [Fname Resolver](#21-fname-resolver)
60+
2. [Snapchain Config Registry](#22-snapchain-config-registry)
6061
3. [Offchain Systems](#3-off-chain-systems)
6162
1. [Fname Registry](#31-fname-registry)
6263
2. [Hubs](#32-hubs)
@@ -357,6 +358,8 @@ The TierRegistry contract may need to be upgraded in case a bug is discovered or
357358

358359
The Fname Resolver contract is deployed on L1 Mainnet (chainid: 1).
359360

361+
The Snapchain Config Registry is deployed twice, once per Snapchain network: on L1 Mainnet (chainid: 1) for the Mainnet network, and on Sepolia (chainid: 11155111) for the Testnet network.
362+
360363
# 2.1. Fname Resolver
361364

362365
The Fname Resolver contract validates usernames issued under the \*.fcast.id domain onchain by implementing [ERC-3668](https://eips.ethereum.org/EIPS/eip-3668) and [ENSIP-10](https://docs.ens.domains/ens-improvement-proposals/ensip-10-wildcard-resolution). The resolver contains the url of the server which issues the usernames and proofs. It maintains a list of valid signers for the server and also validates proofs returned by the server.
@@ -369,6 +372,29 @@ The Fname Resolver contract validates usernames issued under the \*.fcast.id dom
369372

370373
An `owner` can update the list of valid signers associated with the server.
371374

375+
# 2.2. Snapchain Config Registry
376+
377+
The Snapchain Config Registry is the canonical record of a Snapchain network's validator sets and gossip peer lists. It holds an append-only history of validator sets, each carrying the Snapchain block height it becomes effective at, the shards it governs, and the Ed25519 public keys in the set. It renders that state, together with the two peer lists, as a TOML fragment that a node merges into its config before starting. Nothing onchain is read by a running node: the contract is the source of truth, and a startup step pulls from it.
378+
379+
See [snapchain-config-registry.md](./snapchain-config-registry.md) for the schema and the rendered-output specification, which is part of the contract's public API.
380+
381+
### Invariants
382+
383+
1. History is append-only: only the latest validator set may be amended or removed, and earlier entries are immutable.
384+
2. Ordering is per-shard: a validator set's `effectiveAt` must be greater than or equal to that of the most recent preceding set governing a shard in common. Sets governing disjoint shards are unconstrained relative to one another, because each shard's height is an independent counter.
385+
3. Rendered output cannot be escaped: validator keys are stored as `bytes32` and rendered through a fixed hex alphabet, and peer strings are restricted to a character allowlist that excludes the quote and the backslash.
386+
4. `configVersion` increases by exactly one on every mutation.
387+
388+
### Assumptions
389+
390+
1. `effectiveAt` is a Snapchain block height, not an EVM block number and not a timestamp. Nothing onchain can validate it.
391+
2. The owner supplies well-formed validator keys. A malformed key is a startup panic on every node that reads it, not a warning.
392+
3. Entry 0 remains the genesis entry, since a consuming node seeds its lookup with it and an empty registry has no valid answer to return.
393+
394+
### Administration
395+
396+
An `owner` appends validator sets, amends or removes the latest one, and sets the two peer lists. Ownership uses the two-step transfer, so a mistyped address cannot strand the registry.
397+
372398
# 3. Offchain Systems
373399

374400
# 3.1. Fname Registry

docs/snapchain-config-registry.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,14 +382,27 @@ validators does.
382382

383383
## 10. Deployment shape
384384

385-
Two registries, both on Ethereum L1 mainnet, distinguished by address and by CREATE2 salt: one for
386-
Snapchain mainnet, one for Snapchain testnet. Testnet validators already point at the same L1 RPC
387-
endpoint as mainnet ones, so a separate chain would mean provisioning a second RPC secret for no
388-
isolation benefit — the isolation that matters is between config sets, not between chains.
385+
One registry per Snapchain network, on separate chains: mainnet on Ethereum L1, testnet on Sepolia.
386+
Separate chains rather than two addresses on one, so a testnet config mistake cannot touch mainnet
387+
state and the rehearsal costs nothing real. Identical creation code, constructor argument and salt
388+
put the two at the same address on their respective chains — a convenience, not something to depend
389+
on, since a redeploy on either breaks it.
389390

390391
The constructor takes only `_initialOwner` and no config. Seeding happens in the deploy script's
391392
setup phase, which keeps the creation code — and therefore any CREATE2 vanity address — independent
392-
of the config payload, so revising seed data before launch does not force re-mining a salt.
393+
of the config payload, so revising seed data before launch does not force re-mining a salt. The
394+
argument is the deploying address, which owns the registry through seeding and hands off afterwards,
395+
so the mined salt is bound to the key that broadcasts.
396+
397+
Deployed through the canonical deterministic-deployment proxy at
398+
`0x4e59b44847b379578588920cA78FbF26c0B4956C` rather than the ImmutableCreate2Factory the rest of the
399+
repo uses. That factory requires the salt's first 20 bytes to equal the caller, which is why every
400+
other salt in `.env.example` carries a deployer prefix and only its trailing 12 bytes were mined; the
401+
proxy checks nothing, so the whole 32-byte salt is searchable. What that gives up is exclusivity:
402+
anyone can submit the same salt and init code first. The deployment itself is harmless — same
403+
creation code and same constructor argument, so the contract that lands is ours, owned by us, at
404+
their expense — but it is the reason the setup phase is gated on `configVersion()` rather than on
405+
whether this run was the one that deployed.
393406

394407
## 11. Consumers
395408

foundry.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ multiline_func_header = "params_first"
3131
base_mainnet = "${BASE_MAINNET_RPC_URL}"
3232
op_mainnet = "${OP_MAINNET_RPC_URL}"
3333
eth_mainnet = "${ETH_MAINNET_RPC_URL}"
34+
eth_sepolia = "${ETH_SEPOLIA_RPC_URL}"
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.21;
3+
4+
import {Test} from "forge-std/Test.sol";
5+
import {SnapchainConfigRegistry} from "../src/SnapchainConfigRegistry.sol";
6+
import {ISnapchainConfigRegistry} from "../src/interfaces/ISnapchainConfigRegistry.sol";
7+
import {SnapchainConfigRegistrySeed} from "./abstract/SnapchainConfigRegistrySeed.sol";
8+
import {console, CanonicalCreate2Deployer} from "./abstract/CanonicalCreate2Deployer.sol";
9+
10+
/**
11+
* @title DeploySnapchainConfigRegistry
12+
*
13+
* @notice Deploys a SnapchainConfigRegistry, seeds it with the config for the chain it lands on,
14+
* and hands ownership to the address that will author changes from then on.
15+
*
16+
* @dev Two instances are expected, one per Snapchain network:
17+
*
18+
* - **Mainnet** on Ethereum L1 (chain 1)
19+
* - **Testnet** on Sepolia (chain 11155111)
20+
*
21+
* Different chains rather than two addresses on one chain, so a testnet mistake cannot touch
22+
* mainnet state and the rehearsal costs nothing real. With identical creation code, constructor
23+
* args, and salt, the two land at the *same address on both chains* -- a convenience, not
24+
* something anything should depend on, since a redeploy on either chain breaks it.
25+
*
26+
* Deployed through the canonical CREATE2 proxy rather than the ImmutableCreate2Factory the rest
27+
* of this repo uses, so that the mined salt is a property of the contract alone and not of the
28+
* key that broadcasts it. CanonicalCreate2Deployer explains the trade.
29+
*/
30+
contract DeploySnapchainConfigRegistry is SnapchainConfigRegistrySeed, CanonicalCreate2Deployer, Test {
31+
struct Salts {
32+
bytes32 snapchainConfigRegistry;
33+
}
34+
35+
struct DeploymentParams {
36+
address deployer;
37+
address owner;
38+
Salts salts;
39+
}
40+
41+
struct Addresses {
42+
address snapchainConfigRegistry;
43+
}
44+
45+
struct Contracts {
46+
SnapchainConfigRegistry snapchainConfigRegistry;
47+
}
48+
49+
function run() public {
50+
runSetup(runDeploy(loadDeploymentParams()));
51+
}
52+
53+
function runDeploy(
54+
DeploymentParams memory params
55+
) public returns (Contracts memory) {
56+
return runDeploy(params, true);
57+
}
58+
59+
function runDeploy(DeploymentParams memory params, bool broadcast) public returns (Contracts memory) {
60+
Addresses memory addrs;
61+
addrs.snapchainConfigRegistry = register(
62+
"SnapchainConfigRegistry",
63+
params.salts.snapchainConfigRegistry,
64+
type(SnapchainConfigRegistry).creationCode,
65+
// The deployer owns the registry through setup, since seeding is owner-gated. Handed off
66+
// at the end of runSetup.
67+
abi.encode(params.deployer)
68+
);
69+
deploy(broadcast);
70+
71+
return Contracts({snapchainConfigRegistry: SnapchainConfigRegistry(addrs.snapchainConfigRegistry)});
72+
}
73+
74+
/**
75+
* @dev Seed the full validator-set history and peer lists, then transfer ownership.
76+
*
77+
* Ownable2Step leaves `params.owner` as `pendingOwner`; it accepts in a separate
78+
* transaction. Until it does, the deployer still owns the registry -- which is the point of
79+
* the two-step, but does mean the handoff is not complete when this script exits.
80+
*
81+
* Gated on the registry being unseeded rather than on `deploymentChanged()`, which reports
82+
* whether *this run* deployed it. The canonical CREATE2 proxy imposes no caller lock, so a
83+
* stranger can submit the same salt and init code first; a run that then found the registry
84+
* already deployed would log "no changes" and leave it permanently unseeded. Asking the
85+
* registry what it holds answers the question that actually matters, and closes the same
86+
* hole for a run interrupted between deploy and setup.
87+
*
88+
* Detects unseeded, not partially seeded. A broadcast that dies midway through the loop
89+
* leaves a nonzero `configVersion` and will be skipped here; finishing it is a manual job.
90+
*/
91+
function runSetup(Contracts memory contracts, DeploymentParams memory params, bool broadcast) public {
92+
if (contracts.snapchainConfigRegistry.configVersion() == 0) {
93+
console.log("Running setup");
94+
95+
// Read before broadcasting: on an unrecognized chain this reverts, and it should do so
96+
// before any transaction is sent rather than halfway through seeding.
97+
Seed memory seed = _seedFor(block.chainid);
98+
uint256 setCount = seed.validatorSets.length;
99+
100+
if (broadcast) vm.startBroadcast();
101+
for (uint256 i; i < setCount; ++i) {
102+
ISnapchainConfigRegistry.ValidatorSet memory validatorSet = seed.validatorSets[i];
103+
contracts.snapchainConfigRegistry.appendValidatorSet(
104+
validatorSet.effectiveAt, validatorSet.shardIds, validatorSet.validatorPublicKeys
105+
);
106+
}
107+
contracts.snapchainConfigRegistry.setBootstrapPeers(seed.bootstrapPeers);
108+
contracts.snapchainConfigRegistry.setDirectPeers(seed.directPeers);
109+
contracts.snapchainConfigRegistry.transferOwnership(params.owner);
110+
if (broadcast) vm.stopBroadcast();
111+
} else {
112+
console.log("Already seeded, skipping setup");
113+
}
114+
}
115+
116+
function runSetup(
117+
Contracts memory contracts
118+
) public {
119+
DeploymentParams memory params = loadDeploymentParams();
120+
runSetup(contracts, params, true);
121+
}
122+
123+
function loadDeploymentParams() internal returns (DeploymentParams memory) {
124+
return DeploymentParams({
125+
deployer: vm.envAddress("DEPLOYER"),
126+
owner: vm.envAddress("SNAPCHAIN_CONFIG_REGISTRY_OWNER_ADDRESS"),
127+
salts: Salts({snapchainConfigRegistry: loadSalt()})
128+
});
129+
}
130+
131+
/**
132+
* @dev One salt serves both chains by default, which puts the two registries at the same
133+
* address. `SNAPCHAIN_CONFIG_REGISTRY_TESTNET_CREATE2_SALT` overrides on Sepolia if that
134+
* address turns out to be taken there.
135+
*/
136+
function loadSalt() internal view returns (bytes32) {
137+
bytes32 salt = vm.envOr("SNAPCHAIN_CONFIG_REGISTRY_CREATE2_SALT", bytes32(0));
138+
if (block.chainid == ETH_SEPOLIA_CHAIN_ID) {
139+
return vm.envOr("SNAPCHAIN_CONFIG_REGISTRY_TESTNET_CREATE2_SALT", salt);
140+
}
141+
return salt;
142+
}
143+
}

0 commit comments

Comments
 (0)