@openzeppelin/confidential-contracts 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/build/contracts/Checkpoints.json +2 -2
  2. package/build/contracts/CheckpointsConfidential.json +2 -2
  3. package/build/contracts/{ConfidentialFungibleToken.json → ERC7984.json} +87 -58
  4. package/build/contracts/{ConfidentialFungibleTokenERC20Wrapper.json → ERC7984ERC20Wrapper.json} +143 -59
  5. package/build/contracts/ERC7984Freezable.json +700 -0
  6. package/build/contracts/ERC7984ObserverAccess.json +710 -0
  7. package/build/contracts/ERC7984Omnibus.json +1028 -0
  8. package/build/contracts/ERC7984Restricted.json +711 -0
  9. package/build/contracts/ERC7984Rwa.json +1385 -0
  10. package/build/contracts/{ConfidentialFungibleTokenUtils.json → ERC7984Utils.json} +4 -4
  11. package/build/contracts/{ConfidentialFungibleTokenVotes.json → ERC7984Votes.json} +142 -113
  12. package/build/contracts/FHESafeMath.json +2 -2
  13. package/build/contracts/{IConfidentialFungibleToken.json → IERC7984.json} +26 -7
  14. package/build/contracts/{IConfidentialFungibleTokenReceiver.json → IERC7984Receiver.json} +2 -2
  15. package/build/contracts/IERC7984Rwa.json +797 -0
  16. package/build/contracts/VestingWalletConfidentialFactory.json +2 -2
  17. package/finance/ERC7821WithExecutor.sol +3 -4
  18. package/finance/VestingWalletCliffConfidential.sol +3 -4
  19. package/finance/VestingWalletConfidential.sol +8 -12
  20. package/finance/VestingWalletConfidentialFactory.sol +7 -12
  21. package/interfaces/{IConfidentialFungibleToken.sol → IERC7984.sol} +6 -5
  22. package/interfaces/{IConfidentialFungibleTokenReceiver.sol → IERC7984Receiver.sol} +3 -3
  23. package/interfaces/IERC7984Rwa.sol +63 -0
  24. package/package.json +4 -4
  25. package/token/{ConfidentialFungibleToken.sol → ERC7984/ERC7984.sol} +81 -82
  26. package/token/{extensions/ConfidentialFungibleTokenERC20Wrapper.sol → ERC7984/extensions/ERC7984ERC20Wrapper.sol} +40 -35
  27. package/token/ERC7984/extensions/ERC7984Freezable.sol +75 -0
  28. package/token/ERC7984/extensions/ERC7984ObserverAccess.sol +63 -0
  29. package/token/ERC7984/extensions/ERC7984Omnibus.sol +209 -0
  30. package/token/ERC7984/extensions/ERC7984Restricted.sol +110 -0
  31. package/token/ERC7984/extensions/ERC7984Rwa.sol +248 -0
  32. package/token/{extensions/ConfidentialFungibleTokenVotes.sol → ERC7984/extensions/ERC7984Votes.sol} +8 -14
  33. package/token/{utils/ConfidentialFungibleTokenUtils.sol → ERC7984/utils/ERC7984Utils.sol} +14 -13
  34. package/utils/FHESafeMath.sol +45 -7
  35. package/utils/structs/temporary-Checkpoints.sol +2 -2
@@ -1,14 +1,16 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.2.0) (token/ConfidentialFungibleToken.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.3.0) (token/ERC7984/ERC7984.sol)
3
3
  pragma solidity ^0.8.27;
4
4
 
