From 6a1f605a88e9e995ef0523d3a47c48b5fcfd8417 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Mon, 3 Nov 2025 02:07:18 +0000 Subject: [PATCH 1/4] setup Sepolia multisig signers update task --- .../2025-11-03-incident-multisig-signers/.env | 8 + .../Makefile | 32 ++++ .../OwnerDiff.json | 11 ++ .../README.md | 50 ++++++ .../foundry.toml | 20 +++ .../script/UpdateSigners.s.sol | 164 ++++++++++++++++++ .../validations/base-signer.json | 124 +++++++++++++ 7 files changed, 409 insertions(+) create mode 100644 sepolia/2025-11-03-incident-multisig-signers/.env create mode 100644 sepolia/2025-11-03-incident-multisig-signers/Makefile create mode 100644 sepolia/2025-11-03-incident-multisig-signers/OwnerDiff.json create mode 100644 sepolia/2025-11-03-incident-multisig-signers/README.md create mode 100644 sepolia/2025-11-03-incident-multisig-signers/foundry.toml create mode 100644 sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol create mode 100644 sepolia/2025-11-03-incident-multisig-signers/validations/base-signer.json diff --git a/sepolia/2025-11-03-incident-multisig-signers/.env b/sepolia/2025-11-03-incident-multisig-signers/.env new file mode 100644 index 00000000..5f70952a --- /dev/null +++ b/sepolia/2025-11-03-incident-multisig-signers/.env @@ -0,0 +1,8 @@ +OP_COMMIT=bfc4cce53463c0d79224f77c6aba65d7c3f0518b +BASE_CONTRACTS_COMMIT=dcd8c98aa881e0ae4ebf872e0d91692a7bf94000 + +OWNER_SAFE=0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f + +SENDER=0x0CF2F86C3338993ce10F74d6f4B095712c7efe26 + +RECORD_STATE_DIFF=true diff --git a/sepolia/2025-11-03-incident-multisig-signers/Makefile b/sepolia/2025-11-03-incident-multisig-signers/Makefile new file mode 100644 index 00000000..23383fc5 --- /dev/null +++ b/sepolia/2025-11-03-incident-multisig-signers/Makefile @@ -0,0 +1,32 @@ +include ../../Makefile +include ../../Multisig.mk +include ../.env +include .env + +SCRIPT = UpdateSigners + +ifndef LEDGER_ACCOUNT +override LEDGER_ACCOUNT = 1 +endif + +.PHONY: deps +deps: new-forge-deps + +.PHONY: new-forge-deps +new-forge-deps: + forge install --no-git safe-global/safe-smart-account@186a21a74b327f17fc41217a927dea7064f74604 + +.PHONY: gen-validation +gen-validation: checkout-signer-tool run-script + +.PHONY: run-script +run-script: + cd $(SIGNER_TOOL_PATH); \ + npm ci; \ + bun run scripts/genValidationFile.ts --rpc-url $(L1_RPC_URL) \ + --workdir .. --forge-cmd 'forge script --rpc-url $(L1_RPC_URL) \ + $(SCRIPT) --sig "sign(address[])" [] --sender $(SENDER)' --out ../validations/base-signer.json; + +.PHONY: execute +execute: + $(call MULTISIG_EXECUTE,$(SIGNATURES)) diff --git a/sepolia/2025-11-03-incident-multisig-signers/OwnerDiff.json b/sepolia/2025-11-03-incident-multisig-signers/OwnerDiff.json new file mode 100644 index 00000000..19afc824 --- /dev/null +++ b/sepolia/2025-11-03-incident-multisig-signers/OwnerDiff.json @@ -0,0 +1,11 @@ +{ + "OwnersToAdd": [ + "0x5F1955729e3D6129FB73813E891742D473fc74F7", + "0x6e427c3212C0b63BE0C382F97715D49b011bFF33" + ], + "OwnersToRemove": [ + "0xaa2489DEbF1EF02ab83bA6Cde4419E662De9254E", + "0x9B43cC2EF6fa521127Ade09e20efD6ABBC5BF799", + "0xf2Fb17eab635f036Da7864B8e39ef8e9A03441df" + ] +} diff --git a/sepolia/2025-11-03-incident-multisig-signers/README.md b/sepolia/2025-11-03-incident-multisig-signers/README.md new file mode 100644 index 00000000..9da9d915 --- /dev/null +++ b/sepolia/2025-11-03-incident-multisig-signers/README.md @@ -0,0 +1,50 @@ +# Update Sepolia Incident Multisig Signers + +Status: TODO[READY TO SIGN|DONE] + +## Description + +We wish to update the owners of our Incident Multisig to be consistent with the current state of our Base Chain Eng team. This involves removing signers that are no longer closely involved with the team, and adding new team members as signers. The exact signer changes are outlined in the [OwnerDiff.json](./OwnerDiff.json) file. + +## Install dependencies + +### 1. Update foundry + +```bash +foundryup +``` + +### 2. Install Node.js if needed + +First, check if you have node installed + +```bash +node --version +``` + +If you see a version output from the above command, you can move on. Otherwise, install node + +```bash +brew install node +``` + +## Approving Signers Update + +### 1. Update repo: + +```bash +cd contract-deployments +git pull +``` + +### 2. Run the signing tool (NOTE: do not enter the task directory. Run this command from the project's root). + +```bash +make sign-task +``` + +### 3. Open the UI at [http://localhost:3000](http://localhost:3000) + +Be sure to select the correct task from the list of available tasks to sign. + +### 4. Send signature to facilitator diff --git a/sepolia/2025-11-03-incident-multisig-signers/foundry.toml b/sepolia/2025-11-03-incident-multisig-signers/foundry.toml new file mode 100644 index 00000000..14499ab0 --- /dev/null +++ b/sepolia/2025-11-03-incident-multisig-signers/foundry.toml @@ -0,0 +1,20 @@ +[profile.default] +src = 'src' +out = 'out' +libs = ['lib'] +broadcast = 'records' +fs_permissions = [{ access = "read-write", path = "./" }] +optimizer = true +optimizer_runs = 999999 +solc_version = "0.8.15" +via-ir = false +remappings = [ + '@eth-optimism-bedrock/=lib/optimism/packages/contracts-bedrock/', + '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts', + '@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts', + '@rari-capital/solmate/=lib/solmate/', + '@base-contracts/=lib/base-contracts', + 'solady/=lib/solady/src/', +] + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol b/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol new file mode 100644 index 00000000..e68858b1 --- /dev/null +++ b/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {Vm} from "forge-std/Vm.sol"; +import {stdJson} from "forge-std/StdJson.sol"; +import {Simulation} from "@base-contracts/script/universal/Simulation.sol"; +import {IMulticall3} from "forge-std/interfaces/IMulticall3.sol"; + +import {MultisigScript} from "@base-contracts/script/universal/MultisigScript.sol"; +import {GnosisSafe} from "safe-smart-account/GnosisSafe.sol"; +import {OwnerManager} from "safe-smart-account/base/OwnerManager.sol"; + +contract UpdateSigners is MultisigScript { + using stdJson for string; + + address public constant SENTINEL_OWNERS = address(0x1); + + address public immutable OWNER_SAFE; + uint256 public immutable THRESHOLD; + address[] public EXISTING_OWNERS; + + address[] public OWNERS_TO_ADD; + address[] public OWNERS_TO_REMOVE; + + mapping(address => address) public ownerToPrevOwner; + mapping(address => address) public ownerToNextOwner; + mapping(address => bool) public expectedOwner; + + constructor() { + OWNER_SAFE = vm.envAddress("OWNER_SAFE"); + + GnosisSafe ownerSafe = GnosisSafe(payable(OWNER_SAFE)); + THRESHOLD = ownerSafe.getThreshold(); + EXISTING_OWNERS = ownerSafe.getOwners(); + + string memory rootPath = vm.projectRoot(); + string memory path = string.concat(rootPath, "/OwnerDiff.json"); + string memory jsonData = vm.readFile(path); + + OWNERS_TO_ADD = abi.decode(jsonData.parseRaw(".OwnersToAdd"), (address[])); + OWNERS_TO_REMOVE = abi.decode(jsonData.parseRaw(".OwnersToRemove"), (address[])); + } + + function setUp() external { + require(OWNERS_TO_ADD.length > 0, "Precheck 00"); + require(OWNERS_TO_REMOVE.length > 0, "Precheck 01"); + require(EXISTING_OWNERS.length == 14, "Precheck 02"); + + GnosisSafe ownerSafe = GnosisSafe(payable(OWNER_SAFE)); + address prevOwner = SENTINEL_OWNERS; + + // Build the linked list from the current on-chain owners first. + for (uint256 i; i < EXISTING_OWNERS.length; i++) { + ownerToPrevOwner[EXISTING_OWNERS[i]] = prevOwner; + ownerToNextOwner[prevOwner] = EXISTING_OWNERS[i]; + prevOwner = EXISTING_OWNERS[i]; + expectedOwner[EXISTING_OWNERS[i]] = true; + } + + for (uint256 i; i < OWNERS_TO_REMOVE.length; i++) { + // Make sure owners to remove are owners + require(ownerSafe.isOwner(OWNERS_TO_REMOVE[i]), "Precheck 05"); + // Prevent duplicates + require(expectedOwner[OWNERS_TO_REMOVE[i]], "Precheck 06"); + expectedOwner[OWNERS_TO_REMOVE[i]] = false; + } + + // Validate owners to add are not already owners and mark as expected post-state owners. + for (uint256 i; i < OWNERS_TO_ADD.length; i++) { + // Make sure owners to add are not already owners + require(!ownerSafe.isOwner(OWNERS_TO_ADD[i]), "Precheck 03"); + // Prevent duplicates across the adds list + require(!expectedOwner[OWNERS_TO_ADD[i]], "Precheck 04"); + expectedOwner[OWNERS_TO_ADD[i]] = true; + } + } + + function _postCheck(Vm.AccountAccess[] memory, Simulation.Payload memory) internal view override { + GnosisSafe ownerSafe = GnosisSafe(payable(OWNER_SAFE)); + address[] memory postCheckOwners = ownerSafe.getOwners(); + uint256 postCheckThreshold = ownerSafe.getThreshold(); + + uint256 expectedLength = EXISTING_OWNERS.length + OWNERS_TO_ADD.length - OWNERS_TO_REMOVE.length; + + require(postCheckThreshold == THRESHOLD, "Postcheck 00"); + require(postCheckOwners.length == expectedLength, "Postcheck 01"); + + for (uint256 i; i < postCheckOwners.length; i++) { + require(expectedOwner[postCheckOwners[i]], "Postcheck 02"); + } + } + + function _buildCalls() internal view override returns (IMulticall3.Call3Value[] memory) { + IMulticall3.Call3Value[] memory calls = + new IMulticall3.Call3Value[](OWNERS_TO_ADD.length + OWNERS_TO_REMOVE.length); + + // Create a working copy of the current owners. We'll mutate this in-memory as we plan removals + address[] memory workingOwners = new address[](EXISTING_OWNERS.length); + for (uint256 i; i < EXISTING_OWNERS.length; i++) { + workingOwners[i] = EXISTING_OWNERS[i]; + } + + // 1) Build removal calls sequentially, deriving prev from the current working list each time + for (uint256 i; i < OWNERS_TO_REMOVE.length; i++) { + address owner = OWNERS_TO_REMOVE[i]; + (bool found, uint256 idx) = _findIndex(workingOwners, owner); + require(found, "owner to remove not in working set"); + + address prev = SENTINEL_OWNERS; + if (idx > 0) { + uint256 j = idx; + while (j > 0) { + j--; + if (workingOwners[j] != address(0)) { + prev = workingOwners[j]; + break; + } + } + } + + calls[i] = IMulticall3.Call3Value({ + target: OWNER_SAFE, + allowFailure: false, + callData: abi.encodeCall(OwnerManager.removeOwner, (prev, owner, THRESHOLD)), + value: 0 + }); + + // Mark the owner as removed for subsequent predecessor computations + workingOwners[idx] = address(0); + } + + // 2) Then add the new owners, keeping the threshold unchanged. + for (uint256 i; i < OWNERS_TO_ADD.length; i++) { + calls[OWNERS_TO_REMOVE.length + i] = IMulticall3.Call3Value({ + target: OWNER_SAFE, + allowFailure: false, + callData: abi.encodeCall(OwnerManager.addOwnerWithThreshold, (OWNERS_TO_ADD[i], THRESHOLD)), + value: 0 + }); + } + + return calls; + } + + function _findIndex(address[] memory arr, address needle) internal pure returns (bool, uint256) { + for (uint256 i; i < arr.length; i++) { + if (arr[i] == needle) return (true, i); + } + return (false, 0); + } + + function _prevInList(address[] memory list, address owner) internal pure returns (address) { + for (uint256 i; i < list.length; i++) { + if (list[i] == owner) { + return i == 0 ? SENTINEL_OWNERS : list[i - 1]; + } + } + return SENTINEL_OWNERS; + } + + function _ownerSafe() internal view override returns (address) { + return OWNER_SAFE; + } +} diff --git a/sepolia/2025-11-03-incident-multisig-signers/validations/base-signer.json b/sepolia/2025-11-03-incident-multisig-signers/validations/base-signer.json new file mode 100644 index 00000000..eaec7928 --- /dev/null +++ b/sepolia/2025-11-03-incident-multisig-signers/validations/base-signer.json @@ -0,0 +1,124 @@ +{ + "taskName": "Update Signers", + "scriptName": "UpdateSigners", + "signature": "sign(address[])", + "sender": "0x0CF2F86C3338993ce10F74d6f4B095712c7efe26", + "args": "[]", + "ledgerId": 0, + "rpcUrl": "https://ethereum-full-sepolia-k8s-dev.cbhq.net", + "expectedDomainAndMessageHashes": { + "address": "0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f", + "domainHash": "0x0127bbb910536860a0757a9c0ffcdf9e4452220f566ed83af1f27f9e833f0e23", + "messageHash": "0xb98540173e236ab4fcebdc816cae97860faba420674242cdb6154909588905e4" + }, + "stateOverrides": [ + { + "name": "CB Signer Safe - Sepolia", + "address": "0x5dfeb066334b67355a15dc9b67317fd2a2e1f77f", + "overrides": [ + { + "key": "0x0000000000000000000000000000000000000000000000000000000000000004", + "value": "0x0000000000000000000000000000000000000000000000000000000000000001", + "description": "Override the threshold to 1 so the transaction simulation can occur." + }, + { + "key": "0x2b407907bc185884e63d41c514b68cde248e6a06f5591d1136d84b85485bfee8", + "value": "0x0000000000000000000000000000000000000000000000000000000000000001", + "description": "Simulates an approval from msg.sender in order for the task simulation to succeed." + } + ] + } + ], + "stateChanges": [ + { + "name": "CB Signer Safe - Sepolia", + "address": "0x5dfeb066334b67355a15dc9b67317fd2a2e1f77f", + "changes": [ + { + "key": "0x0000000000000000000000000000000000000000000000000000000000000003", + "before": "0x000000000000000000000000000000000000000000000000000000000000000e", + "after": "0x000000000000000000000000000000000000000000000000000000000000000d", + "description": "Updates the owner count", + "allowDifference": false + }, + { + "key": "0x0000000000000000000000000000000000000000000000000000000000000004", + "before": "0x0000000000000000000000000000000000000000000000000000000000000001", + "after": "0x0000000000000000000000000000000000000000000000000000000000000003", + "description": "Updates the execution threshold", + "allowDifference": false + }, + { + "key": "0x0000000000000000000000000000000000000000000000000000000000000005", + "before": "0x000000000000000000000000000000000000000000000000000000000000000a", + "after": "0x000000000000000000000000000000000000000000000000000000000000000b", + "description": "Increments the nonce", + "allowDifference": false + }, + { + "key": "0x1b89e6111d27169de2f97ec92e344b249dff7dd6170ee65af7a1d9dda6cf7255", + "before": "0x0000000000000000000000000000000000000000000000000000000000000000", + "after": "0x0000000000000000000000005f1955729e3d6129fb73813e891742d473fc74f7", + "description": "Updates the owners mapping", + "allowDifference": false + }, + { + "key": "0xa17f6b5d5c5960e10cb741113b64d7f023e3513339475444585747297bf07208", + "before": "0x0000000000000000000000009b43cc2ef6fa521127ade09e20efd6abbc5bf799", + "after": "0x000000000000000000000000644d0f5c2c55a4679b4bfe057b87ba203af9ac0d", + "description": "Updates the owners mapping", + "allowDifference": false + }, + { + "key": "0xb06fd2848181e1497bbaa80b8d007c335c7806a2e47211a7bba7a9af63e0ca64", + "before": "0x000000000000000000000000f2fb17eab635f036da7864b8e39ef8e9a03441df", + "after": "0x000000000000000000000000edecf2c444559210a865a22acfc6a2a25590ab1b", + "description": "Updates the owners mapping", + "allowDifference": false + }, + { + "key": "0xc5e3baa34a074e15c9dc99b0b81b573ebbdd07b026df6162630478f6d12892f3", + "before": "0x0000000000000000000000000000000000000000000000000000000000000000", + "after": "0x0000000000000000000000000cf2f86c3338993ce10f74d6f4b095712c7efe26", + "description": "Updates the owners mapping", + "allowDifference": false + }, + { + "key": "0xd5a3955556fe832b0d9dee1f7edb84880eee53ea4dc4e838bfa6bebc90480c2a", + "before": "0x000000000000000000000000644d0f5c2c55a4679b4bfe057b87ba203af9ac0d", + "after": "0x0000000000000000000000000000000000000000000000000000000000000000", + "description": "Updates the owners mapping", + "allowDifference": false + }, + { + "key": "0xdbe1c9b8b04b880813d25a9c5e89056031fb59c22374b4ee4da23606ff9ee5f5", + "before": "0x000000000000000000000000aa2489debf1ef02ab83ba6cde4419e662de9254e", + "after": "0x0000000000000000000000000000000000000000000000000000000000000001", + "description": "Updates the owners mapping", + "allowDifference": false + }, + { + "key": "0xdf293ebdd99010d44bfef5d86d47c7943a4e69db74a426354dc622e6d4d85761", + "before": "0x000000000000000000000000edecf2c444559210a865a22acfc6a2a25590ab1b", + "after": "0x0000000000000000000000000000000000000000000000000000000000000000", + "description": "Updates the owners mapping", + "allowDifference": false + }, + { + "key": "0xe475b9e5509f43031ff937d793c47553513dacb8de5b10e556e7da05bd6d6e54", + "before": "0x0000000000000000000000000000000000000000000000000000000000000001", + "after": "0x0000000000000000000000000000000000000000000000000000000000000000", + "description": "Updates the owners mapping", + "allowDifference": false + }, + { + "key": "0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0", + "before": "0x0000000000000000000000000cf2f86c3338993ce10f74d6f4b095712c7efe26", + "after": "0x0000000000000000000000006e427c3212c0b63be0c382f97715d49b011bff33", + "description": "Sets the head of the owners linked list", + "allowDifference": false + } + ] + } + ] +} From 9f1f7940fded7885578b81be8af058bfcb1e64ad Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Mon, 3 Nov 2025 02:33:06 +0000 Subject: [PATCH 2/4] update the 2025-11-03-incident-multisig-signers task to abstract out the EXISTING_OWNERS_LENGTH constant definition --- sepolia/2025-11-03-incident-multisig-signers/.env | 2 ++ .../script/UpdateSigners.s.sol | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sepolia/2025-11-03-incident-multisig-signers/.env b/sepolia/2025-11-03-incident-multisig-signers/.env index 5f70952a..8ca4a8eb 100644 --- a/sepolia/2025-11-03-incident-multisig-signers/.env +++ b/sepolia/2025-11-03-incident-multisig-signers/.env @@ -6,3 +6,5 @@ OWNER_SAFE=0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f SENDER=0x0CF2F86C3338993ce10F74d6f4B095712c7efe26 RECORD_STATE_DIFF=true + +EXISTING_OWNERS_LENGTH=14 diff --git a/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol b/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol index e68858b1..1cdf65ce 100644 --- a/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol +++ b/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol @@ -16,6 +16,7 @@ contract UpdateSigners is MultisigScript { address public constant SENTINEL_OWNERS = address(0x1); address public immutable OWNER_SAFE; + uint256 public immutable EXISTING_OWNERS_LENGTH; uint256 public immutable THRESHOLD; address[] public EXISTING_OWNERS; @@ -28,6 +29,7 @@ contract UpdateSigners is MultisigScript { constructor() { OWNER_SAFE = vm.envAddress("OWNER_SAFE"); + EXISTING_OWNERS_LENGTH = vm.envUint("EXISTING_OWNERS_LENGTH"); GnosisSafe ownerSafe = GnosisSafe(payable(OWNER_SAFE)); THRESHOLD = ownerSafe.getThreshold(); @@ -44,7 +46,7 @@ contract UpdateSigners is MultisigScript { function setUp() external { require(OWNERS_TO_ADD.length > 0, "Precheck 00"); require(OWNERS_TO_REMOVE.length > 0, "Precheck 01"); - require(EXISTING_OWNERS.length == 14, "Precheck 02"); + require(EXISTING_OWNERS.length == EXISTING_OWNERS_LENGTH, "Precheck 02"); GnosisSafe ownerSafe = GnosisSafe(payable(OWNER_SAFE)); address prevOwner = SENTINEL_OWNERS; From 3e1073fc6346b61de0c74f08a6f7c798c8d52bec Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Mon, 3 Nov 2025 14:52:12 +0000 Subject: [PATCH 3/4] update the UpdateSigners script to be in-line with the most recently deployed script --- .../script/UpdateSigners.s.sol | 83 ++++++------------- 1 file changed, 26 insertions(+), 57 deletions(-) diff --git a/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol b/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol index 1cdf65ce..5bf23520 100644 --- a/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol +++ b/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol @@ -51,7 +51,19 @@ contract UpdateSigners is MultisigScript { GnosisSafe ownerSafe = GnosisSafe(payable(OWNER_SAFE)); address prevOwner = SENTINEL_OWNERS; - // Build the linked list from the current on-chain owners first. + for (uint256 i = OWNERS_TO_ADD.length; i > 0; i--) { + uint256 index = i - 1; + // Make sure owners to add are not already owners + require(!ownerSafe.isOwner(OWNERS_TO_ADD[index]), "Precheck 03"); + // Prevent duplicates + require(!expectedOwner[OWNERS_TO_ADD[index]], "Precheck 04"); + + ownerToPrevOwner[OWNERS_TO_ADD[index]] = prevOwner; + ownerToNextOwner[prevOwner] = OWNERS_TO_ADD[index]; + prevOwner = OWNERS_TO_ADD[index]; + expectedOwner[OWNERS_TO_ADD[index]] = true; + } + for (uint256 i; i < EXISTING_OWNERS.length; i++) { ownerToPrevOwner[EXISTING_OWNERS[i]] = prevOwner; ownerToNextOwner[prevOwner] = EXISTING_OWNERS[i]; @@ -65,15 +77,13 @@ contract UpdateSigners is MultisigScript { // Prevent duplicates require(expectedOwner[OWNERS_TO_REMOVE[i]], "Precheck 06"); expectedOwner[OWNERS_TO_REMOVE[i]] = false; - } - // Validate owners to add are not already owners and mark as expected post-state owners. - for (uint256 i; i < OWNERS_TO_ADD.length; i++) { - // Make sure owners to add are not already owners - require(!ownerSafe.isOwner(OWNERS_TO_ADD[i]), "Precheck 03"); - // Prevent duplicates across the adds list - require(!expectedOwner[OWNERS_TO_ADD[i]], "Precheck 04"); - expectedOwner[OWNERS_TO_ADD[i]] = true; + // Remove from linked list to keep ownerToPrevOwner up to date + // Note: This works as long as the order of OWNERS_TO_REMOVE does not change during `_buildCalls()` + address nextOwner = ownerToNextOwner[OWNERS_TO_REMOVE[i]]; + address prevPtr = ownerToPrevOwner[OWNERS_TO_REMOVE[i]]; + ownerToPrevOwner[nextOwner] = prevPtr; + ownerToNextOwner[prevPtr] = nextOwner; } } @@ -96,47 +106,22 @@ contract UpdateSigners is MultisigScript { IMulticall3.Call3Value[] memory calls = new IMulticall3.Call3Value[](OWNERS_TO_ADD.length + OWNERS_TO_REMOVE.length); - // Create a working copy of the current owners. We'll mutate this in-memory as we plan removals - address[] memory workingOwners = new address[](EXISTING_OWNERS.length); - for (uint256 i; i < EXISTING_OWNERS.length; i++) { - workingOwners[i] = EXISTING_OWNERS[i]; - } - - // 1) Build removal calls sequentially, deriving prev from the current working list each time - for (uint256 i; i < OWNERS_TO_REMOVE.length; i++) { - address owner = OWNERS_TO_REMOVE[i]; - (bool found, uint256 idx) = _findIndex(workingOwners, owner); - require(found, "owner to remove not in working set"); - - address prev = SENTINEL_OWNERS; - if (idx > 0) { - uint256 j = idx; - while (j > 0) { - j--; - if (workingOwners[j] != address(0)) { - prev = workingOwners[j]; - break; - } - } - } - + for (uint256 i; i < OWNERS_TO_ADD.length; i++) { calls[i] = IMulticall3.Call3Value({ target: OWNER_SAFE, allowFailure: false, - callData: abi.encodeCall(OwnerManager.removeOwner, (prev, owner, THRESHOLD)), + callData: abi.encodeCall(OwnerManager.addOwnerWithThreshold, (OWNERS_TO_ADD[i], THRESHOLD)), value: 0 }); - - // Mark the owner as removed for subsequent predecessor computations - workingOwners[idx] = address(0); } - // 2) Then add the new owners, keeping the threshold unchanged. - for (uint256 i; i < OWNERS_TO_ADD.length; i++) { - calls[OWNERS_TO_REMOVE.length + i] = IMulticall3.Call3Value({ + for (uint256 i; i < OWNERS_TO_REMOVE.length; i++) { + calls[OWNERS_TO_ADD.length + i] = IMulticall3.Call3Value({ target: OWNER_SAFE, allowFailure: false, - callData: abi.encodeCall(OwnerManager.addOwnerWithThreshold, (OWNERS_TO_ADD[i], THRESHOLD)), + callData: abi.encodeCall( + OwnerManager.removeOwner, (ownerToPrevOwner[OWNERS_TO_REMOVE[i]], OWNERS_TO_REMOVE[i], THRESHOLD) + ), value: 0 }); } @@ -144,22 +129,6 @@ contract UpdateSigners is MultisigScript { return calls; } - function _findIndex(address[] memory arr, address needle) internal pure returns (bool, uint256) { - for (uint256 i; i < arr.length; i++) { - if (arr[i] == needle) return (true, i); - } - return (false, 0); - } - - function _prevInList(address[] memory list, address owner) internal pure returns (address) { - for (uint256 i; i < list.length; i++) { - if (list[i] == owner) { - return i == 0 ? SENTINEL_OWNERS : list[i - 1]; - } - } - return SENTINEL_OWNERS; - } - function _ownerSafe() internal view override returns (address) { return OWNER_SAFE; } From 45ca26e557ddb52238bb1542338749fda59808b1 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Thu, 6 Nov 2025 22:18:07 +0000 Subject: [PATCH 4/4] update task based on review feedback --- Makefile | 2 +- sepolia/2025-11-03-incident-multisig-signers/.env | 4 +--- .../2025-11-03-incident-multisig-signers/Makefile | 1 + .../2025-11-03-incident-multisig-signers/README.md | 6 ++++-- .../script/UpdateSigners.s.sol | 4 ++-- .../validations/base-signer.json | 13 +++++-------- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index daf4637d..f8a590e1 100644 --- a/Makefile +++ b/Makefile @@ -112,7 +112,7 @@ checkout-base-contracts-commit: ## # Task Signer Tool ## -SIGNER_TOOL_COMMIT=92a4b600252cd7ffe255a876a880c2540802b99c +SIGNER_TOOL_COMMIT=0f8a61c6812b29fe0e7b02d0490ff4fa7b18b593 SIGNER_TOOL_PATH=signer-tool .PHONY: checkout-signer-tool diff --git a/sepolia/2025-11-03-incident-multisig-signers/.env b/sepolia/2025-11-03-incident-multisig-signers/.env index 8ca4a8eb..f402116e 100644 --- a/sepolia/2025-11-03-incident-multisig-signers/.env +++ b/sepolia/2025-11-03-incident-multisig-signers/.env @@ -1,4 +1,4 @@ -OP_COMMIT=bfc4cce53463c0d79224f77c6aba65d7c3f0518b +OP_COMMIT=594bc933a38425f745b46399a3619bcdeb74965d BASE_CONTRACTS_COMMIT=dcd8c98aa881e0ae4ebf872e0d91692a7bf94000 OWNER_SAFE=0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f @@ -6,5 +6,3 @@ OWNER_SAFE=0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f SENDER=0x0CF2F86C3338993ce10F74d6f4B095712c7efe26 RECORD_STATE_DIFF=true - -EXISTING_OWNERS_LENGTH=14 diff --git a/sepolia/2025-11-03-incident-multisig-signers/Makefile b/sepolia/2025-11-03-incident-multisig-signers/Makefile index 23383fc5..12b464f7 100644 --- a/sepolia/2025-11-03-incident-multisig-signers/Makefile +++ b/sepolia/2025-11-03-incident-multisig-signers/Makefile @@ -3,6 +3,7 @@ include ../../Multisig.mk include ../.env include .env +RPC_URL = $(L1_RPC_URL) SCRIPT = UpdateSigners ifndef LEDGER_ACCOUNT diff --git a/sepolia/2025-11-03-incident-multisig-signers/README.md b/sepolia/2025-11-03-incident-multisig-signers/README.md index 9da9d915..344c6f84 100644 --- a/sepolia/2025-11-03-incident-multisig-signers/README.md +++ b/sepolia/2025-11-03-incident-multisig-signers/README.md @@ -1,6 +1,6 @@ # Update Sepolia Incident Multisig Signers -Status: TODO[READY TO SIGN|DONE] +Status: READY TO SIGN ## Description @@ -22,7 +22,7 @@ First, check if you have node installed node --version ``` -If you see a version output from the above command, you can move on. Otherwise, install node +If you see a version output from the above command, at or above version `v18.18`, you can move on. Otherwise, install (or update) node. ```bash brew install node @@ -48,3 +48,5 @@ make sign-task Be sure to select the correct task from the list of available tasks to sign. ### 4. Send signature to facilitator + +You may now kill the Signer Tool process in your terminal window by running `Ctrl + C`. diff --git a/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol b/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol index 5bf23520..b1f7f0b5 100644 --- a/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol +++ b/sepolia/2025-11-03-incident-multisig-signers/script/UpdateSigners.s.sol @@ -15,8 +15,9 @@ contract UpdateSigners is MultisigScript { address public constant SENTINEL_OWNERS = address(0x1); + uint256 public constant EXISTING_OWNERS_LENGTH = 14; + address public immutable OWNER_SAFE; - uint256 public immutable EXISTING_OWNERS_LENGTH; uint256 public immutable THRESHOLD; address[] public EXISTING_OWNERS; @@ -29,7 +30,6 @@ contract UpdateSigners is MultisigScript { constructor() { OWNER_SAFE = vm.envAddress("OWNER_SAFE"); - EXISTING_OWNERS_LENGTH = vm.envUint("EXISTING_OWNERS_LENGTH"); GnosisSafe ownerSafe = GnosisSafe(payable(OWNER_SAFE)); THRESHOLD = ownerSafe.getThreshold(); diff --git a/sepolia/2025-11-03-incident-multisig-signers/validations/base-signer.json b/sepolia/2025-11-03-incident-multisig-signers/validations/base-signer.json index eaec7928..d35a20e8 100644 --- a/sepolia/2025-11-03-incident-multisig-signers/validations/base-signer.json +++ b/sepolia/2025-11-03-incident-multisig-signers/validations/base-signer.json @@ -1,15 +1,11 @@ { - "taskName": "Update Signers", - "scriptName": "UpdateSigners", - "signature": "sign(address[])", - "sender": "0x0CF2F86C3338993ce10F74d6f4B095712c7efe26", - "args": "[]", + "cmd": "forge script --rpc-url https://ethereum-full-sepolia-k8s-dev.cbhq.net UpdateSigners --sig sign(address[]) [] --sender 0x0CF2F86C3338993ce10F74d6f4B095712c7efe26", "ledgerId": 0, "rpcUrl": "https://ethereum-full-sepolia-k8s-dev.cbhq.net", "expectedDomainAndMessageHashes": { "address": "0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f", "domainHash": "0x0127bbb910536860a0757a9c0ffcdf9e4452220f566ed83af1f27f9e833f0e23", - "messageHash": "0xb98540173e236ab4fcebdc816cae97860faba420674242cdb6154909588905e4" + "messageHash": "0xd672c948194b6dcd59f2bd289d5cbe2830861f632e9697d3502b8f4df4a46e2e" }, "stateOverrides": [ { @@ -22,7 +18,7 @@ "description": "Override the threshold to 1 so the transaction simulation can occur." }, { - "key": "0x2b407907bc185884e63d41c514b68cde248e6a06f5591d1136d84b85485bfee8", + "key": "0x5184e114a07bb2874fba528601c90cda5eae014b6c6f7d81c92277500bdb0848", "value": "0x0000000000000000000000000000000000000000000000000000000000000001", "description": "Simulates an approval from msg.sender in order for the task simulation to succeed." } @@ -120,5 +116,6 @@ } ] } - ] + ], + "balanceChanges": [] }