Skip to content

Merge release-v0.4 branch#342

Merged
arr00 merged 13 commits intomasterfrom
merge/release-v0.4
Apr 1, 2026
Merged

Merge release-v0.4 branch#342
arr00 merged 13 commits intomasterfrom
merge/release-v0.4

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot commented Mar 30, 2026

Summary by CodeRabbit

Version 0.4.0 Release

  • New Features

    • ERC165 interface detection support added to ERC7984ERC20Wrapper
    • BatcherConfidential includes wrapper compatibility validation checks
  • Updates

    • Claim functionality now fully permissionless for third-party execution
    • Improved unwrap request tracking with enhanced identifier system
    • Dependency upgrades: @fhevm/solidity to 0.11.1, openzeppelin/contracts to v5.6.1
    • Node.js runtime updated to 24.x

github-actions bot and others added 13 commits March 5, 2026 22:09
* remove properties symlink

* add properties.js

* lint fix
* Remove backwards compatibility from Batcher

* Apply suggestion from @arr00
* Validate `fromToken` on construction

* use `ERC165Checker`

* check toToken as well

* directly validate to and from token
* remove incorrect docs on `unwrap` function

* add finalize unwrap and rate to wrapper interface

* update batcher to use unwrap request ids instead of unwrap amounts

* add `unwrapAmount` to wrapper interface for unwrap request introspection

* additional docs

* call getter for `unwrapAmount` in `finalizeUnwrap`

* update events and errors appropriately

* remove `ERC7984ERC20Wrapper` import

* fix import order

* Move events to interface from `ERC7984ERC20Wrapper`

* add note on request id for `unwrap`

* change mapping to be of `bytes32` identifiers

* add changeset
* Allow claiming on behalf of another account in batcher

* revert on claim if users not part of the batch

