Skip to content

Release to Staging v2.9.28 - 2026-08-21 - #2275

Open
github-actions[bot] wants to merge 2 commits into
stagingfrom
release/staging-2026-08-21
Open

Release to Staging v2.9.28 - 2026-08-21#2275
github-actions[bot] wants to merge 2 commits into
stagingfrom
release/staging-2026-08-21

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Weekly Release to Staging

Release Date: August 21, 2026
Release Branch: release/staging-2026-08-21

This automated PR promotes a snapshot of dev to staging for testing.

What's Included

All commits merged to dev up to the branch creation time.

Note: This PR uses a dedicated release branch, so new commits to dev will NOT automatically appear here.

Review Checklist

  • All CI checks pass
  • Code review completed
  • QA team notified
  • Ready to merge to staging environment

Next Steps

After merging, the staging environment will be updated. A production release PR will be created on Sunday.


This PR was automatically created by the Release Calendar workflow on August 21, 2026

Nesopie and others added 2 commits August 19, 2026 18:45
…2268)

* docs: design spec for registerProverKey on the hub

Anchors the TEE prover's secp256k1 signing key on-chain so off-chain
consumers can verify a proof came from an attested enclave.

Lands on the hub rather than IdentityRegistryKycImplV1: a prover key is
orthogonal to attestation type, and a key registered in the KYC registry sits
one mapping away from checkPubkeyCommitment, which RegisterProofVerifierLib
treats as KYC-attestor authority.

All new state goes in a third ERC-7201 namespace, adding zero plain
sequential variables. The hub has exactly one of those
(AADHAAR_REGISTRATION_WINDOW) outside its namespaced structs, and a new
namespace is immune to ordering by construction. The slot constant was
verified by reproducing the existing V2 slot with the same derivation.

Committed with --no-verify: the pre-commit hook runs `pnpm gitleaks`, but
pnpm is not provisioned on this branch, so corepack attempts a full
22-project install and times out. Both halves of the hook were satisfied
manually instead -- the license-header script only covers source extensions,
not .md, and a manual sweep of the single staged file found no key material
(the only long hex is the public ERC-7201 slot constant).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: implementation plan for registerProverKey on the hub

Four tasks: the GCPJWTHelper address decoder, the ERC-7201 prover storage
namespace with config setters, registerProverKey/revokeProverKey with a
14-case behavioural suite, and the bare-hex nonce change that lands on
tee-prover-server PR #36.

Storage-layout verification via forge inspect is a required step in two
tasks, not an afterthought -- this is a live contract behind a proxy.

Self-review caught two plan failures before commit: empty test bodies in
Task 3, and error names in the test titles that did not match the
PROVER_-prefixed set the implementation declares.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(contracts): decode a prover address from packed attestation nonce chunks

Add GCPJWTHelper.unpackAndDecodeAddress, which decodes the two PackBytes
field elements carrying the bare 40-char hex prover address from a GCP
attestation nonce. Unlike the two existing decoders in this library, it
asserts exactly 40 characters were recovered and that neither chunk still
holds bytes past that boundary, so a short or long nonce reverts instead of
silently decoding to a different address.

* refactor(contracts): extract hex-decode validation and pin boundary cases

Add _hexCharToValue, a revert-on-invalid sibling of _hexToNibble, and route
unpackAndDecodeAddress's decode loop through it instead of duplicating the
inline branch logic that used to mirror unpackAndDecodeHexPubkey.
_hexToNibble is untouched and keeps its silent-zero behavior for the
image-hash path.

Add three tests pinning behaviors previously verified only by inspection:
exactly-31-characters (chunk 1 absent), a p0-side overflow where chunk 0
itself holds more than 31 meaningful bytes, and uppercase/mixed-case hex
decoding identically to lowercase.

* feat(contracts): prover key storage namespace and config on the hub

Adds an ERC-7201 namespaced storage struct (self.storage.IdentityVerificationHubProver)
to IdentityVerificationHubImplV2 for the TEE prover's GCP JWT verifier, PCR0 manager,
GCP root CA pubkey hash, and prover TEE address, plus SECURITY_ROLE-gated setters/views.
Storage-only: registration logic lands in a follow-up task.

