From d0dffd17d8a61d9c08c43f586378b83cb1b0a410 Mon Sep 17 00:00:00 2001 From: rplusq Date: Thu, 9 Jul 2026 20:03:12 +0100 Subject: [PATCH 1/2] chore(deployments): reconcile 10.json to live chain + add Arb/Base timelock deploy infra - 10.json was stale: LTS entries pointed at old immutable contracts; StakeWeight/ Distributor/Pauser impls predated P3/#45. Reconciled all proxy entries to live on-chain values (verified via EIP-1967 slots + getPostClaimHandlers). - Add TimelockDeploy.s.sol + arbitrum/base RPC aliases so ProxyAdmin ownership and the token admin role can be moved behind a per-chain timelock. Co-Authored-By: Claude Opus 4.8 (1M context) --- evm/deployments/10.json | 39 ++++++++++++++++++++------ evm/foundry.toml | 2 ++ evm/script/deploy/TimelockDeploy.s.sol | 36 ++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 evm/script/deploy/TimelockDeploy.s.sol diff --git a/evm/deployments/10.json b/evm/deployments/10.json index 3d3dfc5..046a9d3 100644 --- a/evm/deployments/10.json +++ b/evm/deployments/10.json @@ -16,13 +16,34 @@ } }, "LockedTokenStakerBackers": { - "address": "0x688CfB3e55fCE2540b5491E923Dc6a9C4f240176" + "address": "0x37BAdde9E3c9E108065Db36dCcce91E00556e334", + "admin": "0x8B79481a2d8EEbe0F4d6Fd1705b0598540875407", + "implementation": "0xB52b4633eA577A143fAa811127CeE7163b023bFb", + "proxy": { + "implementation": "0xB52b4633eA577A143fAa811127CeE7163b023bFb", + "admin": "0x8B79481a2d8EEbe0F4d6Fd1705b0598540875407", + "type": "transparent" + } }, "LockedTokenStakerReown": { - "address": "0x5f630a47DE14e346fC28deB8fE379833A6F6B9B2" + "address": "0xCcCf4C051C6F5a82D6bD76a1F66fc3cCdE8b3D6b", + "admin": "0x54594f1aF2A6bd876bF0f6d45FB7c3cb54eFB5d5", + "implementation": "0xABB9F4CCb5025c446c7FF2e000c705332d21f760", + "proxy": { + "implementation": "0xABB9F4CCb5025c446c7FF2e000c705332d21f760", + "admin": "0x54594f1aF2A6bd876bF0f6d45FB7c3cb54eFB5d5", + "type": "transparent" + } }, "LockedTokenStakerWalletConnect": { - "address": "0x8621034C9acD397cc5921d036225f75699c710FA" + "address": "0xb6034870fdd84f09cB63433297C667A1A7771824", + "admin": "0xBa900e340f781A18C34Ff9D73CDb9756329a71d9", + "implementation": "0xEc32c9280163795921caF79205C6850C67023F04", + "proxy": { + "implementation": "0xEc32c9280163795921caF79205C6850C67023F04", + "admin": "0xBa900e340f781A18C34Ff9D73CDb9756329a71d9", + "type": "transparent" + } }, "ManagerTimelock": { "address": "0xB5EFe3783Db55B913C79CBdB81C9d2C0a993f5f0" @@ -39,9 +60,9 @@ "Pauser": { "address": "0x9163de7F22A9f3ad261B3dBfbB9A42886816adE7", "admin": "0x8714E77FA6Aca75A9b21d79295ec7cF04E4821a8", - "implementation": "0xB7D30c8C3bF4b7cb9F06A3D7654E8971021ad58e", + "implementation": "0xf69771dAc1c783F6Ae6ddbC3BF6B4869E7c43a70", "proxy": { - "implementation": "0xB7D30c8C3bF4b7cb9F06A3D7654E8971021ad58e", + "implementation": "0xf69771dAc1c783F6Ae6ddbC3BF6B4869E7c43a70", "admin": "0x8714E77FA6Aca75A9b21d79295ec7cF04E4821a8", "type": "transparent" } @@ -49,9 +70,9 @@ "StakeWeight": { "address": "0x521B4C065Bbdbe3E20B3727340730936912DfA46", "admin": "0x9898b105fe3679f2d31c3A06B58757D913D88e5F", - "implementation": "0xC746f9a45a06CBF2ad761442821C91A479151cc3", + "implementation": "0x6845541121e555D1245fe17c6b44273B089C3844", "proxy": { - "implementation": "0xC746f9a45a06CBF2ad761442821C91A479151cc3", + "implementation": "0x6845541121e555D1245fe17c6b44273B089C3844", "admin": "0x9898b105fe3679f2d31c3A06B58757D913D88e5F", "type": "transparent" } @@ -59,9 +80,9 @@ "StakingRewardDistributor": { "address": "0xF368F535e329c6d08DFf0d4b2dA961C4e7F3fCAF", "admin": "0x28672bf553c6AB214985868f68A3a491E227aCcB", - "implementation": "0xF6d23184E44f282883c0d145C973442fc7A33AB8", + "implementation": "0x98c27ae4C11F67CDE0E3aFDf90fBba01Bf826238", "proxy": { - "implementation": "0xF6d23184E44f282883c0d145C973442fc7A33AB8", + "implementation": "0x98c27ae4C11F67CDE0E3aFDf90fBba01Bf826238", "admin": "0x28672bf553c6AB214985868f68A3a491E227aCcB", "type": "transparent" } diff --git a/evm/foundry.toml b/evm/foundry.toml index a3014d3..8188225 100644 --- a/evm/foundry.toml +++ b/evm/foundry.toml @@ -54,3 +54,5 @@ verbosity = 2 sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}" optimism = "https://optimism-mainnet.infura.io/v3/${API_KEY_INFURA}" optimism_sepolia = "https://optimism-sepolia.infura.io/v3/${API_KEY_INFURA}" + arbitrum = "https://arbitrum-mainnet.infura.io/v3/${API_KEY_INFURA}" + base = "https://base-mainnet.infura.io/v3/${API_KEY_INFURA}" diff --git a/evm/script/deploy/TimelockDeploy.s.sol b/evm/script/deploy/TimelockDeploy.s.sol new file mode 100644 index 0000000..5b0cc0f --- /dev/null +++ b/evm/script/deploy/TimelockDeploy.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.25 <0.9.0; + +import { console2 } from "forge-std/console2.sol"; +import { Timelock } from "src/Timelock.sol"; +import { BaseScript } from "script/Base.s.sol"; + +/// @title TimelockDeploy +/// @notice Deploys a standalone Timelock for chains that lack one (Arbitrum, Base), so the WCT ProxyAdmin +/// owner and token DEFAULT_ADMIN_ROLE can sit behind a timelock rather than the admin multisig +/// directly. The role/ownership hand-off itself is done separately from the multisig — this script +/// ONLY deploys the timelock. +/// @dev Config mirrors EthereumDeploy: 1-week delay, multisig as sole proposer + executor, dedicated +/// canceller. Timelock is self-administered (OZ admin = address(0)), so future role changes require +/// a timelock proposal. +/// +/// IMPORTANT: on production chains BaseScript reverts unless ETH_FROM is set, to avoid falling back to the +/// public test mnemonic. ALWAYS pass your real signer. Example: +/// CHAIN_ID=42161 ETH_FROM= ADMIN_ADDRESS= TIMELOCK_CANCELLER_ADDRESS= \ +/// forge script script/deploy/TimelockDeploy.s.sol --rpc-url arbitrum --broadcast --ledger --sender $ETH_FROM +/// (repeat with CHAIN_ID=8453 --rpc-url base for Base) +contract TimelockDeploy is BaseScript { + function run() public broadcast returns (Timelock timelock) { + address admin = vm.envAddress("ADMIN_ADDRESS"); + address canceller = vm.envAddress("TIMELOCK_CANCELLER_ADDRESS"); + + // 1 weeks == 604800s, the max allowed by Timelock and matching the Ethereum/Optimism delay. + timelock = new Timelock(1 weeks, _singleAddressArray(admin), _singleAddressArray(admin), canceller); + + console2.log("Timelock deployed at:", address(timelock)); + console2.log(" min delay (s): ", timelock.getMinDelay()); + console2.log(" proposer+executor: ", admin); + console2.log(" canceller: ", canceller); + console2.log("Next: from the multisig, hand off ownership (grant DEFAULT_ADMIN_ROLE + transferOwnership, then renounce)."); + } +} From 2e582262c9b9a03d7d308fbf39fc422fd0eb1f9a Mon Sep 17 00:00:00 2001 From: rplusq Date: Tue, 14 Jul 2026 21:38:17 +0100 Subject: [PATCH 2/2] chore(deploy): consolidate Etherscan verification on V2 single key Etherscan V2 uses one API key across all chains (routed by chainid); the per-explorer V1 keys are deprecated. Add arbitrum/base [etherscan] entries and collapse optimism + the Makefile onto ${API_KEY_ETHERSCAN}, dropping API_KEY_OPTIMISTIC_ETHERSCAN from the env sample. Co-Authored-By: Claude Opus 4.8 (1M context) --- evm/.common.example.env | 2 +- evm/Makefile | 4 ++-- evm/foundry.toml | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/evm/.common.example.env b/evm/.common.example.env index 1f311e1..3eeefbe 100644 --- a/evm/.common.example.env +++ b/evm/.common.example.env @@ -1,6 +1,6 @@ # API keys export API_KEY_INFURA= +# Etherscan V2: one key verifies on every supported chain (mainnet, optimism, arbitrum, base, ...) export API_KEY_ETHERSCAN= -export API_KEY_OPTIMISTIC_ETHERSCAN= # Foundry export FOUNDRY_PROFILE="default" diff --git a/evm/Makefile b/evm/Makefile index f1dc64e..0011edb 100644 --- a/evm/Makefile +++ b/evm/Makefile @@ -108,7 +108,7 @@ fund-deployer: _deploy: $(eval include .common.env) $(eval include $(ENV_FILE)) - $(eval ETHSCAN_KEY = $(if $(findstring optimism,$(ENV_FILE)),${API_KEY_OPTIMISTIC_ETHERSCAN},${API_KEY_ETHERSCAN})) + $(eval ETHSCAN_KEY = ${API_KEY_ETHERSCAN}) ifeq ($(IS_ANVIL),) $(eval RPC_URL = https://${CHAIN_NAME}.infura.io/v3/${API_KEY_INFURA}) @@ -174,7 +174,7 @@ endif _p3_deploy: $(eval include .common.env) $(eval include $(ENV_FILE)) - $(eval ETHSCAN_KEY = $(if $(findstring optimism,$(ENV_FILE)),${API_KEY_OPTIMISTIC_ETHERSCAN},${API_KEY_ETHERSCAN})) + $(eval ETHSCAN_KEY = ${API_KEY_ETHERSCAN}) ifeq ($(IS_ANVIL),) $(eval RPC_URL = https://${CHAIN_NAME}.infura.io/v3/${API_KEY_INFURA}) diff --git a/evm/foundry.toml b/evm/foundry.toml index 8188225..34a7319 100644 --- a/evm/foundry.toml +++ b/evm/foundry.toml @@ -42,11 +42,15 @@ verbosity = 2 tab_width = 4 wrap_comments = true +# Etherscan V2: a single API key covers every supported chain (routed by chainid), +# so all entries share ${API_KEY_ETHERSCAN}. The legacy per-explorer V1 keys are deprecated. [etherscan] sepolia = { key = "${API_KEY_ETHERSCAN}" } mainnet = { key = "${API_KEY_ETHERSCAN}" } - optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" } - optimism_sepolia = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" } + optimism = { key = "${API_KEY_ETHERSCAN}" } + optimism_sepolia = { key = "${API_KEY_ETHERSCAN}" } + arbitrum = { key = "${API_KEY_ETHERSCAN}", chain = 42161 } + base = { key = "${API_KEY_ETHERSCAN}", chain = 8453 } [rpc_endpoints] localhost = "http://localhost:8545"