5
5
  import {FHE, externalEuint64, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
6
- import {IConfidentialFungibleToken} from "./../interfaces/IConfidentialFungibleToken.sol";
7
- import {FHESafeMath} from "./../utils/FHESafeMath.sol";
8
- import {ConfidentialFungibleTokenUtils} from "./utils/ConfidentialFungibleTokenUtils.sol";
6
+ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
7
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
8
+ import {IERC7984} from "./../../interfaces/IERC7984.sol";
9
+ import {FHESafeMath} from "./../../utils/FHESafeMath.sol";
10
+ import {ERC7984Utils} from "./utils/ERC7984Utils.sol";
9
11
 
10
12
  /**
11
- * @dev Reference implementation for {IConfidentialFungibleToken}.
13
+ * @dev Reference implementation for {IERC7984}.
12
14
  *
13
15
  * This contract implements a fungible token where balances and transfers are encrypted using the Zama fhEVM,
14
16
  * providing confidentiality to users. Token amounts are stored as encrypted, unsigned integers (`euint64`)
@@ -22,87 +24,94 @@ import {ConfidentialFungibleTokenUtils} from "./utils/ConfidentialFungibleTokenU
22
24
  * - Transfer and call pattern
23
25
  * - Safe overflow/underflow handling for FHE operations
24
26
  */
25
- abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
27
+ abstract contract ERC7984 is IERC7984, ERC165 {
26
28
  mapping(address holder => euint64) private _balances;
27
29
  mapping(address holder => mapping(address spender => uint48)) private _operators;
28
- mapping(uint256 requestId => euint64 encryptedAmount) private _requestHandles;
29
30
  euint64 private _totalSupply;
30
31
  string private _name;
31
32
  string private _symbol;
32
- string private _tokenURI;
33
+ string private _contractURI;
34
+
35
+ /// @dev Emitted when an encrypted amount `encryptedAmount` is requested for disclosure by `requester`.
36
+ event AmountDiscloseRequested(euint64 indexed encryptedAmount, address indexed requester);
33
37
 
34
38
  /// @dev The given receiver `receiver` is invalid for transfers.
35
- error ConfidentialFungibleTokenInvalidReceiver(address receiver);
39
+ error ERC7984InvalidReceiver(address receiver);
36
40
 
37
41
  /// @dev The given sender `sender` is invalid for transfers.
38
- error ConfidentialFungibleTokenInvalidSender(address sender);
42
+ error ERC7984InvalidSender(address sender);
39
43
 
40
44
  /// @dev The given holder `holder` is not authorized to spend on behalf of `spender`.
41
- error ConfidentialFungibleTokenUnauthorizedSpender(address holder, address spender);
45
+ error ERC7984UnauthorizedSpender(address holder, address spender);
42
46
 
43
47
  /// @dev The holder `holder` is trying to send tokens but has a balance of 0.
44
- error ConfidentialFungibleTokenZeroBalance(address holder);
48
+ error ERC7984ZeroBalance(address holder);
45
49
 
46
50
  /**
47
51
  * @dev The caller `user` does not have access to the encrypted amount `amount`.
48
52
  *
49
53
  * NOTE: Try using the equivalent transfer function with an input proof.
50
54
  */
51
- error ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(euint64 amount, address user);
55
+ error ERC7984UnauthorizedUseOfEncryptedAmount(euint64 amount, address user);
52
56
 
53
57
  /// @dev The given caller `caller` is not authorized for the current operation.
54
- error ConfidentialFungibleTokenUnauthorizedCaller(address caller);
58
+ error ERC7984UnauthorizedCaller(address caller);
55
59
 
56
60
  /// @dev The given gateway request ID `requestId` is invalid.
57
- error ConfidentialFungibleTokenInvalidGatewayRequest(uint256 requestId);
61
+ error ERC7984InvalidGatewayRequest(uint256 requestId);
58
62
 
59
- constructor(string memory name_, string memory symbol_, string memory tokenURI_) {
63
+ constructor(string memory name_, string memory symbol_, string memory contractURI_) {
60
64
  _name = name_;
61
65
  _symbol = symbol_;
62
- _tokenURI = tokenURI_;
66
+ _contractURI = contractURI_;
67
+ }
68
+
69
+ /// @inheritdoc ERC165
70
+ function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
71
+ return interfaceId == type(IERC7984).interfaceId || super.supportsInterface(interfaceId);
63
72
  }
64
73
 
65
- /// @inheritdoc IConfidentialFungibleToken
74
+ /// @inheritdoc IERC7984
66
75
  function name() public view virtual returns (string memory) {
67
76
  return _name;
68
77
  }
69
78
 
70
- /// @inheritdoc IConfidentialFungibleToken
79
+ /// @inheritdoc IERC7984
71
80
  function symbol() public view virtual returns (string memory) {
72
81
  return _symbol;
73
82
  }
74
83
 
75
- /// @inheritdoc IConfidentialFungibleToken
84
+ /// @inheritdoc IERC7984
76
85
  function decimals() public view virtual returns (uint8) {
77
86
  return 6;
78
87
  }
79
88
 
80
- /// @inheritdoc IConfidentialFungibleToken
81
- function tokenURI() public view virtual returns (string memory) {
82
- return _tokenURI;
89
+ /// @inheritdoc IERC7984
90
+ function contractURI() public view virtual returns (string memory) {
91
+ return _contractURI;
83
92
  }
84
93
 
85
- /// @inheritdoc IConfidentialFungibleToken
94
+ /// @inheritdoc IERC7984
86
95
  function confidentialTotalSupply() public view virtual returns (euint64) {
87
96
  return _totalSupply;
88
97
  }
89
98
 
90
- /// @inheritdoc IConfidentialFungibleToken
99
+ /// @inheritdoc IERC7984
91
100
  function confidentialBalanceOf(address account) public view virtual returns (euint64) {
92
101
  return _balances[account];
93
102
  }
94
103
 
95
- /// @inheritdoc IConfidentialFungibleToken
104
+ /// @inheritdoc IERC7984
96
105
  function isOperator(address holder, address spender) public view virtual returns (bool) {
97
106
  return holder == spender || block.timestamp <= _operators[holder][spender];
98
107
  }
99
108
 
100
- /// @inheritdoc IConfidentialFungibleToken
109
+ /// @inheritdoc IERC7984
101
110
  function setOperator(address operator, uint48 until) public virtual {
102
111
  _setOperator(msg.sender, operator, until);
103
112
  }
104
113
 
105
- /// @inheritdoc IConfidentialFungibleToken
114
+ /// @inheritdoc IERC7984
106
115
  function confidentialTransfer(
107
116
  address to,
108
117
  externalEuint64 encryptedAmount,
@@ -111,43 +120,37 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
111
120
  return _transfer(msg.sender, to, FHE.fromExternal(encryptedAmount, inputProof));
112
121
  }
113
122
 
114
- /// @inheritdoc IConfidentialFungibleToken
123
+ /// @inheritdoc IERC7984
115
124
  function confidentialTransfer(address to, euint64 amount) public virtual returns (euint64) {
116
- require(
117
- FHE.isAllowed(amount, msg.sender),
118
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
119
- );
125
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
120
126
  return _transfer(msg.sender, to, amount);
121
127
  }
122
128
 
123
- /// @inheritdoc IConfidentialFungibleToken
129
+ /// @inheritdoc IERC7984
124
130
  function confidentialTransferFrom(
125
131
  address from,
126
132
  address to,
127
133
  externalEuint64 encryptedAmount,
128
134
  bytes calldata inputProof
129
135
  ) public virtual returns (euint64 transferred) {
130
- require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
136
+ require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
131
137
  transferred = _transfer(from, to, FHE.fromExternal(encryptedAmount, inputProof));
132
138
  FHE.allowTransient(transferred, msg.sender);
133
139
  }
134
140
 
135
- /// @inheritdoc IConfidentialFungibleToken
141
+ /// @inheritdoc IERC7984
136
142
  function confidentialTransferFrom(
137
143
  address from,
138
144
  address to,
139
145
  euint64 amount
140
146
  ) public virtual returns (euint64 transferred) {
141
- require(
142
- FHE.isAllowed(amount, msg.sender),
143
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
144
- );
145
- require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
147
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
148
+ require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
146
149
  transferred = _transfer(from, to, amount);
147
150
  FHE.allowTransient(transferred, msg.sender);
148
151
  }
149
152
 
150
- /// @inheritdoc IConfidentialFungibleToken
153
+ /// @inheritdoc IERC7984
151
154
  function confidentialTransferAndCall(
152
155
  address to,
153
156
  externalEuint64 encryptedAmount,
@@ -158,21 +161,18 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
158
161
  FHE.allowTransient(transferred, msg.sender);
159
162
  }
160
163
 
161
- /// @inheritdoc IConfidentialFungibleToken
164
+ /// @inheritdoc IERC7984
162
165
  function confidentialTransferAndCall(
163
166
  address to,
164
167
  euint64 amount,
165
168
  bytes calldata data
166
169
  ) public virtual returns (euint64 transferred) {
167
- require(
168
- FHE.isAllowed(amount, msg.sender),
169
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
170
- );
170
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
171
171
  transferred = _transferAndCall(msg.sender, to, amount, data);
172
172
  FHE.allowTransient(transferred, msg.sender);
173
173
  }
174
174
 
175
- /// @inheritdoc IConfidentialFungibleToken
175
+ /// @inheritdoc IERC7984
176
176
  function confidentialTransferFromAndCall(
177
177
  address from,
178
178
  address to,
@@ -180,59 +180,58 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
180
180
  bytes calldata inputProof,
181
181
  bytes calldata data
182
182
  ) public virtual returns (euint64 transferred) {
183
- require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
183
+ require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
184
184
  transferred = _transferAndCall(from, to, FHE.fromExternal(encryptedAmount, inputProof), data);
185
185
  FHE.allowTransient(transferred, msg.sender);
186
186
  }
187
187
 
188
- /// @inheritdoc IConfidentialFungibleToken
188
+ /// @inheritdoc IERC7984
189
189
  function confidentialTransferFromAndCall(
190
190
  address from,
191
191
  address to,
192
192
  euint64 amount,
193
193
  bytes calldata data
194
194
  ) public virtual returns (euint64 transferred) {
195
- require(
196
- FHE.isAllowed(amount, msg.sender),
197
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
198
- );
199
- require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
195
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
196
+ require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
200
197
  transferred = _transferAndCall(from, to, amount, data);
201
198
  FHE.allowTransient(transferred, msg.sender);
202
199
  }
203
200
 
204
201
  /**
205
- * @dev Discloses an encrypted amount `encryptedAmount` publicly via an {IConfidentialFungibleToken-AmountDisclosed}
206
- * event. The caller and this contract must be authorized to use the encrypted amount on the ACL.
202
+ * @dev Starts the process to disclose an encrypted amount `encryptedAmount` publicly by making it
203
+ * publicly decryptable. Emits the {AmountDiscloseRequested} event.
207
204
  *
208
- * NOTE: This is an asynchronous operation where the actual decryption happens off-chain and
209
- * {finalizeDiscloseEncryptedAmount} is called with the result.
205
+ * NOTE: Both `msg.sender` and `address(this)` must have permission to access the encrypted amount
206
+ * `encryptedAmount` to request disclosure of the encrypted amount `encryptedAmount`.
210
207
  */
211
- function discloseEncryptedAmount(euint64 encryptedAmount) public virtual {
208
+ function requestDiscloseEncryptedAmount(euint64 encryptedAmount) public virtual {
212
209
  require(
213
- FHE.isAllowed(encryptedAmount, msg.sender) && FHE.isAllowed(encryptedAmount, address(this)),
214
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
210
+ FHE.isAllowed(encryptedAmount, msg.sender),
211
+ ERC7984UnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
215
212
  );
216
213
 
217
- bytes32[] memory cts = new bytes32[](1);
218
- cts[0] = euint64.unwrap(encryptedAmount);
219
- uint256 requestID = FHE.requestDecryption(cts, this.finalizeDiscloseEncryptedAmount.selector);
220
- _requestHandles[requestID] = encryptedAmount;
214
+ FHE.makePubliclyDecryptable(encryptedAmount);
215
+ emit AmountDiscloseRequested(encryptedAmount, msg.sender);
221
216
  }
222
217
 
223
- /// @dev Finalizes a disclose encrypted amount request.
224
- function finalizeDiscloseEncryptedAmount(
225
- uint256 requestId,
226
- uint64 amount,
227
- bytes[] memory signatures
218
+ /**
219
+ * @dev Publicly discloses an encrypted value with a given decryption proof. Emits the {AmountDisclosed} event.
220
+ *
221
+ * NOTE: May not be tied to a prior request via {requestDiscloseEncryptedAmount}.
222
+ */
223
+ function discloseEncryptedAmount(
224
+ euint64 encryptedAmount,
225
+ uint64 cleartextAmount,
226
+ bytes calldata decryptionProof
228
227
  ) public virtual {
229
- FHE.checkSignatures(requestId, signatures);
228
+ bytes32[] memory handles = new bytes32[](1);
229
+ handles[0] = euint64.unwrap(encryptedAmount);
230
230
 
231
- euint64 requestHandle = _requestHandles[requestId];
232
- require(FHE.isInitialized(requestHandle), ConfidentialFungibleTokenInvalidGatewayRequest(requestId));
233
- emit AmountDisclosed(requestHandle, amount);
231
+ bytes memory cleartextMemory = abi.encode(cleartextAmount);
234
232
 
235
- _requestHandles[requestId] = euint64.wrap(0);
233
+ FHE.checkSignatures(handles, cleartextMemory, decryptionProof);
234
+ emit AmountDisclosed(encryptedAmount, cleartextAmount);
236
235
  }
237
236
 
238
237
  function _setOperator(address holder, address operator, uint48 until) internal virtual {
@@ -241,18 +240,18 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
241
240
  }
242
241
 
243
242
  function _mint(address to, euint64 amount) internal returns (euint64 transferred) {
244
- require(to != address(0), ConfidentialFungibleTokenInvalidReceiver(address(0)));
243
+ require(to != address(0), ERC7984InvalidReceiver(address(0)));
245
244
  return _update(address(0), to, amount);
246
245
  }
247
246
 
248
247
  function _burn(address from, euint64 amount) internal returns (euint64 transferred) {
249
- require(from != address(0), ConfidentialFungibleTokenInvalidSender(address(0)));
248
+ require(from != address(0), ERC7984InvalidSender(address(0)));
250
249
  return _update(from, address(0), amount);
251
250
  }
252
251
 
253
252
  function _transfer(address from, address to, euint64 amount) internal returns (euint64 transferred) {
254
- require(from != address(0), ConfidentialFungibleTokenInvalidSender(address(0)));
255
- require(to != address(0), ConfidentialFungibleTokenInvalidReceiver(address(0)));
253
+ require(from != address(0), ERC7984InvalidSender(address(0)));
254
+ require(to != address(0), ERC7984InvalidReceiver(address(0)));
256
255
  return _update(from, to, amount);
257
256
  }
258
257
 
@@ -266,7 +265,7 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
266
265
  euint64 sent = _transfer(from, to, amount);
267
266
 
268
267
  // Perform callback
269
- ebool success = ConfidentialFungibleTokenUtils.checkOnTransferReceived(msg.sender, from, to, sent, data);
268
+ ebool success = ERC7984Utils.checkOnTransferReceived(msg.sender, from, to, sent, data);
270
269
 
271
270
  // Try to refund if callback fails
272
271
  euint64 refund = _update(to, from, FHE.select(success, FHE.asEuint64(0), sent));
@@ -283,7 +282,7 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
283
282
  _totalSupply = ptr;
284
283
  } else {
285
284
  euint64 fromBalance = _balances[from];
286
- require(FHE.isInitialized(fromBalance), ConfidentialFungibleTokenZeroBalance(from));
285
+ require(FHE.isInitialized(fromBalance), ERC7984ZeroBalance(from));
287
286
  (success, ptr) = FHESafeMath.tryDecrease(fromBalance, amount);
288
287
  FHE.allowThis(ptr);
289
288
  FHE.allow(ptr, from);
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.2.0) (token/extensions/ConfidentialFungibleTokenERC20Wrapper.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.3.0) (token/ERC7984/extensions/ERC7984ERC20Wrapper.sol)
3
3
 
4
4
  pragma solidity ^0.8.27;
5
5
 
@@ -9,23 +9,27 @@ import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
9
9
  import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
10
10
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
11
11
  import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
12
- import {ConfidentialFungibleToken} from "./../ConfidentialFungibleToken.sol";
12
+ import {ERC7984} from "./../ERC7984.sol";
13
13
 
14
14
  /**
15
- * @dev A wrapper contract built on top of {ConfidentialFungibleToken} that allows wrapping an `ERC20` token
16
- * into a confidential fungible token. The wrapper contract implements the `IERC1363Receiver` interface
15
+ * @dev A wrapper contract built on top of {ERC7984} that allows wrapping an `ERC20` token
16
+ * into an `ERC7984` token. The wrapper contract implements the `IERC1363Receiver` interface
17
17
  * which allows users to transfer `ERC1363` tokens directly to the wrapper with a callback to wrap the tokens.
18
18
  *
19
19
  * WARNING: Minting assumes the full amount of the underlying token transfer has been received, hence some non-standard
20
20
  * tokens such as fee-on-transfer or other deflationary-type tokens are not supported by this wrapper.
21
21
  */
22
- abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleToken, IERC1363Receiver {
22
+ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
23
23
  IERC20 private immutable _underlying;
24
24
  uint8 private immutable _decimals;
25
25
  uint256 private immutable _rate;
26
26
 
27
- /// @dev Mapping from gateway decryption request ID to the address that will receive the tokens
28
- mapping(uint256 decryptionRequest => address) private _receivers;
27
+ mapping(euint64 unwrapAmount => address recipient) private _unwrapRequests;
28
+
29
+ event UnwrapRequested(address indexed receiver, euint64 amount);
30
+ event UnwrapFinalized(address indexed receiver, euint64 encryptedAmount, uint64 cleartextAmount);
31
+
32
+ error InvalidUnwrapRequest(euint64 amount);
29
33
 
30
34
  constructor(IERC20 underlying_) {
31
35
  _underlying = underlying_;
@@ -41,7 +45,7 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
41
45
  }
42
46
  }
43
47
 
44
- /// @inheritdoc ConfidentialFungibleToken
48
+ /// @inheritdoc ERC7984
45
49
  function decimals() public view virtual override returns (uint8) {
46
50
  return _decimals;
47
51
  }
@@ -71,7 +75,7 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
71
75
  bytes calldata data
72
76
  ) public virtual returns (bytes4) {
73
77
  // check caller is the token contract
74
- require(address(underlying()) == msg.sender, ConfidentialFungibleTokenUnauthorizedCaller(msg.sender));
78
+ require(address(underlying()) == msg.sender, ERC7984UnauthorizedCaller(msg.sender));
75
79
 
76
80
  // mint confidential token
77
81
  address to = data.length < 20 ? from : address(bytes20(data));
@@ -102,15 +106,11 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
102
106
  * @dev Unwraps tokens from `from` and sends the underlying tokens to `to`. The caller must be `from`
103
107
  * or be an approved operator for `from`. `amount * rate()` underlying tokens are sent to `to`.
104
108
  *
105
- * NOTE: This is an asynchronous function and waits for decryption to be completed off-chain before disbursing
106
- * tokens.
109
+ * NOTE: The unwrap request created by this function must be finalized by calling {finalizeUnwrap}.
107
110
  * NOTE: The caller *must* already be approved by ACL for the given `amount`.
108
111
  */
109
112
  function unwrap(address from, address to, euint64 amount) public virtual {
110
- require(
111
- FHE.isAllowed(amount, msg.sender),
112
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
113
- );
113
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
114
114
  _unwrap(from, to, amount);
115
115
  }
116
116
 
@@ -127,35 +127,40 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
127
127
  _unwrap(from, to, FHE.fromExternal(encryptedAmount, inputProof));
128
128
  }
129
129
 
130
- /**
131
- * @dev Fills an unwrap request for a given request id related to a decrypted unwrap amount.
132
- */
133
- function finalizeUnwrap(uint256 requestID, uint64 amount, bytes[] memory signatures) public virtual {
134
- FHE.checkSignatures(requestID, signatures);
135
- address to = _receivers[requestID];
136
- require(to != address(0), ConfidentialFungibleTokenInvalidGatewayRequest(requestID));
137
- delete _receivers[requestID];
130
+ /// @dev Fills an unwrap request for a given cipher-text `burntAmount` with the `cleartextAmount` and `decryptionProof`.
131
+ function finalizeUnwrap(
132
+ euint64 burntAmount,
133
+ uint64 burntAmountCleartext,
134
+ bytes calldata decryptionProof
135
+ ) public virtual {
136
+ address to = _unwrapRequests[burntAmount];
137
+ require(to != address(0), InvalidUnwrapRequest(burntAmount));
138
+ delete _unwrapRequests[burntAmount];
138
139
 
139
- SafeERC20.safeTransfer(underlying(), to, amount * rate());
140
+ bytes32[] memory handles = new bytes32[](1);
141
+ handles[0] = euint64.unwrap(burntAmount);
142
+
143
+ bytes memory cleartexts = abi.encode(burntAmountCleartext);
144
+
145
+ FHE.checkSignatures(handles, cleartexts, decryptionProof);
146
+
147
+ SafeERC20.safeTransfer(underlying(), to, burntAmountCleartext * rate());
148
+
149
+ emit UnwrapFinalized(to, burntAmount, burntAmountCleartext);
140
150
  }
141
151
 
142
152
  function _unwrap(address from, address to, euint64 amount) internal virtual {
143
- require(to != address(0), ConfidentialFungibleTokenInvalidReceiver(to));
144
- require(
145
- from == msg.sender || isOperator(from, msg.sender),
146
- ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender)
147
- );
153
+ require(to != address(0), ERC7984InvalidReceiver(to));
154
+ require(from == msg.sender || isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
148
155
 
149
156
  // try to burn, see how much we actually got
150
157
  euint64 burntAmount = _burn(from, amount);
158
+ FHE.makePubliclyDecryptable(burntAmount);
151
159
 
152
- // decrypt that burntAmount
153
- bytes32[] memory cts = new bytes32[](1);
154
- cts[0] = euint64.unwrap(burntAmount);
155
- uint256 requestID = FHE.requestDecryption(cts, this.finalizeUnwrap.selector);
160
+ assert(_unwrapRequests[burntAmount] == address(0));
161
+ _unwrapRequests[burntAmount] = to;
156
162
 
157
- // register who is getting the tokens
158
- _receivers[requestID] = to;
163
+ emit UnwrapRequested(to, burntAmount);
159
164
  }
160
165
 
161
166
  /**
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Confidential Contracts (last updated v0.3.0) (token/ERC7984/extensions/ERC7984Freezable.sol)
3
+
4
+ pragma solidity ^0.8.27;
5
+
6
+ import {FHE, ebool, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
7
+ import {FHESafeMath} from "../../../utils/FHESafeMath.sol";
8
+ import {ERC7984} from "../ERC7984.sol";
9
+
10
+ /**
11
+ * @dev Extension of {ERC7984} that implements a confidential
12
+ * freezing mechanism that can be managed by an authorized account with
13
+ * {setConfidentialFrozen} functions.
14
+ *
15
+ * The freezing mechanism provides the guarantee to the contract owner
16
+ * (e.g. a DAO or a well-configured multisig) that a specific confidential
17
+ * amount of tokens held by an account won't be transferable until those
18
+ * tokens are unfrozen.
19
+ *
20
+ * Inspired by https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Freezable.sol
21
+ */
22
+ abstract contract ERC7984Freezable is ERC7984 {
23
+ /// @dev Confidential frozen amount of tokens per address.
24
+ mapping(address account => euint64 encryptedAmount) private _frozenBalances;
25
+
26
+ /// @dev Emitted when a confidential amount of token is frozen for an account
27
+ event TokensFrozen(address indexed account, euint64 encryptedAmount);
28
+
29
+ /// @dev Returns the confidential frozen balance of an account.
30
+ function confidentialFrozen(address account) public view virtual returns (euint64) {
31
+ return _frozenBalances[account];
32
+ }
33
+
34
+ /// @dev Returns the confidential available (unfrozen) balance of an account. Gives ACL allowance to `account`.
35
+ function confidentialAvailable(address account) public virtual returns (euint64) {
36
+ euint64 amount = _confidentialAvailable(account);
37
+ FHE.allowThis(amount);
38
+ FHE.allow(amount, account);
39
+ return amount;
40
+ }
41
+
42
+ /// @dev Internal function to calculate the available balance of an account. Does not give any allowances.
43
+ function _confidentialAvailable(address account) internal virtual returns (euint64) {
44
+ (ebool success, euint64 unfrozen) = FHESafeMath.tryDecrease(
45
+ confidentialBalanceOf(account),
46
+ confidentialFrozen(account)
47
+ );
48
+ return FHE.select(success, unfrozen, FHE.asEuint64(0));
49
+ }
50
+
51
+ /// @dev Internal function to freeze a confidential amount of tokens for an account.
52
+ function _setConfidentialFrozen(address account, euint64 encryptedAmount) internal virtual {
53
+ FHE.allowThis(encryptedAmount);
54
+ FHE.allow(encryptedAmount, account);
55
+ _frozenBalances[account] = encryptedAmount;
56
+ emit TokensFrozen(account, encryptedAmount);
57
+ }
58
+
59
+ /**
60
+ * @dev See {ERC7984-_update}.
61
+ *
62
+ * The `from` account must have sufficient unfrozen balance,
63
+ * otherwise 0 tokens are transferred.
64
+ * The default freezing behavior can be changed (for a pass-through for instance) by overriding
65
+ * {_confidentialAvailable}. The internal function is used for actual gating (not the public function)
66
+ * to avoid unnecessarily granting ACL allowances.
67
+ */
68
+ function _update(address from, address to, euint64 encryptedAmount) internal virtual override returns (euint64) {
69
+ if (from != address(0)) {
70
+ euint64 unfrozen = _confidentialAvailable(from);
71
+ encryptedAmount = FHE.select(FHE.le(encryptedAmount, unfrozen), encryptedAmount, FHE.asEuint64(0));
72
+ }
73
+ return super._update(from, to, encryptedAmount);
74
+ }
75
+ }
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Confidential Contracts (last updated v0.3.0) (token/ERC7984/extensions/ERC7984ObserverAccess.sol)
3
+
4
+ pragma solidity ^0.8.27;
5
+
6
+ import {FHE, euint64} from "@fhevm/solidity/lib/FHE.sol";
7
+ import {ERC7984} from "../ERC7984.sol";
8
+
9
+ /**
10
+ * @dev Extension of {ERC7984} that allows each account to add a observer who is given
11
+ * permanent ACL access to its transfer and balance amounts. A observer can be added or removed at any point in time.
12
+ */
13
+ abstract contract ERC7984ObserverAccess is ERC7984 {
14
+ mapping(address account => address) private _observers;
15
+
16
+ /// @dev Emitted when the observer is changed for the given account `account`.
17
+ event ERC7984ObserverAccessObserverSet(address account, address oldObserver, address newObserver);
18
+
19
+ /// @dev Thrown when an account tries to set a `newObserver` for a given `account` without proper authority.
20
+ error Unauthorized();
21
+
22
+ /**
23
+ * @dev Sets the observer for the given account `account` to `newObserver`. Can be called by the
24
+ * account or the existing observer to abdicate the observer role (may only set to `address(0)`).
25
+ */
26
+ function setObserver(address account, address newObserver) public virtual {
27
+ address oldObserver = observer(account);
28
+ require(msg.sender == account || (msg.sender == oldObserver && newObserver == address(0)), Unauthorized());
29
+ if (oldObserver != newObserver) {
30
+ if (newObserver != address(0)) {
31
+ euint64 balanceHandle = confidentialBalanceOf(account);
32
+ if (FHE.isInitialized(balanceHandle)) {
33
+ FHE.allow(balanceHandle, newObserver);
34
+ }
35
+ }
36
+
37
+ emit ERC7984ObserverAccessObserverSet(account, oldObserver, _observers[account] = newObserver);
38
+ }
39
+ }
40
+
41
+ /// @dev Returns the observer for the given account `account`.
42
+ function observer(address account) public view virtual returns (address) {
43
+ return _observers[account];
44
+ }
45
+
46
+ function _update(address from, address to, euint64 amount) internal virtual override returns (euint64 transferred) {
47
+ transferred = super._update(from, to, amount);
48
+
49
+ address fromObserver = observer(from);
50
+ address toObserver = observer(to);
51
+
52
+ if (fromObserver != address(0)) {
53
+ FHE.allow(confidentialBalanceOf(from), fromObserver);
54
+ FHE.allow(transferred, fromObserver);
55
+ }
56
+ if (toObserver != address(0)) {
57
+ FHE.allow(confidentialBalanceOf(to), toObserver);
58
+ if (toObserver != fromObserver) {
59
+ FHE.allow(transferred, toObserver);
60
+ }
61
+ }
62
+ }
63
+ }