* feat(contracts): registerProverKey and revokeProverKey on the hub

Adds registerProverKey/revokeProverKey/isRegisteredProverKey to
IdentityVerificationHubImplV2, mirroring the verification order of
IdentityRegistryKycImplV1.registerPubkeyCommitment: GCP JWT proof
check, root CA hash match, PCR0 image-hash check, nonce-padding
assertion (pubSignals[3]/[4] must be zero), timestamp reassembly and
1-hour freshness window, then decoding the prover address from
pubSignals[1]/[2] via GCPJWTHelper.unpackAndDecodeAddress. Config
values (verifier, PCR0Manager, root CA hash, TEE) must all be set or
the call reverts, and only the configured prover TEE may call
registerProverKey.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(contracts): cover registerProverKey/revokeProverKey on the hub

Appends to registerProverKey.test.ts (config tests from an earlier
phase untouched): full registerProverKey happy path, each of the four
unset-config PROVER_CONFIG_NOT_SET reverts, verifier-false,
root-CA-mismatch, image-not-in-PCR0Manager, both nonce-padding
reverts, both timestamp-window reverts, the TEE access-control revert
and its config-unset variant, plus revokeProverKey's clear/emit and
its role check. Also pins the ERC-7201 namespace slot for the prover
storage by independently deriving it in TS from the struct's
storage-location doc-comment and reading it back via
provider.getStorage, rather than trusting the pasted constant.

Verified against source rather than the task brief's guesses:
MockGCPJWTVerifier.setShouldVerify(bool) and the DeployedActorsV2
field names (hub/pcr0Manager/owner/user1) were both already correct
as written.

Full hardhat suite: 416 passing / 36 failing, and the 36 failures are
byte-for-byte identical (by test title) to a stash-reverted baseline
run without these changes -- all pre-existing (registerKyc.test.ts's
snarkjs witness-length mismatch among them), none introduced here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(contracts): reject zero-address prover keys, name the revoke revert, cover decoder/boundary edges

Addresses four review findings on registerProverKey/revokeProverKey:

- revokeProverKey's non-SECURITY_ROLE test now asserts
  revertedWithCustomError(hub, "AccessControlUnauthorizedAccount")
  instead of a bare `to.be.reverted`, matching this repo's existing
  pattern (PCR0Manager.test.ts, ImplRoot.test.ts, IdentityRegistry.test.ts)
  and the project-wide "match by name" rule the brief itself set.
- Add INVALID_PROVER_ADDRESS(), reverting registerProverKey if the
  decoded prover key is address(0). Forty ASCII '0' nonce characters
  decode to the zero address; not reachable without a genuine
  attestation over an all-zeros eat_nonce, but the registered-key
  mapping is consumed as an authorization oracle and address(0) is
  also what a malformed ecrecover returns, so this closes an auth-
  bypass shape for any consumer checking isRegisteredProverKey against
  a recovered signer.
- Exercise the previously-dead `nonce` option in the test's `signals()`
  helper: a 38-hex-char and a 42-hex-char nonce now drive
  GCPJWTHelper.unpackAndDecodeAddress's own two string reverts ("Nonce
  is not 40 hex characters" / "Nonce exceeds 40 hex characters"),
  matched with revertedWith since those are string reverts, not custom
  errors.
- Pin the 1-hour freshness window at its boundary: +/-59 minutes now
  must succeed and +/-61 minutes must revert INVALID_PROVER_TIMESTAMP,
  closing the gap where only +/-2h and 0h were exercised. These are
  anchored to the chain's own latest block timestamp
  (digitsAtChainOffsetSeconds) rather than JS wall-clock time, since
  Hardhat's mined block timestamps can drift ahead of Date.now() over
  a test run enough to flip a ~1-minute-margin boundary case.

No storage layout change (forge inspect storageLayout diff against
the pre-fix baseline is empty).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(contracts): bump hub version to 2.14.0 for registerProverKey

