Skip to content

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Nov 28, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ERC165 interface support for ERC7984 token standard
    • Introduced new encrypted amount disclosure mechanism with request-finalize workflow
  • Dependencies

    • Updated @fhevm/solidity dependency to v0.9.1
    • Updated @fhevm/hardhat-plugin to v0.3.0-1
  • Chores

    • Bumped package version to 0.3.0
    • Updated network configuration defaults to Ethereum mainnet compatibility

✏️ Tip: You can customize this high-level summary in your review settings.

github-actions bot and others added 20 commits October 9, 2025 12:49
* Release v0.3.0 (rc)

* Update changelog

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: James Toussaint <33313130+james-toussaint@users.noreply.github.com>
* generate versioned docs

* publish docs even on pre-release
* N-05: Named mapping var in `ERC7984ObserverAccess`

* Update contracts/token/ERC7984/extensions/ERC7984ObserverAccess.sol

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

---------

Co-authored-by: James Toussaint <33313130+james-toussaint@users.noreply.github.com>
* N-08: constant names are screaming camel case

* fix lint
* Support ERC-165 interface detection on ERC-7984

* update link format

* Add ERC7984 impl changeset

* Update changeset

---------

Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
* M-03: grant allowances to agent in `ERC7984Rwa`

* up
* chore: fhevm-v9

* chore: port all tests for fhevm v9

* Merge pull request #1 from OpenZeppelin/chore/update-disclose-flow

update disclose flow

* Update wrapper contract (#2)

* Update wrapper contract

* fix tests

* fix mock

* update docs

* add changeset

* request id unnecessary

* Update contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol

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

* remove unused params

* Update test/token/ERC7984/ERC7984.test.ts

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

* `cts` -> `handles`

* `cleartext` -> `cleartextAmount`

* Update test/token/ERC7984/extensions/ERC7984Wrapper.test.ts

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

* nit

---------

Co-authored-by: 0xalexbel <alexandre.belhoste@zama.ai>
Co-authored-by: James Toussaint <33313130+james-toussaint@users.noreply.github.com>
* M-11: fix `ERC7984Rwa` docs

* add docs

* Update contracts/token/ERC7984/extensions/ERC7984Rwa.sol
* L-05: Grant allowances in `confidentialAvailable`

* fix doc
…241)

* L-01: `tryDecrease` return initialized value if delta is initialized

* add comment

* Add changeset
* Upgrade to use fhevm contracts v0.9.1

* bump sub package as well
* Release v0.3.0

* Update changelog (#259)

* Update changelog

* Update CHANGELOG.md

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

---------

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

* remove duplicate entry

---------

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>
@github-actions github-actions bot requested a review from a team as a code owner November 28, 2025 13:41
@netlify
Copy link

netlify bot commented Nov 28, 2025

Deploy Preview for confidential-tokens ready!

Name Link
🔨 Latest commit f0914b6
🔍 Latest deploy log https://app.netlify.com/projects/confidential-tokens/deploys/6929a6a224b6860008fbdd5a
😎 Deploy Preview https://deploy-preview-260--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
Contributor

coderabbitai bot commented Nov 28, 2025

Walkthrough

This pull request upgrades OpenZeppelin Confidential Contracts from v0.2.0 to v0.3.0, consolidating multiple feature changes: refactoring ERC7984 disclosure flows to use a request-based pattern, adding ERC165 interface support, introducing role-based access controls in ERC7984Rwa, updating FHESafeMath's uninitialized value handling, migrating from SepoliaConfig to ZamaEthereumConfig across mocks, and upgrading @fhevm/solidity dependency to 0.9.1.

Changes

Cohort / File(s) Summary
Changesets Removal
.changeset/beige-fans-call.md, fast-ravens-lose.md, floppy-otters-dance.md, floppy-wasps-clap.md, fruity-bananas-smile.md, new-crews-boil.md, public-dolls-lose.md, seven-books-dig.md, six-maps-follow.md, sour-ties-warn.md, stupid-fans-bow.md
Removed changeset entries documenting prior release notes for v0.3.0 features; consolidating into unified CHANGELOG.md entry.
Version & Dependency Updates
package.json, contracts/package.json, docs/antora.yml
Bumped package versions from 0.2.0 to 0.3.0; updated @fhevm/solidity from 0.8.0 to 0.9.1 and hardhat-plugin to 0.3.0-1; updated relayer-sdk to 0.3.0-5.
Workflow & Scripts
.github/workflows/docs.yml, scripts/update-docs-branch.js
Updated docs workflow to trigger on release-v* branches instead of master; modified release script to use dynamic docs branch naming (docs-v0.{minor}.x for major version 0).
ERC7984 Core Enhancements
contracts/token/ERC7984/ERC7984.sol, contracts/interfaces/IERC7984.sol
Added ERC165 interface support via inheritance; introduced supportsInterface implementation; refactored disclosure flow to request-based pattern (requestDiscloseEncryptedAmount + discloseEncryptedAmount with cleartext validation).
ERC7984Rwa Advanced Features
contracts/token/ERC7984/extensions/ERC7984Rwa.sol, contracts/interfaces/IERC7984Rwa.sol
Removed ERC165 from inheritance (now only extends ERC7984); added agent/admin role management (isAdmin, isAgent, addAgent, removeAgent); introduced pause/unpause and blockUser/unblockUser controls; enhanced mint/burn/forceConfidentialTransferFrom with post-operation FHE.allow calls and return value semantics.
ERC7984ERC20Wrapper Refactoring
contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol
Refactored unwrap request tracking from requestId-based to burntAmount-based mapping; updated finalizeUnwrap signature to accept (euint64, uint64, bytes) with explicit cleartext validation; added UnwrapRequested/UnwrapFinalized events.
ERC7984 Extension Utilities
contracts/token/ERC7984/extensions/ERC7984Freezable.sol, ERC7984ObserverAccess.sol, ERC7984Omnibus.sol, ERC7984Restricted.sol, ERC7984Votes.sol
Freezable: introduced _confidentialAvailable internal helper and updated confidentialAvailable to grant ACLs via FHE.allow; ObserverAccess: renamed mapping key to named parameter; Omnibus: reordered transferred assignment after ACL checks; Restricted: minor documentation updates; Votes: version header update.
Swap Mock Contract
contracts/mocks/docs/SwapERC7984ToERC20.sol
Renamed from SwapConfidentialToERC20 to SwapERC7984ToERC20; replaced decryption oracle flow with direct finalization via finalizeSwap(euint64, uint64, bytes); refactored error and mapping structure from requestId to amount-based tracking.
Storage Location Constants
contracts/finance/ERC7821WithExecutor.sol, VestingWalletCliffConfidential.sol, VestingWalletConfidential.sol
Renamed private storage location constants from camelCase (e.g., VestingWalletStorageLocation) to UPPER_SNAKE_CASE (e.g., VESTING_WALLET_STORAGE_LOCATION) with assembly slot assignments updated accordingly.
Vesting & Finance Headers
contracts/finance/VestingWalletCliffConfidentialFactory.sol
Updated version header from v0.2.0 to v0.3.0; no functional changes.
Interface Renames & Updates
contracts/interfaces/IERC7984Receiver.sol
Updated header comment to reflect rename from IConfidentialFungibleTokenReceiver to IERC7984Receiver; no signature changes.
Config Migration in Mocks
contracts/mocks/finance/VestingWalletCliffConfidentialMock.sol, VestingWalletConfidentialFactoryMock.sol, VestingWalletConfidentialMock.sol, contracts/mocks/token/ERC7984*.sol, contracts/mocks/utils/FHESafeMath*.sol, HandleAccessManager*.sol
Replaced SepoliaConfig with ZamaEthereumConfig across all mock contracts; updated coprocesor configuration call from getSepoliaConfig() to getEthereumCoprocessorConfig(); removed obsolete SepoliaConfig import from ERC7984ObserverAccessMock.
FHESafeMath Core Updates
contracts/utils/FHESafeMath.sol
Updated documentation to clarify uninitialized euint64 handling; modified tryDecrease logic when oldValue is uninitialized and delta is initialized to return (delta == 0, 0) instead of comparing oldValue to delta.
Utility Headers & Helpers
contracts/utils/structs/temporary-Checkpoints.sol, contracts/token/ERC7984/utils/ERC7984Utils.sol
Updated version headers from v0.2.0 to v0.3.0; updated documentation note in ERC7984Utils on transfer failure behavior (refund vs. reverse).
Changelog & Documentation
CHANGELOG.md, test/helpers/accounts.ts
Added comprehensive 0.3.0 section with migration notes and feature breakdown (Token, Utils, Governance, Finance, Misc); updated ACL artifact path from core-contracts to host-contracts.
Test Suite Updates
test/token/ERC7984/ERC7984.test.ts, extensions/ERC7984Rwa.test.ts, extensions/ERC7984Wrapper.test.ts, utils/FHESafeMath.test.ts
Updated ERC7984 tests to verify ERC165 support and request-based disclosure flow (requestDiscloseEncryptedAmount + discloseEncryptedAmount); refactored ERC7984Rwa interface tests to reorganize function collection lists; replaced oracle-await pattern with publicDecryptAndFinalizeUnwrap helper in wrapper tests; aligned FHESafeMath test expectations for uninitialized value cases.

Sequence Diagram

sequenceDiagram
    actor User
    participant Contract as ERC7984
    participant FHE as FHE Lib
    participant Caller

    Note over User,Caller: New ERC7984 Disclosure Request-Finalize Pattern

    User->>Contract: requestDiscloseEncryptedAmount(euint64)
    activate Contract
    Contract->>FHE: makePubliclyDecryptable(amount)
    FHE-->>Contract: handle (publicly decryptable)
    Contract->>Contract: emit AmountDiscloseRequested(amount, requester)
    deactivate Contract
    Note over User,Contract: Encrypted amount is now decryptable<br/>Requester is recorded in event

    Note over User,Caller: External Decryption Phase
    Caller->>Caller: Decrypt amount off-chain<br/>Generate proof

    Caller->>Contract: discloseEncryptedAmount(amount, cleartext, proof)
    activate Contract
    Contract->>FHE: checkSignatures(handles, proof, cleartext)
    FHE-->>Contract: validation result
    Contract->>Contract: emit AmountDisclosed(amount, cleartext)
    deactivate Contract
    Note over Caller,Contract: Disclosure finalized with<br/>validated cleartext amount
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • ERC7984 disclosure flow refactoring (contracts/token/ERC7984/ERC7984.sol): New request-based pattern with FHE.makePubliclyDecryptable and signature validation; ensures correct event emission and state tracking.
  • ERC7984Rwa role management and ACL grants (contracts/token/ERC7984/extensions/ERC7984Rwa.sol): Extensive new public API for agent/admin roles, post-operation FHE.allow calls on mint/burn/transfer, and interaction with Pausable/AccessControl; verify proper authorization checks and ACL semantics.
  • ERC7984ERC20Wrapper unwrap finalization (contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol): State mapping change from requestId to burntAmount; validate proper cleanup and signature verification in finalizeUnwrap.
  • FHESafeMath uninitialized value handling (contracts/utils/FHESafeMath.sol): Logic change in tryDecrease when both operands uninitialized vs. delta initialized; verify correctness and test coverage alignment.
  • Config migration from SepoliaConfig to ZamaEthereumConfig (multiple mock files): Ensure all mock contracts properly inherit new config and call updated coprocesor method; validate FHEVM compatibility.

Possibly related PRs

Suggested reviewers

  • james-toussaint
  • arr00

Poem

🐰 Hoppity-hop, the contracts now grow,
ERC7984 with disclosure flow,
Role-based guards and freezable states,
FHE allowances at every gate!
From v0.2 to 0.3 we leap,
Secrets safe in cryptographic keep. 🔐

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary action: merging the release-v0.3 branch into the master branch. It is concise, clear, and specific enough for teammates to understand the main change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch merge/release-v0.3

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

Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
contracts/token/ERC7984/extensions/ERC7984Rwa.sol (1)

242-247: Hardcoded function selectors are incorrect and will cause runtime failures.

The hardcoded selectors 0x6c9c3c85 and 0x44fd6e40 do not match either the signatures in the comments OR the actual function signatures in the code:

  • Claimed (in comments): forceConfidentialTransferFrom(address,address,bytes32)0x95efd2e2 (not 0x6c9c3c85)
  • Claimed (in comments): forceConfidentialTransferFrom(address,address,bytes32,bytes)0xdee2729b (not 0x44fd6e40)
  • Actual (in code): forceConfidentialTransferFrom(address,address,externalEuint64,bytes)0xad357416
  • Actual (in code): forceConfidentialTransferFrom(address,address,euint64)0xac49b3e4

The actual function parameters are euint64 and externalEuint64, not bytes32. The selector matching will fail at runtime, breaking the force transfer compliance bypass mechanism.

Fix: Use computed selectors for both overloads:

     function _isForceTransfer() private pure returns (bool) {
         return
-            msg.sig == 0x6c9c3c85 || // bytes4(keccak256("forceConfidentialTransferFrom(address,address,bytes32)"))
-            msg.sig == 0x44fd6e40; // bytes4(keccak256("forceConfidentialTransferFrom(address,address,bytes32,bytes)"))
+            msg.sig == bytes4(keccak256("forceConfidentialTransferFrom(address,address,euint64)")) ||
+            msg.sig == bytes4(keccak256("forceConfidentialTransferFrom(address,address,externalEuint64,bytes)"));
     }
🧹 Nitpick comments (7)
contracts/token/ERC7984/extensions/ERC7984Freezable.sol (1)

42-49: Internal _confidentialAvailable implementation is correct; consider marking as view if compatible

Using FHESafeMath.tryDecrease(confidentialBalanceOf(account), confidentialFrozen(account)) and falling back to encrypted zero via FHE.select(success, unfrozen, FHE.asEuint64(0)) correctly models “available = max(balance − frozen, 0)” and aligns with the transfer-gating docstring.

If the underlying FHE/FHESafeMath operations used here are view/pure, consider declaring this helper as view for clarity and better mutability signaling:

-function _confidentialAvailable(address account) internal virtual returns (euint64) {
+function _confidentialAvailable(address account) internal view virtual returns (euint64) {

Otherwise, current mutability is fine.

contracts/token/ERC7984/extensions/ERC7984Votes.sol (1)

2-2: ERC7984Votes voting integration looks correct; minor doc typo

  • The header/path update to v0.3.0 is consistent with the rest of the release.
  • confidentialTotalSupply, _update, and _getVotingUnits follow the expected Votes‑style pattern: supply and balances are updated in super._update, and _transferVotingUnits is driven by the transferred amount, with voting units sourced from confidentialBalanceOf(account).

Minor nit: in the docstring, "Voing power" should be "Voting power".

Also applies to: 9-15, 16-30

CHANGELOG.md (1)

4-25: 0.3.0 changelog entries align with code changes

The new 0.3.0 section correctly documents the ERC7984 renames/extensions and the @fhevm/solidity 0.9.1 migration, and the formatting tweaks in the 0.2.0 block are harmless. As an optional copyedit, you could change “Real World Assets (RWAs)” to “real‑world assets (RWAs)” for standard hyphenation, but this is non‑blocking.

Also applies to: 31-31, 39-39, 45-51

scripts/update-docs-branch.js (1)

14-22: Release‑branch detection and docs‑branch naming look correct

The new release-v<major>.<minor>[.<patch>] branch regex, the “not on a release branch” guard, and the docs-v0.<minor>.x / docs-v<major>.x selection all match the intended release/docs branching scheme.

You may want to parse minor and publishedMinor as numbers before if (minor < publishedMinor) to avoid future string‑comparison surprises once minor versions reach double digits (e.g., "10" < "9" lexicographically). This is pre‑existing behavior but easy to harden:

-const minor = current?.minor ?? pkgVersion.split('.')[1];
+const minor = Number(current?.minor ?? pkgVersion.split('.')[1]);
 ...
-const publishedMinor = publishedVersion.match(/\d+\.(?<minor>\d+)\.\d+/).groups.minor;
-if (minor < publishedMinor) {
+const publishedMinor = Number(publishedVersion.match(/\d+\.(?<minor>\d+)\.\d+/).groups.minor);
+if (minor < publishedMinor) {

Also applies to: 26-35, 55-58

test/token/ERC7984/extensions/ERC7984Wrapper.test.ts (1)

273-277: Verify that passing 0 for the cleartext amount works correctly with ethers.

The test passes 0 as the second argument to finalizeUnwrap. While this should work (ethers handles number-to-uint64 conversion), using 0n would be more explicit for BigInt consistency with other tests in this file.

       await expect(
-        this.wrapper.connect(this.holder).finalizeUnwrap(ethers.ZeroHash, 0, '0x'),
+        this.wrapper.connect(this.holder).finalizeUnwrap(ethers.ZeroHash, 0n, '0x'),
       ).to.be.revertedWithCustomError(this.wrapper, 'InvalidUnwrapRequest');
contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol (1)

160-161: Consider using require instead of assert for the handle collision check.

The assert statement will consume all remaining gas if it fails. While euint64 handles from FHE operations are expected to be unique, using require would provide a cleaner failure mode with a descriptive error:

-       assert(_unwrapRequests[burntAmount] == address(0));
+       require(_unwrapRequests[burntAmount] == address(0), "Duplicate unwrap request");
        _unwrapRequests[burntAmount] = to;

That said, if handle uniqueness is a fundamental FHE invariant that should never be violated, assert is semantically correct for indicating a broken invariant.

contracts/token/ERC7984/ERC7984.sol (1)

60-61: Remove unused error ERC7984InvalidGatewayRequest to reduce bytecode size.

Verification confirms this error is defined but completely unused throughout the codebase. With the disclosure flow refactored from request-based to amount-based, this error is dead code and can be safely removed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29bd17c and f0914b6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (54)
  • .changeset/beige-fans-call.md (0 hunks)
  • .changeset/fast-ravens-lose.md (0 hunks)
  • .changeset/floppy-otters-dance.md (0 hunks)
  • .changeset/floppy-wasps-clap.md (0 hunks)
  • .changeset/fruity-bananas-smile.md (0 hunks)
  • .changeset/new-crews-boil.md (0 hunks)
  • .changeset/public-dolls-lose.md (0 hunks)
  • .changeset/seven-books-dig.md (0 hunks)
  • .changeset/six-maps-follow.md (0 hunks)
  • .changeset/sour-ties-warn.md (0 hunks)
  • .changeset/stupid-fans-bow.md (0 hunks)
  • .github/workflows/docs.yml (1 hunks)
  • CHANGELOG.md (1 hunks)
  • contracts/finance/ERC7821WithExecutor.sol (3 hunks)
  • contracts/finance/VestingWalletCliffConfidential.sol (3 hunks)
  • contracts/finance/VestingWalletConfidential.sol (3 hunks)
  • contracts/finance/VestingWalletConfidentialFactory.sol (1 hunks)
  • contracts/interfaces/IERC7984.sol (2 hunks)
  • contracts/interfaces/IERC7984Receiver.sol (1 hunks)
  • contracts/interfaces/IERC7984Rwa.sol (1 hunks)
  • contracts/mocks/docs/SwapERC7984ToERC20.sol (2 hunks)
  • contracts/mocks/finance/VestingWalletCliffConfidentialMock.sol (1 hunks)
  • contracts/mocks/finance/VestingWalletConfidentialFactoryMock.sol (3 hunks)
  • contracts/mocks/finance/VestingWalletConfidentialMock.sol (1 hunks)
  • contracts/mocks/token/ERC7984ERC20WrapperMock.sol (1 hunks)
  • contracts/mocks/token/ERC7984FreezableMock.sol (2 hunks)
  • contracts/mocks/token/ERC7984Mock.sol (1 hunks)
  • contracts/mocks/token/ERC7984ObserverAccessMock.sol (0 hunks)
  • contracts/mocks/token/ERC7984ReceiverMock.sol (1 hunks)
  • contracts/mocks/token/ERC7984RestrictedMock.sol (1 hunks)
  • contracts/mocks/token/ERC7984RwaMock.sol (2 hunks)
  • contracts/mocks/utils/FHESafeMathMock.sol (1 hunks)
  • contracts/mocks/utils/HandleAccessManagerMock.sol (1 hunks)
  • contracts/mocks/utils/HandleAccessManagerUserMock.sol (1 hunks)
  • contracts/package.json (2 hunks)
  • contracts/token/ERC7984/ERC7984.sol (4 hunks)
  • contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol (5 hunks)
  • contracts/token/ERC7984/extensions/ERC7984Freezable.sol (3 hunks)
  • contracts/token/ERC7984/extensions/ERC7984ObserverAccess.sol (2 hunks)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (2 hunks)
  • contracts/token/ERC7984/extensions/ERC7984Restricted.sol (3 hunks)
  • contracts/token/ERC7984/extensions/ERC7984Rwa.sol (11 hunks)
  • contracts/token/ERC7984/extensions/ERC7984Votes.sol (1 hunks)
  • contracts/token/ERC7984/utils/ERC7984Utils.sol (2 hunks)
  • contracts/utils/FHESafeMath.sol (2 hunks)
  • contracts/utils/structs/temporary-Checkpoints.sol (1 hunks)
  • docs/antora.yml (1 hunks)
  • package.json (3 hunks)
  • scripts/update-docs-branch.js (1 hunks)
  • test/helpers/accounts.ts (1 hunks)
  • test/token/ERC7984/ERC7984.test.ts (4 hunks)
  • test/token/ERC7984/extensions/ERC7984Rwa.test.ts (1 hunks)
  • test/token/ERC7984/extensions/ERC7984Wrapper.test.ts (7 hunks)
  • test/utils/FHESafeMath.test.ts (1 hunks)
💤 Files with no reviewable changes (12)
  • .changeset/fruity-bananas-smile.md
  • .changeset/new-crews-boil.md
  • .changeset/sour-ties-warn.md
  • .changeset/six-maps-follow.md
  • .changeset/beige-fans-call.md
  • .changeset/fast-ravens-lose.md
  • .changeset/seven-books-dig.md
  • .changeset/stupid-fans-bow.md
  • .changeset/public-dolls-lose.md
  • contracts/mocks/token/ERC7984ObserverAccessMock.sol
  • .changeset/floppy-wasps-clap.md
  • .changeset/floppy-otters-dance.md
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-15T14:43:25.644Z
Learnt from: arr00
Repo: OpenZeppelin/openzeppelin-confidential-contracts PR: 186
File: contracts/token/ERC7984/extensions/ERC7984Omnibus.sol:140-167
Timestamp: 2025-09-15T14:43:25.644Z
Learning: In ERC7984Omnibus callback functions like confidentialTransferFromAndCallOmnibus, the encrypted sender and recipient addresses are not passed to the callback recipient - only the standard transfer parameters (omnibusFrom, omnibusTo, amount, data) are passed. The ACL grants for the encrypted addresses are for omnibus event emission and future access, not for callback usage.

Applied to files:

  • contracts/mocks/token/ERC7984ReceiverMock.sol
  • contracts/token/ERC7984/utils/ERC7984Utils.sol
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol
  • contracts/token/ERC7984/extensions/ERC7984Freezable.sol
  • contracts/token/ERC7984/ERC7984.sol
  • contracts/token/ERC7984/extensions/ERC7984Rwa.sol
📚 Learning: 2025-09-22T09:21:34.470Z
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).

Applied to files:

  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol
  • contracts/mocks/token/ERC7984FreezableMock.sol
  • contracts/mocks/docs/SwapERC7984ToERC20.sol
  • test/token/ERC7984/ERC7984.test.ts
  • contracts/token/ERC7984/extensions/ERC7984Freezable.sol
  • contracts/token/ERC7984/ERC7984.sol
  • contracts/token/ERC7984/extensions/ERC7984Rwa.sol
📚 Learning: 2025-09-22T09:21:34.470Z
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: For force transfers in ERC7984Freezable, the frozen balance should be reset to the new balance if the transfer amount exceeded the available balance. If the transfer amount was within the available balance, the frozen amount behavior needs clarification from the user.

Applied to files:

  • contracts/token/ERC7984/extensions/ERC7984Freezable.sol
  • contracts/token/ERC7984/ERC7984.sol
  • contracts/token/ERC7984/extensions/ERC7984Rwa.sol
🧬 Code graph analysis (2)
test/token/ERC7984/extensions/ERC7984Rwa.test.ts (1)
test/helpers/interface.ts (1)
  • getFunctions (4-8)
test/token/ERC7984/ERC7984.test.ts (1)
test/helpers/interface.ts (2)
  • getFunctions (4-8)
  • getInterfaceId (10-15)
🪛 LanguageTool
CHANGELOG.md

[style] ~16-~16: Try using a synonym here to strengthen your writing.
Context: ... each account to add an observer who is given access to their transfer and balance am...

(GIVE_PROVIDE)


[uncategorized] ~19-~19: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...f ERC7984, that supports confidential Real World Assets (RWAs). ([#160](https://github.c...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: Publish to npm
🔇 Additional comments (57)
test/helpers/accounts.ts (1)

21-21: Clean migration from @fhevm/core-contracts to @fhevm/host-contracts verified; no stale references found.

The artifact path migration at line 21 is complete with no remaining references to the old @fhevm/core-contracts package. The new @fhevm/host-contracts path is the only reference to this artifact in the codebase, confirming a successful and consistent upgrade transition.

contracts/utils/FHESafeMath.sol (1)

34-43: LGTM!

The updated logic correctly handles the case where oldValue is uninitialized and delta is initialized. Returning (FHE.eq(delta, 0), FHE.asEuint64(0)) is semantically correct since:

  • 0 - delta only succeeds when delta == 0
  • The result is always 0 (either 0-0=0 on success, or the original value 0 on failure)

This change ensures an explicitly initialized zero is returned rather than an uninitialized value, which aligns with the updated documentation.

test/utils/FHESafeMath.test.ts (1)

61-69: LGTM!

The test data updates correctly reflect the behavior change in tryDecrease:

  • [undefined, 0, 0, true]: When decreasing uninitialized value by 0, result is now explicitly 0
  • [undefined, 1, 0, false]: When decreasing uninitialized value by 1, result is 0 with failure

These align with the contract change that returns FHE.asEuint64(0) instead of an uninitialized value when delta is initialized.

contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (2)

2-2: Header version bump is consistent with v0.3.0 upgrade

The updated “last updated v0.3.0” header matches the PR’s version upgrade and pragma; nothing else to adjust here.


192-208: Call reordering in _confidentialTransferFromAndCallOmnibus preserves ACL and callback behavior

Placing confidentialTransferFromAndCall(omnibusFrom, omnibusTo, amount, data) between the ACL grants for sender/recipient and the ACL grants for transferred keeps the effects–interaction–post‑effects structure clear and aligned with _confidentialTransferFromOmnibus. The callback still only receives omnibusFrom, omnibusTo, amount, and data, and the ACL grants on encrypted sender/recipient remain for omnibus event emission and future access, not for direct callback parameters, so this reordering does not introduce new exposure or reentrancy surface beyond the underlying ERC7984 call. Based on learnings, this maintains the intended omnibus callback privacy model.

Also applies to: 200-201

contracts/token/ERC7984/extensions/ERC7984Freezable.sol (4)

2-2: Header version annotation looks correct

The header now reflects last updated v0.3.0, which matches the release being merged; no issues here.


34-40: confidentialAvailable delegation and ACL behavior look sound

Delegating to _confidentialAvailable for the computation and then granting ACLs via FHE.allowThis(amount) and FHE.allow(amount, account) cleanly separates calculation from permissioning. The docstring correctly reflects that the allowance is given to account, and using the internal helper avoids leaking extra ACLs when the value is used for internal gating.


65-66: Doc update matches the new gating pattern

The comment now explicitly calling out _confidentialAvailable as the hook for customizing freezing behavior, and noting that it is used for actual gating to avoid extra ACL grants, is consistent with the implementation and helps future overriders.


70-71: Using _confidentialAvailable in _update achieves gating without extra ACLs

Switching _update to use _confidentialAvailable(from) ensures the transfer is still limited to the unfrozen portion (encryptedAmount <= unfrozen ? encryptedAmount : 0) while avoiding the ACL side effects of the public confidentialAvailable. This matches the documented “sufficient unfrozen balance or 0 transfer” behavior.

docs/antora.yml (1)

3-3: Docs component version aligned with release

Bumping the Antora component version to '0.3' is consistent with the 0.3.0 release and keeps docs versioning in sync.

.github/workflows/docs.yml (1)

5-5: Docs workflow now only runs on release-v* branches

Changing on.push.branches to release-v* means docs will no longer build on pushes to master, only on branches like release-v0.3. Please confirm this matches the intended release/docs flow and actual branch naming (e.g., release-v0.3 vs release/v0.3).

contracts/package.json (1)

4-4: Contracts package version and FHEVM peer dependency look consistent

The contracts package version bump to 0.3.0 and the @fhevm/solidity peer dependency pin to 0.9.1 match the root package updates and the 0.3.0 release scope.

Please double‑check that all tooling and contracts using euint/FHE types are validated against @fhevm/solidity@0.9.1 (compile, test, and gas workflows), since the peer dependency is pinned to this exact version.

Also applies to: 35-35

package.json (1)

4-4: Release and FHE/Zama tooling versions are aligned

The root package version bump to 0.3.0 and the devDependency updates for @fhevm/hardhat-plugin, @fhevm/solidity, and @zama-fhe/relayer-sdk are consistent with the contracts package and the 0.3.0 release.

Please verify that:

  • The updated Hardhat plugin and relayer SDK versions are compatible with each other and with Node >=20.19.0.
  • CI (compile, tests, coverage, docs) passes under this new toolchain matrix.

Also applies to: 48-49, 66-66

contracts/finance/VestingWalletConfidentialFactory.sol (1)

2-2: VestingWalletConfidentialFactory header updated for v0.3.0

Header metadata now correctly reflects v0.3.0 and the file path; no behavioral changes in the factory logic.

contracts/utils/structs/temporary-Checkpoints.sol (1)

2-2: Temporary Checkpoints header now matches v0.3.0

The version header for this procedurally generated temporary file is updated to v0.3.0; implementation remains unchanged.

contracts/finance/ERC7821WithExecutor.sol (1)

2-2: ERC7821WithExecutor storage slot rename is consistent

  • Header updated to v0.3.0 matches the release.
  • Renaming the storage location constant to ERC7821_WITH_EXECUTOR_STORAGE_LOCATION and wiring it into _getERC7821WithExecutorStorage via $.slot := ERC7821_WITH_EXECUTOR_STORAGE_LOCATION keeps the erc7201 storage layout pattern intact, assuming the bytes32 literal was not altered.

Given this contract is upgradeable, it’s worth double‑checking that the slot value exactly matches the one used in previous releases to avoid storage layout breaks.

Also applies to: 18-19, 40-43

contracts/interfaces/IERC7984Receiver.sol (1)

2-2: Header version bump only; interface remains stable

The v0.3.0 header update here is purely informational; the IERC7984Receiver interface and callback signature are unchanged and consistent with existing usage.

contracts/finance/VestingWalletConfidential.sol (1)

2-2: Storage-slot constant rename is layout‑preserving

Renaming the storage slot constant and wiring it through _getVestingWalletStorage keeps the keccak‑derived slot and storage layout intact while aligning with the new naming convention. No behavioral impact beyond the v0.3.0 header update.

Also applies to: 37-38, 136-139

contracts/token/ERC7984/extensions/ERC7984Restricted.sol (1)

2-2: Restriction docs now match the custom error semantics

The v0.3.0 header plus the _update / _checkRestriction comments now correctly reference UserRestricted and clarify the default behavior; implementation remains unchanged and consistent.

Also applies to: 54-55, 86-89

contracts/finance/VestingWalletCliffConfidential.sol (1)

2-2: Cliff wallet storage-slot rename preserves layout

The uppercase storage location constant and matching use in _getVestingWalletCliffStorage keep the storage slot and vesting behavior intact while standardizing naming for v0.3.0.

Also applies to: 19-20, 67-70

contracts/mocks/token/ERC7984FreezableMock.sol (1)

5-6: Config migration and internal helper usage are consistent

Switching to ZamaEthereumConfig matches the broader config migration, and routing confidentialAvailableAccess through _confidentialAvailable keeps the “available = balance − frozen” semantics while cleanly exposing the handle for tests via getHandleAllowance.

Also applies to: 34-38

contracts/token/ERC7984/extensions/ERC7984ObserverAccess.sol (1)

2-2: Observer mapping key naming is clearer with no behavior change

The v0.3.0 header plus the mapping(address account => address) declaration improve clarity around the key without changing how observers are stored or read.

Also applies to: 14-14

contracts/mocks/finance/VestingWalletConfidentialMock.sol (1)

4-7: Configuration migration looks correct.

The change from SepoliaConfig to ZamaEthereumConfig aligns with the broader migration across mock contracts in this release. The inheritance chain remains properly structured.

contracts/interfaces/IERC7984.sol (2)

36-37: Documentation formatting improvement.

The ERC-7572 reference link format is now consistent with standard EIP documentation style.


6-9: ERC165 interface support is properly implemented and correctly integrated.

The verification confirms that ERC7984 correctly extends both IERC7984 and ERC165, and the supportsInterface method (lines 70-72 in contracts/token/ERC7984/ERC7984.sol) properly reports support for the IERC7984 interface ID:

function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
    return interfaceId == type(IERC7984).interfaceId || super.supportsInterface(interfaceId);
}

The implementation correctly handles the inheritance chain and delegates to parent implementations for other interface checks. This is a well-structured addition that follows ERC165 best practices.

contracts/token/ERC7984/utils/ERC7984Utils.sol (1)

19-20: Documentation clarification accurately reflects FHE transfer semantics.

The change from "reversing the transfer" to "refund the from address" is semantically correct for FHE contexts where encrypted operations cannot be truly reversed—the caller must handle rejection by transferring tokens back.

contracts/mocks/utils/HandleAccessManagerUserMock.sol (1)

4-8: Configuration migration consistent with other mocks.

The switch to ZamaEthereumConfig aligns with the broader mock migration in this release. No functional changes to the test utility.

contracts/mocks/token/ERC7984RestrictedMock.sol (1)

5-9: Configuration migration applied correctly.

The inheritance update to ZamaEthereumConfig is consistent with the broader mock migration pattern across the codebase. The mock functionality remains unchanged.

contracts/mocks/token/ERC7984Mock.sol (1)

5-10: ZamaEthereumConfig migration in ERC7984Mock looks consistent

Updating the import and base class to ZamaEthereumConfig cleanly aligns this mock with the new FHE config without touching the token/FHE logic. No issues from the inheritance ordering (ERC7984, ZamaEthereumConfig) as the config contract is stateless.

contracts/mocks/utils/FHESafeMathMock.sol (1)

4-8: FHESafeMathMock config base swap is safe

Switching FHESafeMathMock to inherit ZamaEthereumConfig (and updating the import) is a mechanical change; the FHESafeMath wrapper logic and emitted events are untouched. This should preserve behavior while aligning with the new config.

contracts/mocks/utils/HandleAccessManagerMock.sol (1)

4-8: HandleAccessManagerMock now using ZamaEthereumConfig

The change to import and inherit ZamaEthereumConfig is consistent with the broader migration and does not affect the mock’s logic (_validateHandleAllowance remains a no‑op as intended).

contracts/interfaces/IERC7984Rwa.sol (1)

2-9: IERC7984Rwa inheritance simplification keeps ERC165 via IERC7984

Updating IERC7984Rwa to extend only IERC7984 (with IERC165 now provided by IERC7984) simplifies the hierarchy without changing the effective interface surface or ERC165 support. The added header comment correctly documents the v0.3.0 update.

contracts/mocks/token/ERC7984RwaMock.sol (1)

6-22: supportsInterface override correctly resolves the multiple‑inheritance diamond

Importing ERC7984 and overriding supportsInterface as

function supportsInterface(bytes4 interfaceId)
    public
    view
    virtual
    override(ERC7984Rwa, ERC7984)
    returns (bool)
{
    return super.supportsInterface(interfaceId);
}

is the right pattern here: it explicitly resolves the two inheritance branches that define supportsInterface and preserves the base contracts’ interface reporting via super. This keeps ERC165/IERC7984/IERC7984Rwa support coherent on the mock.

contracts/mocks/token/ERC7984ReceiverMock.sol (1)

4-8: ERC7984ReceiverMock now aligned with ZamaEthereumConfig

The switch to importing and inheriting ZamaEthereumConfig is consistent with the rest of the mocks and does not alter the callback logic of onConfidentialTransferReceived.

contracts/mocks/finance/VestingWalletCliffConfidentialMock.sol (1)

4-7: VestingWalletCliffConfidentialMock config update is purely structural

Hooking ZamaEthereumConfig into this abstract mock (via import and inheritance) is a straightforward config migration; no vesting or FHE logic is modified.

contracts/mocks/token/ERC7984ERC20WrapperMock.sol (1)

4-8: ERC7984ERC20WrapperMock config migration preserves wrapper behavior

The import and inheritance change to ZamaEthereumConfig is consistent with the rest of the suite; the ERC20 wrapper and underlying ERC7984 constructor wiring remain unchanged, so runtime behavior should be identical aside from using the new config.

test/token/ERC7984/extensions/ERC7984Rwa.test.ts (1)

25-34: LGTM! Interface support validation correctly restructured.

The separation of erc7984RwaFunctions and erc7984Functions properly reflects the updated inheritance hierarchy where IERC7984Rwa extends IERC7984 directly. The test now correctly validates all three interface IDs: ERC7984Rwa (combined with ERC7984), ERC7984 (combined with ERC165), and ERC165 standalone.

contracts/mocks/finance/VestingWalletConfidentialFactoryMock.sol (1)

4-4: LGTM! Config migration from SepoliaConfig to ZamaEthereumConfig is consistent.

The mock contract correctly migrates to the new ZamaEthereumConfig and uses ZamaConfig.getEthereumCoprocessorConfig() for FHE coprocessor initialization. This aligns with the broader configuration changes across the codebase.

Also applies to: 10-10, 49-53, 68-68

test/token/ERC7984/extensions/ERC7984Wrapper.test.ts (2)

323-329: LGTM! Helper function cleanly encapsulates the unwrap finalization flow.

The publicDecryptAndFinalizeUnwrap helper correctly:

  1. Queries the UnwrapRequested event to get the recipient and amount handle
  2. Performs public decryption via fhevm.publicDecrypt
  3. Calls finalizeUnwrap with the decryption results
  4. Asserts the UnwrapFinalized event emission with expected arguments

This reduces test duplication and improves maintainability.


242-271: Good addition of edge case tests for unwrap finalization.

The "finalized with invalid signature" test properly validates that a truncated decryption proof is rejected. The truncation approach (slice(0, length - 2)) is a reasonable way to corrupt the signature.

contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol (1)

130-150: LGTM! The new finalization flow correctly validates decryption proofs.

The finalizeUnwrap function properly:

  1. Retrieves and validates the recipient from _unwrapRequests
  2. Deletes the request before external calls (CEI pattern)
  3. Constructs the handles array and encodes cleartexts for signature verification
  4. Calls FHE.checkSignatures to validate the decryption proof
  5. Transfers the underlying tokens using SafeERC20.safeTransfer

This is a clean implementation of the request-based unwrap pattern.

test/token/ERC7984/ERC7984.test.ts (2)

54-67: LGTM! ERC165 interface support tests properly validate interface IDs.

The tests correctly:

  1. Compute interface IDs by combining function selectors from IERC7984__factory and IERC165__factory
  2. Verify supportsInterface returns true for valid interfaces
  3. Verify supportsInterface returns false for an invalid selector (0xbadbadba)

This follows the same pattern established in the ERC7984Rwa tests.


596-619: LGTM! The disclosure flow test correctly validates the request-then-finalize pattern.

The afterEach hook properly:

  1. Queries the AmountDiscloseRequested event
  2. Validates the expected handle and requester against the event arguments
  3. Performs public decryption via fhevm.publicDecrypt
  4. Calls discloseEncryptedAmount with the cleartext values and proof
  5. Asserts the AmountDisclosed event emission

This aligns with the new disclosure API changes in the contract.

contracts/token/ERC7984/ERC7984.sol (4)

2-2: LGTM! ERC165 integration properly implemented.

The version bump to v0.3.0, ERC165 import, and updated inheritance are correctly structured. The contract now properly extends both IERC7984 and ERC165 for interface introspection support.

Also applies to: 6-7, 27-27


35-36: LGTM! Event declaration is appropriate.

The AmountDiscloseRequested event with indexed parameters allows efficient filtering by encrypted amount and requester address.


69-72: LGTM! Standard ERC165 implementation.

The supportsInterface function correctly advertises IERC7984 support and delegates to the parent for other interface checks.


218-235: LGTM! Disclosure finalization is correctly implemented.

The function properly validates the decryption proof using FHE.checkSignatures before emitting the disclosure event. The permissionless design is appropriate since the cryptographic proof validates the claim.

contracts/mocks/docs/SwapERC7984ToERC20.sol (3)

30-42: LGTM! Finalization logic is correctly implemented.

The function follows checks-effects-interactions pattern: validates the proof, checks receiver existence, deletes the mapping entry, then performs the external transfer. Using SafeERC20.safeTransfer is appropriate for safe ERC20 interactions.


9-19: LGTM! Contract structure and constructor are appropriate.

The mock contract correctly initializes both token references and uses clear naming conventions aligned with the ERC7984 standard.


12-12: I'll help you verify the review comment about the potential key collision risk with euint64 as a mapping key. Let me start by examining the file and understanding the context.
<function_calls>

First, let's read the file to understand the context

cat -n contracts/mocks/docs/SwapERC7984ToERC20.sol


</function_calls>

Now let me research euint64 and fhEVM handle uniqueness to verify the core claim:
<function_calls>

fhEVM euint64 handle uniqueness ciphertext collision

</function_calls>

contracts/token/ERC7984/extensions/ERC7984Rwa.sol (7)

2-2: LGTM! Version and inheritance structure updated correctly.

The contract properly inherits from the required base contracts and the version is updated to v0.3.0.

Also applies to: 22-22


51-56: LGTM! Interface support correctly extends parent chain.

The supportsInterface properly advertises IERC7984Rwa and delegates to the parent for inherited interface detection including IERC7984 and IERC165.


117-136: LGTM! Mint functions correctly return amount with ACL grant.

The confidentialMint functions now return the minted amount and grant the calling agent access via FHE.allow. This enables agents to track and use the minted amounts for subsequent operations.


138-158: LGTM! Burn functions correctly return amount with ACL grant.

Consistent with the mint pattern, confidentialBurn functions return the burnt amount and grant the calling agent access.


160-185: LGTM! Force transfer functions properly documented and implemented.

The forceConfidentialTransferFrom variants correctly bypass sender restrictions while maintaining receiver restrictions, and the agent access pattern is consistent with mint/burn.


93-96: Semantic change confirmed: _resetUser sets restriction to DEFAULT, not ALLOWED.

The change from _allowUser(account) to _resetUser(account) is a material behavioral difference:

  • _allowUser sets the user's restriction state to Restriction.ALLOWED (value 2)
  • _resetUser sets the user's restriction state to Restriction.DEFAULT (value 0)

While tests confirm both states allow transfers, they are distinct restriction levels. This asymmetry means blockUser → BLOCKED, but unblockUser → DEFAULT (not back to ALLOWED), which could break integrations expecting an ALLOWED state after unblocking.


223-230: The review comment is incorrect regarding the pause check bypass.

The comment states that _forceUpdate bypasses the paused state by directly calling super._update. However, this is factually incorrect.

When _forceUpdate (defined in ERC7984Rwa) calls super._update at line 227, the method resolution order resolves to ERC7984Rwa._update at line 214, which includes the whenNotPaused modifier. Therefore, the pause check is not bypassed—the function will still revert if the contract is paused.

The comment at line 226 is misleading and should be removed or corrected to accurately reflect that the pause check is still enforced through the inherited _update method.

Likely an incorrect or invalid review comment.

Comment on lines 201 to 216
/**
* @dev Discloses an encrypted amount `encryptedAmount` publicly via an {IERC7984-AmountDisclosed}
* event. The caller and this contract must be authorized to use the encrypted amount on the ACL.
* @dev Starts the process to disclose an encrypted amount `encryptedAmount` publicly by making it
* publicly decryptable. Emits the {AmountDiscloseRequested} event.
*
* NOTE: This is an asynchronous operation where the actual decryption happens off-chain and
* {finalizeDiscloseEncryptedAmount} is called with the result.
* NOTE: Both `msg.sender` and `address(this)` must have permission to access the encrypted amount
* `encryptedAmount` to request disclosure of the encrypted amount `encryptedAmount`.
*/
function discloseEncryptedAmount(euint64 encryptedAmount) public virtual {
function requestDiscloseEncryptedAmount(euint64 encryptedAmount) public virtual {
require(
FHE.isAllowed(encryptedAmount, msg.sender),
ERC7984UnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
);

bytes32[] memory cts = new bytes32[](1);
cts[0] = euint64.unwrap(encryptedAmount);
FHE.requestDecryption(cts, this.finalizeDiscloseEncryptedAmount.selector);
FHE.makePubliclyDecryptable(encryptedAmount);
emit AmountDiscloseRequested(encryptedAmount, msg.sender);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find FHE-related files in the repository
find . -type f -name "*.sol" | head -20

Repository: OpenZeppelin/openzeppelin-confidential-contracts

Length of output: 1066


🏁 Script executed:

#!/bin/bash
# Search for FHE imports and the makePubliclyDecryptable function
rg -n "makePubliclyDecryptable" --type solidity

Repository: OpenZeppelin/openzeppelin-confidential-contracts

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Search for FHE interface or library definitions
rg -n "interface FHE\|library FHE" --type solidity -A 5

Repository: OpenZeppelin/openzeppelin-confidential-contracts

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Look for FHE-related documentation or README files
find . -type f \( -name "*.md" -o -name "*.txt" \) | xargs grep -l "FHE\|fhEVM" 2>/dev/null | head -5

Repository: OpenZeppelin/openzeppelin-confidential-contracts

Length of output: 118


🌐 Web query:

fhEVM FHE.makePubliclyDecryptable ACL permission requirement

💡 Result:

Short answer: the caller must already have ACL permission on the ciphertext (i.e., be an allowed address/owner) to call FHE.makePubliclyDecryptable (which flips the ACL to allow anyone to decrypt). See the fhEVM/FHE docs: only an address with isAllowed on that ciphertext may grant allowances such as makePubliclyDecryptable / allowGlobal. [1][2]

Sources

  • FHE/ACL examples (shows FHE.makePubliclyDecryptable / make public decryption and describes allow/isAllowed behavior). [1]
  • ACL overview / implementation notes (newly created ciphertexts are only accessible to creator for the transaction; additional allowances require calling allow/allowTransient/allowGlobal and checks of isAllowed). [2]

🏁 Script executed:

#!/bin/bash
# Search for other usages of FHE.makePubliclyDecryptable
rg -n "makePubliclyDecryptable" -A 3 -B 3

Repository: OpenZeppelin/openzeppelin-confidential-contracts

Length of output: 1908


🏁 Script executed:

#!/bin/bash
# Search for FHE.isAllowed to see how it's typically used
rg -n "FHE\.isAllowed" -A 2 -B 2

Repository: OpenZeppelin/openzeppelin-confidential-contracts

Length of output: 6869


Missing ACL permission check for the contract itself before calling FHE.makePubliclyDecryptable.

The NatSpec comment correctly states that both msg.sender and address(this) must have permission to access the encrypted amount. However, the implementation only verifies FHE.isAllowed(encryptedAmount, msg.sender). According to fhEVM documentation, only an address with ACL permission on the ciphertext may call FHE.makePubliclyDecryptable. Since this function is invoked from within the contract, address(this) is the actual caller and must have permission—a requirement not enforced by the current code.

To match the docstring, add a check for the contract's ACL permission:

require(
    FHE.isAllowed(encryptedAmount, msg.sender) && FHE.isAllowed(encryptedAmount, address(this)),
    ERC7984UnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
);
🤖 Prompt for AI Agents
In contracts/token/ERC7984/ERC7984.sol around lines 201 to 216, the function
requestDiscloseEncryptedAmount only checks ACL for msg.sender but not for the
contract itself (address(this) is the effective caller of
FHE.makePubliclyDecryptable); update the require to also verify
FHE.isAllowed(encryptedAmount, address(this)) before calling
FHE.makePubliclyDecryptable so both msg.sender and the contract have permission,
keeping the same revert type/signature.

* to handle potential overflows in FHE operations.
*
* NOTE: An uninitialized `euint64` value (equivalent to euint64.wrap(bytes32(0))) is evaluated as 0.
* This library will may return an uninitialized value if all inputs are uninitialized.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo: "will may" should be "may".

- * This library will may return an uninitialized value if all inputs are uninitialized.
+ * This library may return an uninitialized value if all inputs are uninitialized.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* This library will may return an uninitialized value if all inputs are uninitialized.
* This library may return an uninitialized value if all inputs are uninitialized.
🤖 Prompt for AI Agents
In contracts/utils/FHESafeMath.sol around line 12, the comment contains a typo
"will may" — update the sentence to read "may return an uninitialized value if
all inputs are uninitialized." to remove the extra word and keep grammar
correct.

Comment on lines +540 to +545
let requester: HardhatEthersSigner | undefined;

beforeEach(async function () {
expectedAmount = undefined;
expectedHandle = undefined;
requester = undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Type mismatch: requester declared as HardhatEthersSigner but assigned string addresses.

The variable is declared as HardhatEthersSigner | undefined but is assigned this.holder.address and this.recipient.address which are strings. This works at runtime because the comparison on line 604 only needs the string value, but the type annotation is incorrect:

-   let requester: HardhatEthersSigner | undefined;
+   let requester: string | undefined;

Also applies to: 553-554, 575-576

🤖 Prompt for AI Agents
In test/token/ERC7984/ERC7984.test.ts around lines 540-545 (and also at the
related assignments around 553-554 and 575-576), the variable requester is typed
as HardhatEthersSigner | undefined but you assign string addresses
(this.holder.address / this.recipient.address); update the type to string |
undefined if you intend to store addresses, or instead assign actual signer
objects and keep HardhatEthersSigner | undefined — make the declaration and
assignments consistent (prefer changing the type to string | undefined if the
tests only compare addresses).

@arr00 arr00 merged commit c718cc3 into master Dec 1, 2025
15 checks passed
@arr00 arr00 deleted the merge/release-v0.3 branch December 1, 2025 10:24
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.

3 participants