Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stupid-fans-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-confidential-contracts': minor
---

`ERC7984Omnibus`: Add an extension of `ERC7984` that exposes new functions for transferring between confidential subaccounts on omnibus wallets.
26 changes: 25 additions & 1 deletion contracts/mocks/token/ERC7984Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {FHE, eaddress, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7984} from "../../token/ERC7984/ERC7984.sol";

// solhint-disable func-name-mixedcase
contract ERC7984Mock is ERC7984, SepoliaConfig {
address private immutable _OWNER;

event EncryptedAmountCreated(euint64 amount);
event EncryptedAddressCreated(eaddress addr);

constructor(
string memory name_,
string memory symbol_,
Expand All @@ -17,6 +21,22 @@ contract ERC7984Mock is ERC7984, SepoliaConfig {
_OWNER = msg.sender;
}

function createEncryptedAmount(uint64 amount) public returns (euint64 encryptedAmount) {
FHE.allowThis(encryptedAmount = FHE.asEuint64(amount));
FHE.allow(encryptedAmount, msg.sender);

emit EncryptedAmountCreated(encryptedAmount);
}

function createEncryptedAddress(address addr) public returns (eaddress) {
eaddress encryptedAddr = FHE.asEaddress(addr);
FHE.allowThis(encryptedAddr);
FHE.allow(encryptedAddr, msg.sender);

emit EncryptedAddressCreated(encryptedAddr);
return encryptedAddr;
}

function _update(address from, address to, euint64 amount) internal virtual override returns (euint64 transferred) {
transferred = super._update(from, to, amount);
FHE.allow(confidentialTotalSupply(), _OWNER);
Expand All @@ -30,6 +50,10 @@ contract ERC7984Mock is ERC7984, SepoliaConfig {
return _mint(to, FHE.fromExternal(encryptedAmount, inputProof));
}

function $_mint(address to, uint64 amount) public returns (euint64 transferred) {
return _mint(to, FHE.asEuint64(amount));
}

function $_transfer(
address from,
address to,
Expand Down
17 changes: 17 additions & 0 deletions contracts/mocks/token/ERC7984OmnibusMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {euint64} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7984Omnibus} from "../../token/ERC7984/extensions/ERC7984Omnibus.sol";
import {ERC7984Mock, ERC7984} from "./ERC7984Mock.sol";

abstract contract ERC7984OmnibusMock is ERC7984Omnibus, ERC7984Mock {
function _update(
address from,
address to,
euint64 amount
) internal virtual override(ERC7984Mock, ERC7984) returns (euint64) {
return super._update(from, to, amount);
}
}
208 changes: 208 additions & 0 deletions contracts/token/ERC7984/extensions/ERC7984Omnibus.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {FHE, euint64, externalEuint64, externalEaddress, eaddress} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7984} from "../ERC7984.sol";

/**
* @dev Extension of {ERC7984} that emits additional events for omnibus transfers.
* These events contain encrypted addresses for the sub-account sender and recipient.
*
* NOTE: There is no onchain accounting for sub-accounts--integrators must track sub-account
* balances externally.
*/
abstract contract ERC7984Omnibus is ERC7984 {
/**
* @dev Emitted when a confidential transfer is made representing the onchain settlement of
* an omnibus transfer from `sender` to `recipient` of amount `amount`. Settlement occurs between
* `omnibusFrom` and `omnibusTo` and is represented in a matching {IERC7984-ConfidentialTransfer} event.
*
* NOTE: `omnibusFrom` and `omnibusTo` get permanent ACL allowances for `sender` and `recipient`.
*/
event OmnibusConfidentialTransfer(
address indexed omnibusFrom,
address indexed omnibusTo,
eaddress sender,
eaddress indexed recipient,
euint64 amount
);

/**
* @dev The caller `user` does not have access to the encrypted address `addr`.
*
* NOTE: Try using the equivalent transfer function with an input proof.
*/
error ERC7984UnauthorizedUseOfEncryptedAddress(eaddress addr, address user);

/// @dev Wraps the {confidentialTransfer-address-externalEuint64-bytes} function and emits the {OmnibusConfidentialTransfer} event.
function confidentialTransferOmnibus(
address omnibusTo,
externalEaddress externalSender,
externalEaddress externalRecipient,
externalEuint64 externalAmount,
bytes calldata inputProof
) public virtual returns (euint64) {
return
confidentialTransferFromOmnibus(
msg.sender,
omnibusTo,
externalSender,
externalRecipient,
externalAmount,
inputProof
);
}

/// @dev Wraps the {confidentialTransfer-address-euint64} function and emits the {OmnibusConfidentialTransfer} event.
function confidentialTransferOmnibus(
address omnibusTo,
eaddress sender,
eaddress recipient,
euint64 amount
) public virtual returns (euint64) {
return confidentialTransferFromOmnibus(msg.sender, omnibusTo, sender, recipient, amount);
}

/// @dev Wraps the {confidentialTransferFrom-address-address-externalEuint64-bytes} function and emits the {OmnibusConfidentialTransfer} event.
function confidentialTransferFromOmnibus(
address omnibusFrom,
address omnibusTo,
externalEaddress externalSender,
externalEaddress externalRecipient,
externalEuint64 externalAmount,
bytes calldata inputProof
) public virtual returns (euint64) {
eaddress sender = FHE.fromExternal(externalSender, inputProof);
eaddress recipient = FHE.fromExternal(externalRecipient, inputProof);
euint64 amount = FHE.fromExternal(externalAmount, inputProof);

return _confidentialTransferFromOmnibus(omnibusFrom, omnibusTo, sender, recipient, amount);
}

/// @dev Wraps the {confidentialTransferFrom-address-address-euint64} function and emits the {OmnibusConfidentialTransfer} event.
function confidentialTransferFromOmnibus(
address omnibusFrom,
address omnibusTo,
eaddress sender,
eaddress recipient,
euint64 amount
) public virtual returns (euint64) {
require(FHE.isAllowed(sender, msg.sender), ERC7984UnauthorizedUseOfEncryptedAddress(sender, msg.sender));
require(FHE.isAllowed(recipient, msg.sender), ERC7984UnauthorizedUseOfEncryptedAddress(recipient, msg.sender));

return _confidentialTransferFromOmnibus(omnibusFrom, omnibusTo, sender, recipient, amount);
}

/// @dev Wraps the {confidentialTransferAndCall-address-externalEuint64-bytes-bytes} function and emits the {OmnibusConfidentialTransfer} event.
function confidentialTransferAndCallOmnibus(
address omnibusTo,
externalEaddress externalSender,
externalEaddress externalRecipient,
externalEuint64 externalAmount,
bytes calldata inputProof,
bytes calldata data
) public virtual returns (euint64) {
return
confidentialTransferFromAndCallOmnibus(
msg.sender,
omnibusTo,
externalSender,
externalRecipient,
externalAmount,
inputProof,
data
);
}

/// @dev Wraps the {confidentialTransferAndCall-address-euint64-bytes} function and emits the {OmnibusConfidentialTransfer} event.
function confidentialTransferAndCallOmnibus(
address omnibusTo,
eaddress sender,
eaddress recipient,
euint64 amount,
bytes calldata data
) public virtual returns (euint64) {
return confidentialTransferFromAndCallOmnibus(msg.sender, omnibusTo, sender, recipient, amount, data);
}

/// @dev Wraps the {confidentialTransferFromAndCall-address-address-externalEuint64-bytes-bytes} function and emits the {OmnibusConfidentialTransfer} event.
function confidentialTransferFromAndCallOmnibus(
address omnibusFrom,
address omnibusTo,
externalEaddress externalSender,
externalEaddress externalRecipient,
externalEuint64 externalAmount,
bytes calldata inputProof,
bytes calldata data
) public virtual returns (euint64) {
eaddress sender = FHE.fromExternal(externalSender, inputProof);
eaddress recipient = FHE.fromExternal(externalRecipient, inputProof);
euint64 amount = FHE.fromExternal(externalAmount, inputProof);

return _confidentialTransferFromAndCallOmnibus(omnibusFrom, omnibusTo, sender, recipient, amount, data);
}

/// @dev Wraps the {confidentialTransferFromAndCall-address-address-euint64-bytes} function and emits the {OmnibusConfidentialTransfer} event.
function confidentialTransferFromAndCallOmnibus(
address omnibusFrom,
address omnibusTo,
eaddress sender,
eaddress recipient,
euint64 amount,
bytes calldata data
) public virtual returns (euint64) {
require(FHE.isAllowed(sender, msg.sender), ERC7984UnauthorizedUseOfEncryptedAddress(sender, msg.sender));
require(FHE.isAllowed(recipient, msg.sender), ERC7984UnauthorizedUseOfEncryptedAddress(recipient, msg.sender));

return _confidentialTransferFromAndCallOmnibus(omnibusFrom, omnibusTo, sender, recipient, amount, data);
}

/// @dev Handles the ACL allowances, does the transfer without a callback, and emits {OmnibusConfidentialTransfer}.
function _confidentialTransferFromOmnibus(
address omnibusFrom,
address omnibusTo,
eaddress sender,
eaddress recipient,
euint64 amount
) internal virtual returns (euint64) {
FHE.allowThis(sender);
FHE.allow(sender, omnibusFrom);
FHE.allow(sender, omnibusTo);

FHE.allowThis(recipient);
FHE.allow(recipient, omnibusFrom);
FHE.allow(recipient, omnibusTo);

euint64 transferred = confidentialTransferFrom(omnibusFrom, omnibusTo, amount);
emit OmnibusConfidentialTransfer(omnibusFrom, omnibusTo, sender, recipient, transferred);
return transferred;
}

/// @dev Handles the ACL allowances, does the transfer with a callback, and emits {OmnibusConfidentialTransfer}.
function _confidentialTransferFromAndCallOmnibus(
address omnibusFrom,
address omnibusTo,
eaddress sender,
eaddress recipient,
euint64 amount,
bytes calldata data
) internal virtual returns (euint64) {
euint64 transferred = confidentialTransferFromAndCall(omnibusFrom, omnibusTo, amount, data);

FHE.allowThis(sender);
FHE.allow(sender, omnibusFrom);
FHE.allow(sender, omnibusTo);

FHE.allowThis(recipient);
FHE.allow(recipient, omnibusFrom);
FHE.allow(recipient, omnibusTo);

FHE.allowThis(transferred);
FHE.allow(transferred, omnibusFrom);
FHE.allow(transferred, omnibusTo);

emit OmnibusConfidentialTransfer(omnibusFrom, omnibusTo, sender, recipient, transferred);
return transferred;
}
}
2 changes: 2 additions & 0 deletions contracts/token/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This set of interfaces, contracts, and utilities are all related to `ERC7984`, a
- {ERC7984Freezable}: An extension of {ERC7984}, which allows accounts granted the "freezer" role to freeze and unfreeze tokens.
- {ERC7984ObserverAccess}: An extension of {ERC7984}, which allows each account to add an observer who is given access to their transfer and balance amounts.
- {ERC7984Restricted}: An extension of {ERC7984} that implements user account transfer restrictions.
- {ERC7984Omnibus}: An extension of {ERC7984} that emits additional events for omnibus transfers, which contain encrypted addresses for the sub-account sender and recipient.
- {ERC7984Utils}: A library that provides the on-transfer callback check used by {ERC7984}.

== Core
Expand All @@ -20,6 +21,7 @@ This set of interfaces, contracts, and utilities are all related to `ERC7984`, a
{{ERC7984Freezable}}
{{ERC7984ObserverAccess}}
{{ERC7984Restricted}}
{{ERC7984Omnibus}}

== Utilities
{{ERC7984Utils}}
Loading