@@ -3,18 +3,37 @@ pragma solidity ^0.8.27;
33
44import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol " ;
55import {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 */
1214contract 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
0 commit comments