Repository navigation
Give the timelock hand-off its own script, so a second pass can run it - #354
Merged
Merged
Conversation
Arc and Robinhood both launched with every proxy, both beacons and the group factory owned by one deployer EOA with no code, while their 2-of-5 Safes and TimelockControllers sat deployed, correctly wired to each other, and owning nothing. Arc's ProxyAdmin has since been handed over; the rest has not, and Robinhood is untouched. The cause is structural rather than a typo. Those transfers live inside the `:deploy` scripts that create the contracts, and on a two-pass chain launch that is the wrong place for them: pass 1 runs with protocolTimelock unset, the transfer is skipped, the script records its migration id anyway, and pass 2 - the pass that has the timelock - never runs it again. The instruction those scripts print, "run deploy again after setting protocolTimelock", is not something anyone can actually follow. Clearing the stale records would unstick it, but it also re-runs deployBeacon against whatever contracts ref the run pins, shipping a new implementation when all that was wanted was to move an owner. So the transfer gets its own script and its own id, which no existing record blocks. It deploys nothing. Every action is guarded on the current owner: already the timelock is reported and left alone, the deployer is transferred, anything else is reported and never forced. A chain that is already correct is a no-op, and a chain handed to something other than the deployer is never quietly overwritten. A contract a chain does not have is skipped rather than taking the run down. The two scripts that caused this keep their own behaviour - they did deploy what they were asked to, and their ids are honestly recorded - but they no longer print an instruction that cannot be carried out. They point here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc9MzY447rkqkXopXT6vmP
Written by scripts/deploy-chain.sh. Timelock: not yet deployed
Written by scripts/deploy-chain.sh. Timelock: not yet deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Arc and Robinhood both launched with every proxy, both beacons and the group factory owned by one deployer EOA with no code —
0x8cfd836B4FCD05414f63523BC02f24bda992aa20— while their 2-of-5 Safes and TimelockControllers sat deployed, correctly wired to each other, and owning nothing. Arc's ProxyAdmin has since been handed over (#353); the rest has not, and Robinhood is entirely untouched.Mainnet is correct for contrast — its
ProxyAdmin.owner()is the configured timelock. This is specific to the two chains launched after the bug existed, and both are live with real loans.Why it happened
The transfers live inside the
:deployscripts that create those contracts, and on a two-pass chain launch that is the wrong place for them:protocolTimelockunset → transfer skippedreturn trues anyway → hardhat-deploy records its migration idSo the instruction those scripts print, "run deploy again after setting protocolTimelock", is not something anyone can actually follow.
Why a new script rather than clearing the records
Clearing the stale ids would unstick it, but those ids gate
:deployscripts — re-running them also re-runsdeployBeaconagainst whatever contracts ref the run pins, shipping a new beacon implementation when all that was wanted was to move an owner.So the transfer gets its own id, which no existing record blocks. It deploys nothing.
Every action is guarded on the current owner:
A chain that is already correct is a no-op. A chain handed to something other than the deployer is never quietly overwritten. A contract a chain doesn't have is skipped rather than taking the run down. It covers
CollateralEscrowBeacon,LenderCommitmentGroupBeaconV2,LenderCommitmentGroupFactory_V2and the ProxyAdmin, and it skips entirely whereprotocolTimelockis unset — the one case where recording an id would be wrong.Modelled on
38_transfer_apechain_timelock_ownership.ts, which does the same thing for one chain; this is the chain-agnostic version.The two scripts that caused it
They keep their behaviour — they did deploy what they were asked to, and their ids are honestly recorded — but they no longer print an instruction that cannot be carried out. They point here instead.
To run
with
<CHAIN>_TIMELOCK_ADDRESSset. Arc's is0x10b5089c2707e6d04750c10ee9D2Cc877761AE5C, Robinhood's is0x81B014d0318361D646f7930D27C174D104724106.Not verified
No
node_modulesin this checkout, so nothing was compiled or type-checked.🤖 Generated with Claude Code
https://claude.ai/code/session_01Gc9MzY447rkqkXopXT6vmP
Generated by Claude Code