From 31263e04721f3b633ec66930fd9841aa0746048b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ros=C5=82aniec?= Date: Thu, 4 Jun 2026 12:24:39 +0000 Subject: [PATCH 1/7] fix(ecdsa/deploy): allowlist redeploy-safe networks for EcdsaDkgValidator Invert mainnet-only denylist to explicit allowlist (hardhat, development, sepolia). Future production-like networks added downstream now default to the safe behavior (preserve existing deployment) instead of silently overwriting deployments//EcdsaDkgValidator.json. --- solidity/ecdsa/deploy/02_deploy_dkg_validator.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/solidity/ecdsa/deploy/02_deploy_dkg_validator.ts b/solidity/ecdsa/deploy/02_deploy_dkg_validator.ts index fd0bd97546..c24b50b70c 100644 --- a/solidity/ecdsa/deploy/02_deploy_dkg_validator.ts +++ b/solidity/ecdsa/deploy/02_deploy_dkg_validator.ts @@ -15,11 +15,15 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const EcdsaSortitionPool = await deployments.get("EcdsaSortitionPool") - // Non-mainnet: skipIfAlreadyDeployed false so hardhat-deploy can redeploy when bytecode - // changes (e.g. groupSize 100 → 3). Mainnet: true so bytecode/artifact drift cannot - // silently overwrite deployments/mainnet/EcdsaDkgValidator.json while WalletRegistry - // still points at the old on-chain validator (THRESHOLD_FORCE_DKG_COMPILE only forces compile). - const skipIfAlreadyDeployed = hre.network.name === "mainnet" + // Allowlist of networks where bytecode redeploy on artifact change is safe + // (e.g. groupSize 100 → 3 during local/testnet iteration). Any other network + // (mainnet and any future production-like alias) keeps the existing + // deployment record so bytecode/artifact drift cannot silently overwrite + // deployments//EcdsaDkgValidator.json while WalletRegistry still + // points at the old on-chain validator. THRESHOLD_FORCE_DKG_COMPILE only + // forces compile, not redeploy. + const redeploySafeNetworks = new Set(["hardhat", "development", "sepolia"]) + const skipIfAlreadyDeployed = !redeploySafeNetworks.has(hre.network.name) const EcdsaDkgValidator = await deployments.deploy("EcdsaDkgValidator", { from: deployer, From a9eaff9b5afb71043aa4673f7a6d8515e37bf214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ros=C5=82aniec?= Date: Thu, 4 Jun 2026 12:24:58 +0000 Subject: [PATCH 2/7] docs(ecdsa/deploy): document vendored random-beacon-export format policy Add README explaining that 05_approve_random_beacon_in_token_staking.js intentionally diverges from its tsc-compiled siblings to carry an ifaceHasFunction precheck for Threshold TokenStaking. A blind regeneration from upstream would silently drop the precheck and reintroduce a hard failure on networks without approveApplication. --- .../random-beacon-export/deploy/README.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 solidity/ecdsa/external/random-beacon-export/deploy/README.md diff --git a/solidity/ecdsa/external/random-beacon-export/deploy/README.md b/solidity/ecdsa/external/random-beacon-export/deploy/README.md new file mode 100644 index 0000000000..a28244da57 --- /dev/null +++ b/solidity/ecdsa/external/random-beacon-export/deploy/README.md @@ -0,0 +1,34 @@ +# `random-beacon-export/deploy` + +Vendored copies of `@keep-network/random-beacon`'s `export/deploy` scripts. +Resolved by `hardhat.config.ts:resolveRandomBeaconExport()` as the second +preference after `../random-beacon/export/` (gitignored upstream) and before +the published `node_modules/@keep-network/random-beacon/export`. + +## Format + +These files intentionally mix two formats: + +- **`01..04, 06..09_*.js`**: `tsc`-compiled ES5 output from the upstream + package's TypeScript sources (`__awaiter` / `__generator` runtime helpers, + `var` declarations). Treat as build artifacts; do not hand-edit. +- **`05_approve_random_beacon_in_token_staking.js`**: hand-written modern + async/await. Adds an `ifaceHasFunction("approveApplication")` precheck plus + an exception backstop so the script is idempotent against the Threshold + `TokenStaking` ABI (which does not expose `approveApplication`). + **Do not regenerate from upstream without preserving this precheck** — + blind regeneration will reintroduce a hard failure on networks running the + Threshold staking contract. + +## Regeneration policy + +When syncing from upstream: + +1. Regenerate `01..04, 06..09_*.js` from `@keep-network/random-beacon`'s + `export/deploy` source via its `tsc` build. +2. **Skip `05_*.js`** during regeneration, or re-apply the + `ifaceHasFunction` precheck and the try/catch around `execute(...)` after + regenerating. +3. Verify by running deploys against both a network that exposes + `approveApplication` (legacy Keep TokenStaking) and one that does not + (Threshold TokenStaking). From eb956f9b7dc0f6dcd3f7dd2b0ca66bc4b8a73068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ros=C5=82aniec?= Date: Thu, 4 Jun 2026 12:25:48 +0000 Subject: [PATCH 3/7] fix(ecdsa/hardhat): restore HardhatUserConfig type annotation The conditional etherscan spread inside the object literal forced removal of the HardhatUserConfig annotation. Assign etherscan via a post-declaration if-block instead so the annotation (and type-checking) is preserved without relying on TS 4.9 'satisfies' (this package is on TS 4.5). --- solidity/ecdsa/hardhat.config.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/solidity/ecdsa/hardhat.config.ts b/solidity/ecdsa/hardhat.config.ts index cc82572c97..deff48b233 100644 --- a/solidity/ecdsa/hardhat.config.ts +++ b/solidity/ecdsa/hardhat.config.ts @@ -20,6 +20,8 @@ import "./tasks" import { task } from "hardhat/config" import { TASK_TEST } from "hardhat/builtin-tasks/task-names" +import type { HardhatUserConfig } from "hardhat/config" + const TASK_CHECK_ACCOUNTS_COUNT = "check-accounts-count" const hardhatVerifyEnabled = process.env.DISABLE_HARDHAT_VERIFY !== "true" @@ -79,7 +81,7 @@ export const testConfig = { operatorsCount: 100, } -const config = { +const config: HardhatUserConfig = { solidity: { compilers: [ { @@ -171,13 +173,6 @@ const config = { username: "thesis", project: "", }, - ...(hardhatVerifyEnabled - ? { - etherscan: { - apiKey: process.env.ETHERSCAN_API_KEY, - }, - } - : {}), namedAccounts: { deployer: { default: 1, // take the second account @@ -291,4 +286,14 @@ task(TASK_CHECK_ACCOUNTS_COUNT, "Checks accounts count").setAction(async () => { } }) +if (hardhatVerifyEnabled) { + // Assigned post-declaration so the HardhatUserConfig type annotation above + // remains intact (a conditional spread inside the literal breaks inference). + ;(config as HardhatUserConfig & { + etherscan?: { apiKey?: string } + }).etherscan = { + apiKey: process.env.ETHERSCAN_API_KEY, + } +} + export default config From 7740fb4aaca003c9a66b5a9328eabd5a5b67c1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ros=C5=82aniec?= Date: Thu, 4 Jun 2026 12:26:16 +0000 Subject: [PATCH 4/7] docs(ecdsa/hardhat): note sepolia named-account role collapse Sepolia maps deployer/governance/chaosnetOwner/esdm all to account index 0 because the testnet deploy uses a single key from ACCOUNTS_PRIVATE_KEYS. Add a header comment so readers don't expect role-separation branches (e.g. initialize-wallet-owner.ts's owner-vs-governance fork) to fire on Sepolia. --- solidity/ecdsa/hardhat.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/solidity/ecdsa/hardhat.config.ts b/solidity/ecdsa/hardhat.config.ts index deff48b233..f8de665347 100644 --- a/solidity/ecdsa/hardhat.config.ts +++ b/solidity/ecdsa/hardhat.config.ts @@ -173,6 +173,12 @@ const config: HardhatUserConfig = { username: "thesis", project: "", }, + // Sepolia: all four roles collapse to account index 0 (the single key in + // ACCOUNTS_PRIVATE_KEYS). Single-key operation is intentional for our + // testnet deploy flow; downstream branches that distinguish deployer vs. + // governance vs. esdm (e.g. tasks/initialize-wallet-owner.ts's + // owner-vs-governance fork, Ownable.transferOwnership flows) are inactive + // on Sepolia by design. namedAccounts: { deployer: { default: 1, // take the second account From d6d03c4bfcf1b3a6befa850ed41e23fc1393b64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ros=C5=82aniec?= Date: Thu, 4 Jun 2026 12:26:34 +0000 Subject: [PATCH 5/7] docs(ecdsa/tasks): explain TOCTOU recheck of WR.governance() The second WalletRegistry.governance() read immediately before execute() is a deliberate recheck against concurrent transferGovernance on shared networks (sepolia/mainnet); annotate so it isn't mistaken for dead code. --- solidity/ecdsa/tasks/initialize-wallet-owner.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/solidity/ecdsa/tasks/initialize-wallet-owner.ts b/solidity/ecdsa/tasks/initialize-wallet-owner.ts index 830eac02a2..3071fb2733 100644 --- a/solidity/ecdsa/tasks/initialize-wallet-owner.ts +++ b/solidity/ecdsa/tasks/initialize-wallet-owner.ts @@ -53,6 +53,9 @@ async function initializeWalletOwner( return } + // TOCTOU recheck: governance can be transferred between the early read + // above and this execute() on shared networks. Re-read immediately before + // the tx so a concurrent transferGovernance doesn't slip past the gate. const wrGovernanceNow = await read("WalletRegistry", {}, "governance") if (!helpers.address.equal(wrg.address, wrGovernanceNow)) { throw new Error( From f97dc40cc7de9124c3323675fca1892f34524195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ros=C5=82aniec?= Date: Thu, 4 Jun 2026 12:27:15 +0000 Subject: [PATCH 6/7] fix(ecdsa/deploy): apply tenderly verify-or-continue helper everywhere Add verifyOnTenderlyOrContinue mirroring verifyOnEtherscanOrContinue and wrap tenderly.verify in all four deploy scripts (01, 02, 03, 09). Before this, only 03 swallowed Tenderly errors; a Tenderly outage during deploy would fail 01/02/09 while 03 kept going. Now all four behave the same. --- .../deploy/01_deploy_ecdsa_sortition_pool.ts | 11 ++++++---- .../ecdsa/deploy/02_deploy_dkg_validator.ts | 11 ++++++---- .../ecdsa/deploy/03_deploy_wallet_registry.ts | 9 ++++----- .../09_deploy_wallet_registry_governance.ts | 11 ++++++---- solidity/ecdsa/deploy/tenderlyVerification.ts | 20 +++++++++++++++++++ 5 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 solidity/ecdsa/deploy/tenderlyVerification.ts diff --git a/solidity/ecdsa/deploy/01_deploy_ecdsa_sortition_pool.ts b/solidity/ecdsa/deploy/01_deploy_ecdsa_sortition_pool.ts index 8003895569..800bab4aa1 100644 --- a/solidity/ecdsa/deploy/01_deploy_ecdsa_sortition_pool.ts +++ b/solidity/ecdsa/deploy/01_deploy_ecdsa_sortition_pool.ts @@ -1,4 +1,5 @@ import verifyOnEtherscanOrContinue from "./etherscanVerification" +import verifyOnTenderlyOrContinue from "./tenderlyVerification" import type { HardhatRuntimeEnvironment } from "hardhat/types" import type { DeployFunction } from "hardhat-deploy/types" @@ -49,10 +50,12 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { } if (hre.network.tags.tenderly) { - await hre.tenderly.verify({ - name: "EcdsaSortitionPool", - address: EcdsaSortitionPool.address, - }) + await verifyOnTenderlyOrContinue(hre, () => + hre.tenderly.verify({ + name: "EcdsaSortitionPool", + address: EcdsaSortitionPool.address, + }) + ) } return true diff --git a/solidity/ecdsa/deploy/02_deploy_dkg_validator.ts b/solidity/ecdsa/deploy/02_deploy_dkg_validator.ts index c24b50b70c..56ee25a748 100644 --- a/solidity/ecdsa/deploy/02_deploy_dkg_validator.ts +++ b/solidity/ecdsa/deploy/02_deploy_dkg_validator.ts @@ -1,4 +1,5 @@ import verifyOnEtherscanOrContinue from "./etherscanVerification" +import verifyOnTenderlyOrContinue from "./tenderlyVerification" import type { HardhatRuntimeEnvironment } from "hardhat/types" import type { DeployFunction } from "hardhat-deploy/types" @@ -43,10 +44,12 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { } if (hre.network.tags.tenderly) { - await hre.tenderly.verify({ - name: "EcdsaDkgValidator", - address: EcdsaDkgValidator.address, - }) + await verifyOnTenderlyOrContinue(hre, () => + hre.tenderly.verify({ + name: "EcdsaDkgValidator", + address: EcdsaDkgValidator.address, + }) + ) } return true diff --git a/solidity/ecdsa/deploy/03_deploy_wallet_registry.ts b/solidity/ecdsa/deploy/03_deploy_wallet_registry.ts index 4925c0c4f4..49d513f794 100644 --- a/solidity/ecdsa/deploy/03_deploy_wallet_registry.ts +++ b/solidity/ecdsa/deploy/03_deploy_wallet_registry.ts @@ -1,4 +1,5 @@ import verifyOnEtherscanOrContinue from "./etherscanVerification" +import verifyOnTenderlyOrContinue from "./tenderlyVerification" import type { HardhatRuntimeEnvironment } from "hardhat/types" import type { DeployFunction } from "hardhat-deploy/types" @@ -78,14 +79,12 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { } if (hre.network.tags.tenderly) { - try { - await hre.tenderly.verify({ + await verifyOnTenderlyOrContinue(hre, () => + hre.tenderly.verify({ name: "WalletRegistry", address: walletRegistry.address, }) - } catch (err) { - hre.deployments.log(`Tenderly verification skipped: ${err}`) - } + ) } return true diff --git a/solidity/ecdsa/deploy/09_deploy_wallet_registry_governance.ts b/solidity/ecdsa/deploy/09_deploy_wallet_registry_governance.ts index 441377e215..09a9831e3a 100644 --- a/solidity/ecdsa/deploy/09_deploy_wallet_registry_governance.ts +++ b/solidity/ecdsa/deploy/09_deploy_wallet_registry_governance.ts @@ -1,4 +1,5 @@ import verifyOnEtherscanOrContinue from "./etherscanVerification" +import verifyOnTenderlyOrContinue from "./tenderlyVerification" import type { HardhatRuntimeEnvironment } from "hardhat/types" import type { DeployFunction } from "hardhat-deploy/types" @@ -32,10 +33,12 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { } if (hre.network.tags.tenderly) { - await hre.tenderly.verify({ - name: "WalletRegistryGovernance", - address: WalletRegistryGovernance.address, - }) + await verifyOnTenderlyOrContinue(hre, () => + hre.tenderly.verify({ + name: "WalletRegistryGovernance", + address: WalletRegistryGovernance.address, + }) + ) } } diff --git a/solidity/ecdsa/deploy/tenderlyVerification.ts b/solidity/ecdsa/deploy/tenderlyVerification.ts new file mode 100644 index 0000000000..bb17d51cab --- /dev/null +++ b/solidity/ecdsa/deploy/tenderlyVerification.ts @@ -0,0 +1,20 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" + +/** + * Runs Tenderly verification without failing the deploy when the Tenderly + * API errors (outages, missing project config, rate limits). Mirrors + * verifyOnEtherscanOrContinue so all post-deploy verification hooks behave + * the same way across scripts. + */ +export default async function verifyOnTenderlyOrContinue( + hre: HardhatRuntimeEnvironment, + verify: () => Promise +): Promise { + try { + await verify() + } catch (err) { + hre.deployments.log( + `Tenderly verification skipped (deploy continues): ${err}` + ) + } +} From 40e522f9149f1258f230cef8cc26e247cb4efa95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ros=C5=82aniec?= Date: Thu, 4 Jun 2026 13:02:16 +0000 Subject: [PATCH 7/7] fix(ecdsa/deploy): move README out of deploy/ dir hardhat-deploy walks deploy/ and requires() every file; a Markdown sibling there crashes deployments.fixture() in tests. Move the regeneration-policy README one level up to external/random-beacon-export/README.md (still discoverable, no longer in the require path). --- .../random-beacon-export/{deploy => }/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) rename solidity/ecdsa/external/random-beacon-export/{deploy => }/README.md (74%) diff --git a/solidity/ecdsa/external/random-beacon-export/deploy/README.md b/solidity/ecdsa/external/random-beacon-export/README.md similarity index 74% rename from solidity/ecdsa/external/random-beacon-export/deploy/README.md rename to solidity/ecdsa/external/random-beacon-export/README.md index a28244da57..b852e3c91e 100644 --- a/solidity/ecdsa/external/random-beacon-export/deploy/README.md +++ b/solidity/ecdsa/external/random-beacon-export/README.md @@ -1,9 +1,14 @@ -# `random-beacon-export/deploy` +# `random-beacon-export` -Vendored copies of `@keep-network/random-beacon`'s `export/deploy` scripts. -Resolved by `hardhat.config.ts:resolveRandomBeaconExport()` as the second -preference after `../random-beacon/export/` (gitignored upstream) and before -the published `node_modules/@keep-network/random-beacon/export`. +Vendored copies of `@keep-network/random-beacon`'s `export/deploy` scripts, +under `./deploy/`. Resolved by `hardhat.config.ts:resolveRandomBeaconExport()` +as the second preference after `../random-beacon/export/` (gitignored +upstream) and before the published +`node_modules/@keep-network/random-beacon/export`. + +This README lives one level above `deploy/` because hardhat-deploy walks +that directory and tries to `require()` every file; a Markdown sibling +there would crash deployment. ## Format