Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 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
4a2e41e
Init compliance modules for confidential RWAs
james-toussaint Sep 5, 2025
3946b30
Add abstract compliance modules
james-toussaint Sep 5, 2025
4debb47
Compliance implements interface
james-toussaint Sep 5, 2025
86d5250
Add post transfer hook
james-toussaint Sep 8, 2025
30ca7df
Typo
james-toussaint Sep 8, 2025
57ab500
Init investor cap module
james-toussaint Sep 9, 2025
ed06057
Move rwa compliance contracts
james-toussaint Sep 9, 2025
4b4d558
Support confidential rwa module
james-toussaint Sep 10, 2025
4c45948
Rename rwa mock functions
james-toussaint Sep 10, 2025
9974fae
Immutable token in balance cap module
james-toussaint Sep 10, 2025
3332581
Switch to always-on/transfer-only compliance modules
james-toussaint Sep 11, 2025
c81b703
Typo
james-toussaint Sep 11, 2025
7d438c3
Use enum for compliance module type
james-toussaint Sep 11, 2025
fea22af
Enable token handles access to modules
james-toussaint Sep 15, 2025
b74c5ba
Increase coverage on modular compliance flow
james-toussaint Sep 16, 2025
b964c8b
Should not post update investors if not compliant
james-toussaint Sep 17, 2025
3be8b00
Rename to `ModularCompliance` & `ComplianceModule`
james-toussaint Sep 17, 2025
1bc4c3c
Add balance cap module tests
james-toussaint Sep 17, 2025
1336085
Add max investor tests
james-toussaint Sep 17, 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
9e56fa5
Merge remote-tracking branch 'origin/feature/confidential-rwa' into f…
james-toussaint Sep 18, 2025
020fe73
Merge remote-tracking branch 'origin' into feature/confidential-rwa-c…
james-toussaint Sep 30, 2025
53ec926
Restore restricted and freezable
james-toussaint Sep 30, 2025
d3240e1
Update styling
james-toussaint Sep 30, 2025
89f6c72
Merge branch 'master' into feature/confidential-rwa-compliance
arr00 Jan 29, 2026
830aece
run checks even on 0 transfer
arr00 Jan 27, 2026
e132aac
fix compilation
arr00 Jan 27, 2026
7c078bd
add comments and rename modules
arr00 Jan 27, 2026
15174b7
create identity module
arr00 Jan 28, 2026
f659ac3
revert unnecessary change and ensure token has access to transfer amount
arr00 Jan 28, 2026
f9a28d5
revamp `ERC7984RwaInvestorCapModule`
arr00 Jan 29, 2026
4420c8b
compiles
arr00 Jan 29, 2026
546bea2
fix `_postTransfer` hook
arr00 Jan 29, 2026
980e6ec
nit
arr00 Jan 29, 2026
7292495
add `onInstall` and `onUninstall` hooks to compliance modules
arr00 Jan 30, 2026
d6fb493
create mock compliance module
arr00 Feb 2, 2026
a0b1ffb
remove compliance module
arr00 Feb 2, 2026
65b8830
remove compliance module mock
arr00 Feb 2, 2026
1a89821
rename file
arr00 Feb 2, 2026
3df5aae
update tests
arr00 Feb 2, 2026
53f0231
tests passing
arr00 Feb 3, 2026
81aa09a
fix lint
arr00 Feb 3, 2026
1700b8e
rename default compliance module to standard
arr00 Feb 3, 2026
d246367
nit
arr00 Feb 3, 2026
d36c031
add coverage
arr00 Feb 4, 2026
46c18a0
optimize
arr00 Feb 4, 2026
978567b
remove compliance module from token dir
arr00 Feb 4, 2026
1a5ccfa
fix test
arr00 Feb 4, 2026
44f0e17
Merge branch 'master' into feat/update-compliance-modules
arr00 Feb 4, 2026
e8e0368
fix tests
arr00 Feb 4, 2026
974c5a6
override supports interface on `ERC7984RwaModularCompliance`
arr00 Feb 4, 2026
3b19d07
fix slither
arr00 Feb 4, 2026
6bf6fc6
add only admin modifier to abstract module
arr00 Feb 10, 2026
f19df16
move modular compliance file
arr00 Feb 24, 2026
f146dd6
allow compliance modules to read transfer amount
arr00 Feb 24, 2026
1566cab
up
arr00 Feb 24, 2026
39e7bd0
Remove `IERC7984RwaComplianceModule` interface. Instead use `IComplia…
arr00 Feb 26, 2026
a1b1dd0
review feedback
arr00 Feb 28, 2026
ccdca7e
Merge branch 'master' into feat/update-compliance-modules
arr00 Mar 5, 2026
62f22cc
remove `isAdminOrAgent` function
arr00 Mar 5, 2026
bb28fe8
rename compliance functions
arr00 Mar 13, 2026
666f621
max number of compliance modules
arr00 Mar 13, 2026
38bce20
switch to use 165 for module verification
arr00 Mar 17, 2026
fbee7ae
remove todo block
arr00 Mar 17, 2026
fb75f9b
remove force transfer modules
arr00 Mar 17, 2026
60bf89e
update docs and add modules getter
arr00 Mar 18, 2026
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/wet-results-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-confidential-contracts': minor
---

`ERC7984RwaModularCompliance`: Support compliance modules for confidential RWAs.
90 changes: 90 additions & 0 deletions contracts/finance/compliance/ComplianceModuleConfidential.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {IComplianceModuleConfidential} from "./../../interfaces/IComplianceModuleConfidential.sol";
import {IERC7984Rwa} from "./../../interfaces/IERC7984Rwa.sol";
import {HandleAccessManager} from "./../../utils/HandleAccessManager.sol";

/**
* @dev A contract which allows to build a transfer compliance module for confidential Real World Assets (RWAs).
*/
abstract contract ComplianceModuleConfidential is IComplianceModuleConfidential, ERC165 {
error UnauthorizedUseOfEncryptedAmount(euint64 encryptedAmount, address sender);

/// @dev Thrown when the sender is not authorized to call the given function.
error NotAuthorized(address account);

/// @dev Thrown when the sender is not an admin of the token.
modifier onlyTokenAdmin(address token) {
require(IERC7984Rwa(token).isAdmin(msg.sender), NotAuthorized(msg.sender));
_;
}

/// @dev Thrown when the sender is not an agent of the token.
modifier onlyTokenAgent(address token) {
require(IERC7984Rwa(token).isAgent(msg.sender), NotAuthorized(msg.sender));
_;
}

/// @inheritdoc IComplianceModuleConfidential
function isCompliantTransfer(address from, address to, euint64 encryptedAmount) public virtual returns (ebool) {
ebool compliant = _isCompliantTransfer(msg.sender, from, to, encryptedAmount);
FHE.allowTransient(compliant, msg.sender);
return compliant;
}

/// @inheritdoc IComplianceModuleConfidential
function postTransfer(address from, address to, euint64 encryptedAmount) public virtual {
_postTransfer(msg.sender, from, to, encryptedAmount);
}

/// @inheritdoc IComplianceModuleConfidential
function onInstall(bytes calldata initData) public virtual {}

/// @inheritdoc IComplianceModuleConfidential
function onUninstall(bytes calldata deinitData) public virtual {}
Comment thread
arr00 marked this conversation as resolved.

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

/**
* @dev Internal function which checks if a transfer is compliant. Transient access is already granted to the module
* for `encryptedAmount`. If additional handle access is needed from the token, call {_getTokenHandleAllowance}.
*/
function _isCompliantTransfer(
address token,
address from,
address to,
euint64 encryptedAmount
) internal virtual returns (ebool);

/**
* @dev Internal function which performs operations after transfers. Transient access is already granted to the module
* for `encryptedAmount`. If additional handle access is needed from the token, call {_getTokenHandleAllowance}.
*/
function _postTransfer(
address /*token*/,
address /*from*/,
address /*to*/,
euint64 /*encryptedAmount*/
) internal virtual {
// default to no-op
}

/// @dev Allow modules to get access to token handles during transaction.
function _getTokenHandleAllowance(address token, euint64 handle) internal virtual {
_getTokenHandleAllowance(token, handle, false);
}

/// @dev Allow modules to get access to token handles.
function _getTokenHandleAllowance(address token, euint64 handle, bool persistent) internal virtual {
if (FHE.isInitialized(handle)) {
HandleAccessManager(token).getHandleAllowance(euint64.unwrap(handle), address(this), persistent);
}
}
}
24 changes: 24 additions & 0 deletions contracts/interfaces/IComplianceModuleConfidential.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

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

/// @dev Interface for confidential RWA transfer compliance module.
interface IComplianceModuleConfidential is IERC165 {
/**
* @dev Checks if a transfer is compliant. Should be non-mutating. Transient access is already granted
* to the module for `encryptedAmount`.
*/
function isCompliantTransfer(address from, address to, euint64 encryptedAmount) external returns (ebool);

/// @dev Performs operation after transfer.
function postTransfer(address from, address to, euint64 encryptedAmount) external;

/// @dev Performs operations after installation.
function onInstall(bytes calldata initData) external;

/// @dev Performs operations after uninstallation.
function onUninstall(bytes calldata deinitData) external;
}
35 changes: 34 additions & 1 deletion contracts/interfaces/IERC7984Rwa.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,95 @@
// OpenZeppelin Confidential Contracts (last updated v0.3.0) (interfaces/IERC7984Rwa.sol)
pragma solidity ^0.8.24;

import {externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {ebool, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {IERC7984} from "./IERC7984.sol";

/// @dev Interface for confidential RWA contracts.
interface IERC7984Rwa is IERC7984 {
/// @dev Returns true if the contract is paused, false otherwise.
function paused() external view returns (bool);

/// @dev Returns true if has admin role, false otherwise.
function isAdmin(address account) external view returns (bool);

/// @dev Returns true if agent, false otherwise.
function isAgent(address account) external view returns (bool);

/// @dev Returns whether an account is allowed to interact with the token.
function canTransact(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);
}

/// @dev Interface for confidential RWA with modular compliance.
interface IERC7984RwaModularCompliance {
/// @dev Checks if a compliance module is installed.
function isModuleInstalled(address module) external view returns (bool);

/// @dev Installs a transfer compliance module.
function installModule(address module, bytes calldata initData) external;

/// @dev Uninstalls a transfer compliance module.
function uninstallModule(address module, bytes calldata deinitData) external;
}
58 changes: 58 additions & 0 deletions contracts/mocks/token/ComplianceModuleConfidentialMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {ComplianceModuleConfidential} from "./../../finance/compliance/ComplianceModuleConfidential.sol";
import {IERC7984} from "./../../interfaces/IERC7984.sol";

contract ComplianceModuleConfidentialMock is ComplianceModuleConfidential, ZamaEthereumConfig {
bool public isCompliant = true;
bool public revertOnUninstall = false;

event PostTransfer();
event PreTransfer();

event OnInstall(bytes initData);
event OnUninstall(bytes deinitData);
Comment thread
arr00 marked this conversation as resolved.

function onInstall(bytes calldata initData) public override {
emit OnInstall(initData);
super.onInstall(initData);
}

function onUninstall(bytes calldata deinitData) public override {
if (revertOnUninstall) {
revert("Revert on uninstall");
}

emit OnUninstall(deinitData);
super.onUninstall(deinitData);
}

function setIsCompliant(bool isCompliant_) public {
isCompliant = isCompliant_;
}

function setRevertOnUninstall(bool revertOnUninstall_) public {
revertOnUninstall = revertOnUninstall_;
}

function _isCompliantTransfer(address token, address from, address, euint64) internal override returns (ebool) {
euint64 fromBalance = IERC7984(token).confidentialBalanceOf(from);

if (euint64.unwrap(fromBalance) != 0) {
_getTokenHandleAllowance(token, fromBalance);
assert(FHE.isAllowed(fromBalance, address(this)));
}

emit PreTransfer();
return FHE.asEbool(isCompliant);
}

function _postTransfer(address token, address from, address to, euint64 amount) internal override {
emit PostTransfer();
super._postTransfer(token, from, to, amount);
}
}
7 changes: 7 additions & 0 deletions contracts/mocks/token/ERC7984Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ contract ERC7984Mock is ERC7984, ZamaEthereumConfig {
return encryptedAddr;
}

function confidentialTransfer(address to, uint64 amount) public returns (euint64) {
euint64 ciphertext = FHE.asEuint64(amount);
FHE.allowTransient(ciphertext, msg.sender);

return confidentialTransfer(to, ciphertext);
}

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 Down
33 changes: 33 additions & 0 deletions contracts/mocks/token/ERC7984RwaModularComplianceMock.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 {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {ERC7984} from "./../../token/ERC7984/ERC7984.sol";
import {ERC7984Rwa} from "./../../token/ERC7984/extensions/ERC7984Rwa.sol";
import {ERC7984RwaModularCompliance} from "./../../token/ERC7984/extensions/ERC7984RwaModularCompliance.sol";
import {ERC7984Mock} from "./ERC7984Mock.sol";

contract ERC7984RwaModularComplianceMock is ERC7984RwaModularCompliance, ERC7984Mock {
constructor(
string memory name,
string memory symbol,
string memory tokenUri,
address admin
) ERC7984Rwa(admin) ERC7984Mock(name, symbol, tokenUri) {}

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

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