Skip to content

Commit bea4181

Browse files
committed
chore(protocol-contracts): simplify implementation and add supportsInterface support
1 parent 4ff893f commit bea4181

File tree

7 files changed

+282
-165
lines changed

7 files changed

+282
-165
lines changed

protocol-contracts/confidential-tokens-registry/contracts/ConfidentialTokensRegistry.sol

Lines changed: 100 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,37 @@ pragma solidity ^0.8.27;
33

44
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
55
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
6+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
67

78
/**
89
* @title ConfidentialTokensRegistry
9-
* @notice A registry contract to map ERC20 token addresses to their ERC7984 confidential token addresses.
10+
* @notice A registry contract to map ERC20 token addresses to their corresponding ERC7984
11+
* confidential fhevm wrapper addresses, also called confidential tokens.
1012
* @dev This contract allows an owner to register new entries and flag revoked ones.
1113
*/
1214
contract ConfidentialTokensRegistry is Ownable2StepUpgradeable, UUPSUpgradeable {
15+
/// @notice Struct to represent a (token, confidential token, is revoked) tuple.
16+
struct ConfidentialTokenPair {
17+
/// @notice The address of the token.
18+
address tokenAddress;
19+
/// @notice The address of the confidential token.
20+
address confidentialTokenAddress;
21+
/// @notice If the confidential token has been revoked.
22+
bool isRevoked;
23+
}
24+
1325
/// @custom:storage-location erc7201:fhevm_protocol.storage.ConfidentialTokensRegistry
1426
struct ConfidentialTokensRegistryStorage {
27+
/// @notice Mapping from token address to confidential token address.
1528
mapping(address tokenAddress => address confidentialTokenAddress) _tokensToConfidentialTokens;
29+
/// @notice Mapping from confidential token address to token address.
1630
mapping(address confidentialTokenAddress => address tokenAddress) _confidentialTokensToTokens;
31+
/// @notice If a confidential token has been revoked.
1732
mapping(address confidentialTokenAddress => bool isRevoked) _revokedConfidentialTokens;
33+
/// @notice Index of registered tokens.
34+
mapping(address tokenAddress => uint256 index) _tokenIndex;
35+
/// @notice Registered token and confidential token pairs.
36+
ConfidentialTokenPair[] _tokenConfidentialTokenPairs;
1837
}
1938

2039
// keccak256(abi.encode(uint256(keccak256("fhevm_protocol.storage.ConfidentialTokensRegistry")) - 1)) & ~bytes32(uint256(0xff))
@@ -27,6 +46,13 @@ contract ConfidentialTokensRegistry is Ownable2StepUpgradeable, UUPSUpgradeable
2746
/// @notice Error thrown when the confidential token address is zero.
2847
error ConfidentialTokenZeroAddress();
2948

49+
/// @notice Error thrown when a confidential token is not a valid ERC7984 confidential token
50+
/// because it does not support the ERC7984 interface (0x4958f2a4).
51+
error NotERC7984(address confidentialTokenAddress);
52+
53+
/// @notice Error thrown when a confidential token does not support ERC165's `supportsInterface`.
54+
error ConfidentialTokenDoesNotSupportERC165(address confidentialTokenAddress);
55+
3056
/// @notice Error thrown when a confidential token is already associated with a token.
3157
error ConfidentialTokenAlreadyAssociatedWithToken(address tokenAddress, address existingConfidentialTokenAddress);
3258

@@ -48,10 +74,6 @@ contract ConfidentialTokensRegistry is Ownable2StepUpgradeable, UUPSUpgradeable
4874
/// @notice Emitted when a confidential token is revoked and the association with a token is removed.
4975
event ConfidentialTokenRevoked(address indexed tokenAddress, address indexed confidentialTokenAddress);
5076

51-
/// @notice Emitted when a confidential token no longer revoked and thus is allowed to be
52-
/// associated with a token again.
53-
event ConfidentialTokenReinstated(address indexed confidentialTokenAddress);
54-
5577
/// @custom:oz-upgrades-unsafe-allow constructor
5678
constructor() {
5779
_disableInitializers();
@@ -77,28 +99,39 @@ contract ConfidentialTokensRegistry is Ownable2StepUpgradeable, UUPSUpgradeable
7799
revert ConfidentialTokenZeroAddress();
78100
}
79101

80-
// The confidential token must not be revoked.
81-
if (isConfidentialTokenRevoked(confidentialTokenAddress)) {
82-
revert RevokedConfidentialToken(confidentialTokenAddress);
83-
}
102+
// The confidential token must support the ERC7984 interface (0x4958f2a4) via
103+
// `supportsInterface` from ERC165.
104+
// See https://eips.ethereum.org/EIPS/eip-7984.
105+
_validateERC7984(confidentialTokenAddress);
84106

85107
// The confidential token must not be already associated with a token.
86-
address existingTokenAddress = getTokenAddress(confidentialTokenAddress);
108+
(, address existingTokenAddress) = getTokenAddress(confidentialTokenAddress);
87109
if (existingTokenAddress != address(0)) {
88110
revert ConfidentialTokenAlreadyAssociatedWithToken(confidentialTokenAddress, existingTokenAddress);
89111
}
90112

91113
// The token must not be already associated with a confidential token.
92-
address existingConfidentialTokenAddress = getConfidentialTokenAddress(tokenAddress);
114+
(, address existingConfidentialTokenAddress) = getConfidentialTokenAddress(tokenAddress);
93115
if (existingConfidentialTokenAddress != address(0)) {
94116
revert TokenAlreadyAssociatedWithConfidentialToken(tokenAddress, existingConfidentialTokenAddress);
95117
}
96118

97119
ConfidentialTokensRegistryStorage storage $ = _getConfidentialTokensRegistryStorage();
98120

121+
// Register the token and confidential token mappings.
99122
$._tokensToConfidentialTokens[tokenAddress] = confidentialTokenAddress;
100123
$._confidentialTokensToTokens[confidentialTokenAddress] = tokenAddress;
101124

125+
// Register the token and confidential token pairs in the array and keep track of their indexes.
126+
$._tokenConfidentialTokenPairs.push(
127+
ConfidentialTokenPair({
128+
tokenAddress: tokenAddress,
129+
confidentialTokenAddress: confidentialTokenAddress,
130+
isRevoked: false
131+
})
132+
);
133+
$._tokenIndex[tokenAddress] = $._tokenConfidentialTokenPairs.length - 1;
134+
102135
emit ConfidentialTokenRegistered(tokenAddress, confidentialTokenAddress);
103136
}
104137