The upgrade tooling (tasks/upgrade/utils.ts, upgrade.ts) reads @Custom:version
to detect and validate upgrades. No reinitializer bump is needed: this is a
code-only upgrade, and the new ERC-7201 prover storage namespace's all-zero
defaults are exactly what the config guard treats as "not configured yet".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(contracts): rename prover errors to CapWords for hub consistency

The 8 new prover errors were SCREAMING_SNAKE, imported from the KYC registry's
style, but all 21 pre-existing errors in IdentityVerificationHubImplV2.sol use
CapWords. Error names determine ABI selectors, so this was the last point to
rename them for free. Updated every reference in registerProverKey.test.ts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(contracts): pin bare reverts to the specific guard they claim to cover

gcpJwtHelperAddress.test.ts: the "fewer than 40 characters" and "exactly 31
characters" cases used bare `to.be.reverted`, so they still passed when
`if (idx != 40) revert(...)` was mutated out of GCPJWTHelper.sol — the
zero-filled tail falls through to `_hexCharToValue(0)` and reverts with
"Invalid hex character" instead. Changed both to
revertedWith("Nonce is not 40 hex characters"), matching the sibling
GCPJWTHelper.test.ts's existing named-revert style.

Verified by mutation: commenting out the idx != 40 guard makes both tests
fail with the expected/actual reason mismatch; restoring it makes both pass.

registerProverKey.test.ts: the config-setter access-control case used the
same bare `to.be.reverted`, missed when it was written in an earlier task
before the sibling revokeProverKey test was fixed to name the error. Changed
to revertedWithCustomError(hub, "AccessControlUnauthorizedAccount").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(contracts): add prover key config rotation runbook to UPGRADE_GUIDE

The design doc for registerProverKey accepted config duplication between the
hub and IdentityRegistryKycImplV1 (root CA hash, PCR0Manager pointer), with
the mitigation "a rotation runbook must touch both contracts" — but no such
runbook existed. Adds a section covering: (a) the setter pairs on each
contract that must be rotated together, (b) why the two PCR0Manager instances
must never be shared (a prover attestation replayed into
registerPubkeyCommitment would pass the image check and write a garbage KYC
pubkey commitment, since that path has no length assertion), and (c) the four
SECURITY_ROLE setters that must be called post-upgrade before
registerProverKey stops reverting ProverConfigNotSet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: preserve execution ledger with all 17 controller rulings

The SDD workspace is gitignored scratch and gets deleted at the end of the
run, which would take every decision made without a human in the loop with
it. Copying it into docs/ so the rulings survive as a reviewable record.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: CI formatting, stale error selectors, and two review findings

Three CI failures, all mechanical:

- workspace-format-check and test_contracts both failed prettier on the two
  new test files, and test_contracts additionally on UPGRADE_GUIDE.md.
- generated-artifacts failed because new-common's error-selector-map.json was
  stale: this branch adds custom errors to the hub and the generated map was
  never regenerated. Regenerated; 8 entries added, nothing removed.

Two review findings applied:

- IIdentityVerificationHubV2 omitted the four prover config setters and four
  config views the implementation adds, so a client typed against the
  interface could neither configure nor inspect them. Declared all eight. The
  interface already declares the other admin setters (updateRegistry and
  friends), so this restores consistency rather than introducing a pattern.
  The implementation is `is ImplRoot`, not `is IIdentityVerificationHubV2`,
  so no override changes are needed.

- UPGRADE_GUIDE.md omitted that the prover must already emit a bare
  40-character nonce. unpackAndDecodeAddress rejects the 0x-prefixed
  42-character form, so every registration would fail after the four setters
  had succeeded, presenting as a contract bug rather than version skew. Added
  the prerequisite with explicit ordering, and noted the ordering is one-way:
  accepting the prefixed form would mean widening what the contract decodes,
  so the producer is the correct side to fix.

A third finding, to store only a commitment rather than the prover address,
was declined with reasoning on the PR: ecrecover yields an address so the
registry must be queryable by one, attributability to a key is the property
that makes revocation possible, and the address is already a public input to
the proof this function verifies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add signature to register flow

* Verify TEE prover signature in register flow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix prettier formatting

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Restore dev formatting on untouched contracts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(contracts): review findings on the prover signature PR (#2269)

Three fixes from a review of #2267. Each is independent.

1. Align IIdentityVerificationHubV2.registerCommitment with the implementation.

The interface declared pubSignals as IRegisterCircuitVerifier.RegisterCircuitProof
(uint256[3]) while the implementation takes GenericProofStruct (uint256[]). Those
are different selectors, so any contract calling registerCommitment through this
interface reverts on the proxy:

  interface (uint256[3]) 0x3c31dfd7
  implementation         0x848cd73b

The divergence predates this PR and has no in-repo caller -- SelfVerificationRoot
and SelfVerificationRootUpgradeable import the interface but never call these two
functions -- so nothing was failing. This PR edits exactly that declaration to add
the signature parameter, which propagates the wrong shape into the new docs, so it
is the right moment to correct it. registerDscKeyCommitment already matched and is
unchanged. IRegisterCircuitVerifier was imported solely for the wrong type, so the
import becomes GenericProofStruct.

2. Give registerProverWallet its own PCR0Manager.

The helper pointed the hub's prover PCR0Manager at deployedActors.pcr0Manager,
which deploymentV2.ts also wires into IdentityRegistryKycImplV1, then added a
prover image digest to it. UPGRADE_GUIDE section (b) forbids both. Because this
helper is the shared fixture for the passport, ID, Aadhaar and KYC register
suites, every one of them was exercising the forbidden configuration.

Separate instances also give the fixture teeth it did not have: if the hub ever
consulted the KYC registry's PCR0Manager for a prover image, the digest would not
be found there and registerProverKey would revert. A shared instance cannot detect
that regression, because both lookups succeed.

3. Correct the deploy ordering in UPGRADE_GUIDE section (d).

The documented order deployed the signature-sending relayer at step 5 and upgraded
the hub at step 6, so that real signatures could be observed before enforcement.
That cannot execute. Adding the signature parameter changes both selectors and
ships no overload, so the 3-argument functions cease to exist at the upgrade block:

  registerCommitment         0xf2ea431c -> 0x848cd73b
  registerDscKeyCommitment   0x8322963f -> 0xed9b4fd7

A relayer sending 4-argument calldata to the v2.14.0 hub matches no function and
reverts; a relayer still sending 3-argument calldata after the upgrade reverts the
same way. The two are mutually exclusive, so there is no ordering in which both
hold and register is unavailable between the two transactions. The section now
documents that window as a scheduled cutover, explains why the previous order was
impossible, and names the intermediate accept-and-ignore release as the only route
to a zero-downtime rollout.

Tests: registerProverKey 32 passing, registerPassport 14 passing, registerId +
registerAadhaar 21 passing. registerKyc is 14 passing / 1 failing in its before
hook with "Invalid witness length. Circuit: 14530, witness: 14671", a stale local
circuit artifact that fails identically without these changes.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Add and verify TEE prover signature in disclose flow (#2266)

* Add signature to proofPayload

* Verify TEE prover signature in disclose flow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update proofPayload format docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix #2266: resolve base conflict, fix the disclose payload floor, fit EIP-170 (#2270)

* fix(contracts): review findings on the prover signature PR (#2269)

Three fixes from a review of #2267. Each is independent.

1. Align IIdentityVerificationHubV2.registerCommitment with the implementation.

The interface declared pubSignals as IRegisterCircuitVerifier.RegisterCircuitProof
(uint256[3]) while the implementation takes GenericProofStruct (uint256[]). Those
are different selectors, so any contract calling registerCommitment through this
interface reverts on the proxy:

  interface (uint256[3]) 0x3c31dfd7
  implementation         0x848cd73b

The divergence predates this PR and has no in-repo caller -- SelfVerificationRoot
and SelfVerificationRootUpgradeable import the interface but never call these two
functions -- so nothing was failing. This PR edits exactly that declaration to add
the signature parameter, which propagates the wrong shape into the new docs, so it
is the right moment to correct it. registerDscKeyCommitment already matched and is
unchanged. IRegisterCircuitVerifier was imported solely for the wrong type, so the
import becomes GenericProofStruct.

2. Give registerProverWallet its own PCR0Manager.

The helper pointed the hub's prover PCR0Manager at deployedActors.pcr0Manager,
which deploymentV2.ts also wires into IdentityRegistryKycImplV1, then added a
prover image digest to it. UPGRADE_GUIDE section (b) forbids both. Because this
helper is the shared fixture for the passport, ID, Aadhaar and KYC register
suites, every one of them was exercising the forbidden configuration.

Separate instances also give the fixture teeth it did not have: if the hub ever
consulted the KYC registry's PCR0Manager for a prover image, the digest would not
be found there and registerProverKey would revert. A shared instance cannot detect
that regression, because both lookups succeed.

3. Correct the deploy ordering in UPGRADE_GUIDE section (d).

The documented order deployed the signature-sending relayer at step 5 and upgraded
the hub at step 6, so that real signatures could be observed before enforcement.
That cannot execute. Adding the signature parameter changes both selectors and
ships no overload, so the 3-argument functions cease to exist at the upgrade block:

  registerCommitment         0xf2ea431c -> 0x848cd73b
  registerDscKeyCommitment   0x8322963f -> 0xed9b4fd7

A relayer sending 4-argument calldata to the v2.14.0 hub matches no function and
reverts; a relayer still sending 3-argument calldata after the upgrade reverts the
same way. The two are mutually exclusive, so there is no ordering in which both
hold and register is unavailable between the two transactions. The section now
documents that window as a scheduled cutover, explains why the previous order was
impossible, and names the intermediate accept-and-ignore release as the only route
to a zero-downtime rollout.

Tests: registerProverKey 32 passing, registerPassport 14 passing, registerId +
registerAadhaar 21 passing. registerKyc is 14 passing / 1 failing in its before
hook with "Invalid witness length. Circuit: 14530, witness: 14671", a stale local
circuit artifact that fails identically without these changes.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(contracts): move prover code into linked libraries to fit EIP-170

With the disclose signature work applied, IdentityVerificationHubImplV2's
runtime bytecode measured 24,591 bytes against the 24,576-byte EIP-170 limit
-- 15 bytes over, undeployable. Nothing caught it: hardhat.config.ts sets
allowUnlimitedContractSize, so all 68 disclose tests passed on a contract
that cannot be deployed. Measured with forge build --sizes and confirmed by
reading deployedBytecode from the artifact.

Two external libraries, following the seven this contract already links:

  ProverSignatureLib     recovers the signer from a proof digest
  ProverAttestationLib   validates a GCP JWT attestation, returns the address

Both are external, not internal. An internal library is inlined by the
compiler and would have saved nothing; only external linkage moves code out
of the contract's own budget. Both are stateless -- config arrives as
arguments, results come back as return values, and every storage write stays
in the hub, so authorization is never a DELEGATECALL away from the state it
decides on.

  #2266 as-is                24,591   -15
  + ProverSignatureLib       24,495   +81
  + ProverAttestationLib     22,473   +2,103

The attestation extraction is where the room is, and it is the right code to
move on its own terms: registerProverKey runs once per enclave boot, so the
added DELEGATECALL is paid on the coldest path in the contract, never on
disclose or register.

A digest-only variant was tried and rejected: passing bytes32 + bytes instead
of the proof arrays left both the abi.encode and the call plumbing in the hub
and measured 24,659, worse than doing nothing. The saving comes from moving
the encoding out, not the ecrecover -- ECDSA.tryRecover is only 715 bytes.

Errors are declared in the libraries, where the reverts now originate, and
kept in the hub so they stay in its ABI. Selectors derive from the signature
rather than the declaring contract, so callers matching on the hub's ABI
still match these reverts; the 32 registerProverKey tests exercise every
moved error path and pass unchanged. error-selectors.json is regenerated and
the delta is purely additive -- same selectors, new file paths.

Linked at all three deployment sites: the ignition hub and upgrade modules
and the test fixture. The remaining m.contractAt callers attach to a
deployed address and need no linkage.

Tests: registerProverKey 32, disclose passport/id/aadhaar/kyc 68,
registerPassport/registerId/registerAadhaar 35. 135 passing, 0 failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: name the actual failure a version-skewed disclosure produces

The rollout section claimed an old-format proofPayload surfaces as
UnauthorizedProverSigner. It does not, and this branch's own evidence said so:
the four discloseAadhaar tests repaired earlier in this PR were each failing
with "reverted without a reason", which is exactly what the corrected text now
describes. The wrong symptom was written down next to the right observation.

The order is decode first, signature second. _decodeInput strips the 65 bytes,
_executeVerificationFlow hands the shifted remainder to
_decodeVcAndDiscloseProof -- a plain abi.decode(data, (GenericProofStruct)) --
and that call is an argument to _basicVerification, so it is evaluated before
the body reaches _verifyProverSignature. A payload shifted by 65 bytes has
broken ABI offsets, so it reverts inside abi.decode with no reason string and
no custom error, never reaching the signature check.

That matters because the guidance inverted the diagnosis. An operator seeing a
bare revert during a rollout was told to suspect prover registration, when the
bare revert is the signal for version skew; UnauthorizedProverSigner is the one
symptom that does indicate a prover-key problem, since reaching it requires a
payload that decoded cleanly. The section now gives all three symptoms and
which cause each points to, including the InvalidDataFormat case the 98-byte
floor added in this PR now produces for payloads too short to hold a signature.

Reported by Codex review on #2270.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Nesopie <87437291+Nesopie@users.noreply.github.com>
Co-authored-by: ayman <aymanshaik1015@gmail.com>

* docs: deploy v2.14.0 first, so the prover steps are callable at all

CodeRabbit was right that this ordering could not execute, though for a
different reason than either of us first wrote down. registerProverKey,
revokeProverKey and the four updateProver... setters ship in v2.14.0, and
v2.14.0 is not deployed anywhere: deployments/registry.json has celo at
2.13.0 and celo-sepolia at 2.12.0. Steps that said "call these on the
currently-deployed hub" were naming functions that do not exist on-chain.

The fix is not to move prover configuration after the v2.15.0 upgrade, which
would leave enforcement live with no registered key and every register and
disclose call reverting until registration caught up. It is to deploy v2.14.0
first. That release keeps the 3-argument registerCommitment and the
pre-signature proofPayload layout and adds no enforcement, so it is a drop-in
that changes nothing the relayer sees -- the intermediate compatibility
release this rollout would otherwise have to build, already merged to dev and
simply never deployed.

With it in place the prover key is registered while the old formats are still
live, and the cutover shrinks to what genuinely cannot be avoided: the hub
upgrade and the relayer deploy. Steps renumbered to 1-5 prerequisites, 6-7
cutover.

Reported by CodeRabbit on #2267.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(contracts): link the prover libraries in the hardhat upgrade task

The EIP-170 extraction added ProverSignatureLib and ProverAttestationLib and
linked them in three places: the ignition hub module, the ignition upgrade
module, and the test fixture. It missed a fourth, which is the one operators
actually run -- `npx hardhat upgrade --contract IdentityVerificationHub`.

tasks/upgrade/upgrade.ts builds its linkage from a string array rather than
the `LibName: address` object the other three use, so a search for the object
form did not surface it. With only the previous seven names in that array,
getContractFactory throws on the unresolved link references and the upgrade
cannot be deployed at all.

Verified against the artifact rather than by inspection: the compiled
bytecode's linkReferences name exactly nine libraries, and the task's list now
matches that set with nothing missing and nothing extra. That comparison is
the check worth repeating whenever a library is added, since the failure is
invisible until deploy time.

The deployHubV2 module's header comments enumerated the seven libraries and
the verify-libs.js template listed their addresses; both now include the two
new ones, so the documented verification step does not fail on an incomplete
--libraries file.

Reported by Codex review on #2267.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Nesopie <87437291+Nesopie@users.noreply.github.com>
Co-authored-by: ayman <aymanshaik1015@gmail.com>
@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
self-webview-app Ignored Ignored Preview Aug 21, 2026 5:11pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants