From 0d83fe2cfc062a302ec83a8728b9874cb1b29259 Mon Sep 17 00:00:00 2001 From: maclane Date: Mon, 27 Jul 2026 14:38:49 -0400 Subject: [PATCH 1/4] build(random-beacon): raise typechain to 8 so its output type-checks `random-beacon/tsconfig.json` sets `noImplicitAny: true`, but typechain 7 emits its ABI literals as bare `const _abi = [{ inputs: [], ... }]`. Under that flag an empty array literal infers `any[]`, so every factory the generator produced raised TS7018 -- 21 errors, all of them in generated code that no one can edit. typechain 8 emits the same literals `as const` and the whole class disappears. The alternative was dropping `noImplicitAny`, which would have bought a clean run by checking our own code less. This keeps the flag. `@typechain/hardhat` is pinned to ^7 rather than the current ^9 because 9 defaults to the `ethers-v6` target and this package is still on v5. The bump changes the output layout: v8 nests modules under their source path instead of emitting flat files, so five deep imports move. Four are Solidity library namespaces (`Groups`, `BeaconDkg`), which still have to come from the module of a contract that uses them; the fifth, `RandomBeaconGovernance`, is an ordinary contract type and now comes from the barrel with its neighbours. `yarn test` is unchanged at 955 passing, 0 failing. Co-Authored-By: Claude Opus 5 (1M context) --- solidity/random-beacon/package.json | 7 +- solidity/random-beacon/test/Groups.test.ts | 2 +- .../test/RandomBeacon.Callback.test.ts | 2 +- .../test/RandomBeacon.GroupCreation.test.ts | 2 +- .../test/RandomBeacon.Relay.test.ts | 2 +- solidity/random-beacon/test/utils/dkg.ts | 2 +- solidity/random-beacon/yarn.lock | 111 ++++++++++-------- 7 files changed, 70 insertions(+), 58 deletions(-) diff --git a/solidity/random-beacon/package.json b/solidity/random-beacon/package.json index 6d7bc1c9d9..537f248241 100644 --- a/solidity/random-beacon/package.json +++ b/solidity/random-beacon/package.json @@ -30,6 +30,7 @@ "lint:fix:sol": "solhint 'contracts/**/*.sol' --fix && prettier --write '**/*.sol'", "lint:config": "prettier --check '**/*.@(json|yaml)'", "lint:config:fix": "prettier --write '**/*.@(json|yaml)'", + "typecheck": "tsc --noEmit -p tsconfig.json", "prepack": "tsc -p tsconfig.export.json && hardhat export-artifacts --including-no-public-functions export/artifacts", "prepublishOnly": "hardhat prepare-artifacts --network $npm_config_network" }, @@ -48,8 +49,8 @@ "@openzeppelin/hardhat-upgrades": "^1.20.0", "@tenderly/hardhat-tenderly": "1.0.12", "@thesis-co/eslint-config": "https://codeload.github.com/thesis/eslint-config/tar.gz/e63608fab2a1ad5c8fe89873bf0d4d4f9ef4a081", - "@typechain/ethers-v5": "^9.0.0", - "@typechain/hardhat": "^4.0.0", + "@typechain/ethers-v5": "^11.1.2", + "@typechain/hardhat": "^7.0.0", "@types/chai": "^4.2.22", "@types/mocha": "^9.0.0", "@types/node": "^16.10.5", @@ -69,7 +70,7 @@ "solhint-config-keep": "https://codeload.github.com/keep-network/solhint-config-keep/tar.gz/5e1751e58c0f1c507305ffc8c7f6c58047657ada", "solidity-docgen": "^0.6.0-beta.35", "ts-node": "^10.2.1", - "typechain": "^7.0.0", + "typechain": "^8.3.2", "typescript": "^5.9.3" }, "engines": { diff --git a/solidity/random-beacon/test/Groups.test.ts b/solidity/random-beacon/test/Groups.test.ts index 07b830ef6b..1d49635ae4 100644 --- a/solidity/random-beacon/test/Groups.test.ts +++ b/solidity/random-beacon/test/Groups.test.ts @@ -8,7 +8,7 @@ import { hashUint32Array } from "./utils/groups" import type { GroupsStub } from "../typechain" import type { ContractTransaction } from "ethers" -import type { Groups } from "../typechain/GroupsStub" +import type { Groups } from "../typechain/contracts/test/GroupsStub" const { keccak256 } = ethers.utils diff --git a/solidity/random-beacon/test/RandomBeacon.Callback.test.ts b/solidity/random-beacon/test/RandomBeacon.Callback.test.ts index 2c905527d0..53aa2d05fc 100644 --- a/solidity/random-beacon/test/RandomBeacon.Callback.test.ts +++ b/solidity/random-beacon/test/RandomBeacon.Callback.test.ts @@ -8,13 +8,13 @@ import { constants, params, randomBeaconDeployment } from "./fixtures" import { createGroup } from "./utils/groups" import { registerOperators } from "./utils/operators" -import type { RandomBeaconGovernance } from "../typechain/RandomBeaconGovernance" import type { DeployedContracts } from "./fixtures" import type { RandomBeaconStub, T, CallbackContractStub, RandomBeacon, + RandomBeaconGovernance, } from "../typechain" import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" diff --git a/solidity/random-beacon/test/RandomBeacon.GroupCreation.test.ts b/solidity/random-beacon/test/RandomBeacon.GroupCreation.test.ts index 839f720b95..25b78d2bfc 100644 --- a/solidity/random-beacon/test/RandomBeacon.GroupCreation.test.ts +++ b/solidity/random-beacon/test/RandomBeacon.GroupCreation.test.ts @@ -22,7 +22,7 @@ import { fakeTokenStaking } from "./mocks/staking" import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" import type { BigNumber, BytesLike, ContractTransaction } from "ethers" import type { Operator } from "./utils/operators" -import type { BeaconDkg as DKG } from "../typechain/RandomBeaconStub" +import type { BeaconDkg as DKG } from "../typechain/contracts/test/RandomBeaconStub" import type { Mock } from "./helpers/mock" import type { RandomBeacon, SortitionPool, T, TokenStaking } from "../typechain" diff --git a/solidity/random-beacon/test/RandomBeacon.Relay.test.ts b/solidity/random-beacon/test/RandomBeacon.Relay.test.ts index 6ee2929264..bef7d70188 100644 --- a/solidity/random-beacon/test/RandomBeacon.Relay.test.ts +++ b/solidity/random-beacon/test/RandomBeacon.Relay.test.ts @@ -17,7 +17,7 @@ import { signOperatorInactivityClaim } from "./utils/inactivity" import { registerOperators } from "./utils/operators" import { fakeTokenStaking } from "./mocks/staking" -import type { Groups } from "../typechain/RandomBeacon" +import type { Groups } from "../typechain/contracts/RandomBeacon" import type { Operator, OperatorID } from "./utils/operators" import type { Mock } from "./helpers/mock" import type { diff --git a/solidity/random-beacon/test/utils/dkg.ts b/solidity/random-beacon/test/utils/dkg.ts index afd7c91c4d..8db5733159 100644 --- a/solidity/random-beacon/test/utils/dkg.ts +++ b/solidity/random-beacon/test/utils/dkg.ts @@ -12,7 +12,7 @@ import type { ContractTransaction } from "ethers" import type { BeaconDkg as DKG, DkgResultSubmittedEvent, -} from "../../typechain/BeaconDkg" +} from "../../typechain/contracts/libraries/BeaconDkg" const { provider } = waffle diff --git a/solidity/random-beacon/yarn.lock b/solidity/random-beacon/yarn.lock index 268dd6e754..6ae0aa40e1 100644 --- a/solidity/random-beacon/yarn.lock +++ b/solidity/random-beacon/yarn.lock @@ -1184,8 +1184,8 @@ __metadata: "@thesis-co/eslint-config": "https://codeload.github.com/thesis/eslint-config/tar.gz/e63608fab2a1ad5c8fe89873bf0d4d4f9ef4a081" "@thesis/solidity-contracts": "github:thesis/solidity-contracts#4985bcf" "@threshold-network/solidity-contracts": "npm:1.3.0-dev.14" - "@typechain/ethers-v5": "npm:^9.0.0" - "@typechain/hardhat": "npm:^4.0.0" + "@typechain/ethers-v5": "npm:^11.1.2" + "@typechain/hardhat": "npm:^7.0.0" "@types/chai": "npm:^4.2.22" "@types/mocha": "npm:^9.0.0" "@types/node": "npm:^16.10.5" @@ -1205,7 +1205,7 @@ __metadata: solhint-config-keep: "https://codeload.github.com/keep-network/solhint-config-keep/tar.gz/5e1751e58c0f1c507305ffc8c7f6c58047657ada" solidity-docgen: "npm:^0.6.0-beta.35" ts-node: "npm:^10.2.1" - typechain: "npm:^7.0.0" + typechain: "npm:^8.3.2" typescript: "npm:^5.9.3" languageName: unknown linkType: soft @@ -1969,6 +1969,22 @@ __metadata: languageName: node linkType: hard +"@typechain/ethers-v5@npm:^11.1.2": + version: 11.1.2 + resolution: "@typechain/ethers-v5@npm:11.1.2" + dependencies: + lodash: "npm:^4.17.15" + ts-essentials: "npm:^7.0.1" + peerDependencies: + "@ethersproject/abi": ^5.0.0 + "@ethersproject/providers": ^5.0.0 + ethers: ^5.1.3 + typechain: ^8.3.2 + typescript: ">=4.3.0" + checksum: 10c0/5da6109ded6e02701e5ad718479b8a316011c5366adcbfbd8b7ee1c149c02960714c6906d823d76ab1839046aad127f9c8793f4b36a65f4299d7ce4314265ae1 + languageName: node + linkType: hard + "@typechain/ethers-v5@npm:^2.0.0": version: 2.0.0 resolution: "@typechain/ethers-v5@npm:2.0.0" @@ -1981,33 +1997,19 @@ __metadata: languageName: node linkType: hard -"@typechain/ethers-v5@npm:^9.0.0": - version: 9.0.0 - resolution: "@typechain/ethers-v5@npm:9.0.0" - dependencies: - lodash: "npm:^4.17.15" - ts-essentials: "npm:^7.0.1" - peerDependencies: - "@ethersproject/abi": ^5.0.0 - "@ethersproject/bytes": ^5.0.0 - "@ethersproject/providers": ^5.0.0 - ethers: ^5.1.3 - typechain: ^7.0.0 - typescript: ">=4.0.0" - checksum: 10c0/4aa3d0669a3d5a2cab14a39d4d3e5bc616ee909dd90b9e01be211a0c05759c67af9118a4530515db47c8a7b3a1a5a06a26daa9d14ea24766feb7c043b636c4d8 - languageName: node - linkType: hard - -"@typechain/hardhat@npm:^4.0.0": - version: 4.0.0 - resolution: "@typechain/hardhat@npm:4.0.0" +"@typechain/hardhat@npm:^7.0.0": + version: 7.0.0 + resolution: "@typechain/hardhat@npm:7.0.0" dependencies: fs-extra: "npm:^9.1.0" peerDependencies: - hardhat: ^2.0.10 - lodash: ^4.17.15 - typechain: ^7.0.0 - checksum: 10c0/f1004bfb11e08ea786a930401be0d22bab58eb1ee3c8901baa8cd467e45fe8a451d6f1e6b86d53ac84338c9fd3b7f422184d68274cbf7877196e695e2e301238 + "@ethersproject/abi": ^5.4.7 + "@ethersproject/providers": ^5.4.7 + "@typechain/ethers-v5": ^11.0.0 + ethers: ^5.4.7 + hardhat: ^2.9.9 + typechain: ^8.2.0 + checksum: 10c0/80732203ec94fd6933eedbef24d2b74ce167e0bdaf697ca1a98edbcb88775814a02e1e0fabdb2fd1e53d53730204fcad392580b42c0b47beeb30c403535dd652 languageName: node linkType: hard @@ -7388,6 +7390,20 @@ __metadata: languageName: node linkType: hard +"glob@npm:7.1.7, glob@npm:~7.1.7": + version: 7.1.7 + resolution: "glob@npm:7.1.7" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.0.4" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/173245e6f9ccf904309eb7ef4a44a11f3bf68e9e341dff5a28b5db0dd7123b7506daf41497f3437a0710f57198187b758c2351eeaabce4d16935e956920da6a4 + languageName: node + linkType: hard + "glob@npm:^10.3.10, glob@npm:^10.4.5": version: 10.5.0 resolution: "glob@npm:10.5.0" @@ -7415,7 +7431,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.2, glob@npm:^7.1.6": +"glob@npm:^7.1.2": version: 7.2.0 resolution: "glob@npm:7.2.0" dependencies: @@ -7443,20 +7459,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:~7.1.7": - version: 7.1.7 - resolution: "glob@npm:7.1.7" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.0.4" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10c0/173245e6f9ccf904309eb7ef4a44a11f3bf68e9e341dff5a28b5db0dd7123b7506daf41497f3437a0710f57198187b758c2351eeaabce4d16935e956920da6a4 - languageName: node - linkType: hard - "global@npm:~4.4.0": version: 4.4.0 resolution: "global@npm:4.4.0" @@ -11133,6 +11135,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^2.3.1": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: 10c0/463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a + languageName: node + linkType: hard + "private@npm:^0.1.6, private@npm:^0.1.8": version: 0.1.8 resolution: "private@npm:0.1.8" @@ -13511,25 +13522,25 @@ __metadata: languageName: node linkType: hard -"typechain@npm:^7.0.0": - version: 7.0.0 - resolution: "typechain@npm:7.0.0" +"typechain@npm:^8.3.2": + version: 8.3.2 + resolution: "typechain@npm:8.3.2" dependencies: "@types/prettier": "npm:^2.1.1" - debug: "npm:^4.1.1" + debug: "npm:^4.3.1" fs-extra: "npm:^7.0.0" - glob: "npm:^7.1.6" + glob: "npm:7.1.7" js-sha3: "npm:^0.8.0" lodash: "npm:^4.17.15" mkdirp: "npm:^1.0.4" - prettier: "npm:^2.1.2" + prettier: "npm:^2.3.1" ts-command-line-args: "npm:^2.2.0" ts-essentials: "npm:^7.0.1" peerDependencies: - typescript: ">=4.1.0" + typescript: ">=4.3.0" bin: typechain: dist/cli/cli.js - checksum: 10c0/b8a724f019cd6886c82f982aeb43ec1055cb64ab87a3c86452704677a334d193a3b0014a1fc82813af385e9c4ad959742085cd0b3f4d955e6630fb701a69672e + checksum: 10c0/1ea660cc7c699c6ac68da67b76454eb4e9395c54666d924ca67f983ae8eb5b5e7dab0a576beb55dbfad75ea784a3f68cb1ca019d332293b7291731c156ead5b5 languageName: node linkType: hard From 70b82e8a10eb80120bff7f9bcaf369ad1ae5169c Mon Sep 17 00:00:00 2001 From: maclane Date: Mon, 27 Jul 2026 14:38:49 -0400 Subject: [PATCH 2/4] fix(ecdsa): pass relay entry bytes without the BigNumber round-trip `submitRelayEntry` takes `bytes`, and the test handed it a `BigNumber` built from 32 random bytes, which `tsc` rejects as not `BytesLike`. It was not only a typing problem. ethers renders a `BigNumber` argument through `toHexString()`, which drops leading zero bytes, so about one run in 256 submitted an entry shorter than 32 bytes. The stub only keccak-hashes the value, so nothing failed and nothing noticed. The random bytes are a `Uint8Array`, already `BytesLike`, so they go in directly. `yarn test` is unchanged at 673 passing, 0 failing. Co-Authored-By: Claude Opus 5 (1M context) --- .../ecdsa/test/WalletRegistry.RandomBeacon.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/solidity/ecdsa/test/WalletRegistry.RandomBeacon.test.ts b/solidity/ecdsa/test/WalletRegistry.RandomBeacon.test.ts index 700b0b5230..f59c7cbab9 100644 --- a/solidity/ecdsa/test/WalletRegistry.RandomBeacon.test.ts +++ b/solidity/ecdsa/test/WalletRegistry.RandomBeacon.test.ts @@ -16,7 +16,7 @@ import type { WalletRegistry, WalletRegistryStub, } from "../typechain" -import type { BigNumber, ContractTransaction } from "ethers" +import type { ContractTransaction } from "ethers" const { createSnapshot, restoreSnapshot } = helpers.snapshot @@ -211,9 +211,12 @@ describe("WalletRegistry - Random Beacon", async () => { }) it("should succeed", async () => { - const entry: BigNumber = ethers.BigNumber.from( - ethers.utils.randomBytes(32) - ) + // `submitRelayEntry` takes `bytes`, and the stub only keccak-hashes + // it, so the random bytes go in directly. Wrapping them in a + // BigNumber first was not just redundant: ethers renders one via + // `toHexString()`, which drops leading zero bytes, so roughly one run + // in 256 submitted a short entry. + const entry = ethers.utils.randomBytes(32) const tx = await randomBeaconMock.submitRelayEntry(entry) From 4db4b53c403ea023f6d089ee907531ef53b27a82 Mon Sep 17 00:00:00 2001 From: maclane Date: Mon, 27 Jul 2026 14:38:49 -0400 Subject: [PATCH 3/4] ci(solidity): gate both packages on tsc --noEmit Nothing in CI ran the type checker. `yarn lint` is eslint, and hardhat transpiles tests without checking them, so a type error reached main only if someone happened to run `tsc` by hand. Both packages are at zero errors as of the two commits before this one, and this keeps them there. The step goes in `contracts-lint`, which already runs `yarn build` -- `typecheck` needs the generated `typechain/` to exist. Worth being precise about what this enforces: neither tsconfig sets `strict`, so zero is zero under the settings the packages already use. random-beacon additionally has `noImplicitAny`; ecdsa does not, and turning it on there would surface 112 errors (27 in typechain 6 output, 85 in hand-written code). Raising ecdsa to typechain 8 and then to `noImplicitAny` is worth doing separately. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/contracts-ecdsa.yml | 3 +++ .github/workflows/contracts-random-beacon.yml | 3 +++ solidity/ecdsa/package.json | 1 + 3 files changed, 7 insertions(+) diff --git a/.github/workflows/contracts-ecdsa.yml b/.github/workflows/contracts-ecdsa.yml index b3c4ecd237..c5c0703f84 100644 --- a/.github/workflows/contracts-ecdsa.yml +++ b/.github/workflows/contracts-ecdsa.yml @@ -67,6 +67,9 @@ jobs: - name: Lint run: yarn lint + - name: Typecheck + run: yarn typecheck + contracts-slither: needs: contracts-detect-changes runs-on: ubuntu-latest diff --git a/.github/workflows/contracts-random-beacon.yml b/.github/workflows/contracts-random-beacon.yml index a7be8c6eab..7569872066 100644 --- a/.github/workflows/contracts-random-beacon.yml +++ b/.github/workflows/contracts-random-beacon.yml @@ -67,6 +67,9 @@ jobs: - name: Lint run: yarn lint + - name: Typecheck + run: yarn typecheck + contracts-slither: needs: contracts-detect-changes runs-on: ubuntu-latest diff --git a/solidity/ecdsa/package.json b/solidity/ecdsa/package.json index a6f06aa761..df34263d35 100644 --- a/solidity/ecdsa/package.json +++ b/solidity/ecdsa/package.json @@ -27,6 +27,7 @@ "lint:fix:sol": "solhint 'contracts/**/*.sol' --fix && prettier --write '**/*.sol'", "lint:config": "prettier --check '**/*.@(json|yaml)'", "lint:config:fix": "prettier --write '**/*.@(json|yaml)'", + "typecheck": "tsc --noEmit -p tsconfig.json", "clean": "hardhat clean && rm -rf cache/ export/ external/npm typechain/ export.json", "build": "hardhat compile", "test": "USE_EXTERNAL_DEPLOY=true TEST_USE_STUBS_ECDSA=true hardhat test", From b8682b4384f070096f48108ae6a60c60bd42472b Mon Sep 17 00:00:00 2001 From: maclane Date: Mon, 27 Jul 2026 14:39:20 -0400 Subject: [PATCH 4/4] chore(ecdsa): ignore yarn berry runtime state `yarn install` in this package drops a 1 MB `.yarn/install-state.gz` that shows up as untracked and is easy to sweep into a commit. random-beacon already ignores it; the root `.gitignore` rule does not apply here because a pattern containing a slash is anchored to its own directory. Co-Authored-By: Claude Opus 5 (1M context) --- solidity/ecdsa/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/solidity/ecdsa/.gitignore b/solidity/ecdsa/.gitignore index 0706648c4b..2359e54ca7 100644 --- a/solidity/ecdsa/.gitignore +++ b/solidity/ecdsa/.gitignore @@ -16,6 +16,11 @@ deployments/* # OpenZeppelin .openzeppelin/unknown-*.json +# Yarn Berry runtime state +.yarn/install-state.gz +.yarn/cache/ +.yarn/build-state.yml + # Environment variables (NEVER commit private keys!) .env .env.local