@@ -117,74 +150,95 @@ contract ConfidentialTokensRegistry is Ownable2StepUpgradeable, UUPSUpgradeable
117150
}
118151

119152
// The confidential token must be associated with a token.
120-
address tokenAddress = getTokenAddress(confidentialTokenAddress);
153+
(, address tokenAddress) = getTokenAddress(confidentialTokenAddress);
121154
if (tokenAddress == address(0)) {
122155
revert NoTokenAssociatedWithConfidentialToken(confidentialTokenAddress);
123156
}
124157

125158
ConfidentialTokensRegistryStorage storage $ = _getConfidentialTokensRegistryStorage();
126159

127-
delete $._confidentialTokensToTokens[confidentialTokenAddress];
128-
delete $._tokensToConfidentialTokens[tokenAddress];
129160
$._revokedConfidentialTokens[confidentialTokenAddress] = true;
130161

131-
emit ConfidentialTokenRevoked(tokenAddress, confidentialTokenAddress);
132-
}
162+
// Set token's confidential token address to zero to indicate that it has been revoked.
163+
uint256 index = $._tokenIndex[tokenAddress];
164+
$._tokenConfidentialTokenPairs[index].isRevoked = true;
133165

134-
/**
135-
* @notice Reinstate an ERC7984 confidential token in case it was revoked. It does not restore
136-
* the association with a token, it only allows it to be associated with one again.
137-
* @param confidentialTokenAddress The address of the ERC7984 confidential token to reinstate.
138-
*/
139-
function reinstateConfidentialToken(address confidentialTokenAddress) external onlyOwner {
140-
ConfidentialTokensRegistryStorage storage $ = _getConfidentialTokensRegistryStorage();
141-
if (confidentialTokenAddress == address(0)) {
142-
revert ConfidentialTokenZeroAddress();
143-
}
144-
145-
// The confidential token must be revoked.
146-
if (!isConfidentialTokenRevoked(confidentialTokenAddress)) {
147-
revert ConfidentialTokenNotRevoked(confidentialTokenAddress);
148-
}
149-
150-
$._revokedConfidentialTokens[confidentialTokenAddress] = false;
151-
emit ConfidentialTokenReinstated(confidentialTokenAddress);
166+
emit ConfidentialTokenRevoked(tokenAddress, confidentialTokenAddress);
152167
}
153-
154168
/**
155169
* @notice Returns the address of the confidential token associated with a token. A null address
156-
* is returned if no confidential token is associated with the token, meaning it either has
157-
* never been registered or it has been revoked.
170+
* is returned if no confidential token has been registered for the token.
158171
* @param tokenAddress The address of the token.
172+
* @return True if the confidential token has been revoked, false otherwise.
159173
* @return The address of the confidential token.
160174
*/
161-
function getConfidentialTokenAddress(address tokenAddress) public view returns (address) {
162-
return _getConfidentialTokensRegistryStorage()._tokensToConfidentialTokens[tokenAddress];
175+
function getConfidentialTokenAddress(address tokenAddress) public view returns (bool, address) {
176+
address confidentialTokenAddress = _getConfidentialTokensRegistryStorage()._tokensToConfidentialTokens[
177+
tokenAddress
178+
];
179+
bool isRevoked = isConfidentialTokenRevoked(confidentialTokenAddress);
180+
return (isRevoked, confidentialTokenAddress);
163181
}
164182

165183
/**
166184
* @notice Returns the address of the token associated with a confidential token.
167-
* A null address is returned if no token is associated with the confidential token, meaning
168-
* it either has never been registered or it has been revoked.
185+
* A null address is returned if the confidential token has not been registered for any token.
169186
* @param confidentialTokenAddress The address of the confidential token.
187+
* @return True if the confidential token has been revoked, false otherwise.
170188
* @return The address of the token.
171189
*/
172-
function getTokenAddress(address confidentialTokenAddress) public view returns (address) {
173-
return _getConfidentialTokensRegistryStorage()._confidentialTokensToTokens[confidentialTokenAddress];
190+
function getTokenAddress(address confidentialTokenAddress) public view returns (bool, address) {
191+
bool isRevoked = isConfidentialTokenRevoked(confidentialTokenAddress);
192+
address tokenAddress = _getConfidentialTokensRegistryStorage()._confidentialTokensToTokens[
193+
confidentialTokenAddress
194+
];
195+
return (isRevoked, tokenAddress);
196+
}
197+
198+
/**
199+
* @notice Returns the array of (token address, confidential token address, is revoked) tuples.
200+
* A tuple containing a revoked confidential token is kept in the array and addresses are not
201+
* affected, only the isRevoked flag is set to true.
202+
* @return The array of (token address, confidential token address, is revoked) tuples.
203+
*/
204+
function getTokenConfidentialTokenPairs() public view returns (ConfidentialTokenPair[] memory) {
205+
return _getConfidentialTokensRegistryStorage()._tokenConfidentialTokenPairs;
174206
}
175207

176208
/**
177-
* @notice Returns true if a confidential token is revoked, false otherwise. A revoked
178-
* confidential token is not allowed to be associated with a token again.
209+
* @notice Returns true if a confidential token has been revoked, false otherwise.
179210
* @param confidentialTokenAddress The address of the confidential token.
180-
* @return True if the confidential token is revoked, false otherwise.
211+
* @return True if the confidential token has been revoked, false otherwise.
181212
*/
182213
function isConfidentialTokenRevoked(address confidentialTokenAddress) public view returns (bool) {
183214
return _getConfidentialTokensRegistryStorage()._revokedConfidentialTokens[confidentialTokenAddress];
184215
}
185216

186217
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
187218

219+
/**
220+
* @notice Validates that a confidential token supports the ERC7984 interface (0x4958f2a4) via
221+
* `supportsInterface` from ERC165.
222+
* See https://eips.ethereum.org/EIPS/eip-7984.
223+
* @param confidentialTokenAddress The address of the confidential token to validate.
224+
*/
225+
function _validateERC7984(address confidentialTokenAddress) internal view {
226+
(bool success, bytes memory returnData) = confidentialTokenAddress.staticcall(
227+
abi.encodeWithSelector(IERC165.supportsInterface.selector, bytes4(0x4958f2a4))
228+
);
229+
230+
// Check if the address supports the `supportsInterface` function
231+
if (!success || returnData.length < 32) {
232+
revert ConfidentialTokenDoesNotSupportERC165(confidentialTokenAddress);
233+
}
234+
235+
// Check if the confidential token supports the ERC7984 interface (0x4958f2a4).
236+
bool isERC7984InterfaceSupported = abi.decode(returnData, (bool));
237+
if (!isERC7984InterfaceSupported) {
238+
revert NotERC7984(confidentialTokenAddress);
239+
}
240+
}
241+
188242
function _getConfidentialTokensRegistryStorage()
189243
private
190244
pure
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
pragma solidity ^0.8.24;
3+
4+
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
5+
6+
/**
7+
* @title ERC165Mock
8+
* @notice Minimal mock contract that implements ERC165 interface for testing purposes.
9+
*/
10+
contract ERC165Mock is ERC165 {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
pragma solidity ^0.8.24;
3+
4+
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
5+
import {IERC7984} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";
6+
7+
/**
8+
* @title ERC7984Mock
9+
* @notice Minimal mock contract that implements ERC7984 interface for testing purposes.
10+
*/
11+
contract ERC7984Mock is ERC165 {
12+
/**
13+
* @dev See {IERC165-supportsInterface}.
14+
*/
15+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
16+
return interfaceId == type(IERC7984).interfaceId || super.supportsInterface(interfaceId);
17+
}
18+
}

protocol-contracts/confidential-tokens-registry/package-lock.json

Lines changed: 75 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protocol-contracts/confidential-tokens-registry/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@nomicfoundation/hardhat-network-helpers": "^1.1.0",
2424
"@nomicfoundation/hardhat-verify": "^2.1.0",
2525
"@openzeppelin/hardhat-upgrades": "^3.9.1",
26+
"@openzeppelin/confidential-contracts": "^0.3.0",
2627
"@typechain/ethers-v6": "^0.5.1",
2728
"@typechain/hardhat": "^9.1.0",
2829
"@types/chai": "^4.3.20",

0 commit comments

Comments
 (0)