Repository navigation
Conversation
Brings the numeric-namespace change onto the upgrade-tooling branch and adds the PR-scoped upgrade scripts, pinned pre-upgrade snapshots, and fork tests that swap the registry, PoP controller, PoP rules, and reverse resolver proxies in place and redeploy the lens, so every address is kept. The fork suite issues a lite username against live state to prove it lands as a subname beneath its numeric container.
Brings the generic SubnodeUtils, the unified lite-node derivation, the registry write returning the subnode, and the lens store-row and cold-path fixes onto the in-place upgrade branch, so the swapped implementations carry them.
Brings the clarified `persist` documentation and the registry store-write tests onto the in-place upgrade branch so it stays in step with the feature branch.
## Description Upgrades the deployed proxies in place so the numeric-namespace change reaches live state while every address is kept. It swaps the registry, PoP controller, PoP rules, and reverse resolver implementations behind their existing proxies and redeploys the immutable lens, repointing the `popLens` key. Each proxy swap resolves its target from the on-disk manifest and diffs the new storage layout against a pinned pre-upgrade snapshot, failing closed if a slot moves, shrinks, or changes type. Fork tests run each script against live Paseo Asset Hub state through the ETH-RPC adapter. One test per upgrade proves the swap keeps the address, the owner, and the state; an end-to-end test issues a lite username and checks it lands as a subname beneath its numeric container, owned in the registry rather than held as a token. Every upgrade script, pre-upgrade snapshot, and fork test in this change is scoped to this pull request and removed before merge, so the default branch keeps no upgrade scaffolding and the fork suite runs zero tests between upgrades. This branch is based on the upgrade-tooling branch and carries the numeric-namespace contract change so the swapped implementations contain it. Migration note: lite usernames issued before this upgrade were recorded as atomic labels under the top level. The upgraded code addresses a lite name at the container-then-stem node, so a pre-upgrade lite name is not read by the new path and its subname node can be issued afresh. That overwrite is accepted. ## Type - [ ] Bug fix - [x] Feature - [x] Breaking change - [ ] Documentation - [ ] Chore - [ ] Refactor - [ ] Security ## Scope - [x] Registration - [x] Resolver - [ ] Store - [x] Proof of Personhood - [x] Deployment scripts - [x] Tests ## Related Issues - #287 ## Fixes ## Checklist ### Code - [x] Follows project style - [x] `forge build` passes - [x] `forge test` passes - [x] No new compiler warnings ### Testing - [x] New tests added for changed behavior - [ ] Fuzz tests added where applicable - [ ] Invariant tests verified ### Security - [x] No new `selfdestruct` or `delegatecall` - [x] Access control reviewed - [x] No storage layout conflicts (for upgradeable contracts) ### Documentation - [x] NatSpec updated on changed interfaces - [ ] README updated if needed ### Breaking Changes - [ ] No breaking changes - [x] Breaking changes documented below **Breaking changes:** - Lite usernames issued before this upgrade are addressed at a different node afterwards and are not migrated. Their subname node can be issued afresh, so a pre-upgrade lite name is not preserved. ## How to test Fork tests need the local ETH-RPC adapter on `paseo_local`. ```bash bun run test:fork ``` ## Notes Upgrade scripts run through `scripts/deploy/upgrade.sh`, for example `SCRIPT=UpgradeRegistry ACCOUNT_NAME=<keystore> RPC_URL=<network> ./scripts/deploy/upgrade.sh`.
Brings the dev branch up to tag v0.8.0 (3c8a9e9), so the upgrade artefacts sit on top of the released code instead of a fork point 43 commits behind it. This is step 1 of the Paseo in-place upgrade: #301 (writeNewLabel), #304 (protocol version and codehash declarations), #298 (UUPS StoreFactory) and #305 (deferred store-write gate) all arrive before any upgrade script is written against them. The textual merge was conflict-free, but it left one semantic conflict: DotnsRegistryOld calls StoreUtils.writeLabel, which #301 removed. The snapshot has to keep reproducing the implementation deployed on chain, so it cannot move to writeNewLabel. Resolved by adding contracts/utils/StoreUtilsOld.sol, a verbatim PR-scoped snapshot of the library as deployed, and pointing the Old registry at it. It is deleted along with every other *Old.sol before the upgrade branch merges to master. Outside *Old.sol, the contract diff against master is the four storage-layout fixes this branch carries and nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Description Merges master at v0.8.0 (3c8a9e9) into the upgrade branch ## Type - [ ] Bug fix - [ ] Feature - [ ] Breaking change - [ ] Documentation - [x] Chore - [ ] Refactor - [ ] Security ## Scope - [ ] Registration - [ ] Resolver - [x] Store - [ ] Proof of Personhood - [x] Deployment scripts - [ ] Tests ## Related Issues ## Fixes ## Checklist ### Code - [x] Follows project style - [x] `forge build` passes - [x] `forge test` passes - [x] No new compiler warnings ### Testing - [ ] New tests added for changed behavior - [ ] Fuzz tests added where applicable - [ ] Invariant tests verified ### Security - [x] No new `selfdestruct` or `delegatecall` - [x] Access control reviewed - [x] No storage layout conflicts (for upgradeable contracts) ### Documentation - [x] NatSpec updated on changed interfaces - [x] README updated if needed ### Breaking Changes - [x] No breaking changes - [ ] Breaking changes documented below **Breaking changes:** ## How to test ```bash forge test --mt <testName> ``` ## Notes
The `*Old.sol` snapshots on this branch described implementations that were replaced in place months ago. All four built to different bytecode from what Paseo Asset Hub Next is running, and nothing noticed: the OpenZeppelin layout diff compares the new implementation against whatever the snapshot happens to be, so a stale reference produces an honest diff of the wrong pair, and a change that lives in calldata leaves no trace in a layout at all. Regenerate all of them from the build that is actually deployed (c852004), and prove it. `scripts/shell/verify-snapshots.sh` builds each snapshot and compares runtime bytecode against the implementation behind the proxy, masking only `UUPSUpgradeable.__self` and the trailing CBOR metadata. All twelve upgraded proxies match byte for byte. `fork-tests.sh` runs it before the suite, so the check cannot be skipped by anyone running the tests the normal way. The snapshot set is larger than the twelve contracts because master has moved 26 contract files since the deployed build. A snapshot that imports the current tree stops reproducing the deployed bytecode, and one that imports a snapshot hands a renamed type to a signature expecting the current one. The set is therefore closed over both: everything reachable that changed, plus everything that reaches one of those. Sixteen unchanged interfaces and libraries stay shared, unrenamed. CI now runs on pull requests into `spha/**`. It previously triggered on master alone, so a PR into this branch got lint and nothing else, which is how the stale snapshots reached review in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the gap it caught The storage-layout diff only ran inside an upgrade script, which meant it only ran when someone brought up the ETH-RPC adapter and ran the fork suite. Lift it into a plain unit test, one case per proxy, so every push answers the question the upgrade actually turns on: would `upgradeProxy` accept this layout change. It found one on its first run. `DotnsProtocolRegistry` appends `_expectedCodehash` and `_protocolVersion` for the declarations added in #304, but left `__gap` at 50, so the gap starts two slots later than on the deployed proxy and the contract's footprint grows. The validator rejects it, and any field appended after the gap in a later release would land on a slot the live proxy does not expect. Sized to 48, which keeps the 54-slot footprint. This is a fifth storage-layout correction of the same family as the four already on this branch, and like those it stays here per the branch policy. The fix moves no bytecode: a gap reserves slots and emits no code, so the implementation still builds byte-identical to the v0.8.0 tag. The release-drift finding is unchanged, and `DotnsRegistrarController` remains the only proxy whose deployed code will differ from the tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ation Thirteen scripts and thirteen fork tests, paired one to one. Twelve swap a proxy in place; the thirteenth migrates the store factory, which cannot be swapped because what is deployed is a plain contract from an earlier release and the current one puts the factory behind a proxy at a different address. Each fork test reads live state through the deployed implementation, runs the script's own internal path rather than reimplementing the upgrade, and reads the same state back. Each asserts the implementation address actually changed, without which a swap that silently did nothing would satisfy every state-preservation assertion. The assertions are per contract: the registry drives the new deferral gate against the live controller set, which a unit test cannot reach; the registrar controller reads back the protocol registry pointer that the retained slot exists to keep in place; the escrow asserts its redeem window is both unchanged and non-zero, that being the field an upgrade has dropped before. `StoreFactoryMigrator` closes the migration without a release. It is the shipped factory with four fields widened and a one-shot owner-only import, swapped into the proxy for a single transaction and swapped back out, so the deployment ends on the shipped implementation with the bindings in place. Both directions are layout-diffed. The holder list it needs cannot be read from the factory: the mapping has no enumeration, and the list it does keep holds store addresses, which do not know which user they belong to. Only `LabelStoreDeployed` carries the pairing, so `scripts/shell/store-holders.sh` replays it and reconciles the result against the factory's own count. It refuses to print a list it cannot account for, which matters because the public gateway answers log queries with an empty result rather than an error: a naive replay against it looks like it worked and is empty. The count also moves, from 57 to 58 while this was being written, so the list is read immediately before broadcasting and never reused. `DeclareRelease` extends `WireDeployments` rather than restating its key list, so a release that adds a key cannot be declared on a fresh deploy and silently skipped on every upgrade. Docs carry the parts that are not obvious from the code. CONTRIBUTING now says the artefacts stay on a branch that never merges, and that a snapshot is of the deployed code rather than of the previous release. DEPLOYMENTS and a folder README record why this network's chain id does not identify it, why its gateway's empty log results are a trap, and why `verify --tag` will report one key as drift permanently. The README no longer claims a lite name's node cannot collide with a subname, which stopped being true when lite names became subnames. The note stayed out of the manifest deliberately: the deploy pipeline parses every key there as an address, so a text field fails it on first read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four things an AI review caught, the first of which was mine and the worst. The migration was built on a false premise. I claimed the deployed factory had no enumeration and that stores did not know their user, and wrote a log-replay helper, a count-reconciliation guard and two fork tests around that. Both claims are false: `getLabelStores(0, n)` answers on the live factory, and `IDotnsStore.owner()` on a live store round-trips back through `getLabelStore`. `importStores` now takes only the old factory and reads the count, the list and each store's owner on chain, checking every pairing back through the mapping before it writes. `store-holders.sh` is deleted. That also closes a race the old shape carried. The set was read off chain and passed in, so a store created between the read and the import was silently left unbound, and its holder would be handed an empty store on their next registration. The count on this network moved while this branch was being written. Reading inside the transaction removes the window, and a test covers a binding created after the decision to migrate. The script could not have been broadcast. It read the destination from the manifest key `StoreFactory`, which still names the factory being migrated from, and nothing deployed the replacement: the CREATE3 address for it is empty on chain. Meanwhile the fork test quietly deployed the proxy itself, so the two told different stories and the test passed. `MigrateStoreFactory` now deploys the replacement through the pipeline's own helper, which also adopts one a previous run left behind, and records it and its beacons in the manifest. The success condition was never asserted. After a real migration every mint resolves `STORE_FACTORY` through the protocol registry and calls `ensureLabelStore`; nothing exercised that path. The fork test now upgrades the protocol registry first, because the rewire declares a codehash that entrypoint does not have yet, runs all four legs, and asserts a live holder resolves to the store they already had with no extra store deployed. Two smaller ones. `verify-snapshots.sh` ran only from `fork-tests.sh`, and CI calls `forge test` directly, so the check this branch leans on was absent from the one place reviewers see; it now runs in the fork job. `UserStore` added `_protocolRegistry` without shrinking its gap, the same defect already fixed on the protocol registry; it is harmless at zero user stores and would not stay harmless through a beacon rotation. `docs/PASEO-V080-RUNBOOK.md` writes down the broadcast order. Nothing in the scripts enforces it and they are independently runnable by design, so the three dependencies that fix the order live there: registry first because every `version()` reads through it, the migration before the declaration because it rewires a key the declaration records, and the declaration last because it claims something about the whole deployment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d factory Three findings from the second review, all correct. The fork test could not have run the deploy leg it claims to exercise. `_deployReplacement` reaches `_create3Factory()`, which falls back to reading `DotnsProtocolRegistry` out of the manifest, and the manifest is only populated by `initDeployment`, which `run` calls and the harness does not. The test would have failed at CREATE3 resolution before reaching its assertion, and the unit tests cannot see that. `_deployReplacement` now adopts the CREATE3 factory from the protocol registry it was already handed, so the leg depends on no ambient state and production and the test run the same path. The script erased the only durable pointer to the factory it migrates away from. The deploy reuses the `StoreFactory` label and the replacement mints its own beacons, so after a successful run all three manifest entries described the new deployment, while the imported stores stayed on the old beacons. Those beacons answer to the old factory, and it is the only contract that can ever rotate the implementation behind those stores. The outgoing addresses are now written first, as `StoreFactoryLegacy`, `LabelStoreBeaconLegacy` and `UserStoreBeaconLegacy`, and the runbook says which one a later rotation goes through. The resume claim was too strong. Adoption covers a run that died between the deploy and the import, and nothing else: once the import has landed a second run reverts on the first user it tries to bind, and if the proxy is still on the migrator the adopt is refused, because the deployer requires the occupant to delegate to the implementation that run deployed. The NatSpec said the step was resumable; it now says what is actually true, and the runbook marks step 13 as the one step to inspect and continue rather than re-run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Description Brings Paseo Asset Hub Next to the current release in place: 12 proxies swapped, store factory migrated, release declared last. Only two shipped contracts change (`DotnsProtocolRegistry`, new `StoreFactoryMigrator`); the rest is upgrade tooling, snapshots and tests. Two things were broken and passing every check: - **All four `Old.sol` snapshots were stale**, describing implementations replaced in place months ago, so every layout diff compared the wrong pair. Nothing can catch that: the diff compares whatever it is handed, and a calldata-only change leaves no trace in a layout. Regenerated from `c8520046` (the build actually deployed) and proved by `scripts/shell/verify-snapshots.sh`, which compares each snapshot's bytecode against the implementation behind the proxy. All 12 match; `fork-tests.sh` runs it before the suite. - **`DotnsProtocolRegistry` could not be upgraded onto the live proxy.** #304 appended two fields but left `__gap` at 50, so the gap starts two slots late and the validator rejects it. Now 48. Found by the new layout suite, which moves the diff into a plain unit test so it runs on every push. No bytecode change. The store factory cannot be swapped: what is deployed is a plain contract, and the release puts the factory behind a proxy elsewhere. Its bindings are proxy storage with no export, so re-pointing `STORE_FACTORY` at a fresh factory gives every existing holder a second empty store and drops their labels from enumeration. `StoreFactoryMigrator` avoids that without a release: the shipped factory with four fields widened plus a one-shot owner-only import, swapped in for one transaction and back out, both directions layout-diffed. ## Type - [ ] Bug fix - [ ] Feature - [ ] Breaking change - [ ] Documentation - [x] Chore - [ ] Refactor - [ ] Security ## Scope - [ ] Registration - [ ] Resolver - [ ] Store - [ ] Proof of Personhood - [x] Deployment scripts - [ ] Tests ## Related Issues Follows #308 (master merged into this branch). Supersedes #306. ## Fixes Fixes deployment upgrade to v0.8.0 on Paseo ## Checklist ### Code - [ ] Follows project style - [ ] `forge build` passes - [ ] `forge test` passes - [ ] No new compiler warnings ### Testing - [ ] New tests added for changed behavior - [ ] Fuzz tests added where applicable - [ ] Invariant tests verified ### Security - [ ] No new `selfdestruct` or `delegatecall` - [ ] Access control reviewed - [ ] No storage layout conflicts (for upgradeable contracts) ### Documentation - [ ] NatSpec updated on changed interfaces - [ ] README updated if needed ### Breaking Changes - [ ] No breaking changes - [ ] Breaking changes documented below **Breaking changes:** ## How to test ```bash forge test --match-path 'test/unit/upgrade/LayoutCompatibility.t.sol' forge test --match-path 'test/unit/store/StoreFactoryMigrator.t.sol' ``` ## Notes **Permanent drift on `DotnsRegistrarController`.** Its retained slot changes emitted slot constants, so the deployed code will differ from the v0.8.0 tag forever. A tag build reads `protocolRegistry` as zero and bricks the contract, so the branch build is the only deployable one. `verify --tag` reports that one key as drift by design; a second one is a real finding. Recorded in DEPLOYMENTS.md and `deployments/paseo-assethub/README.md`. **Holder list for the migration** cannot be read from the factory: no enumeration, and the list it keeps holds stores, which do not know their user. `scripts/shell/store-holders.sh` replays `LabelStoreDeployed` and reconciles against the factory's count, refusing a list it cannot account for. The public gateway returns empty for `eth_getLogs` instead of erroring, so a naive replay looks like it worked. The count moved 57 to 58 mid-work, so read it immediately before broadcasting. **Manifest deliberately untouched.** `initDeployment` parses every key as an address, so a note field breaks the pipeline; it went in a sibling README.
Prepares the rename of `spha/registrar-upgrade`. GitHub retargets open pull requests and leaves a redirect, but it does not touch file contents, so these four references would have gone stale silently. The CI trigger is the one that matters. It matched `spha/**`, and a renamed branch stops matching, which puts pull requests into it back to running lint and nothing else. That is the gap that let a storage snapshot describing a replaced implementation reach review in the first place. Now matched as `dev/**`, by prefix rather than by name, so the next long-lived branch is covered the day it is created instead of the day someone remembers this file. The other three are prose: CONTRIBUTING on why the artefacts stay on a branch that never merges, and DEPLOYMENTS and the manifest folder README on why this network's deployed code matches no release tag. Land this before the rename, not after. Nothing here touches `contracts/**`, `test/**` or `**.sol`, so the test workflow's path filter skips this commit under either branch name and no window opens either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
) Follows the rename of `spha/registrar-upgrade` to `dev/testnet-upgrades`. GitHub retargets open pull requests and leaves a redirect, but it does not touch file contents, so four references would have gone stale silently. The CI trigger is the one that matters. It matched `spha/**`, and the renamed branch stops matching, which puts pull requests into it back to running lint and nothing else. That is the gap that let a storage snapshot describing a replaced implementation reach review in the first place. Now matched as `dev/**`, by prefix rather than by name, so the next long-lived branch is covered the day it is created. The other three are prose: CONTRIBUTING on why the artefacts stay on a branch that never merges, and DEPLOYMENTS plus the manifest folder README on why this networks deployed code matches no release tag. Nothing here touches `contracts/**`, `test/**` or `**.sol`, so the test workflow path filter skips this commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…uite `DeclareRelease` would have aborted on Paseo Asset Hub Next, at step 14, after every swap and the store migration had already been broadcast. The registry's self-reference key resolves to the zero address there, and `_verifyDeployment` asserts it matches the manifest. Sixteen of the seventeen keys are wired and agree; that one is a hole a fresh deploy never has, left by a network wired before the key existed. `_wireMissingKeys` sets keys that are unset and leaves every other key exactly as it is. A key pointing somewhere unexpected still fails verification, because that is drift, and repairing it here would make the check that follows tautological and hide the thing it exists to surface. The fork suite has now been run, for the first time. All 15 tests pass against live state, including the three for the store migration, which could not have run at all before the CREATE3 resolution fix in the previous commit: they would have died resolving the factory out of a manifest the harness never loaded. They went unrun because they needed Docker. The adapter only translates for the same node the hosted endpoint already fronts, so `PASEO_FORK_RPC` now overrides the fork endpoint and the whole suite runs in under a minute with no container. The default stays on the local alias, so CI and anyone mid-deployment are not silently pointed at a public gateway. `fork-tests.sh` and the runbook carry the invocation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The deployment key's custody includes a laptop belonging to someone who has left, so it is treated as exposed. Rotation was queued as a follow-up for after v0.8.0; an exposed owner key makes that ordering backwards, and it is now the step everything else waits on. Rotation, not redeployment, because ownership is a storage field: every address, registry key, host destination, SDK pin and codehash declaration is untouched, and the only thing that changes is which key the onlyOwner gates answer to. `RotateOwnership.s.sol` transfers the fourteen owned contracts, probed on chain rather than assumed: twelve proxies, the cost-model registry, and the store factory, plus the outgoing factory once the migration has recorded it. The lens, the pricing helper and Multicall3 expose no owner in the deployed release, and the CREATE3 factory answers to a different key on purpose. The inventory is checked from both ends. Contracts that must be owned hard-fail on any surprise, every transfer is verified by readback because the one-step variant makes a wrong destination permanent, and the run ends by scanning the whole manifest for anything owner-answering that the lists missed, so a contract added by a later release stops the rotation instead of surviving it. Re-running after a partial failure skips what already moved, which is what lets the old key finish an interrupted run; a completed one re-runs as a loud no-op. The fork test proves the three facts the step exists for, against live state: every contract answers to the fresh key at its unchanged address, the old key can no longer drive an upgrade script, and the fresh key can. The script parses the manifest it reads itself instead of the deployer's ambient field, which is the lesson from the migration's unreachable deploy leg applied before the test could find it again. The runbook gains the step and its aftermath: swap the signer before step 1, sweep the old account's gas, and delete both stored copies of the old key, this repository's and the organisation's, which the deployment records show control the same account. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…master The dispatch button only exists for workflows on the default branch, and nothing that signs with the owner key is going there. A pull_request workflow is taken from the PR itself, so this file lives on the dev branch alone and the open review PR into master becomes the broadcast console: add `run:<Script>`, the run pauses on the `paseo-upgrade` environment until a reviewer approves, one script broadcasts through the local ETH-RPC adapter, the record and the manifest land as artifacts, the outcome is commented, and the label is removed so re-adding it is the retry. The run pins the PR's head commit at label time, not the merge ref: master merged into this branch is not the code that was reviewed or fork-tested, and pinning means a push after labelling cannot change what an approval executes. Runs are serialised and never cancelled, because two broadcasts would race nonces and a queued step must not be dropped by an eager second label. Key handling is asymmetric on purpose. Step 0 signs with the repository-level secret, because its value cannot be read and therefore cannot be moved into an environment; that exposure is time-boxed by being exactly what step 0 eliminates. The fresh key is entered as an environment secret under the same name, shadowing the repository one for every gated step after, and the repository copy is then deleted. The snapshot check runs automatically for step 0 only: one broadcast later the snapshots stop matching the chain by design, so anything else would be a gate that pretends. The manifest cannot be pushed back from the runner: this branch requires signed commits, which a runner cannot produce, so step 13's manifest change is committed by the operator from the artifact. The runbook carries the label loop, the environment setup, and where the manual steps sit between labels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Step 0 exists because the deployment key must be treated as leaked: copies have spread across sources the project does not control. How that came to be adds nothing a reader of this repository needs, so the script header and the runbook now say only what operationally matters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he network One key owns every testnet deployment stamped from these releases, and the environment protects the key, so `paseo-upgrade` was the wrong scope: a previewnet campaign would either duplicate the secret into a second environment or borrow a gate named after a different network. Renamed to `testnet-upgrades`, matching the branch. A network with its own key gets its own environment, mainnet above all. Renamed before the environment exists, because the workflow string is what a first label auto-creates when it finds no match, unprotected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new owner was to be funded by faucet or teleport before step 0, and the first attempt showed why that path deserves to die: the funds landed on the sibling Asset Hub, where a mapped account has no revive behind it and therefore no key that can ever spend it. Meanwhile the funding source was sitting in plain sight, the old account holds 954 on the target chain, and the step 0 run is the one signer that balance has, since the old key cannot be extracted for a manual transfer. So the rotation sweeps. After every owner has moved and been read back, the old account sends its balance minus a two-unit gas buffer to the new owner. One approved run now strips the leaked key of authority and of money together, instead of leaving the money for a race with whoever else holds the key. The buffer keeps a re-run able to pay its own fees; what it leaves behind is dust on a powerless account. The fork test asserts the transfer against the live balance, whatever it is on the day: the property is that nearly all of it moves, not any particular number. Runbook step 0 loses its funding instruction and gains the warning the mistake earned: never send to a mapped address on a chain without revive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Committed as 644, which nothing noticed until the broadcast workflow invoked it directly and died on permission denied, one step before the snapshot check and several before anything could sign. `fork-tests.sh` never tripped on it because a script sourced or run through bash does not consult its mode; the one caller that executes it by path is the workflow, and now every script any caller executes by path carries 755. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The adapter opens its RPC socket before it has subscribed to the node and filled its block cache. In that window, calls that pass straight through succeed, and anything touching the block index answers "Ethereum block not found", which is exactly the split the first rotation attempt fell into: the snapshot verification sailed through and the forge script died on its opening block fetch. Nothing broadcast, verified on chain afterwards, which is the fail-safe doing its job, but a readiness check that only proves the socket is open is a readiness check for the wrong thing. Ready now means the latest block resolves with a body and its number advances while we watch, which proves the subscription is live and the cache filling. Every caller shares the same script, so the fork suite gains the same patience. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Forty-odd sequential calls, each single-shot, so one hiccup anywhere failed the whole check, and upstream of step 0 that means a wasted label-approve cycle. Three attempts with a short backoff absorb the blips; a real outage still fails, as it must. The broadcast leg stays deliberately retry-free. Under --slow a failure leaves a clean prefix on chain, every script re-runs into that state by design, and the relabel puts the human approval back in the loop. An automatic retry around a broadcaster trades that legibility for the chance of double-driving partial state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
forge pins its fork to a block hash it fetched moments earlier, and with --eth-pruning 1 the adapter forgets that hash as soon as the next block lands, which on an advancing chain is immediately. The rotation run died at startup on "failed to get block hash", before simulation, chain untouched: fourth firing, fourth failure, still zero transactions, and this one was predicted as plan B two failures ago. A thousand blocks is minutes of history, ample for any broadcast and trivial for a runner. The fork tests never tripped on the one-block window because a test fork tolerates what a script pipeline pins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
UpgradeRegistry: success. Run. Broadcast record and manifest attached as artifacts. Re-add |
|
UpgradePopRules: success. Run. Broadcast record and manifest attached as artifacts. Re-add |
|
UpgradePopResolverAndController: success. Run. Broadcast record and manifest attached as artifacts. Re-add |
|
RedeployPopLens: success. Run. Broadcast record and manifest attached as artifacts. Re-add |
|
DeclareRelease: success. Run. Broadcast record and manifest attached as artifacts. Re-add |
|
Closing without merging: the v1.0.0 upgrade of Paseo Asset Hub Next is complete.
Each step was verified on chain before the next label: implementation moved and runs the branch build, owner unchanged, live records and pricing identical before and after. The console workflow is disabled again and the campaign is tagged |
## Description Paseo Asset Hub Next was upgraded in place from v0.8.0 to v1.0.0 on 2026-10-06 (content #331, broadcast via #332). The four upgraded contracts kept their proxies; the only address that moved is the PoP lens, redeployed at a release-scoped CREATE3 salt. | Key | Before | After | |---|---|---| | `DotnsPopLens` | `0xAE374b07c7e6f473CBa21d57e36AC15C631Abc51` | `0x309C5ff21f9082A53211500c6f33cA2a21024Ae4` | This brings master's manifest in line with the chain. ## Type - [ ] Bug fix - [ ] Feature - [ ] Breaking change - [ ] Documentation - [x] Chore - [ ] Refactor - [ ] Security ## Scope - [ ] Registration - [ ] Resolver - [ ] Store - [ ] Proof of Personhood - [x] Deployment scripts - [ ] Tests ## Related Issues See #331, #332 ## Fixes ## Checklist ### Code - [x] Follows project style - [ ] `forge build` passes - [ ] `forge test` passes - [ ] No new compiler warnings ### Testing - [ ] New tests added for changed behavior - [ ] Fuzz tests added where applicable - [ ] Invariant tests verified ### Security - [x] No new `selfdestruct` or `delegatecall` - [x] Access control reviewed - [x] No storage layout conflicts (for upgradeable contracts) ### Documentation - [ ] NatSpec updated on changed interfaces - [ ] README updated if needed ### Breaking Changes - [x] No breaking changes - [ ] Breaking changes documented below **Breaking changes:** ## How to test ```bash node scripts/js/release-metadata.mjs verify --network paseo-assethub \ --rpc https://eth-rpc-paseo-next.polkadot.io --tag v1.0.0 ``` ## Notes
Description
Review only. Never merged. Keep open until the upgrade is declared and verified: this PR is the broadcast console.
Upgrades Paseo Asset Hub Next in place from v0.8.0 to v1.0.0. Adding a
run:<Script>label triggers one gated step, which pauses on thetestnet-upgradesenvironment until a reviewer approves it, then comments the outcome and removes its label.docs/PASEO-V100-RUNBOOK.mdis the order and the operating manual. Campaign content was reviewed in #331.run:UpgradeRegistryrun:UpgradePopRulesrun:UpgradePopResolverAndControllerrun:RedeployPopLens0x309C5ff21f9082A53211500c6f33cA2a21024Ae4; then commit the manifest artifactrun:DeclareRelease1.0.0Shipped contracts differ from master only by the branch's layout fixes from the v0.8.0 campaign, unchanged here: gaps sized to the live proxies (DotnsRegistrar, DotnsPopController, DotnsProtocolRegistry, UserStore), the retained whitelist slot in DotnsRegistrarController, and the retained AccessControl namespace in DotnsRegistrarController and DotnsNameWhitelist. Everything else is upgrade material: snapshots of the deployed code (from
upgrades/paseo-nv2-v0.8.0), five step scripts, fork tests, the console and the runbook.Type
Scope
Related Issues
Follows #312 (v0.8.0). Content merged via #331.
Fixes
Checklist
Code
forge buildpassesforge testpassesTesting
Security
selfdestructordelegatecallDocumentation
Breaking Changes
Breaking changes:
How to test
Notes
From step 1 on, CI's full verify-snapshots here fails by design; the console checks only each step's own contracts. Labelled deployment-record because the step 4 manifest commit lands on this branch.