Skip to content

Commit 9fd6946

Browse files
committed
feat(protocol-contracts): add ConfidentialTokensRegistry contract
1 parent e3b2c34 commit 9fd6946

File tree

14 files changed

+11987
-0
lines changed

14 files changed

+11987
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Authentication (use one of these)
2+
MNEMONIC=
3+
PRIVATE_KEY=
4+
5+
# RPC URLs
6+
MAINNET_RPC_URL=
7+
SEPOLIA_RPC_URL=
8+
9+
# API Keys
10+
ETHERSCAN_API_KEY=
11+
12+
# Gas Reporter
13+
REPORT_GAS=false
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build artifacts
5+
artifacts/
6+
cache/
7+
cache_forge/
8+
out/
9+
types/
10+
11+
# Coverage
12+
coverage/
13+
coverage.json
14+
15+
# Environment
16+
.env
17+
18+
# IDE
19+
.idea/
20+
.vscode/
21+
22+
# OS
23+
.DS_Store
24+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# directories
2+
.coverage_artifacts
3+
.coverage_cache
4+
.coverage_contracts
5+
artifacts
6+
build
7+
cache
8+
coverage
9+
dist
10+
node_modules
11+
types
12+
lib
13+
14+
# files
15+
*.env
16+
*.log
17+
.DS_Store
18+
.pnp.*
19+
coverage.json
20+
package-lock.json
21+
yarn.lock
22+
README.md
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"printWidth": 120,
3+
"singleQuote": true,
4+
"trailingComma": "all",
5+
"arrowParens": "avoid",
6+
"overrides": [
7+
{
8+
"files": "*.sol",
9+
"options": {
10+
"singleQuote": false
11+
}
12+
}
13+
],
14+
"plugins": ["prettier-plugin-solidity"]
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
prettier:
2+
npx prettier . --write
3+
4+
lint:
5+
npm run lint
6+
7+
compile:
8+
npx hardhat compile
9+
10+
clean:
11+
npx hardhat clean
12+
13+
# Define it as a phony target to avoid conflicts with the test directory
14+
.PHONY: test
15+
test: clean
16+
npx hardhat test --network hardhat $(if $(GREP),--grep '$(GREP)',)
17+
18+
# Conform to pre-commit checks
19+
conformance: prettier lint
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
pragma solidity ^0.8.27;
3+
4+
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
5+
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
6+
7+
/**
8+
* @title ConfidentialTokensRegistry
9+
* @notice A registry contract to map ERC20 token addresses to their ERC7984 confidential token addresses.
10+
* @dev This contract allows an owner to register new entries and flag revoked ones.
11+
*/
12+
contract ConfidentialTokensRegistry is Ownable2StepUpgradeable, UUPSUpgradeable {
13+
/// @custom:storage-location erc7201:fhevm_protocol.storage.ConfidentialTokensRegistry
14+
struct ConfidentialTokensRegistryStorage {
15+
mapping(address tokenAddress => address confidentialTokenAddress) _tokensToConfidentialTokens;
16+
mapping(address confidentialTokenAddress => address tokenAddress) _confidentialTokensToTokens;
17+
mapping(address confidentialTokenAddress => bool isRevoked) _revokedConfidentialTokens;
18+
}
19+
20+
// keccak256(abi.encode(uint256(keccak256("fhevm_protocol.storage.ConfidentialTokensRegistry")) - 1)) & ~bytes32(uint256(0xff))
21+
bytes32 private constant CONFIDENTIAL_TOKENS_REGISTRY_STORAGE_LOCATION =
22+
0x25094496394205337c7da64eaca0c35bf780125467d04de96cd0ee9b701d6c00;
23+
24+
/// @notice Error thrown when the token address is zero.
25+
error TokenZeroAddress();
26+
27+
/// @notice Error thrown when the confidential token address is zero.
28+
error ConfidentialTokenZeroAddress();
29+
30+
/// @notice Error thrown when a confidential token is already associated with a token.
31+
error ConfidentialTokenAlreadyAssociatedWithToken(address tokenAddress, address existingConfidentialTokenAddress);
32+
33+
/// @notice Error thrown when a token is already associated with a confidential token.
34+
error TokenAlreadyAssociatedWithConfidentialToken(address tokenAddress, address existingConfidentialTokenAddress);
35+
36+
/// @notice Error thrown when a confidential token is revoked.
37+
error RevokedConfidentialToken(address confidentialTokenAddress);
38+
39+
/// @notice Error thrown when no token is associated with a confidential token.
40+
error NoTokenAssociatedWithConfidentialToken(address confidentialTokenAddress);
41+
42+
/// @notice Error thrown when a confidential token is not revoked.
43+
error ConfidentialTokenNotRevoked(address confidentialTokenAddress);
44+
45+
/// @notice Emitted when a token is registered and associated with a confidential token.
46+
event ConfidentialTokenRegistered(address indexed tokenAddress, address indexed confidentialTokenAddress);
47+
48+
/// @notice Emitted when a confidential token is revoked and the association with a token is removed.
49+
event ConfidentialTokenRevoked(address indexed tokenAddress, address indexed confidentialTokenAddress);
50+
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+
55+
/// @custom:oz-upgrades-unsafe-allow constructor
56+
constructor() {
57+
_disableInitializers();
58+
}
59+
60+
/**
61+
* @notice Initialize the ConfidentialTokensRegistry contract.
62+
* @param initialOwner The initial owner of the contract.
63+
*/
64+
function initialize(address initialOwner) public initializer {
65+
__Ownable_init(initialOwner);
66+
}
67+
68+
/**
69+
* @notice Register a new ERC20 token and associate it with a validated corresponding ERC7984 confidential token.
70+
* @param tokenAddress The address of the ERC20 token contract to register.
71+
*/
72+
function registerConfidentialToken(address tokenAddress, address confidentialTokenAddress) external onlyOwner {
73+
if (tokenAddress == address(0)) {
74+
revert TokenZeroAddress();
75+
}
76+
if (confidentialTokenAddress == address(0)) {
77+
revert ConfidentialTokenZeroAddress();
78+
}
79+
80+
// The confidential token must not be revoked.
81+
if (isConfidentialTokenRevoked(confidentialTokenAddress)) {
82+
revert RevokedConfidentialToken(confidentialTokenAddress);
83+
}
84+
85+
// The confidential token must not be already associated with a token.
86+
address existingTokenAddress = getTokenAddress(confidentialTokenAddress);
87+
if (existingTokenAddress != address(0)) {
88+
revert ConfidentialTokenAlreadyAssociatedWithToken(confidentialTokenAddress, existingTokenAddress);
89+
}
90+
91+
// The token must not be already associated with a confidential token.
92+
address existingConfidentialTokenAddress = getConfidentialTokenAddress(tokenAddress);
93+
if (existingConfidentialTokenAddress != address(0)) {
94+
revert TokenAlreadyAssociatedWithConfidentialToken(tokenAddress, existingConfidentialTokenAddress);
95+
}
96+
97+
ConfidentialTokensRegistryStorage storage $ = _getConfidentialTokensRegistryStorage();
98+
99+
$._tokensToConfidentialTokens[tokenAddress] = confidentialTokenAddress;
100+
$._confidentialTokensToTokens[confidentialTokenAddress] = tokenAddress;
101+
102+
emit ConfidentialTokenRegistered(tokenAddress, confidentialTokenAddress);
103+
}
104+
105+
/**
106+
* @notice Revoke an ERC7984 confidential token.
107+
* @param confidentialTokenAddress The address of the ERC7984 confidential token to revoke.
108+
*/
109+
function revokeConfidentialToken(address confidentialTokenAddress) external onlyOwner {
110+
if (confidentialTokenAddress == address(0)) {
111+
revert ConfidentialTokenZeroAddress();
112+
}
113+
114+
// The confidential token must not be already revoked.
115+
if (isConfidentialTokenRevoked(confidentialTokenAddress)) {
116+
revert RevokedConfidentialToken(confidentialTokenAddress);
117+
}
118+
119+
// The confidential token must be associated with a token.
120+
address tokenAddress = getTokenAddress(confidentialTokenAddress);
121+
if (tokenAddress == address(0)) {
122+
revert NoTokenAssociatedWithConfidentialToken(confidentialTokenAddress);
123+
}
124+
125+
ConfidentialTokensRegistryStorage storage $ = _getConfidentialTokensRegistryStorage();
126+
127+
delete $._confidentialTokensToTokens[confidentialTokenAddress];
128+
delete $._tokensToConfidentialTokens[tokenAddress];
129+
$._revokedConfidentialTokens[confidentialTokenAddress] = true;
130+
131+
emit ConfidentialTokenRevoked(tokenAddress, confidentialTokenAddress);
132+
}
133+
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);
152+
}
153+
154+
/**
155+
* @notice Returns the address of the confidential token associated with a token.
156+
* @param tokenAddress The address of the token.
157+
* @return The address of the confidential token.
158+
*/
159+
function getConfidentialTokenAddress(address tokenAddress) public view returns (address) {
160+
return _getConfidentialTokensRegistryStorage()._tokensToConfidentialTokens[tokenAddress];
161+
}
162+
163+
/**
164+
* @notice Returns the address of the token associated with a confidential token.
165+
* @param confidentialTokenAddress The address of the confidential token.
166+
* @return The address of the token.
167+
*/
168+
function getTokenAddress(address confidentialTokenAddress) public view returns (address) {
169+
return _getConfidentialTokensRegistryStorage()._confidentialTokensToTokens[confidentialTokenAddress];
170+
}
171+
172+
/**
173+
* @notice Returns true if a confidential token is revoked, false otherwise. A revoked confidential token
174+
* is not allowed to be associated with a token again.
175+
* @param confidentialTokenAddress The address of the confidential token.
176+
* @return True if the confidential token is revoked, false otherwise.
177+
*/
178+
function isConfidentialTokenRevoked(address confidentialTokenAddress) public view returns (bool) {
179+
return _getConfidentialTokensRegistryStorage()._revokedConfidentialTokens[confidentialTokenAddress];
180+
}
181+
182+
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
183+
184+
function _getConfidentialTokensRegistryStorage()
185+
private
186+
pure
187+
returns (ConfidentialTokensRegistryStorage storage $)
188+
{
189+
assembly {
190+
$.slot := CONFIDENTIAL_TOKENS_REGISTRY_STORAGE_LOCATION
191+
}
192+
}
193+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[profile.default]
2+
src = 'contracts'
3+
libs = ['node_modules']
4+
solc = '0.8.27'
5+
optimizer_runs = 200
6+

0 commit comments

Comments
 (0)