Skip to content

Conversation

@arr00
Copy link
Contributor

@arr00 arr00 commented Aug 26, 2025

Summary by Sourcery

Introduce an omnibus confidential transfer extension for ERC7984 and add corresponding mock, tests, and helpers.

New Features:

  • Add ERC7984Omnibus extension with confidentialTransferOmnibus and confidentialTransferFromOmnibus functions and OmnibusTransfer event
  • Expose omnibus extension in a mock contract ERC7984OmnibusMock for testing
  • Add public $_mint helper in ERC7984Mock to simplify minting with uint64 inputs

Tests:

  • Add comprehensive tests for omnibus transfers covering normal and over-balance scenarios for both transfer and transferFrom

Chores:

  • Add changeset for the omnibus extension release

Summary by CodeRabbit

  • New Features

    • Added ERC7984Omnibus extension to enable confidential subaccount transfers via omnibus wallets, emitting OmnibusConfidentialTransfer events and enforcing encrypted-address access controls.
  • Tests

    • Added comprehensive omnibus test suite covering transfer/from, callback, proof/no-proof permutations, balance edge cases, and permission checks; adjusted one test input value.
  • Documentation

    • Updated token docs to include ERC7984Omnibus, its event, and usage guidance.
  • Chores

    • Added package changeset and mock helpers/events for creating encrypted addresses and amounts; introduced omnibus-aware mock for testing.

@netlify
Copy link

netlify bot commented Aug 26, 2025

Deploy Preview for confidential-tokens ready!

Name Link
🔨 Latest commit c3fb2f4
🔍 Latest deploy log https://app.netlify.com/projects/confidential-tokens/deploys/68c93f115f64ee00083eeb6b
😎 Deploy Preview https://deploy-preview-186--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.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Aug 26, 2025

Reviewer's Guide

Implement ERC7984Omnibus extension by adding two new confidential transfer methods that decrypt external inputs, configure FHE ACL for sender and recipient, delegate to base transfer logic, and emit OmnibusTransfer events. Support is added in mocks with helper methods, complemented by end-to-end tests and a version changeset.

Sequence diagram for confidentialTransferFromOmnibus method

sequenceDiagram
    participant Caller
    participant ERC7984Omnibus
    participant FHE
    participant ERC7984
    Caller->>ERC7984Omnibus: confidentialTransferFromOmnibus(omnibusFrom, omnibusTo, sender, recipient, externalAmount, inputProof)
    ERC7984Omnibus->>FHE: fromExternal(recipient, inputProof)
    ERC7984Omnibus->>FHE: fromExternal(sender, inputProof)
    ERC7984Omnibus->>FHE: allowThis(recipient_)
    ERC7984Omnibus->>FHE: allow(recipient_, omnibusTo)
    ERC7984Omnibus->>FHE: allow(recipient_, omnibusFrom)
    ERC7984Omnibus->>FHE: allowThis(sender_)
    ERC7984Omnibus->>FHE: allow(sender_, omnibusTo)
    ERC7984Omnibus->>FHE: allow(sender_, omnibusFrom)
    ERC7984Omnibus->>ERC7984: confidentialTransferFrom(omnibusFrom, omnibusTo, externalAmount, inputProof)
    ERC7984Omnibus-->>Caller: emit OmnibusTransfer(omnibusFrom, omnibusTo, sender_, recipient_, transferred)
    ERC7984Omnibus-->>Caller: return transferred
Loading

ER diagram for OmnibusTransfer event data structure

erDiagram
    OMNIBUSTRANSFER {
        address omnibusFrom
        address omnibusTo
        eaddress sender
        eaddress recipient
        euint64 amount
    }
Loading

Class diagram for ERC7984Omnibus extension and related contracts

classDiagram
    class ERC7984 {
        <<abstract>>
        +confidentialTransfer()
        +confidentialTransferFrom()
    }
    class ERC7984Omnibus {
        <<abstract>>
        +confidentialTransferOmnibus(omnibusTo, sender, recipient, externalAmount, inputProof)
        +confidentialTransferFromOmnibus(omnibusFrom, omnibusTo, sender, recipient, externalAmount, inputProof)
        +OmnibusTransfer event
    }
    class ERC7984Mock {
        +_mint(to, encryptedAmount, inputProof)
        +$_mint(to, amount)
        +$_transfer(from, to, amount)
    }
    class ERC7984OmnibusMock {
        <<abstract>>
        +_update(from, to, amount)
    }
    ERC7984Omnibus --|> ERC7984
    ERC7984OmnibusMock --|> ERC7984Omnibus
    ERC7984OmnibusMock --|> ERC7984Mock
Loading

File-Level Changes

Change Details Files
Add omnibus transfer extension with custom event
  • Declare OmnibusTransfer event
  • Decrypt sender and recipient from external inputs
  • Configure ACL via FHE.allow for both addresses
  • Call base confidentialTransfer and confidentialTransferFrom
  • Emit OmnibusTransfer and return transferred amount
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol
Extend mocks for omnibus testing
  • Introduce ERC7984OmnibusMock combining omnibus extension and base mock
  • Add $_mint helper in ERC7984Mock for uint64 minting
contracts/mocks/token/ERC7984Mock.sol
contracts/mocks/token/ERC7984OmnibusMock.sol
Implement comprehensive omnibus transfer tests
  • Test confidentialTransferOmnibus and confidentialTransferFromOmnibus flows
  • Encrypt inputs and invoke methods for normal and overbalance scenarios
  • Assert OmnibusTransfer event args and ACL permissions
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts
Add version changeset
  • Define minor bump for omnibus extension
.changeset/stupid-fans-bow.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@arr00 arr00 linked an issue Aug 26, 2025 that may be closed by this pull request
@arr00 arr00 closed this Aug 27, 2025
@arr00 arr00 reopened this Aug 27, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 4, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new abstract extension ERC7984Omnibus exposing omnibus confidential-transfer APIs, events, and error; introduces omnibus mocks and helpers for encrypted handles; adds end-to-end tests covering proof/no-proof, callbacks, and transferFrom paths; updates README and tweaks one test handle amount.

Changes

