Skip to content

feat: pro 541 improve wrapper deployment - #245

Open
Seth-Schmidt wants to merge 11 commits into
mainfrom
feature/pro-541-improve-wrapper-deployment-scripts-to-only-take-an
Open

feat: pro 541 improve wrapper deployment#245
Seth-Schmidt wants to merge 11 commits into
mainfrom
feature/pro-541-improve-wrapper-deployment-scripts-to-only-take-an

Conversation

@Seth-Schmidt

Copy link
Copy Markdown
Contributor

Summary

Introduces a params-driven deployment workflow for ConfidentialWrapper, so live deployments are driven by reviewed, committed JSON config rather than ad hoc .env values. Hardhat tasks and network naming are updated to support this model, and documentation is consolidated around the new entry process.

  • Add deploy-params/ with network.json and wrappers.json for Ethereum mainnet and Sepolia, seeded with all existing wrapper configurations (including initialObservers and deny-list selectors).
  • Add deployParams.ts to resolve network tier, load network config, and look up wrapper entries by underlying address.
  • Refactor deploy tasks: artifact names keyed by symbol, pluggable deployer signer hook, optional --name / --label on task:deployConfidentialWrapperImpl, and removal of redundant batch implementation deploy/verify tasks.
  • Rename Hardhat networks from mainnet / testnet to ethereum / sepolia; enable dual Solidity compilers (0.8.27 + 0.8.29) to support proxy verification; commit live-network deployment and OpenZeppelin manifests.
  • Improve verification to run on Blockscout and Sourcify (best-effort).
  • Add a deploy params entry runbook and update existing deployment docs to point contributors at the PR-based workflow.

@Seth-Schmidt Seth-Schmidt self-assigned this Aug 19, 2026
@cla-bot cla-bot Bot added the cla-signed label Aug 19, 2026
@Seth-Schmidt
Seth-Schmidt marked this pull request as ready for review August 19, 2026 21:44
@Seth-Schmidt
Seth-Schmidt requested a review from a team as a code owner August 19, 2026 21:44
@Seth-Schmidt

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown

Claude timed out while processing the request. — View run

// Get the proxy deployment name for a confidential wrapper
export function getConfidentialWrapperProxyName(tokenName: string): string {
return `ConfidentialWrapper_${tokenName}_Proxy`;
export function getConfidentialWrapperUpgradeImplName(label: string, name?: string): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead f label, version or at least versionTag would be a bit more precise.

Also not sure why we consider an (optiona ?) name here and not a symbol like you've updated for the other functions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to include the option for a name here because of the planned treasury changes that will likely require us to have multiple (probably 2) implementations at the same time per version.

I think versionTag makes sense here!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated here: 47ed7b7

// --blocked-users '["0x1111111111111111111111111111111111111111"]' \
// --underlying-deny-list-selector "0xfe575a87" \
// --network testnet
// --initial-observers '[]' \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'll need pauser

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added here: 796e129

const initialObservers =
initialObserversEnv === undefined || initialObserversEnv.trim() === ''
? []
: (JSON.parse(initialObserversEnv) as string[]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'll need pauser

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added here: 796e129

// npx hardhat task:deployConfidentialWrapperImpl --network testnet
async function deployConfidentialWrapperImpl(hre: HardhatRuntimeEnvironment) {
const { getNamedAccounts, ethers, deployments, network } = hre;
// Deploy a bare ConfidentialWrapper implementation (no proxy), for an upgrade proposal: deploy it,

@melanciani melanciani Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is obsolete / not true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@melanciani

Copy link
Copy Markdown
Collaborator

since claude did not want to review here, I made it review locally. I removed some findings so hence the wrong ordering. Basically: I agree I'm not convinced about the new 0.8.29 compiler version added + let's make sure the verify change makes sense:

HIGH

1. contracts/confidential-wrapper/tasks/verify.ts:34isAlreadyVerified substring match silently swallows real verification failures

OZ's verify:etherscan interceptor does not throw per-contract errors; it accumulates them and throws a single UpgradesError whose message is a summary (verify-proxy.js:85-97). "Already verified" does not always get consumed internally: Etherscan.getVerificationStatus returns (rather than throws) an EtherscanResponse when isAlreadyVerified() (etherscan.js:199-202), so OZ's verifyContractWithConstructorArgs sees isSuccess() === false and calls recordVerificationError(..., "Contract source code already verified").

Concrete failure: re-run task:verifyConfidentialWrapper on a mainnet wrapper whose proxy is already verified but whose implementation fails for a real reason (bytecode mismatch, missing build-info, API error). The summary then contains both Error 1: Failed to verify implementation contract at 0x…: <real error> and Error 2: …: Contract source code already verified. The /already verified/i test matches the combined string, so the task prints "Already verified on Etherscan", exits 0, and the operator believes the implementation is verified.

Fix: only swallow when every recorded error line is an already-verified message; for the non-proxy path, check err instanceof ContractAlreadyVerifiedError instead of pattern-matching prose.


MEDIUM

4. contracts/confidential-wrapper/tasks/deploy.ts:186 — removed guard on CONFIDENTIAL_WRAPPER_INITIAL_OBSERVERS_{i}

getRequiredJsonEnvVar (which threw on a missing var) was replaced by a silent [] default. A typo'd or forgotten CONFIDENTIAL_WRAPPER_INITIAL_OBSERVERS_2 in a mainnet batch now deploys the wrapper with no observers — no party able to decrypt amounts — instead of aborting, fixable only by a privileged post-deploy transaction.

Worse, test/tasks/deploy.ts:41-47 was changed to mirror the exact same fallback, so expect(await confidentialWrapper.observers()).to.deep.equal(initialObservers) compares [] to [] and passes. The test cannot catch this class of misconfiguration.

Fix: keep the required-env-var behaviour for the batch task; the CLI task can keep an explicit --initial-observers '[]' for the genuinely-empty case.

5. contracts/confidential-wrapper/tasks/deploy.ts:283task:verifyConfidentialWrapperImpl was not migrated and now breaks on Sourcify

This task still calls run('verify:verify', …). With sourcify.enabled: true newly set in the config, verify:verify runs verify:etherscan then verify:sourcify with no error handling (hardhat-verify/index.js:150-160), so a Sourcify failure throws after Etherscan already succeeded — precisely the hazard documented in the new helper's own docstring at verify.ts:14-16.

This is Step 4 of docs/deployment/deploy-wrapper-runbook.md for mainnet upgrades: the operator gets a red failure on a successful verification, and Blockscout is skipped entirely. Route this task through verifyOnEnabledExplorers too.

6. contracts/confidential-wrapper/hardhat.config.ts:73 — every contract except ConfidentialWrapper.sol silently moves from solc 0.8.27 to 0.8.29

Verified empirically via compile:solidity:get-compilation-jobs against this config. The 0.8.27 job emits only contracts/ConfidentialWrapper.sol and contracts/mocks/ERC7984ReceiverMock.sol (the latter only because it pins pragma solidity 0.8.27). The 0.8.29 job emits contracts/token/ERC7984Upgradeable.sol, contracts/extensions/ERC7984ERC20WrapperUpgradeable.sol, contracts/fhevm/*, and all remaining mocks.

The deployed implementation's bytecode is preserved — that part of the design works — but the audited base contracts and every mock the unit tests and npm run coverage exercise are now built with a compiler that never produces production bytecode. A 0.8.27/0.8.29 codegen difference would leave CI green while shipping different code. It also contradicts .claude/CLAUDE.md ("Solidity version: 0.8.27").

Fix: pin the whole contracts/** tree via overrides, or keep a single 0.8.27 compiler and add 0.8.29 only where hardhat-verify genuinely needs it — see the note under "Other observations" on whether it needs it at all.

7. contracts/confidential-wrapper/package.json:71test:sepolia still points at the deleted network

"test:sepolia": "hardhat test --network testnet". After the testnetsepolia rename, npm run test:sepolia fails with HH100: Network testnet doesn't exist. package.json is not in the PR's file list at all.

8. contracts/confidential-wrapper/.gitignore:38 — the un-ignore claims deployment/manifest state is committed, but none of it is

The only file added under deployments/ is deployments/sepolia/.chainId. No deployments/*/**.json, and no .openzeppelin/mainnet.json or sepolia.json. Combined with the testnetsepolia rename (hardhat-deploy keys directories by network name), every previously saved record is orphaned.

Concretely: the runbook's batch step task:verifyAllConfidentialWrappers --network ethereum hits deployments.get() with nothing on disk on a fresh clone, and because verify.ts:101-103 only console.errors and continues, the task prints "An error occurred" per wrapper and still exits 0 — CI and operators read it as success. Separately, OZ upgrades.* on mainnet has no manifest to validate upgrade safety against.

Fix: either commit the real records, or drop the "is committed" claim and re-ignore them.


LOW

10. contracts/confidential-wrapper/hardhat.config.ts:111DEPLOYMENT_RPC_URL overrides both networks and is undocumented

It takes precedence over ETHEREUM_RPC_URL and SEPOLIA_RPC_URL, and appears nowhere in .env.example or the README. A leftover DEPLOYMENT_RPC_URL from a Sepolia run makes --network ethereum connect to Sepolia. Hardhat's ChainIdValidatorProvider (providers/construction.js:161) does catch this, so the outcome is a confusing chain-id error rather than a wrong-chain deploy — but one var shadowing two networks is a footgun worth documenting or splitting per-network.

11. contracts/confidential-wrapper/tasks/deploy.ts:26resolveDeployerAddress is dead code, and its guard is missing where it matters

Nothing in the repo calls resolveDeployerAddress, so its 'No signer configured: set PRIVATE_KEY or MNEMONIC' check never runs. getDeployerSigner (lines 21-22), which is used, has no such guard: with no MNEMONIC/PRIVATE_KEY, accounts is undefined, getNamedAccounts() resolves deployer to undefined, and hre.ethers.getSigner(undefined) throws an opaque ethers error instead of the intended actionable message. Move the guard into getDeployerSigner, or drop the unused export.

12. contracts/confidential-wrapper/hardhat.config.ts:95 — the overrides block duplicates the 0.8.27 settings

compilers[0] and overrides['contracts/ConfidentialWrapper.sol'] carry identical but independently declared optimizer/evmVersion settings. A future bump of optimizer runs or evmVersion in compilers[0] silently will not apply to the one contract whose bytecode is actually deployed and verified — a config that looks changed but ships the old settings, with no test or CI signal. Hoist the settings object into a shared const referenced by both.

13. contracts/confidential-wrapper/README.md:20 — documented network name doesn't exist

The table says AMOY_RPC_URL is "RPC URL for the amoy network", but the config declares the network as polygon-amoy; --network amoy fails with HH100.

14. contracts/confidential-wrapper/tasks/verify.ts:41 — Blockscout/Sourcify can never verify the proxy, so every run logs failures

Both are invoked with the proxy address and no constructor args, but the proxy is OZ's precompiled ERC1967Proxy — not a project artifact, and it has a 2-argument constructor. Those two calls are structurally guaranteed to warn on every mainnet/sepolia verification, which trains operators to ignore "best-effort" warnings that would matter for the implementation. The code comment acknowledges this; consider skipping Blockscout/Sourcify for the proxy address so warnings only appear where success is actually possible.


Other observations (not filed as findings)

  • The PR description does not match the diff. It describes deploy-params/network.json, deploy-params/wrappers.json, deployParams.ts, and "removal of redundant batch implementation deploy/verify tasks" — none of those files exist in the branch, and the task set is unchanged from main. Reviewers cannot review the headline feature as described.

  • The rationale comment at hardhat.config.ts:69-72 is only partly right. OZ's proxy verification calls the Etherscan API directly with its own bundled 0.8.29 build-info and never consults config.solidity. Listing 0.8.29 only avoids CompilerVersionsMismatchError on OZ's fallback path (attemptVerifyOrFallback), and that fallback would then fail with DeployedBytecodeMismatchError anyway since ERC1967Proxy isn't a project artifact. Worth confirming the 0.8.29 entry is load-bearing at all before accepting the project-wide compiler bump in finding 6 — if it isn't, dropping it is cleaner than pinning the tree.

@Seth-Schmidt

Copy link
Copy Markdown
Contributor Author

Regarding the claude review:

  1. Looks valid. Will confirm and change the error handling here.

  2. Valid, we can require all fields for batch.

  3. Not valid. The Impl side of deployment is handled in the upgrade + proposal ci workflow to come

  4. Valid. I think I will drop the compiler version here and look for a better solution. We should change from the ^ carrot pragmas if we want to enforce 0.8.27 everywhere, though. The override was more to preserve behavior, but claude is right that it's probably not the best pattern here.

  5. Valid.

  6. Not valid. This commit introduces committed deployment artifacts.

  7. Valid. The override is intentional, but I will add back some brief documentation.

  8. Not valid. This is used as a hook by the internal repo so that we do not have to reproduce the internal specific account derivation.

  9. Dropping override

  10. I think this is valid. I'm considering just dropping proxy verification altogether. They are normally automatically verified, and if not, I think the exceptional manual verification is fine.

@Seth-Schmidt
Seth-Schmidt force-pushed the feature/pro-541-improve-wrapper-deployment-scripts-to-only-take-an branch from 738bac5 to 975375a Compare August 25, 2026 20:30
@Seth-Schmidt

Seth-Schmidt commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Regarding the Claude review:

HIGH

  1. contracts/confidential-wrapper/tasks/verify.ts:34isAlreadyVerified substring match swallows real failures

The summary message is split into its individual Error N: / Warning N: entries and only swallowed when every entry is an already-verified message. 47ed7b7

MEDIUM

  1. contracts/confidential-wrapper/tasks/deploy.ts:186 — removed guard on CONFIDENTIAL_WRAPPER_INITIAL_OBSERVERS_{i}

Required env vars restored for the batch path, and the CLI task keeps an explicit --initial-observers '[]'. README now states every batch variable is required and you opt out with [] / the zero address. 796e129 + 975375a

  1. contracts/confidential-wrapper/tasks/deploy.ts:283task:verifyConfidentialWrapperImpl still on verify:verify

The task moved into tasks/verify.ts and goes through verifyOnEnabledExplorers. 47ed7b7

  1. contracts/confidential-wrapper/hardhat.config.ts:73 — project-wide bump to solc 0.8.29

Dropped the 0.8.29 compiler entirely back to a single 0.8.27. 975375a

  1. contracts/confidential-wrapper/package.json:71test:sepolia pointed at the deleted testnet network

Now hardhat test --network sepolia. 975375a

  1. contracts/confidential-wrapper/.gitignore:38 — un-ignore claims committed deployment state

No change, this branch is what starts committing those artifacts.

LOW

  1. contracts/confidential-wrapper/hardhat.config.ts:111DEPLOYMENT_RPC_URL overrides both networks and is undocumented

The override is gone, each network reads only its own *_RPC_URL. 975375a

  1. contracts/confidential-wrapper/tasks/deploy.ts:26resolveDeployerAddress is dead code

No change. It is intentionally exported as a hook so the internal repo can plug in its own account derivation instead of us reproducing it here.

  1. contracts/confidential-wrapper/hardhat.config.ts:95overrides block duplicates the 0.8.27 settings

The whole overrides block is gone. 975375a

  1. contracts/confidential-wrapper/README.md:20 — README documents a non-existent amoy network

README now says polygon-amoy. 975375a

  1. contracts/confidential-wrapper/tasks/verify.ts:41 — Blockscout/Sourcify can never verify the proxy

verifyOnEnabledExplorers takes bestEffort: false. Implementation verification is unchanged. 47ed7b7

@Seth-Schmidt
Seth-Schmidt force-pushed the feature/pro-541-improve-wrapper-deployment-scripts-to-only-take-an branch from 975375a to e05b4b1 Compare August 28, 2026 15:04
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