Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
61729c2
Init `ERC7984Rwa` extension.
james-toussaint Aug 12, 2025
639e5a2
Fix typos
james-toussaint Aug 12, 2025
85546dd
Add agent role
james-toussaint Aug 12, 2025
0029ad7
Update spelling
james-toussaint Aug 12, 2025
b2174ea
Add pausable & roles tests
james-toussaint Aug 18, 2025
ce8286c
Add mint/burn/force/transfer tests
james-toussaint Aug 19, 2025
6cb98a5
Remove tmp freezable
james-toussaint Aug 25, 2025
46d6800
Merge remote-tracking branch 'origin/master' into feature/confidentia…
james-toussaint Aug 25, 2025
d2562aa
Name ERC7984Rwa
james-toussaint Aug 26, 2025
f58c1f3
Name confidential
james-toussaint Aug 26, 2025
76b21ae
Move RWA test
james-toussaint Aug 26, 2025
127aff5
Test with & without proof
james-toussaint Aug 26, 2025
84af687
Rwa mock uses freezable
james-toussaint Aug 26, 2025
b0d5ffa
Check transferred amounts in tests
james-toussaint Aug 26, 2025
6fb7f97
Bypass hardhat fhevm behaviour
james-toussaint Aug 26, 2025
0cb0208
Add support interface test
james-toussaint Aug 26, 2025
90ebfa0
Add should not force transfer if anyone
james-toussaint Aug 26, 2025
c5d07fe
Move some modifiers to mock
james-toussaint Aug 26, 2025
e484066
Update doc
james-toussaint Aug 26, 2025
4ec0bd1
Merge remote-tracking branch 'origin/master' into feature/confidentia…
james-toussaint Aug 29, 2025
64c6c9b
Swap items in doc
james-toussaint Aug 29, 2025
1ad9ccd
Add suggestions
james-toussaint Aug 29, 2025
628d143
Remove lint annotation
james-toussaint Aug 29, 2025
0b1d87c
Update test name
james-toussaint Aug 29, 2025
b6b6827
Add restriction to ERC7984Rwa
james-toussaint Sep 3, 2025
3185336
Move gates
james-toussaint Sep 3, 2025
b4f8c03
Remove ExpectedPause error
james-toussaint Sep 3, 2025
28973a2
Rename block functions
james-toussaint Sep 3, 2025
0facae5
Rename fixture
james-toussaint Sep 3, 2025
2d0ef0e
Force transfer with all update effects
james-toussaint Sep 4, 2025
5451777
Update set frozen doc
james-toussaint Sep 4, 2025
3065002
Refactor event checks in freezable tests
james-toussaint Sep 4, 2025
35b0771
Merge remote-tracking branch 'origin' into feature/confidential-rwa
james-toussaint Sep 17, 2025
4f04222
Use agent for operations
james-toussaint Sep 17, 2025
57eee95
Natively bypass freezable and restricted extensions on force transfer
james-toussaint Sep 18, 2025
e94961e
Expose single rwa interface
james-toussaint Sep 19, 2025
ea6214c
Fix force transfer receiver blocked test
james-toussaint Sep 19, 2025
a32468f
Remove freezer check since internal parent
james-toussaint Sep 19, 2025
c27150c
Add pre check and post hook on transfers
james-toussaint Sep 19, 2025
7710529
Add access control tests
james-toussaint Sep 22, 2025
f884a5e
Remove dup test
james-toussaint Sep 22, 2025
ed2b6fe
Improve pre update wording in freezable
james-toussaint Sep 22, 2025
1de3990
Rename to preTransferCheck
james-toussaint Sep 22, 2025
7c0172d
Remove restricted interface
james-toussaint Sep 22, 2025
f43ceaa
Check acl & order functions
james-toussaint Sep 22, 2025
b3af5e9
Remove pre and post
james-toussaint Sep 23, 2025
299d0fe
More explicit wording
james-toussaint Sep 23, 2025
3da562e
Remove freezer from mock and fix typo
james-toussaint Sep 23, 2025
ac8b822
Update RWA (#214)
arr00 Sep 25, 2025
0eecd53
update tests
arr00 Sep 25, 2025
10a664c
review feedback
arr00 Sep 25, 2025
0cb3508
up
arr00 Sep 26, 2025
9d1fe1b
Nit
james-toussaint Sep 26, 2025
341060e
inherit interface and inline selector calc
arr00 Sep 26, 2025
f773fd8
Lint import order
james-toussaint Sep 26, 2025
09c2789
Precompute force transfer signatures
james-toussaint Sep 26, 2025
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/new-crews-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-confidential-contracts': minor
---

`ERC7984Rwa`: An extension of `ERC7984`, that supports confidential Real World Assets (RWAs).
63 changes: 63 additions & 0 deletions contracts/interfaces/IERC7984Rwa.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
import {IERC7984} from "./IERC7984.sol";

/// @dev Interface for confidential RWA contracts.
interface IERC7984Rwa is IERC7984, IERC165 {
/// @dev Returns true if the contract is paused, false otherwise.
function paused() external view returns (bool);
/// @dev Returns whether an account is allowed to interact with the token.
function isUserAllowed(address account) external view returns (bool);
/// @dev Returns the confidential frozen balance of an account.
function confidentialFrozen(address account) external view returns (euint64);
/// @dev Returns the confidential available (unfrozen) balance of an account. Up to {IERC7984-confidentialBalanceOf}.
function confidentialAvailable(address account) external returns (euint64);
/// @dev Pauses contract.
function pause() external;
/// @dev Unpauses contract.
function unpause() external;
/// @dev Blocks a user account.
function blockUser(address account) external;
/// @dev Unblocks a user account.
function unblockUser(address account) external;
/// @dev Sets confidential amount of token for an account as frozen with proof.
function setConfidentialFrozen(
address account,
externalEuint64 encryptedAmount,
bytes calldata inputProof
) external;
/// @dev Sets confidential amount of token for an account as frozen.
function setConfidentialFrozen(address account, euint64 encryptedAmount) external;
/// @dev Mints confidential amount of tokens to account with proof.
function confidentialMint(
address to,
externalEuint64 encryptedAmount,
bytes calldata inputProof
) external returns (euint64);
/// @dev Mints confidential amount of tokens to account.
function confidentialMint(address to, euint64 encryptedAmount) external returns (euint64);
/// @dev Burns confidential amount of tokens from account with proof.
function confidentialBurn(
address account,
externalEuint64 encryptedAmount,
bytes calldata inputProof
) external returns (euint64);
/// @dev Burns confidential amount of tokens from account.
function confidentialBurn(address account, euint64 encryptedAmount) external returns (euint64);
/// @dev Forces transfer of confidential amount of tokens from account to account with proof by skipping compliance checks.
function forceConfidentialTransferFrom(
address from,
address to,
externalEuint64 encryptedAmount,
bytes calldata inputProof
) external returns (euint64);
/// @dev Forces transfer of confidential amount of tokens from account to account by skipping compliance checks.
function forceConfidentialTransferFrom(
address from,
address to,
euint64 encryptedAmount
) external returns (euint64);
}
16 changes: 2 additions & 14 deletions contracts/mocks/token/ERC7984FreezableMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,15 @@ pragma solidity ^0.8.27;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ERC7984} from "../../token/ERC7984/ERC7984.sol";
import {ERC7984Freezable} from "../../token/ERC7984/extensions/ERC7984Freezable.sol";
import {HandleAccessManager} from "../../utils/HandleAccessManager.sol";
import {ERC7984Mock} from "./ERC7984Mock.sol";

contract ERC7984FreezableMock is ERC7984Mock, ERC7984Freezable, AccessControl, HandleAccessManager {
bytes32 public constant FREEZER_ROLE = keccak256("FREEZER_ROLE");

contract ERC7984FreezableMock is ERC7984Mock, ERC7984Freezable, HandleAccessManager {
error UnallowedHandleAccess(bytes32 handle, address account);

constructor(
string memory name,
string memory symbol,
string memory tokenUri,
address freezer
) ERC7984Mock(name, symbol, tokenUri) {
_grantRole(FREEZER_ROLE, freezer);
}
constructor(string memory name, string memory symbol, string memory tokenUri) ERC7984Mock(name, symbol, tokenUri) {}

function _update(
address from,
Expand All @@ -48,6 +38,4 @@ contract ERC7984FreezableMock is ERC7984Mock, ERC7984Freezable, AccessControl, H
}

function _validateHandleAllowance(bytes32 handle) internal view override {}

function _checkFreezer() internal override onlyRole(FREEZER_ROLE) {}
}
33 changes: 33 additions & 0 deletions contracts/mocks/token/ERC7984RwaMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

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

// solhint-disable func-name-mixedcase
contract ERC7984RwaMock is ERC7984Rwa, ERC7984Mock, HandleAccessManager {
constructor(
string memory name,
string memory symbol,
string memory tokenUri,
address admin
) ERC7984Rwa(admin) ERC7984Mock(name, symbol, tokenUri) {}

function _update(
address from,
address to,
euint64 amount
) internal virtual override(ERC7984Mock, ERC7984Rwa) returns (euint64) {
return super._update(from, to, amount);
}

function _validateHandleAllowance(bytes32 handle) internal view override onlyAgent {}

// solhint-disable-next-line func-name-mixedcase
function $_setConfidentialFrozen(address account, uint64 amount) public virtual {
_setConfidentialFrozen(account, FHE.asEuint64(amount));
}
}
10 changes: 5 additions & 5 deletions contracts/token/ERC7984/extensions/ERC7984Freezable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,19 @@ abstract contract ERC7984Freezable is ERC7984 {

/// @dev Internal function to freeze a confidential amount of tokens for an account.
function _setConfidentialFrozen(address account, euint64 encryptedAmount) internal virtual {
_checkFreezer();
FHE.allowThis(encryptedAmount);
FHE.allow(encryptedAmount, account);
_frozenBalances[account] = encryptedAmount;
emit TokensFrozen(account, encryptedAmount);
}

/// @dev Unimplemented function that must revert if `msg.sender` is not authorized as a freezer.
function _checkFreezer() internal virtual;

/**
* @dev See {ERC7984-_update}. The `from` account must have sufficient unfrozen balance,
* @dev See {ERC7984-_update}.
*
* The `from` account must have sufficient unfrozen balance,
* otherwise 0 tokens are transferred.
* The default freezing behavior can be changed (for a pass-through for instance) by overriding
* {confidentialAvailable}.
*/
function _update(address from, address to, euint64 encryptedAmount) internal virtual override returns (euint64) {
if (from != address(0)) {
Expand Down
27 changes: 25 additions & 2 deletions contracts/token/ERC7984/extensions/ERC7984Restricted.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,13 @@ abstract contract ERC7984Restricted is ERC7984 {
*
* * `from` must be allowed to transfer tokens (see {isUserAllowed}).
* * `to` must be allowed to receive tokens (see {isUserAllowed}).
*
* The default restriction behaviour can be changed (for a pass-through for instance) by overriding
* {_checkSenderRestriction} and/or {_checkRecipientRestriction}.
*/
function _update(address from, address to, euint64 value) internal virtual override returns (euint64) {
if (from != address(0)) _checkRestriction(from); // Not minting
if (to != address(0)) _checkRestriction(to); // Not burning
_checkSenderRestriction(from);
_checkRecipientRestriction(to);
return super._update(from, to, value);
}

Expand Down Expand Up @@ -91,4 +94,24 @@ abstract contract ERC7984Restricted is ERC7984 {
function _checkRestriction(address account) internal view virtual {
require(isUserAllowed(account), UserRestricted(account));
}

/**
* @dev Internal function which checks restriction of the `from` account before a transfer.
* Working with {_update} function.
*/
function _checkSenderRestriction(address account) internal view virtual {
if (account != address(0)) {
_checkRestriction(account); // Not minting
}
}

/**
* @dev Internal function which checks restriction of the `to` account before a transfer.
* Working with {_update} function.
*/
function _checkRecipientRestriction(address account) internal view virtual {
if (account != address(0)) {
_checkRestriction(account); // Not burning
}
}
}
Loading