Cohort / File(s) Summary
Changeset metadata
.changeset/stupid-fans-bow.md
New minor changeset announcing addition of ERC7984Omnibus.
ERC7984 mock enhancements
contracts/mocks/token/ERC7984Mock.sol
Import eaddress; add events EncryptedAmountCreated and EncryptedAddressCreated; add helpers createEncryptedAmount(uint64) and createEncryptedAddress(address); add public $_mint(address,uint64) overload converting to euint64.
Omnibus extension (core)
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol
New abstract contract ERC7984Omnibus extending ERC7984; adds OmnibusConfidentialTransfer event and ERC7984UnauthorizedUseOfEncryptedAddress error; adds omnibus confidential-transfer APIs (external-proof and handle-based variants, with/without callbacks, and transferFrom variants); enforces cross-address FHE allow permissions and delegates to base confidential-transfer logic.
Omnibus mock
contracts/mocks/token/ERC7984OmnibusMock.sol
New abstract mock ERC7984OmnibusMock inheriting ERC7984Omnibus and ERC7984Mock; implements internal _update(...) override delegating to super._update for multi-inheritance compatibility in tests.
Tests for omnibus flows
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts
New comprehensive test suite exercising combinations of transfer vs transferFrom, callbacks, and proof/no-proof; constructs encrypted inputs or external proofs; invokes omnibus APIs; asserts OmnibusConfidentialTransfer event fields, decrypted values, ACL allowances, and behavior for normal vs over-balance transfers.
Docs update
contracts/token/README.adoc
Documents ERC7984Omnibus in Core and Extensions references and describes omnibus event and encrypted-address usage.
Handle test tweak
test/utils/HandleAccessManager.test.ts
Test-only change: create handle amount updated from 100 to 101.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant Omnibus as ERC7984Omnibus
  participant FHE as FHE Lib
  participant Core as ERC7984 (core)
  participant ACL as ACL

  rect rgb(250,250,255)
    note right of Caller: External-proof path (single proof contains sender, recipient, amount)
    Caller->>Omnibus: confidentialTransferOmnibus(..., externalAmount, inputProof)
    Omnibus->>FHE: fromExternal(inputProof)
    FHE-->>Omnibus: (sender, recipient, amount)
  end

  rect rgb(245,255,245)
    note right of Caller: Handle-based path (separate encrypted handles)
    Caller->>Omnibus: confidentialTransferOmnibus(..., senderHandle, recipientHandle, amountHandle)
    Omnibus->>FHE: resolve handles & set allow permissions
    FHE-->>Omnibus: (sender, recipient, amount)
  end

  Omnibus->>FHE: allowThis(sender, recipient, amount)
  Omnibus->>FHE: allow(omnibusAddr, amount)
  Omnibus->>ACL: (implicit ACL checks)
  Omnibus->>Core: confidentialTransfer(sender, recipient, amount)
  Core-->>Omnibus: euint64 transferred
  Omnibus-->>Caller: euint64 transferred
  Omnibus-->>Caller: emit OmnibusConfidentialTransfer(omnibusFrom?, omnibusTo, sender, recipient, transferred)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • james-toussaint

Poem

I nibble at bytes and hop through the logs,
Omnibus secrets tucked safe in snug clogs.
Handles and proofs twirl beneath moonbeams,
Events pop like carrots in bright little streams.
Tests pass, I twitch — another code dream. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Omnibus Extension" is concise and directly reflects the primary change—the addition of an omnibus-style extension (ERC7984Omnibus) along with mocks, tests, and docs—so it is related and not misleading. It is somewhat generic but acceptable for quick scanning by reviewers.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/omni-bus

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2267cb5 and c3fb2f4.

📒 Files selected for processing (1)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol
⏰ 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). (5)
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: Sourcery review
  • GitHub Check: slither
  • GitHub Check: coverage
  • GitHub Check: tests

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

@arr00 arr00 marked this pull request as ready for review September 5, 2025 14:23
@arr00 arr00 requested a review from a team as a code owner September 5, 2025 14:23
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `test/token/ERC7984/extensions/ERC7984Omnibus.test.ts:34` </location>
<code_context>
+      it('normal transfer', async function () {
</code_context>

<issue_to_address>
Missing test for invalid encrypted input or proof.

Add a test case with invalid or tampered encrypted input or inputProof to confirm the contract rejects these transfers and does not emit OmnibusTransfer.

Suggested implementation:

```typescript
      it('normal transfer', async function () {
        const caller = transferFrom ? this.operator : this.holder;

        const encryptedInput = await fhevm
          .createEncryptedInput(this.token.target, caller.address)
          .addAddress(this.holder.address)
          .addAddress(this.subaccount.address)
          .add64(100)
          .encrypt();
        const args = [
          this.recipient.address,
        ];

        // ... rest of normal transfer test
      });

      it('reverts on tampered encrypted input or proof', async function () {
        const caller = transferFrom ? this.operator : this.holder;

        // Create a valid encrypted input
        const encryptedInput = await fhevm
          .createEncryptedInput(this.token.target, caller.address)
          .addAddress(this.holder.address)
          .addAddress(this.subaccount.address)
          .add64(100)
          .encrypt();

        // Tamper with the encrypted input (e.g., flip a byte)
        let tamperedInput = encryptedInput;
        if (typeof tamperedInput === 'string') {
          tamperedInput = '0x' + tamperedInput.slice(2, -2) + (tamperedInput.slice(-2) === '00' ? '01' : '00');
        } else if (Buffer.isBuffer(tamperedInput)) {
          tamperedInput = Buffer.from(tamperedInput);
          tamperedInput[0] ^= 0xff;
        }

        const args = [
          this.recipient.address,
          tamperedInput,
          this.inputProof, // assuming inputProof is available in the test context
        ];

        const contractMethod = transferFrom
          ? this.token.connect(caller).omnibusTransferFrom
          : this.token.connect(caller).omnibusTransfer;

        await expect(
          contractMethod(...args)
        ).to.be.revertedWith('Invalid encrypted input or proof');

        // Optionally, check that OmnibusTransfer event was NOT emitted
        // (If using event assertions, e.g. with chai)
      });

```

- Ensure that `this.inputProof` is available in the test context. If not, you may need to generate or mock it similarly to how `encryptedInput` is created.
- Adjust the tampering logic if your encrypted input format is different.
- If your contract uses a different revert message, update `'Invalid encrypted input or proof'` to match the actual revert reason.
- If you want to test tampering with `inputProof` instead, add a similar test where you modify `inputProof` instead of `encryptedInput`.
</issue_to_address>

### Comment 2
<location> `test/token/ERC7984/extensions/ERC7984Omnibus.test.ts:76` </location>
<code_context>
+          fhevm.userDecryptEuint(FhevmType.euint64, omnibusTransferEvent.args[4], this.token.target, this.holder),
+        ).to.eventually.equal(100);
+
+        await expect(this.acl.isAllowed(omnibusTransferEvent.args[2], this.holder)).to.eventually.be.true;
+        await expect(this.acl.isAllowed(omnibusTransferEvent.args[2], this.recipient)).to.eventually.be.true;
+        await expect(this.acl.isAllowed(omnibusTransferEvent.args[2], this.operator)).to.eventually.be.false;
+      });
+
</code_context>

<issue_to_address>
Consider adding assertions for balance changes.

Adding assertions for token balances after transfers will help verify that the confidential transfer logic correctly updates all relevant accounts.

Suggested implementation:

```typescript
        // Get balances before transfer
        const holderBalanceBefore = await this.token.balanceOf(this.holder.address);
        const recipientBalanceBefore = await this.token.balanceOf(this.recipient.address);
        const subaccountBalanceBefore = await this.token.balanceOf(this.subaccount.address);

        await expect(
          fhevm.userDecryptEaddress(omnibusTransferEvent.args[2], this.token.target, this.holder),
        ).to.eventually.equal(this.holder.address);
        await expect(
          fhevm.userDecryptEaddress(omnibusTransferEvent.args[3], this.token.target, this.holder),
        ).to.eventually.equal(this.subaccount.address);
        await expect(
          fhevm.userDecryptEuint(FhevmType.euint64, omnibusTransferEvent.args[4], this.token.target, this.holder),
        ).to.eventually.equal(100);

        // Get balances after transfer
        const holderBalanceAfter = await this.token.balanceOf(this.holder.address);
        const recipientBalanceAfter = await this.token.balanceOf(this.recipient.address);
        const subaccountBalanceAfter = await this.token.balanceOf(this.subaccount.address);

        // Assert balance changes
        expect(holderBalanceAfter).to.equal(holderBalanceBefore.sub(100));
        expect(recipientBalanceAfter).to.equal(recipientBalanceBefore.add(100));
        // If subaccount is involved in the transfer, assert its balance as well
        // expect(subaccountBalanceAfter).to.equal(subaccountBalanceBefore); // Adjust as needed

```

- If your confidential transfer logic involves the subaccount balance changing, adjust the subaccount assertion accordingly.
- Make sure that `this.token.balanceOf` returns the correct type (BigNumber) for arithmetic operations.
- If the transfer amount is not always 100, replace `100` with the actual transfer amount variable.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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: 1

♻️ Duplicate comments (3)
.changeset/stupid-fans-bow.md (1)

5-5: Tighten the changeset description (use extension-focused phrasing).

This mirrors an earlier suggestion. Recommend wording that clearly frames this as an ERC7984 extension and calls out omnibus subaccounts.

-`ERC7984Omnibus`: Add an extension of `ERC7984` that exposes new functions for transferring between confidential subaccounts on omnibus wallets.
+`ERC7984Omnibus`: Add an `ERC7984` extension exposing omnibus confidential transfers between confidential subaccounts on omnibus wallets:
+- `confidentialTransferOmnibus(...)`
+- `confidentialTransferFromOmnibus(...)`
+Emits `OmnibusTransfer` for omnibus transfer operations.
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (1)

34-79: Add negative test for tampered encrypted input/proof.

You already validate the happy path; add a revert test for modified handles or inputProof to ensure the contract rejects malformed inputs.

I can draft a focused spec that flips a byte in one handle or the proof and asserts a revert (with your actual revert string).

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

35-37: Bind emitted sub-wallet metadata to authorizations (attestation + replay protection)

As-is, sender_/recipient_ used in the event are derived from external inputs but not cryptographically tied to omnibusFrom/omnibusTo or operator approval; a malicious operator could misreport sub-wallets in events without affecting token state. If off-chain ledgers rely on these events, add an attested payload (EIP-712) signed by the omnibus manager (and optionally the recipient side) including sender, recipient, amount, omnibusFrom/To, nonce, deadline.

Sketch:

-    function confidentialTransferFromOmnibus(..., bytes calldata inputProof) external returns (euint64) {
+    function confidentialTransferFromOmnibus(
+        ...,
+        bytes calldata inputProof,
+        bytes calldata omnibusFromSig
+    ) external returns (euint64) {
+        _verifyOmnibusFromSig(omnibusFrom, sender, recipient, externalAmount, omnibusTo, nonce, deadline, omnibusFromSig);
         ...
         emit OmnibusTransfer(omnibusFrom, omnibusTo, sender_, recipient_, transferred);

Add nonce management to prevent replay.



Also applies to: 59-61

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>.changeset/stupid-fans-bow.md (1)</summary><blockquote>

`6-6`: **Remove stray character.**

The trailing “6” looks accidental; it will leak into release notes.



```diff
-6
contracts/mocks/token/ERC7984Mock.sol (1)

34-36: Plaintext mint helper looks good; add a brief NatSpec note.

The overload correctly wraps uint64 via FHE.asEuint64. Add a short doc comment to make its testing purpose explicit.

-    function $_mint(address to, uint64 amount) public returns (euint64 transferred) {
+    /// @dev Test helper: mints a plaintext `uint64` amount by encoding it as `euint64`.
+    function $_mint(address to, uint64 amount) public returns (euint64 transferred) {
         return _mint(to, FHE.asEuint64(amount));
     }
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (4)

26-33: Cover unauthorized operator for transferFrom.

When transferFrom is true without prior setOperator or with insufficient allowance, the call should revert. Add a test to lock this in.

       beforeEach(async function () {
         if (transferFrom) {
           await this.token.connect(this.holder).setOperator(this.operator.address, 999999999999);
         }
       });
+
+      if (transferFrom) {
+        it('reverts when operator is not authorized or allowance is insufficient', async function () {
+          const { holder, operator } = this;
+          // remove or reduce allowance
+          await this.token.connect(holder).setOperator(operator.address, 0);
+          await expect(
+            this.token
+              .connect(operator)
+              ['confidentialTransferFromOmnibus(address,address,bytes32,bytes32,bytes32,bytes)'](
+                holder.address,
+                this.recipient.address,
+                ethers.ZeroHash, ethers.ZeroHash, ethers.ZeroHash, '0x'
+              )
+          ).to.be.reverted;
+        });
+      }

81-117: Exercise the “direct amount” variants too.

The PR advertises support for “external proofs” and “direct amounts,” but tests only cover the former. Add cases that invoke the direct-amount overloads to ensure both paths emit OmnibusTransfer with consistent semantics.

Point me to the exact direct-amount signatures you settled on, and I’ll provide drop-in tests using them.


60-75: Optional: assert static-call return equals event amount.

If the functions return the transferred euint64, add a .staticCall prior to sending the tx and compare to the event’s encrypted amount handle.

-        const tx = await this.token
+        const returned = await this.token
           .connect(caller)
-          [
+          .staticCall[
             transferFrom
               ? 'confidentialTransferFromOmnibus(address,address,bytes32,bytes32,bytes32,bytes)'
               : 'confidentialTransferOmnibus(address,bytes32,bytes32,bytes32,bytes)'
-          ](...args);
+          ](...args);
+        const tx = await this.token.connect(caller)[
+          transferFrom
+            ? 'confidentialTransferFromOmnibus(address,address,bytes32,bytes32,bytes32,bytes)'
+            : 'confidentialTransferOmnibus(address,bytes32,bytes32,bytes32,bytes)'
+        ](...args);
         const omnibusTransferEvent = (await tx.wait()).logs.filter(
           (log: any) => log.fragment?.name === 'OmnibusTransfer',
         )[0];
+        expect(omnibusTransferEvent.args[4]).to.equal(returned);

60-65: Minor: also assert sender/recipient in the “over-balance” test.

You only check the amount there; repeat the address assertions for symmetry.

-        const omnibusTransferEvent = (await tx.wait()).logs.filter(
+        const omnibusTransferEvent = (await tx.wait()).logs.filter(
           (log: any) => log.fragment?.name === 'OmnibusTransfer',
         )[0];
+        expect(omnibusTransferEvent.args[0]).to.equal(this.holder.address);
+        expect(omnibusTransferEvent.args[1]).to.equal(this.recipient.address);
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (5)

9-15: Event indexing: consider indexing sender instead of recipient + add brief NatSpec

Consumers are more likely to filter by the originating sub-wallet. With two indexed L1 addresses already, only one eaddress can be indexed. Consider flipping the indexed field and add a short NatSpec block to document the privacy/observability intent.

 event OmnibusTransfer(
   address indexed omnibusFrom,
   address indexed omnibusTo,
-  eaddress sender,
-  eaddress indexed recipient,
+  eaddress indexed sender,
+  eaddress recipient,
   euint64 amount
 );

17-23: Use external instead of public for gas and intent

Functions are not called internally; external is cheaper and communicates API surface.

-function confidentialTransferOmnibus(
+function confidentialTransferOmnibus(
   address omnibusTo,
   externalEaddress sender,
   externalEaddress recipient,
   externalEuint64 externalAmount,
   bytes calldata inputProof
-) public returns (euint64) {
+) external returns (euint64) {

-function confidentialTransferFromOmnibus(
+function confidentialTransferFromOmnibus(
   address omnibusFrom,
   address omnibusTo,
   externalEaddress sender,
   externalEaddress recipient,
   externalEuint64 externalAmount,
   bytes calldata inputProof
-) public returns (euint64) {
+) external returns (euint64) {

Also applies to: 40-47


24-34: Re-evaluate allowThis and duplicate ACL grants

sender_ and recipient_ are only emitted, not used by on-chain logic. If the contract never operates on these ciphertexts, FHE.allowThis(...) is likely unnecessary. Also, ACL grants largely duplicate across both functions.

-        FHE.allowThis(recipient_);
         FHE.allow(recipient_, omnibusTo);
-        FHE.allow(recipient_, msg.sender);
+        FHE.allow(recipient_, msg.sender); // keep if the operator must view

-        FHE.allowThis(sender_);
         FHE.allow(sender_, omnibusTo);
         FHE.allow(sender_, msg.sender);

If you need these grants in multiple places, consider a tiny helper:

function _allow(eaddress who, address a, address b) internal {
    FHE.allow(who, a);
    FHE.allow(who, b);
}

Also applies to: 51-56


51-58: Align ACLs between the two entry points

In confidentialTransferFromOmnibus, the operator (msg.sender) isn’t granted access to sender_/recipient_, unlike the non-From variant. If operators need parity for off-chain reconciliation, mirror the grants; if not, add a short comment stating the rationale.

         FHE.allow(recipient_, omnibusTo);
         FHE.allow(recipient_, omnibusFrom);
+        // Optional (parity with confidentialTransferOmnibus):
+        // FHE.allow(recipient_, msg.sender);

         FHE.allow(sender_, omnibusTo);
         FHE.allow(sender_, omnibusFrom);
+        // Optional (parity with confidentialTransferOmnibus):
+        // FHE.allow(sender_, msg.sender);

8-16: Add brief NatSpec for contract and event

Documenting trust assumptions (operator model vs. attested sub-wallet intent), who can decrypt what, and filtering expectations will save downstream integrators time.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3a86890 and 38f8bd5.

📒 Files selected for processing (5)
  • .changeset/stupid-fans-bow.md (1 hunks)
  • contracts/mocks/token/ERC7984Mock.sol (2 hunks)
  • contracts/mocks/token/ERC7984OmnibusMock.sol (1 hunks)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1 hunks)
  • test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (1)
test/helpers/accounts.ts (1)
  • ACL_ADDRESS (7-7)
🔇 Additional comments (1)
contracts/mocks/token/ERC7984OmnibusMock.sol (1)

10-16: Override chain is correct; super._update preserves ERC7984Mock behavior.

With inheritance ERC7984Omnibus, ERC7984Mock, super._update resolves through ERC7984Mock to ERC7984, keeping the FHE.allow side effect from the mock. No change needed.

If you want to double-check linearization, run a quick compile locally and ensure tests still observe ACL allowances on the emitted eaddress.

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: 0

🧹 Nitpick comments (12)
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (6)

15-21: Tighten contract typing to avoid any casting.

Leverage the generated factory for $ERC7984OmnibusMock to keep strong typings and catch signature drifts at compile time.

-const token = (await ethers.deployContract('$ERC7984OmnibusMock', [
-  name,
-  symbol,
-  uri,
-])) as any as $ERC7984OmnibusMock;
+const factory = await ethers.getContractFactory('$ERC7984OmnibusMock');
+const token = (await factory.deploy(name, symbol, uri)) as unknown as $ERC7984OmnibusMock;
+await token.waitForDeployment();

23-24: Prefer BigInt literals for u64 semantics in tests.

Use 1000n to mirror uint64 intent and avoid accidental JS number coercion.

-await this.token['$_mint(address,uint64)'](this.holder.address, 1000);
+await this.token['$_mint(address,uint64)'](this.holder.address, 1000n);

34-66: Use event assertions instead of manual log filtering.

expect(tx).to.emit(...).withArgs(...) is clearer and fails with better diffs when the event isn’t emitted or args mismatch.

-const tx = await this.token
+const tx = await this.token
   .connect(caller)
   [
     transferFrom
       ? 'confidentialTransferFromOmnibus(address,address,bytes32,bytes32,bytes32,bytes)'
       : 'confidentialTransferOmnibus(address,bytes32,bytes32,bytes32,bytes)'
   ](...args);
-const omnibusConfidentialTransferEvent = (await tx.wait()).logs.filter(
-  (log: any) => log.fragment?.name === 'OmnibusConfidentialTransfer',
-)[0];
-expect(omnibusConfidentialTransferEvent.args[0]).to.equal(this.holder.address);
-expect(omnibusConfidentialTransferEvent.args[1]).to.equal(this.recipient.address);
+await expect(tx)
+  .to.emit(this.token, 'OmnibusConfidentialTransfer')
+  .withArgs(
+    transferFrom ? this.holder.address : caller.address,
+    this.recipient.address,
+    anyValue, // sender (eaddress)
+    anyValue, // recipient (eaddress)
+    anyValue  // amount (euint64)
+  );
+
+const receipt = await tx.wait();
+const omnibusConfidentialTransferEvent = receipt.logs.find((l: any) => l.fragment?.name === 'OmnibusConfidentialTransfer');

(Add const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); at top if not imported.)


66-86: Assert ACL on both sender and recipient ciphertexts.

Current ACL checks validate permissions on the sender ciphertext only. Consider also asserting ACL on the recipient ciphertext for symmetry.

 await expect(
   fhevm.userDecryptEaddress(omnibusConfidentialTransferEvent.args[2], this.token.target, this.holder),
 ).to.eventually.equal(this.holder.address);
 await expect(
   fhevm.userDecryptEaddress(omnibusConfidentialTransferEvent.args[3], this.token.target, this.holder),
 ).to.eventually.equal(this.subaccount.address);
@@
 await expect(this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.operator)).to.eventually.be
   .false;
+
+// Mirror ACL checks on the recipient ciphertext
+await expect(this.acl.isAllowed(omnibusConfidentialTransferEvent.args[3], this.holder)).to.eventually.be.true;
+await expect(this.acl.isAllowed(omnibusConfidentialTransferEvent.args[3], this.recipient)).to.eventually.be.true;
+await expect(this.acl.isAllowed(omnibusConfidentialTransferEvent.args[3], this.operator)).to.eventually.be.false;

88-131: Strengthen “over-balance” scenario with pre/post balance assertions.

Even if confidential logic clamps to 0, asserting observable balances avoids silent regressions in settlement paths.

-const tx = await this.token
+const balanceBefore = await this.token.balanceOf(this.recipient.address);
+const tx = await this.token
   .connect(caller)
   [
     transferFrom
       ? 'confidentialTransferFromOmnibus(address,address,bytes32,bytes32,bytes32,bytes)'
       : 'confidentialTransferOmnibus(address,bytes32,bytes32,bytes32,bytes)'
   ](...args);
@@
 ).to.eventually.equal(0);
+
+const balanceAfter = await this.token.balanceOf(this.recipient.address);
+expect(balanceAfter).to.equal(balanceBefore);

26-33: Minor: use a finite operator window and assert expiry semantics.

Setting an extremely large expiry blurs operator-approval behavior. Consider a short-lived approval and an assertion that calls after expiry revert.

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

8-14: Document sub-account privacy/visibility guarantees explicitly.

Clarify who can decrypt sender/recipient eaddresses post-transfer and for how long (based on FHE.allow*), since integrators rely on this for compliance.


43-56: Factor out repeated FHE allow rules.

The same “permit sender/recipient to A and B” pattern appears across methods. Extract helpers to reduce duplication and risk of divergence.

+    function _allowPair(eaddress a, eaddress b, address x, address y) internal {
+        FHE.allowThis(a);
+        FHE.allow(a, x);
+        FHE.allow(a, y);
+        FHE.allowThis(b);
+        FHE.allow(b, x);
+        FHE.allow(b, y);
+    }
@@
-        FHE.allowThis(recipient);
-        FHE.allow(recipient, omnibusTo);
-        FHE.allow(recipient, msg.sender);
-
-        FHE.allowThis(sender);
-        FHE.allow(sender, omnibusTo);
-        FHE.allow(sender, msg.sender);
+        _allowPair(recipient, sender, omnibusTo, msg.sender);

(Apply similarly in other methods with the appropriate address pairings.)


77-95: Privacy: avoid accidentally granting operator visibility.

In the From variants you intentionally omit msg.sender from FHE.allow. Good. Consider a brief comment to codify this invariant and prevent future regressions.

     function confidentialTransferFromOmnibus(
@@
-        FHE.allowThis(sender);
+        // NOTE: Do not grant msg.sender (operator) visibility to preserve holder privacy.
+        FHE.allowThis(sender);

97-130: Consistency: parameter extraction order.

Extraction order of sender/recipient swaps across methods. Not a bug, but aligning order aids readability.

-        eaddress recipient = FHE.fromExternal(externalRecipient, inputProof);
-        eaddress sender = FHE.fromExternal(externalSender, inputProof);
+        eaddress sender = FHE.fromExternal(externalSender, inputProof);
+        eaddress recipient = FHE.fromExternal(externalRecipient, inputProof);

(Apply similarly to confidentialTransferFromAndCallOmnibus.)


17-20: Event doc: mention ConfidentialTransfer(AndCall) pairing.

The AndCall variants still emit OmnibusConfidentialTransfer. Note in the doc that settlement is paired with either ConfidentialTransfer or ConfidentialTransferAndCall.


112-130: Gas: prefer external for wrapper functions.

Wrappers don’t need Solidity’s ABI re-encoding; marking them external can shave gas for calls from EOA/contracts.

-    ) public virtual returns (euint64) {
+    ) external virtual returns (euint64) {

(Apply to the four wrapper entrypoints that take external proofs.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38f8bd5 and 1b08ed4.

📒 Files selected for processing (2)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1 hunks)
  • test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (1)
test/helpers/accounts.ts (1)
  • ACL_ADDRESS (7-7)
⏰ 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). (4)
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: slither
  • GitHub Check: tests
  • GitHub Check: coverage
🔇 Additional comments (2)
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (2)

21-27: Confirm feasibility of indexed eaddress across toolchains.

Indexing a custom FHE value type in events can be toolchain-sensitive. If eaddress is a user-defined value type it’s fine; if it’s a struct, topics are unsupported.

If needed, drop indexed on recipient to maximize compatibility.

-        eaddress indexed recipient,
+        eaddress recipient,

57-60: Event semantics: confirm omnibusFrom = msg.sender is intended.

For the non-From variant, omnibusFrom is msg.sender. If the omnibus account differs from the caller (meta-tx or forwarding), consider a omnibusFrom parameter or msgSender() abstraction.

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: 2

♻️ Duplicate comments (2)
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (2)

143-147: Same issue: subaccount must be an address.

-                  tx = await this.token.connect(caller).createEncryptedAddress(this.subaccount);
+                  tx = await this.token.connect(caller).createEncryptedAddress(this.subaccount.address);

191-196: Same ACL address-vs-signer issue in the over-balance test.

-                await expect(
-                  this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.holder),
-                ).to.eventually.be.true;
-                await expect(
-                  this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.recipient),
-                ).to.eventually.be.true;
+                await expect(
+                  this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.holder.address),
+                ).to.eventually.be.true;
+                await expect(
+                  this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.recipient.address),
+                ).to.eventually.be.true;
🧹 Nitpick comments (6)
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (6)

100-115: Avoid relying on chai-as-promised ‘eventually’ in decryption checks.

If chai-as-promised isn’t globally configured, these will fail. Prefer awaiting the promise explicitly.

-                await expect(
-                  fhevm.userDecryptEaddress(omnibusConfidentialTransferEvent.args[2], this.token.target, this.holder),
-                ).to.eventually.equal(this.holder.address);
+                expect(
+                  await fhevm.userDecryptEaddress(omnibusConfidentialTransferEvent.args[2], this.token.target, this.holder),
+                ).to.equal(this.holder.address);

(Apply similarly to lines 103–106 and 107–115.)


51-68: DRY the handle-extraction boilerplate and use event assertions.

The log filtering + [0] indexing is brittle and duplicated. Extract a helper to return the first matching arg and use to.emit for clearer assertions.

async function firstEventArg(tx: any, event: string, index: number) {
  const rc = await tx.wait();
  const log = rc.logs.find((l: any) => l.fragment?.name === event);
  expect(log, `Missing ${event}`).to.not.be.undefined;
  return log.args[index];
}

Then:

- encryptedInput.handles.push(
-   (await tx.wait()).logs.filter((log: any) => log.fragment?.name === 'EncryptedAddressCreated')[0].args[0],
- );
+ encryptedInput.handles.push(await firstEventArg(tx, 'EncryptedAddressCreated', 0));

Also applies to: 137-154


124-171: Strengthen over-balance assertions.

Also assert event presence and decrypted sender/recipient eaddresses, matching the normal-transfer test, to prove consistent semantics when amount clamps to 0.

Also applies to: 172-197


33-35: Magic number for operator allowance/expiry.

Extract const OP_LIMIT = 999_999_999_999n; (or an explicit Max/expiry semantic) for clarity and reuse.


15-21: Prefer typed factories over as any casts when deploying the mock.

Use the generated TypeChain factory for $ERC7984OmnibusMock to get typed instances and ABI-safe calls.

-import { $ERC7984OmnibusMock } from '../../../../types/contracts-exposed/mocks/token/ERC7984OmnibusMock.sol/$ERC7984OmnibusMock';
+import { $ERC7984OmnibusMock, $ERC7984OmnibusMock__factory } from '../../../../types/contracts-exposed/mocks/token/ERC7984OmnibusMock.sol';
...
-    const token = (await ethers.deployContract('$ERC7984OmnibusMock', [
-      name,
-      symbol,
-      uri,
-    ])) as any as $ERC7984OmnibusMock;
+    const token = await new $ERC7984OmnibusMock__factory(holder).deploy(name, symbol, uri);

206-230: Avoid brittle string-based function signatures in tests
Tests in ERC7984Omnibus.test.ts use raw signature strings (e.g. 'confidentialTransferFromAndCallOmnibus(address,address,bytes32,bytes32,bytes32,bytes,bytes)'), which will silently break if the ABI changes. Use typed contract calls or token.interface.encodeFunctionData(“confidentialTransferFromAndCallOmnibus”, […args]) to reference ABI fragments by name instead.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa4777c and 19dbe15.

📒 Files selected for processing (4)
  • contracts/mocks/token/ERC7984FreezableMock.sol (0 hunks)
  • contracts/mocks/token/ERC7984Mock.sol (3 hunks)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1 hunks)
  • test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • contracts/mocks/token/ERC7984FreezableMock.sol
🚧 Files skipped from review as they are similar to previous changes (2)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol
  • contracts/mocks/token/ERC7984Mock.sol
🧰 Additional context used
🧬 Code graph analysis (1)
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (1)
test/helpers/accounts.ts (1)
  • ACL_ADDRESS (7-7)
🔇 Additional comments (1)
test/token/ERC7984/extensions/ERC7984Omnibus.test.ts (1)

94-99: Tests correctly reference OmnibusConfidentialTransfer
The ERC7984Omnibus.sol contract declares and emits OmnibusConfidentialTransfer, and the tests filter on that exact name—there is no OmnibusTransfer event. Disregard the mismatch concern.

Likely an incorrect or invalid review comment.

Comment on lines +57 to +61
tx = await this.token.connect(caller).createEncryptedAddress(this.subaccount);
encryptedInput.handles.push(
(await tx.wait()).logs.filter((log: any) => log.fragment?.name === 'EncryptedAddressCreated')[0]
.args[0],
);
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

Pass an address, not a Signer, to createEncryptedAddress.

this.subaccount is a Signer; the contract expects address. This will throw at runtime.

-                  tx = await this.token.connect(caller).createEncryptedAddress(this.subaccount);
+                  tx = await this.token.connect(caller).createEncryptedAddress(this.subaccount.address);
📝 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
tx = await this.token.connect(caller).createEncryptedAddress(this.subaccount);
encryptedInput.handles.push(
(await tx.wait()).logs.filter((log: any) => log.fragment?.name === 'EncryptedAddressCreated')[0]
.args[0],
);
tx = await this.token.connect(caller).createEncryptedAddress(this.subaccount.address);
encryptedInput.handles.push(
(await tx.wait()).logs.filter((log: any) => log.fragment?.name === 'EncryptedAddressCreated')[0]
.args[0],
);
🤖 Prompt for AI Agents
In test/token/ERC7984/extensions/ERC7984Omnibus.test.ts around lines 57 to 61,
the call passes this.subaccount (a Signer) to createEncryptedAddress which
expects an address; replace the Signer with its address (e.g. await
this.subaccount.getAddress()) when calling createEncryptedAddress so the
contract receives a string address, then use the returned event log extraction
as before.

Comment on lines +116 to +121
await expect(
this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.holder),
).to.eventually.be.true;
await expect(
this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.recipient),
).to.eventually.be.true;
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

ACL isAllowed expects an address; a Signer is being passed.

Pass .address to avoid ABI encoding errors.

-                await expect(
-                  this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.holder),
-                ).to.eventually.be.true;
-                await expect(
-                  this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.recipient),
-                ).to.eventually.be.true;
+                await expect(
+                  this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.holder.address),
+                ).to.eventually.be.true;
+                await expect(
+                  this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.recipient.address),
+                ).to.eventually.be.true;
📝 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
await expect(
this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.holder),
).to.eventually.be.true;
await expect(
this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.recipient),
).to.eventually.be.true;
await expect(
this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.holder.address),
).to.eventually.be.true;
await expect(
this.acl.isAllowed(omnibusConfidentialTransferEvent.args[2], this.recipient.address),
).to.eventually.be.true;
🤖 Prompt for AI Agents
In test/token/ERC7984/extensions/ERC7984Omnibus.test.ts around lines 116 to 121,
the ACL.isAllowed call is being passed a Signer (this.holder / this.recipient)
but the function expects an address string; change the calls to pass
this.holder.address and this.recipient.address respectively so the ABI encoding
receives addresses instead of Signer objects.

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: 0

🧹 Nitpick comments (1)
test/utils/HandleAccessManager.test.ts (1)

15-19: Replace magic number with a named constant; consider loosening the exact error-text match.

101 looks arbitrary. Name it to document intent and reduce future churn. Optionally assert with a regex/substring to avoid brittle failures if the message wording changes slightly.

-    const handle = await createHandle(this.mock, 101);
+    const handle = await createHandle(this.mock, UNAUTHORIZED_HANDLE_AMOUNT);
-    await expect(fhevm.userDecryptEuint(FhevmType.euint64, handle, this.mock.target, this.holder)).to.be.rejectedWith(
-      `User ${this.holder.address} is not authorized to user decrypt handle ${handle}`,
-    );
+    await expect(
+      fhevm.userDecryptEuint(FhevmType.euint64, handle, this.mock.target, this.holder),
+    ).to.be.rejectedWith(new RegExp(`not authorized to user decrypt handle ${handle}`));

Add once near the top (inside the describe or file scope):

const UNAUTHORIZED_HANDLE_AMOUNT = 101;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ee756a and 44fe57c.

📒 Files selected for processing (1)
  • test/utils/HandleAccessManager.test.ts (1 hunks)
⏰ 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). (3)
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: slither
  • GitHub Check: coverage

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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44fe57c and 11a451a.

📒 Files selected for processing (1)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1 hunks)
⏰ 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). (5)
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: coverage
  • GitHub Check: lint
  • GitHub Check: tests
  • GitHub Check: slither
🔇 Additional comments (10)
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (10)

8-14: LGTM! Clear documentation with important disclaimer

The contract documentation clearly explains the purpose and includes the crucial note about external sub-account balance tracking.


23-29: LGTM! Event design follows past discussion decisions

The event structure with omnibusFrom, omnibusTo, and indexed recipient follows the previous discussion about indexing priority and encrypted address limitations.


32-36: LGTM! Clear custom error with helpful guidance

The custom error provides clear messaging and actionable guidance for developers encountering access control issues.


38-50: LGTM! External proof handling implemented correctly

The function correctly extracts encrypted values from external proofs and delegates to the internal implementation.


52-59: LGTM! Clean delegation pattern

The function correctly delegates to the confidentialTransferFromOmnibus implementation using msg.sender as the omnibus source.


61-74: LGTM! Consistent external proof handling

The function follows the same pattern as the transfer variant, correctly handling external proofs and delegating to the internal implementation.


76-97: LGTM! Core omnibus logic with proper access control

The implementation correctly:

  • Validates caller access to encrypted addresses
  • Sets up ACL permissions for all involved parties
  • Delegates to the base transfer function
  • Emits the omnibus event

The ACL setup ensures both omnibus accounts have access to the encrypted addresses and amounts.


99-112: LGTM! Consistent pattern for transfer and call variant

The function follows the established pattern for handling external proofs and delegating to the internal andCall implementation.


114-122: LGTM! Clean delegation for transfer and call

The function correctly delegates to the confidentialTransferFromAndCallOmnibus implementation.


124-138: LGTM! External proof handling for transfer and call

The function maintains consistency with other external proof variants, correctly extracting values and delegating.

@arr00
Copy link
Contributor Author

arr00 commented Sep 16, 2025

@james-toussaint Replying to #186 (comment)

The reason we have different allowances is since we need to ensure that there are persistent allowances for event parameters. confidentialTransferFromAndCall return value is not emitted in an event so its just given a transient allowance. confidentialTransferFromAndCallOmnibus in turn emits this returned value so the allowances must persist.

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: 2

♻️ Duplicate comments (1)
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1)

23-29: Event shape looks good given topic limits.

Indexing omnibusFrom, omnibusTo, and recipient balances searchability vs. topic cap; non-indexed encrypted sender is acceptable.

🧹 Nitpick comments (1)
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1)

183-199: Document why ACL grants are after the callback in this path.

Add a brief note to prevent future reordering “optimizations.”

     ) internal virtual returns (euint64) {
-        euint64 transferred = confidentialTransferFromAndCall(omnibusFrom, omnibusTo, amount, data);
+        // NOTE: In the *AndCall* omnibus path, allowances for sender/recipient/transferred
+        // are granted AFTER the callback by design (see PR #186 discussion). The callback
+        // does not consume encrypted addresses; these grants are for event emission and
+        // future access.
+        euint64 transferred = confidentialTransferFromAndCall(omnibusFrom, omnibusTo, amount, data);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11a451a and e647dc2.

📒 Files selected for processing (1)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-15T14:43:25.623Z
Learnt from: arr00
PR: OpenZeppelin/openzeppelin-confidential-contracts#186
File: contracts/token/ERC7984/extensions/ERC7984Omnibus.sol:140-167
Timestamp: 2025-09-15T14:43:25.623Z
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/token/ERC7984/extensions/ERC7984Omnibus.sol
🔇 Additional comments (1)
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1)

153-172: No change needed — base confidentialTransferFrom already persists ACL for transferred.

The internal _transfer called by confidentialTransferFrom sets FHE.allow(transferred, from), FHE.allow(transferred, to) and FHE.allowThis(transferred); the non-callback Omnibus path therefore doesn't need to re-grant. The callback path's explicit grants for transferred are redundant.

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: 0

♻️ Duplicate comments (2)
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (2)

97-116: On checking recipient/amount allowances in public wrappers.

You intentionally rely on base functions for allowance checks; leaving it here avoids duplication but might look inconsistent to readers.

If you keep this approach, consider a short note in NatSpec for the public wrappers stating “Underlying ERC7984 methods enforce encrypted-handle allowances.”

Also applies to: 118-126, 129-144, 146-159


91-95: Fix: custom errors cannot be used as require messages (compile-time error).

Use explicit revert with the custom error.

-        require(FHE.isAllowed(sender, msg.sender), ERC7984UnauthorizedUseOfEncryptedAddress(sender, msg.sender));
-        require(FHE.isAllowed(recipient, msg.sender), ERC7984UnauthorizedUseOfEncryptedAddress(recipient, msg.sender));
+        if (!FHE.isAllowed(sender, msg.sender)) {
+            revert ERC7984UnauthorizedUseOfEncryptedAddress(sender, msg.sender);
+        }
+        if (!FHE.isAllowed(recipient, msg.sender)) {
+            revert ERC7984UnauthorizedUseOfEncryptedAddress(recipient, msg.sender);
+        }
-        require(FHE.isAllowed(sender, msg.sender), ERC7984UnauthorizedUseOfEncryptedAddress(sender, msg.sender));
-        require(FHE.isAllowed(recipient, msg.sender), ERC7984UnauthorizedUseOfEncryptedAddress(recipient, msg.sender));
+        if (!FHE.isAllowed(sender, msg.sender)) {
+            revert ERC7984UnauthorizedUseOfEncryptedAddress(sender, msg.sender);
+        }
+        if (!FHE.isAllowed(recipient, msg.sender)) {
+            revert ERC7984UnauthorizedUseOfEncryptedAddress(recipient, msg.sender);
+        }

Also applies to: 155-159

🧹 Nitpick comments (3)
contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (3)

118-126: Doc fix: wrong function reference in dev comment.

This overload wraps the non-proof variant; it should reference euint64-only version (single bytes arg).

-    /// @dev Wraps the {confidentialTransferAndCall-address-externalEuint64-bytes-bytes} function and emits the {OmnibusConfidentialTransfer} event.
+    /// @dev Wraps the {confidentialTransferAndCall-address-euint64-bytes} function and emits the {OmnibusConfidentialTransfer} event.

182-190: Clarify callback semantics in docs (no encrypted addresses passed to receiver).

Add a brief NOTE to avoid future reordering of ACL grants based on incorrect assumptions.

-    /// @dev Handles the ACL allowances, does the transfer with a callback, and emits {OmnibusConfidentialTransfer}.
+    /// @dev Handles the ACL allowances, does the transfer with a callback, and emits {OmnibusConfidentialTransfer}.
+    /// NOTE: The callback recipient does not receive encrypted sender/recipient addresses; only (omnibusFrom, omnibusTo, amount, data) are used in the callback.
+    ///       ACL grants for `sender`/`recipient` are for event decryption and future access, not for the callback execution.

169-176: De-duplicate ACL grant boilerplate for gas/readability.

Extract helpers to grant address ACLs (and a separate one for transferred) and call them from both paths.

-        FHE.allowThis(sender);
-        FHE.allow(sender, omnibusFrom);
-        FHE.allow(sender, omnibusTo);
-
-        FHE.allowThis(recipient);
-        FHE.allow(recipient, omnibusFrom);
-        FHE.allow(recipient, omnibusTo);
+        _grantOmnibusAddressACL(sender, omnibusFrom, omnibusTo);
+        _grantOmnibusAddressACL(recipient, omnibusFrom, omnibusTo);
-        FHE.allowThis(sender);
-        FHE.allow(sender, omnibusFrom);
-        FHE.allow(sender, omnibusTo);
-
-        FHE.allowThis(recipient);
-        FHE.allow(recipient, omnibusFrom);
-        FHE.allow(recipient, omnibusTo);
+        _grantOmnibusAddressACL(sender, omnibusFrom, omnibusTo);
+        _grantOmnibusAddressACL(recipient, omnibusFrom, omnibusTo);

Add these helpers (place anywhere in the contract body):

function _grantOmnibusAddressACL(eaddress who, address omnibusFrom, address omnibusTo) internal {
    FHE.allowThis(who);
    FHE.allow(who, omnibusFrom);
    FHE.allow(who, omnibusTo);
}

function _grantTransferredACL(euint64 transferred, address omnibusFrom, address omnibusTo) internal {
    FHE.allowThis(transferred);
    FHE.allow(transferred, omnibusFrom);
    FHE.allow(transferred, omnibusTo);
}

Optionally replace Lines 201–203 with _grantTransferredACL(transferred, omnibusFrom, omnibusTo);.

Also applies to: 193-200

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2da39f5 and 2267cb5.

📒 Files selected for processing (1)
  • contracts/token/ERC7984/extensions/ERC7984Omnibus.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-15T14:43:25.623Z
Learnt from: arr00
PR: OpenZeppelin/openzeppelin-confidential-contracts#186
File: contracts/token/ERC7984/extensions/ERC7984Omnibus.sol:140-167
Timestamp: 2025-09-15T14:43:25.623Z
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/token/ERC7984/extensions/ERC7984Omnibus.sol
⏰ 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). (5)
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: Sourcery review
  • GitHub Check: tests
  • GitHub Check: slither
  • GitHub Check: coverage

@arr00 arr00 merged commit 478030b into master Sep 16, 2025
16 checks passed
@arr00 arr00 deleted the feat/omni-bus branch September 16, 2025 13:24
@github-actions github-actions bot mentioned this pull request Oct 9, 2025
@github-actions github-actions bot mentioned this pull request Nov 12, 2025
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.

Omnibus Extensions

3 participants