* add error on quit too
…fidential` (#326)

* Return a bytes32 unwrap id from the `_unwrap` function in `BatcherConfidential`

* modify warning
* Document possible rounding down of user deposits

* add contract level comment as well

* up
* Release v0.4.0 (rc)

* update changelog

* update changelog

* Apply suggestion from @james-toussaint

Co-authored-by: James Toussaint <33313130+james-toussaint@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
Co-authored-by: James Toussaint <33313130+james-toussaint@users.noreply.github.com>
* Release v0.4.0

* update changelog

* remove changelog duplicate

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
@github-actions github-actions bot requested a review from a team as a code owner March 30, 2026 16:48
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 30, 2026

Deploy Preview for confidential-tokens ready!

Name Link
🔨 Latest commit 7ac7cee
🔍 Latest deploy log https://app.netlify.com/projects/confidential-tokens/deploys/69caa95ef76c970008c28452
😎 Deploy Preview https://deploy-preview-342--confidential-tokens.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

Walkthrough

This PR releases version 0.4.0, removing published changeset entries and consolidating release notes in CHANGELOG.md. It introduces significant API changes: the unwrap mechanism transitions from euint64 to bytes32 request IDs, ERC7984ERC20Wrapper gains new public methods, and BatcherConfidential refactors to use wrapper interfaces with updated claim semantics. Multiple contract headers are bumped to v0.4.0, and a new documentation template helper module is added. Node.js CI runtime updated from 20.x to 24.x.

Changes

Cohort / File(s) Summary
Changeset Cleanup
.changeset/fast-humans-run.md, .changeset/full-worlds-rescue.md, .changeset/good-flies-win.md, .changeset/happy-hands-find.md, .changeset/long-items-appear.md, .changeset/modern-snakes-return.md, .changeset/nasty-walls-taste.md, .changeset/pink-areas-double.md, .changeset/shaky-pugs-return.md
Removed 9 published changeset entries documenting dependency upgrades and contract-level API changes for the 0.4.0 release.
Build & Versioning
.github/actions/setup/action.yml, package.json, contracts/package.json, docs/antora.yml
Updated Node.js runtime from 20.x to 24.x in CI setup; bumped npm package versions from 0.3.1 to 0.4.0 across root and contracts manifests; updated docs site version to 0.4.
Release Changelog
CHANGELOG.md
Added v0.4.0 entry documenting dependency upgrades (@fhevm/solidity to 0.11.1, openzeppelin/contracts* to v5.6.1) and summarizing all contract-level changes including unwrap-request-id tracking, new BatcherConfidential primitive, and API renames.
Unwrap Mechanism Refactor
contracts/interfaces/IERC7984ERC20Wrapper.sol, contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol
Changed unwrap return type from euint64 (amount) to bytes32 (request ID); added finalizeUnwrap(bytes32, ...), rate(), and unwrapAmount(bytes32) methods; introduced UnwrapRequested and UnwrapFinalized events for lifecycle tracking.
BatcherConfidential Refactor
contracts/finance/BatcherConfidential.sol
Converted to use wrapper-agnostic IERC7984ERC20Wrapper interface with ERC-165 validation in constructor; replaced Batch.unwrapAmount (euint64) with unwrapRequestId (bytes32); updated claim signature from claim(batchId) to claim(batchId, account) with new ZeroDeposits error; refactored dispatch/finalization logic to use unwrap request IDs.
Contract Metadata Updates
contracts/finance/VestingWalletConfidential.sol, contracts/token/ERC7984/extensions/ERC7984*.sol, contracts/governance/.../VotesConfidential.sol, contracts/utils/*.sol
Updated OpenZeppelin Confidential Contracts version headers from v0.3.x/v0.2.x to v0.4.0 across 10+ files; no functional code changes.
Interface Version Updates
contracts/interfaces/IERC7984Receiver.sol, contracts/interfaces/IERC7984Rwa.sol
Bumped version header metadata to v0.4.0; no interface/function signature changes.
Documentation Template Module
docs/templates/properties.js
New 88-line helper module exporting 11 functions for deriving Solidity AST properties in doc templates: anchors, inheritance chains, function/variable filtering, return-type normalization, and inheritance-level function collection.
Test Updates
test/finance/BatcherConfidential.test.ts, test/token/ERC7984/extensions/ERC7984ERC20Wrapper.test.ts, test/helpers/interface.ts
Updated BatcherConfidential tests for new claim(batchId, account) signature, ZeroDeposits/InvalidWrapperToken error assertions, and unwrapRequestId getter; added relayer/on-behalf-of claim tests; updated ERC7984ERC20Wrapper event assertions; expanded ERC7984ERC20Wrapper interface signatures.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Batcher as BatcherConfidential
    participant Wrapper as ERC7984ERC20Wrapper
    participant Relay as Relayer/Decryptor

    User->>Batcher: claim(batchId, account)
    Batcher->>Batcher: Validate batch dispatched & finalized
    Batcher->>Wrapper: Transfer toToken to account
    Wrapper-->>Batcher: Success
    Batcher->>Batcher: Clear account deposit
    Batcher-->>User: Claimed event

    Relay->>Wrapper: unwrap(from, to, euint64)
    Wrapper->>Wrapper: Store encrypted amount under requestId
    Wrapper-->>Relay: Return bytes32 unwrapRequestId
    Relay->>Wrapper: finalizeUnwrap(unwrapRequestId, cleartext, proof)
    Wrapper->>Wrapper: Verify signature & emit UnwrapFinalized
    Wrapper-->>Relay: Success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

release

Suggested reviewers

  • arr00
  • james-toussaint

Poem

🐰 A rabbit hops through v0.4.0,
With unwrap IDs in bytes we go,
Request-to-finalize, a cleaner way,
BatcherConfidential claims the day,
Tests all pass, the docs shine bright,
Our confidential contracts take flight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Merge release-v0.4 branch' accurately describes the primary action of this pull request: merging a release branch (release-v0.4) into the master branch. The changeset clearly shows a comprehensive v0.4.0 release with version bumps, API changes, and dependency upgrades across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch merge/release-v0.4

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/finance/BatcherConfidential.test.ts (1)

652-654: Resolve the encrypted amount through unwrapAmount(requestId) in these assertions.

These checks still decrypt unwrapRequestId directly. BatcherConfidential itself goes through fromToken().unwrapAmount(requestId), so the test is still coupled to the current wrapper detail that requestId == encryptedAmount. Mirroring the contract path here keeps the test valid for other compliant IERC7984ERC20Wrapper implementations.

♻️ Suggested change
-    const { abiEncodedClearValues, decryptionProof } = await batcher
-      .unwrapRequestId(batchId1)
-      .then(amount => fhevm.publicDecrypt([amount]));
+    const { abiEncodedClearValues, decryptionProof } = await batcher
+      .unwrapRequestId(batchId1)
+      .then(requestId => this.fromToken.unwrapAmount(requestId))
+      .then(amount => fhevm.publicDecrypt([amount]));
...
       batcher
         .unwrapRequestId(batchId2)
-        .then(amount => fhevm.publicDecrypt([amount]))
+        .then(requestId => this.fromToken.unwrapAmount(requestId))
+        .then(amount => fhevm.publicDecrypt([amount]))
         .then(({ abiEncodedClearValues }) => abiEncodedClearValues),

Also applies to: 709-713

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/finance/BatcherConfidential.test.ts` around lines 652 - 654, The test is
decrypting the raw value from batcher.unwrapRequestId(batchId1) instead of
resolving it via the wrapper's unwrapAmount path; change the assertions to call
the wrapper path (use batcher.fromToken().unwrapAmount(requestId) or
batcher.unwrapAmount(requestId) as appropriate) and then pass that result into
fhevm.publicDecrypt to produce decryptionProof/abiEncodedClearValues; update
both occurrences (the block using batchId1 and the similar block at 709-713) to
use unwrapAmount(requestId) rather than directly decrypting unwrapRequestId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/finance/BatcherConfidential.sol`:
- Around line 208-216: The catch block after calling
IERC7984ERC20Wrapper(fromToken()).finalizeUnwrap(unwrapRequestId_,
unwrapAmountCleartext, decryptionProof) is too broad; only treat reverts that
indicate the unwrap was already finalized as the fallback path that validates
using FHE.checkSignatures and continues (handles,
euint64.unwrap(fromToken().unwrapAmount(unwrapRequestId_)),
unwrapAmountCleartext, decryptionProof). Modify the try/catch to inspect the
revert (use catch Error(string memory reason) or catch (bytes memory lowLevel)
to decode) and if the reason matches the wrapper’s “already finalized” sentinel
only then run the existing fallback signature checks and proceed, otherwise
re-throw / revert so real unwrap failures bubble up instead of falling through
to _executeRoute. Ensure you keep the same symbols: unwrapRequestId_,
finalizeUnwrap, fromToken(), unwrapAmountCleartext, decryptionProof, handles,
euint64.unwrap, fromToken().unwrapAmount, and FHE.checkSignatures.
- Around line 355-362: The code in _claim zeroes
_batches[batchId].deposits[account] whenever amountTransferred != 0, but
amountTransferred is in toToken() units while deposit is stored in fromToken()
units, so partial transfers will permanently drop the untransferred remainder;
change the logic to only zero the deposit when the full requested amount was
transferred (compare amountTransferred to amountToSend in toToken units) and
otherwise subtract the actual transferred amount converted back to fromToken
units from deposit (i.e., compute remaining = deposit -
convertToFromUnits(amountTransferred) or use an existing conversion routine),
then FHE.allow/FHE.allowThis on the computed remaining and store that back into
_batches[batchId].deposits[account]; keep references to confidentialTransfer,
amountToSend, amountTransferred, deposit, toToken(),
_batches[batchId].deposits[account], and quit/_claim semantics when
implementing.

In `@contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol`:
- Around line 183-187: The NatSpec comment for function unwrapRequester
incorrectly references `unwrapAmount`; update the doc block to refer to the
current lookup key `unwrapRequestId` and clarify that it returns the address
with a pending unwrap request for that `unwrapRequestId` (or address(0) if
none). Edit the comment above the `unwrapRequester(bytes32 unwrapRequestId)`
function and mention `unwrapRequestId` and `_unwrapRequests[unwrapRequestId]` so
generated docs reflect the current API.

---

Nitpick comments:
In `@test/finance/BatcherConfidential.test.ts`:
- Around line 652-654: The test is decrypting the raw value from
batcher.unwrapRequestId(batchId1) instead of resolving it via the wrapper's
unwrapAmount path; change the assertions to call the wrapper path (use
batcher.fromToken().unwrapAmount(requestId) or batcher.unwrapAmount(requestId)
as appropriate) and then pass that result into fhevm.publicDecrypt to produce
decryptionProof/abiEncodedClearValues; update both occurrences (the block using
batchId1 and the similar block at 709-713) to use unwrapAmount(requestId) rather
than directly decrypting unwrapRequestId.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 448d2ba7-cb48-4aca-8341-5d79e548e40d

📥 Commits

Reviewing files that changed from the base of the PR and between 09ba229 and 7ac7cee.

📒 Files selected for processing (35)
  • .changeset/fast-humans-run.md
  • .changeset/full-worlds-rescue.md
  • .changeset/good-flies-win.md
  • .changeset/happy-hands-find.md
  • .changeset/long-items-appear.md
  • .changeset/modern-snakes-return.md
  • .changeset/nasty-walls-taste.md
  • .changeset/pink-areas-double.md
  • .changeset/shaky-pugs-return.md
  • .changeset/sour-dodos-sniff.md
  • .github/actions/setup/action.yml
  • CHANGELOG.md
  • contracts/finance/BatcherConfidential.sol
  • contracts/finance/VestingWalletConfidential.sol
  • contracts/governance/utils/VotesConfidential.sol
  • contracts/interfaces/IERC7984ERC20Wrapper.sol
  • contracts/interfaces/IERC7984Receiver.sol
  • contracts/interfaces/IERC7984Rwa.sol
  • contracts/package.json
  • contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol
  • contracts/token/ERC7984/extensions/ERC7984Freezable.sol
  • contracts/token/ERC7984/extensions/ERC7984ObserverAccess.sol
  • contracts/token/ERC7984/extensions/ERC7984Restricted.sol
  • contracts/token/ERC7984/extensions/ERC7984Rwa.sol
  • contracts/token/ERC7984/extensions/ERC7984Votes.sol
  • contracts/utils/FHESafeMath.sol
  • contracts/utils/HandleAccessManager.sol
  • contracts/utils/structs/CheckpointsConfidential.sol
  • docs/antora.yml
  • docs/templates/properties.js
  • docs/templates/properties.js
  • package.json
  • test/finance/BatcherConfidential.test.ts
  • test/helpers/interface.ts
  • test/token/ERC7984/extensions/ERC7984ERC20Wrapper.test.ts
💤 Files with no reviewable changes (10)
  • .changeset/nasty-walls-taste.md
  • .changeset/happy-hands-find.md
  • .changeset/sour-dodos-sniff.md
  • .changeset/long-items-appear.md
  • .changeset/fast-humans-run.md
  • .changeset/full-worlds-rescue.md
  • .changeset/shaky-pugs-return.md
  • .changeset/modern-snakes-return.md
  • .changeset/pink-areas-double.md
  • .changeset/good-flies-win.md

Comment on lines +208 to 216
bytes32 unwrapRequestId_ = unwrapRequestId(batchId);
// finalize unwrap call will fail if already called by this contract or by anyone else
try ERC7984ERC20Wrapper(fromToken()).finalizeUnwrap(unwrapAmount_, unwrapAmountCleartext, decryptionProof) {
try IERC7984ERC20Wrapper(fromToken()).finalizeUnwrap(unwrapRequestId_, unwrapAmountCleartext, decryptionProof) {
// No need to validate input since `finalizeUnwrap` request succeeded
} catch {
// Must validate input since `finalizeUnwrap` request failed
bytes32[] memory handles = new bytes32[](1);
handles[0] = euint64.unwrap(unwrapAmount_);
handles[0] = euint64.unwrap(fromToken().unwrapAmount(unwrapRequestId_));
FHE.checkSignatures(handles, abi.encode(unwrapAmountCleartext), decryptionProof);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 30, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't treat every finalizeUnwrap revert as “already finalized”.

Now that wrappers are only validated at the interface boundary, this generic catch is too broad. A real unwrap failure can still fall through to _executeRoute after only checking the proof, even though this batch may never have received its underlying. The fallback path should only be used once unwrap finalization is known to have happened for this batch; otherwise the revert should bubble.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/finance/BatcherConfidential.sol` around lines 208 - 216, The catch
block after calling
IERC7984ERC20Wrapper(fromToken()).finalizeUnwrap(unwrapRequestId_,
unwrapAmountCleartext, decryptionProof) is too broad; only treat reverts that
indicate the unwrap was already finalized as the fallback path that validates
using FHE.checkSignatures and continues (handles,
euint64.unwrap(fromToken().unwrapAmount(unwrapRequestId_)),
unwrapAmountCleartext, decryptionProof). Modify the try/catch to inspect the
revert (use catch Error(string memory reason) or catch (bytes memory lowLevel)
to decode) and if the reason matches the wrapper’s “already finalized” sentinel
only then run the existing fallback signature checks and proceed, otherwise
re-throw / revert so real unwrap failures bubble up instead of falling through
to _executeRoute. Ensure you keep the same symbols: unwrapRequestId_,
finalizeUnwrap, fromToken(), unwrapAmountCleartext, decryptionProof, handles,
euint64.unwrap, fromToken().unwrapAmount, and FHE.checkSignatures.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Made an issue #343 to track this. We may not be able to do further introspection.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@arr00 Understood — thanks for opening #343 to track this. You're right that Solidity's try/catch has limited granularity; decoding custom errors from a catch (bytes memory) block is possible but brittle, and there may not be a reliable sentinel to distinguish "already finalized" from other failure modes depending on how the wrapper implements finalizeUnwrap. Happy to revisit the options when that issue is worked on.


🧠 Learnings used
Learnt from: james-toussaint
Repo: OpenZeppelin/openzeppelin-confidential-contracts PR: 160
File: test/token/ERC7984/extensions/ERC7984Rwa.test.ts:474-479
Timestamp: 2025-09-22T09:21:34.470Z
Learning: In ERC7984Freezable force transfers, the frozen balance is reset to the new balance only when the transferred amount exceeds the available balance (balance - frozen). If the transferred amount is within the available balance, the frozen amount remains unchanged. This is implemented via FHE.select(FHE.gt(encryptedAmount, confidentialAvailable(account)), confidentialBalanceOf(account), frozen).

Learnt from: arr00
Repo: OpenZeppelin/openzeppelin-confidential-contracts PR: 302
File: contracts/finance/compliance/ComplianceModuleConfidential.sol:41-43
Timestamp: 2026-02-04T21:38:19.848Z
Learning: In modules that implement a singleton OpenZeppelin confidential contracts pattern using per-caller accounting (msg.sender-based) for isolation, lifecycle hooks like onInstall/onUninstall intentionally have no access control and each caller only accesses its own accounting on the shared module instance. Apply this guideline to any similar Solidity modules in the repository, ensuring no cross-user access to another's state and that access is scoped to the caller's own accounting within the shared instance.

@arr00 arr00 merged commit bcea1fd into master Apr 1, 2026
16 checks passed
@arr00 arr00 deleted the merge/release-v0.4 branch April 1, 2026 04:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant