Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 17 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ evm-version-flag = --evm-version $(call evm-version,$(1))
# (project/<group>/<selectorName>.json); unset is the flat default (project/<selectorName>.json). It
# threads to the scripts as PROJECT_GROUP; GROUP_DIR locates the same file for the jq repair steps here.
# Honored by the project targets (add-lane, remove-lane, adopt-token, snapshot-chain, doctor,
# roles-check) and the deploy targets (deploy-token/pool/lockbox/lockrelease-pool, deploy-new-chain); the
# roles-check) and the deploy targets (deploy-token/pool/lockbox/lockrelease-pool/siloed-pool, deploy-new-chain); the
# chain-facts targets (add-chain, sync*) ignore it (config/chains is group-independent).
GROUP_DIR := $(if $(GROUP),$(GROUP)/,)

.DEFAULT_GOAL := help
.PHONY: probe-chain adopt-token help tools discover discover-tokens add-chain add-lane remove-lane sync sync-preview sync-all sync-check doctor fmt-config clean-scratch snapshot-chain roles-check roles-check-all deploy-token deploy-pool deploy-lockbox deploy-lockrelease-pool deploy-new-chain preflight verify verify-args verify-execution
.PHONY: probe-chain adopt-token forget-deployment help tools discover discover-tokens add-chain add-lane remove-lane sync sync-preview sync-all sync-check doctor fmt-config clean-scratch snapshot-chain roles-check roles-check-all deploy-token deploy-pool deploy-lockbox deploy-lockrelease-pool deploy-siloed-pool deploy-new-chain preflight verify verify-args verify-execution

# Deploy-time parameters are read by the forge scripts from the environment (vm.env*). Forward a value
# passed on the make command line (make deploy-token TOKEN_NAME=...) to the forge subprocess; a value
Expand All @@ -57,7 +57,7 @@ GROUP_DIR := $(if $(GROUP),$(GROUP)/,)
# still apply.
DEPLOY_VARS := TOKEN_NAME TOKEN_SYMBOL TOKEN_DECIMALS TOKEN_MAX_SUPPLY TOKEN_PRE_MINT \
TOKEN_PRE_MINT_RECIPIENT CCIP_ADMIN_ADDRESS ROLES_RECIPIENT TOKEN TOKEN_POOL LOCK_BOX DECIMALS \
POOL_HOOKS AUTHORIZED_CALLERS FORCE_REDEPLOY REANCHOR
POOL_HOOKS AUTHORIZED_CALLERS FORCE_REDEPLOY REANCHOR SILO
$(foreach v,$(DEPLOY_VARS),$(if $(strip $($(v))),$(eval export $(v))))

# Recipe-time guard: the CHAIN's config file must exist (helpful list + add-chain hint on a miss).
Expand Down Expand Up @@ -131,8 +131,8 @@ tools: ## Check the required tools are installed (forge, curl, jq; CHAIN= adds t
discover: tools ## List the CCIP API chain catalog vs local configs, both planes (FILTER=<term> narrows; ENVIRONMENT=<testnet|mainnet> narrows the plane)
@FILTER="$(FILTER)" ENVIRONMENT="$(ENVIRONMENT)" bash script/config/sync-discover.sh

discover-tokens: tools ## List the CCIP API token catalog for an operator (ADMIN=, SYMBOL=, CHAIN_SELECTOR=, ENVIRONMENT= all narrow; unreviewed tokens included)
@ADMIN="$(ADMIN)" SYMBOL="$(SYMBOL)" CHAIN_SELECTOR="$(CHAIN_SELECTOR)" ENVIRONMENT="$(ENVIRONMENT)" bash script/config/discover-tokens.sh
discover-tokens: tools ## List the CCIP API token catalog for an operator (ADMIN=, SYMBOL=, CHAIN_SELECTOR=, ENVIRONMENT= all narrow; unreviewed tokens included; POOL=1 adds the pool, its API type and version, one request per row)
@ADMIN="$(ADMIN)" SYMBOL="$(SYMBOL)" CHAIN_SELECTOR="$(CHAIN_SELECTOR)" ENVIRONMENT="$(ENVIRONMENT)" POOL="$(POOL)" bash script/config/discover-tokens.sh

add-chain: tools ## Generate config/chains/<CHAIN>.json from the live API (CHAIN= and SELECTOR= required)
$(if $(CHAIN),,$(error CHAIN is required: make add-chain CHAIN=<selectorName> SELECTOR=<selector> - both from the make discover API NAME + SELECTOR columns))
Expand Down Expand Up @@ -203,6 +203,12 @@ else
@FOUNDRY_PROFILE=sync PROJECT_GROUP="$(GROUP)" bash script/config/forge-fork.sh "$(CHAIN)" -- forge script script/config/AdoptToken.s.sol $(call evm-version-flag,$(CHAIN)) --sig "run(string,address,address)" "$(CHAIN)" "$(TOKEN)" "$(or $(TOKEN_POOL),0x0000000000000000000000000000000000000000)"
endif

forget-deployment: tools ## Remove a retired entry from project/[<GROUP>/]<CHAIN>.json deployments{} (CHAIN= NAME=<deployments key> required; refuses an active, registered, still-laned or funded artifact; PREVIEW=1 changes nothing; local edit, nothing sent)
$(if $(CHAIN),,$(error CHAIN is required: make forget-deployment CHAIN=<name> NAME=<deployments key>))
$(if $(NAME),,$(error NAME is required - the addresses.deployments key to remove, e.g. WBTC_BurnMintTokenPool_1.5.1))
$(require-chain-config)
@FOUNDRY_PROFILE=sync PROJECT_GROUP="$(GROUP)" forge script script/config/ForgetDeployment.s.sol $(call evm-version-flag,$(CHAIN)) --sig "run(string,string,bool)" "$(CHAIN)" "$(NAME)" $(if $(PREVIEW),true,false)

sync: tools ## Refresh <CHAIN>'s ccip{} block from the live API (CHAIN= required)
$(if $(CHAIN),,$(error CHAIN is required: make sync CHAIN=<name>))
$(require-chain-config)
Expand Down Expand Up @@ -347,6 +353,12 @@ deploy-lockrelease-pool: tools ## Deploy a LockRelease token pool on <CHAIN> (CH
$(call require-evm-chain,$(CHAIN),$(NON_EVM_DEPLOY_HINT))
$(call run-deploy,script/deploy/DeployLockReleaseTokenPool.s.sol)

deploy-siloed-pool: tools ## Deploy a SiloedLockRelease token pool on <CHAIN>, no lock box (CHAIN= + KEYSTORE_NAME= required; token from the registry, else TOKEN=; opt POOL_HOOKS=; VERIFY=1; FORCE_REDEPLOY=1; GROUP=). Then deploy-lockbox SILO=<label> per silo and configure/siloed/ConfigureLockBoxes
$(if $(CHAIN),,$(error CHAIN is required: make deploy-siloed-pool CHAIN=<name>))
$(require-chain-config)
$(call require-evm-chain,$(CHAIN),$(NON_EVM_DEPLOY_HINT))
$(call run-deploy,script/deploy/DeploySiloedLockReleaseTokenPool.s.sol)

deploy-new-chain: tools ## Guided deploy: add-chain -> deploy-token -> deploy-pool -> doctor (CHAIN= SELECTOR= + KEYSTORE_NAME= required; token params + VERIFY= via env). Register, set-pool, and wire-lane come next - see docs/workflows/greenfield-deploy.md; a green run means deployed, not yet cross-chain-live
$(if $(CHAIN),,$(error CHAIN is required: make deploy-new-chain CHAIN=<selectorName> SELECTOR=<selector> (token params via env)))
$(if $(SELECTOR),,$(error SELECTOR is required - find it with: make discover FILTER=<term>))
Expand Down
3 changes: 3 additions & 0 deletions docs/config-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,9 @@ engine never assumes one:
"address": "0xLockbox...", "owner": "0xGov...",
"authorizedCallers": ["0xPool..."] // enumerable → full two-sided set compare
},
"lockboxes": { // Siloed 2.0 only: one entry per distinct box, keyed by address
"0xBoxA...": { "owner": "0xGov...", "authorizedCallers": ["0xPool..."] }
}, // two-sided: every declared box is mapped, every mapped box declared
"hooks": { // the CCV/allowlist authority (security-critical)
"address": "0xHooks...", "owner": "0xGov...", "policyEngine": "0xPolicy...",
"allowlistEnabled": false, // immutable (set at deploy)
Expand Down
8 changes: 7 additions & 1 deletion docs/deployed-addresses.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Two terms, kept distinct throughout these docs:
| Participant | Role | What it does |
| -------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Deploy scripts (`--broadcast`) | **Writer** | Record each artifact via one `DeploymentRecorder` call → `history/` file + `addresses{}` entry |
| `make adopt-token` | **Writer / reconciler** | Bring an externally deployed token/pool into `addresses{}` after on-chain validation; also the tool that repoints `active.<role>` to reconcile the store to reality |
| `make adopt-token` | **Writer / reconciler** | Bring an externally deployed token/pool into `addresses{}` after on-chain validation (a 2.0 pool's lock boxes too); also the tool that repoints `active.<role>` to reconcile the store to reality |
| `make forget-deployment` | **Remover** | Drop one retired `deployments{}` entry (typically the old pool after a migration). Refuses an entry that is still `active`, still registered, still has lanes, or a lock box still holding tokens; `PREVIEW=1` changes nothing |
| Env vars (`{CHAIN}_TOKEN`, `TOKEN`, …) | **Override (READ-ONLY)** | Win over `addresses.active.<role>` at resolution time only |
| Run-time divergence notice | **Warner** | When an env override differs from `active.<role>`, a broadcasting script prints both values + the exact `make adopt-token …` to reconcile |
| `make doctor` TAR rung | **Warner** | Compares `active.tokenPool` against the on-chain TokenAdminRegistry and WARNs on divergence |
Expand Down Expand Up @@ -105,8 +106,13 @@ Per-artifact keys (`DeploymentRecorder`):
| Token | `{symbol}_Token` | `token` |
| Token pool | `{symbol}_{poolType}TokenPool_{version}` | `tokenPool` |
| LockBox | `{symbol}_LockBox` | `lockBox` |
| Silo lock box (`SILO=<label>`) | `{symbol}_LockBox_{label}` | none |
| Pool hooks | `{symbol}_{poolType}_PoolHooks` | `poolHooks` |

A siloed pool has several boxes and none of them is "the" lock box, so silo boxes get a `deployments{}`
entry only and every per-box script takes `LOCK_BOX=` explicitly. `adopt-token` names an adopted silo box
after the first remote chain it serves (`{symbol}_LockBox_<chain>`).

The pool key includes the pool's **type and version** so distinct artifacts never collide in storage. This
is a mechanical keying property, not a migration workflow: the deploy scripts pin the version
(`DeploymentRecorder.POOL_VERSION` = `"2.0.0"`), so `poolName()` only ever emits the `_2.0.0` key. Deploying
Expand Down
9 changes: 7 additions & 2 deletions docs/enabling-existing-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,19 @@ single registry entry (see [what adopt-token validates](#what-adopt-token-valida
## Scenario 2: existing token and pool

When another toolchain (or an earlier version of this repo) deployed both contracts, adopt them
together:
together. To find what is already registered, and which pool type and version each chain runs,
`make discover-tokens SYMBOL=<sym> POOL=1` lists it from the CCIP API (the API's type and version, not the
on-chain `typeAndVersion()` string, which adoption reads itself):

```bash
make adopt-token CHAIN=ethereum-testnet-sepolia TOKEN=0xYourToken TOKEN_POOL=0xYourPool
```

The pool is resolved through the [pool-version catalog](pool-versions.md) and cross-checked against
the token before anything is written. After a successful adoption the day-2 scripts work with zero
the token before anything is written. A 2.0 LockRelease pool's lock box is recorded too
(`{symbol}_LockBox` + `active.lockBox`), and a Siloed 2.0 pool's boxes as `{symbol}_LockBox_<chain>`, named
after the first remote chain each one serves. A 1.6.x Siloed pool holds its own liquidity, so there is no
box to record. After a successful adoption the day-2 scripts work with zero
exports, exactly as if this repo had deployed the contracts. The command prints the next steps that
still apply to your token: registration (`ClaimAdmin` + `AcceptAdminRole`) if the token is not
registered yet, `SetPool` if the TokenAdminRegistry does not point at this pool, lane wiring, and
Expand Down
1 change: 1 addition & 0 deletions docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ concept page, or a dedicated guide. Grouping is for navigation only.
- [Health-check a deployment before mainnet](health-check.md).
- [Preflight a transfer before sending](preflight-a-transfer.md).
- [Migrate a pool from v1 to v2](migrate-pool-v1-to-v2.md).
- [Migrate a Siloed LockRelease pool from 1.6 to 2.0](migrate-siloed-pool.md).
- [Send, track, manually execute, and diagnose a transfer](send-track-diagnose.md).
3 changes: 3 additions & 0 deletions docs/guides/migrate-pool-v1-to-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ so step 3 below is not performable there and that pool must be migrated by a dir
not clear. This step is optional, and deferring it indefinitely is a valid choice, because a pool that
is out of the `TokenAdminRegistry` is already inert.

Once the old pool is fully retired, `make forget-deployment CHAIN=<name> NAME=<old pool key>` drops its
`deployments{}` entry, which also clears doctor's two-pools warning.

The removal in step 6 is per peer, not per pool: every pool that registered this chain's old pool has to
drop it too, so the blast radius grows with the number of remotes.

Expand Down
44 changes: 44 additions & 0 deletions docs/guides/migrate-siloed-pool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
type: guide
---

# Migrate a Siloed LockRelease pool from 1.6 to 2.0

A 1.6.0 or 1.6.1 `SiloedLockReleaseTokenPool` holds its liquidity on the pool, one balance per siloed chain
plus a shared balance. A 2.0.0 pool holds none: each remote chain maps to an `ERC20LockBox`. Nothing can be
re-pointed, so the migration moves the tokens. The remote BurnMint pools follow
[migrate a pool from v1 to v2](migrate-pool-v1-to-v2.md).

## The workflow

1. **Deploy the 2.0 side.** `make deploy-siloed-pool`, one `make deploy-lockbox SILO=<label>` per silo and
one for the shared chains, and the remote 2.0 pools with their mint and burn roles. Authorize the new
pool, and the account that will move liquidity, on every box (`UpdateAuthorizedCallers`).
2. **Wire it.** `ConfigureLockBoxes` first, then `ApplyChainUpdates` for each lane of the new pools. Keep
both generations reachable: `AddRemotePool` the old peer on every new pool and the new peer on every old
pool, so a message committed against either side still releases.
3. **Move the liquidity**, as the pool owner and rebalancers:
- Silo: `SetSiloRebalancer` (a mainnet silo may hold a placeholder rebalancer such as `0x…01`),
`WithdrawSiloedLiquidity`, then `DepositToLockBox` into that silo's box.
- Shared: `SetRebalancer`, `WithdrawLiquidity`, then `DepositToLockBox` into the shared box.
- Leave a buffer on the old pool that covers inbound messages still in flight: they release from the old
pool, and fail until refunded if it is empty.
4. **Cut over.** `SetPool` on every chain.
5. **Validate and sweep.** One transfer per directed lane on the new pools, then, after the drain window,
move the old pool's buffer into the boxes.
6. **Retire the old generation.** `RemoveRemotePool` the old peer from every new pool, `RemoveChain` every
lane of the old pools, and `RevokeTokenRole` the old burn-mint pools' minter and burner roles. Then
`make forget-deployment NAME=<old pool key>` on each chain: doctor warns while two pools are recorded,
and the command refuses a pool that is still registered, still has lanes, or is still `active`.

`GetSiloedPoolState` reads either generation's layout; run it before and after each step. Every write
above supports `MODE=safe`. A timelock-owned pool needs the same calls scheduled through its timelock.

## Checks that catch real mistakes

- Box balances per chain after step 3 equal the old per-chain balances minus the buffer.
- A 2.0 pool keeps its transfer fee on the pool; reconcile liquidity from the boxes only.
- The ccip-sdk reads a LockRelease destination's balance through `getLockBox()`, which a Siloed 2.0 pool
does not have, so any balance figure it reports for one is not the box balance. With ccip-cli 1.13.0,
sends and `make preflight` into a Siloed 2.0 pool still went through on the v2 staging plane; read the
boxes with `GetSiloedPoolState` rather than trusting a reported balance.
4 changes: 3 additions & 1 deletion docs/guides/preflight-a-transfer.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ counterpart to the static [health check](health-check.md).
So a project that already keeps a key in `.env` gets a sender-scoped estimate with no extra flag. To
scope it to a keystore account instead, set `WALLET=foundry:<account>` together with
`FOUNDRY_KEYSTORE_PASSWORD` (the wrapper requires the password up front, because `--no-interactive`
makes `ccip-cli` swallow the failure and quietly estimate with no sender at all). With none of them the
makes `ccip-cli` swallow the failure and quietly estimate with no sender at all). A passwordless keystore
is `FOUNDRY_KEYSTORE_PASSWORD=` (empty); ccip-cli 1.13.0 and earlier cannot open one without a terminal,
so the wrapper refuses it on those versions - use the `.env` key there. With none of them the
estimate still runs, unscoped, and the wrapper says so. There is no way to scope it to an address you
hold no key for: `--wallet` takes a wallet, not an address.

Expand Down
1 change: 1 addition & 0 deletions docs/operations/chains.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ reference: [Config architecture](../config-architecture.md).
| I want to | Run |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| See which chains exist / find a selector | `make discover FILTER=<term>` |
| See which tokens exist, and each one's pool type and version | `make discover-tokens SYMBOL=<sym> POOL=1` (also `ADMIN=`, `CHAIN_SELECTOR=`, `ENVIRONMENT=`) |
| Onboard a new chain | `make add-chain CHAIN=<name> SELECTOR=<sel>`, then `make doctor CHAIN=<name>` |
| Check whether any config drifted from the API (routine; what CI runs weekly) | `make sync-check` (CI/automation: `bash script/config/sync-check.sh` for the 0/1/2 exit codes) |
| Inspect what the API currently has for one chain before changing anything | `make sync-preview CHAIN=<name>` |
Expand Down
Loading
Loading