@openzeppelin/confidential-contracts 0.2.0-rc.2 → 0.3.0-rc.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 (44) hide show
  1. package/build/contracts/Checkpoints.json +2 -2
  2. package/build/contracts/CheckpointsConfidential.json +3 -9
  3. package/build/contracts/{ConfidentialFungibleToken.json → ERC7984.json} +28 -33
  4. package/build/contracts/{ConfidentialFungibleTokenERC20Wrapper.json → ERC7984ERC20Wrapper.json} +34 -39
  5. package/build/contracts/ERC7984Freezable.json +666 -0
  6. package/build/contracts/ERC7984ObserverAccess.json +676 -0
  7. package/build/contracts/ERC7984Omnibus.json +994 -0
  8. package/build/contracts/ERC7984Restricted.json +677 -0
  9. package/build/contracts/ERC7984Rwa.json +1370 -0
  10. package/build/contracts/{ConfidentialFungibleTokenUtils.json → ERC7984Utils.json} +4 -4
  11. package/build/contracts/{ConfidentialFungibleTokenVotes.json → ERC7984Votes.json} +99 -81
  12. package/build/contracts/{TFHESafeMath.json → FHESafeMath.json} +4 -4
  13. package/build/contracts/HandleAccessManager.json +34 -0
  14. package/build/contracts/{IConfidentialFungibleToken.json → IERC7984.json} +15 -15
  15. package/build/contracts/{IConfidentialFungibleTokenReceiver.json → IERC7984Receiver.json} +2 -2
  16. package/build/contracts/IERC7984Rwa.json +797 -0
  17. package/build/contracts/VestingWalletCliffConfidential.json +3 -3
  18. package/build/contracts/VestingWalletConfidential.json +3 -3
  19. package/build/contracts/VestingWalletConfidentialFactory.json +18 -153
  20. package/build/contracts/VotesConfidential.json +23 -0
  21. package/finance/ERC7821WithExecutor.sol +2 -2
  22. package/finance/VestingWalletCliffConfidential.sol +15 -4
  23. package/finance/VestingWalletConfidential.sol +32 -20
  24. package/finance/VestingWalletConfidentialFactory.sol +34 -123
  25. package/governance/utils/VotesConfidential.sol +18 -4
  26. package/interfaces/{IConfidentialFungibleToken.sol → IERC7984.sol} +6 -6
  27. package/interfaces/{IConfidentialFungibleTokenReceiver.sol → IERC7984Receiver.sol} +3 -3
  28. package/interfaces/IERC7984Rwa.sol +64 -0
  29. package/package.json +4 -4
  30. package/token/{ConfidentialFungibleToken.sol → ERC7984/ERC7984.sol} +73 -84
  31. package/token/{extensions/ConfidentialFungibleTokenERC20Wrapper.sol → ERC7984/extensions/ERC7984ERC20Wrapper.sol} +36 -27
  32. package/token/ERC7984/extensions/ERC7984Freezable.sol +66 -0
  33. package/token/ERC7984/extensions/ERC7984ObserverAccess.sol +63 -0
  34. package/token/ERC7984/extensions/ERC7984Omnibus.sol +209 -0
  35. package/token/ERC7984/extensions/ERC7984Restricted.sol +118 -0
  36. package/token/ERC7984/extensions/ERC7984Rwa.sol +236 -0
  37. package/token/ERC7984/extensions/ERC7984Votes.sol +31 -0
  38. package/token/{utils/ConfidentialFungibleTokenUtils.sol → ERC7984/utils/ERC7984Utils.sol} +12 -12
  39. package/utils/FHESafeMath.sol +72 -0
  40. package/utils/HandleAccessManager.sol +29 -0
  41. package/utils/structs/CheckpointsConfidential.sol +1 -6
  42. package/utils/structs/temporary-Checkpoints.sol +2 -2
  43. package/token/extensions/ConfidentialFungibleTokenVotes.sol +0 -29
  44. package/utils/TFHESafeMath.sol +0 -37
@@ -1,14 +1,14 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.2.0-rc.2) (token/ConfidentialFungibleToken.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.3.0-rc.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 {TFHESafeMath} from "./../utils/TFHESafeMath.sol";
8
- import {ConfidentialFungibleTokenUtils} from "./utils/ConfidentialFungibleTokenUtils.sol";
6
+ import {IERC7984} from "./../../interfaces/IERC7984.sol";
7
+ import {FHESafeMath} from "./../../utils/FHESafeMath.sol";
8
+ import {ERC7984Utils} from "./utils/ERC7984Utils.sol";
9
9
 
10
10
  /**
11
- * @dev Reference implementation for {IConfidentialFungibleToken}.
11
+ * @dev Reference implementation for {IERC7984}.
12
12
  *
13
13
  * This contract implements a fungible token where balances and transfers are encrypted using the Zama fhEVM,
14
14
  * providing confidentiality to users. Token amounts are stored as encrypted, unsigned integers (`euint64`)
@@ -22,87 +22,86 @@ import {ConfidentialFungibleTokenUtils} from "./utils/ConfidentialFungibleTokenU
22
22
  * - Transfer and call pattern
23
23
  * - Safe overflow/underflow handling for FHE operations
24
24
  */
25
- abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
25
+ abstract contract ERC7984 is IERC7984 {
26
26
  mapping(address holder => euint64) private _balances;
27
27
  mapping(address holder => mapping(address spender => uint48)) private _operators;
28
- mapping(uint256 requestId => euint64 encryptedAmount) private _requestHandles;
29
28
  euint64 private _totalSupply;
30
29
  string private _name;
31
30
  string private _symbol;
32
- string private _tokenURI;
31
+ string private _contractURI;
33
32
 
34
33
  /// @dev The given receiver `receiver` is invalid for transfers.
35
- error ConfidentialFungibleTokenInvalidReceiver(address receiver);
34
+ error ERC7984InvalidReceiver(address receiver);
36
35
 
37
36
  /// @dev The given sender `sender` is invalid for transfers.
38
- error ConfidentialFungibleTokenInvalidSender(address sender);
37
+ error ERC7984InvalidSender(address sender);
39
38
 
40
39
  /// @dev The given holder `holder` is not authorized to spend on behalf of `spender`.
41
- error ConfidentialFungibleTokenUnauthorizedSpender(address holder, address spender);
40
+ error ERC7984UnauthorizedSpender(address holder, address spender);
42
41
 
43
42
  /// @dev The holder `holder` is trying to send tokens but has a balance of 0.
44
- error ConfidentialFungibleTokenZeroBalance(address holder);
43
+ error ERC7984ZeroBalance(address holder);
45
44
 
46
45
  /**
47
46
  * @dev The caller `user` does not have access to the encrypted amount `amount`.
48
47
  *
49
48
  * NOTE: Try using the equivalent transfer function with an input proof.
50
49
  */
51
- error ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(euint64 amount, address user);
50
+ error ERC7984UnauthorizedUseOfEncryptedAmount(euint64 amount, address user);
52
51
 
53
52
  /// @dev The given caller `caller` is not authorized for the current operation.
54
- error ConfidentialFungibleTokenUnauthorizedCaller(address caller);
53
+ error ERC7984UnauthorizedCaller(address caller);
55
54
 
56
55
  /// @dev The given gateway request ID `requestId` is invalid.
57
- error ConfidentialFungibleTokenInvalidGatewayRequest(uint256 requestId);
56
+ error ERC7984InvalidGatewayRequest(uint256 requestId);
58
57
 
59
- constructor(string memory name_, string memory symbol_, string memory tokenURI_) {
58
+ constructor(string memory name_, string memory symbol_, string memory contractURI_) {
60
59
  _name = name_;
61
60
  _symbol = symbol_;
62
- _tokenURI = tokenURI_;
61
+ _contractURI = contractURI_;
63
62
  }
64
63
 
65
- /// @inheritdoc IConfidentialFungibleToken
64
+ /// @inheritdoc IERC7984
66
65
  function name() public view virtual returns (string memory) {
67
66
  return _name;
68
67
  }
69
68
 
70
- /// @inheritdoc IConfidentialFungibleToken
69
+ /// @inheritdoc IERC7984
71
70
  function symbol() public view virtual returns (string memory) {
72
71
  return _symbol;
73
72
  }
74
73
 
75
- /// @inheritdoc IConfidentialFungibleToken
74
+ /// @inheritdoc IERC7984
76
75
  function decimals() public view virtual returns (uint8) {
77
76
  return 6;
78
77
  }
79
78
 
80
- /// @inheritdoc IConfidentialFungibleToken
81
- function tokenURI() public view virtual returns (string memory) {
82
- return _tokenURI;
79
+ /// @inheritdoc IERC7984
80
+ function contractURI() public view virtual returns (string memory) {
81
+ return _contractURI;
83
82
  }
84
83
 
85
- /// @inheritdoc IConfidentialFungibleToken
84
+ /// @inheritdoc IERC7984
86
85
  function confidentialTotalSupply() public view virtual returns (euint64) {
87
86
  return _totalSupply;
88
87
  }
89
88
 
90
- /// @inheritdoc IConfidentialFungibleToken
89
+ /// @inheritdoc IERC7984
91
90
  function confidentialBalanceOf(address account) public view virtual returns (euint64) {
92
91
  return _balances[account];
93
92
  }
94
93
 
95
- /// @inheritdoc IConfidentialFungibleToken
94
+ /// @inheritdoc IERC7984
96
95
  function isOperator(address holder, address spender) public view virtual returns (bool) {
97
96
  return holder == spender || block.timestamp <= _operators[holder][spender];
98
97
  }
99
98
 
100
- /// @inheritdoc IConfidentialFungibleToken
99
+ /// @inheritdoc IERC7984
101
100
  function setOperator(address operator, uint48 until) public virtual {
102
101
  _setOperator(msg.sender, operator, until);
103
102
  }
104
103
 
105
- /// @inheritdoc IConfidentialFungibleToken
104
+ /// @inheritdoc IERC7984
106
105
  function confidentialTransfer(
107
106
  address to,
108
107
  externalEuint64 encryptedAmount,
@@ -111,43 +110,37 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
111
110
  return _transfer(msg.sender, to, FHE.fromExternal(encryptedAmount, inputProof));
112
111
  }
113
112
 
114
- /// @inheritdoc IConfidentialFungibleToken
113
+ /// @inheritdoc IERC7984
115
114
  function confidentialTransfer(address to, euint64 amount) public virtual returns (euint64) {
116
- require(
117
- FHE.isAllowed(amount, msg.sender),
118
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
119
- );
115
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
120
116
  return _transfer(msg.sender, to, amount);
121
117
  }
122
118
 
123
- /// @inheritdoc IConfidentialFungibleToken
119
+ /// @inheritdoc IERC7984
124
120
  function confidentialTransferFrom(
125
121
  address from,
126
122
  address to,
127
123
  externalEuint64 encryptedAmount,
128
124
  bytes calldata inputProof
129
125
  ) public virtual returns (euint64 transferred) {
130
- require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
126
+ require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
131
127
  transferred = _transfer(from, to, FHE.fromExternal(encryptedAmount, inputProof));
132
128
  FHE.allowTransient(transferred, msg.sender);
133
129
  }
134
130
 
135
- /// @inheritdoc IConfidentialFungibleToken
131
+ /// @inheritdoc IERC7984
136
132
  function confidentialTransferFrom(
137
133
  address from,
138
134
  address to,
139
135
  euint64 amount
140
136
  ) 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));
137
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
138
+ require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
146
139
  transferred = _transfer(from, to, amount);
147
140
  FHE.allowTransient(transferred, msg.sender);
148
141
  }
149
142
 
150
- /// @inheritdoc IConfidentialFungibleToken
143
+ /// @inheritdoc IERC7984
151
144
  function confidentialTransferAndCall(
152
145
  address to,
153
146
  externalEuint64 encryptedAmount,
@@ -158,21 +151,18 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
158
151
  FHE.allowTransient(transferred, msg.sender);
159
152
  }
160
153
 
161
- /// @inheritdoc IConfidentialFungibleToken
154
+ /// @inheritdoc IERC7984
162
155
  function confidentialTransferAndCall(
163
156
  address to,
164
157
  euint64 amount,
165
158
  bytes calldata data
166
159
  ) public virtual returns (euint64 transferred) {
167
- require(
168
- FHE.isAllowed(amount, msg.sender),
169
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
170
- );
160
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
171
161
  transferred = _transferAndCall(msg.sender, to, amount, data);
172
162
  FHE.allowTransient(transferred, msg.sender);
173
163
  }
174
164
 
175
- /// @inheritdoc IConfidentialFungibleToken
165
+ /// @inheritdoc IERC7984
176
166
  function confidentialTransferFromAndCall(
177
167
  address from,
178
168
  address to,
@@ -180,29 +170,26 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
180
170
  bytes calldata inputProof,
181
171
  bytes calldata data
182
172
  ) public virtual returns (euint64 transferred) {
183
- require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
173
+ require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
184
174
  transferred = _transferAndCall(from, to, FHE.fromExternal(encryptedAmount, inputProof), data);
185
175
  FHE.allowTransient(transferred, msg.sender);
186
176
  }
187
177
 
188
- /// @inheritdoc IConfidentialFungibleToken
178
+ /// @inheritdoc IERC7984
189
179
  function confidentialTransferFromAndCall(
190
180
  address from,
191
181
  address to,
192
182
  euint64 amount,
193
183
  bytes calldata data
194
184
  ) 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));
185
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
186
+ require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
200
187
  transferred = _transferAndCall(from, to, amount, data);
201
188
  FHE.allowTransient(transferred, msg.sender);
202
189
  }
203
190
 
204
191
  /**
205
- * @dev Discloses an encrypted amount `encryptedAmount` publicly via an {IConfidentialFungibleToken-AmountDisclosed}
192
+ * @dev Discloses an encrypted amount `encryptedAmount` publicly via an {IERC7984-AmountDisclosed}
206
193
  * event. The caller and this contract must be authorized to use the encrypted amount on the ACL.
207
194
  *
208
195
  * NOTE: This is an asynchronous operation where the actual decryption happens off-chain and
@@ -210,29 +197,34 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
210
197
  */
211
198
  function discloseEncryptedAmount(euint64 encryptedAmount) public virtual {
212
199
  require(
213
- FHE.isAllowed(encryptedAmount, msg.sender) && FHE.isAllowed(encryptedAmount, address(this)),
214
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
200
+ FHE.isAllowed(encryptedAmount, msg.sender),
201
+ ERC7984UnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
215
202
  );
216
203
 
217
204
  bytes32[] memory cts = new bytes32[](1);
218
205
  cts[0] = euint64.unwrap(encryptedAmount);
219
- uint256 requestID = FHE.requestDecryption(cts, this.finalizeDiscloseEncryptedAmount.selector);
220
- _requestHandles[requestID] = encryptedAmount;
206
+ FHE.requestDecryption(cts, this.finalizeDiscloseEncryptedAmount.selector);
221
207
  }
222
208
 
223
- /// @dev May only be called by the gateway contract. Finalizes a disclose encrypted amount request.
209
+ /**
210
+ * @dev Finalizes a disclose encrypted amount request.
211
+ * For gas saving purposes, the `requestId` might not be related to a
212
+ * {discloseEncryptedAmount} request. As a result, the current {finalizeDiscloseEncryptedAmount}
213
+ * function might emit a disclosed amount related to another decryption request context.
214
+ * In this case it would only display public information
215
+ * since the handle would have already been allowed for public decryption through a previous
216
+ * `FHE.requestDecryption` call.
217
+ * The downside of this behavior is that a {finalizeDiscloseEncryptedAmount} watcher might observe
218
+ * unexpected `AmountDisclosed` events.
219
+ */
224
220
  function finalizeDiscloseEncryptedAmount(
225
221
  uint256 requestId,
226
- uint64 amount,
227
- bytes[] memory signatures
222
+ bytes calldata cleartexts,
223
+ bytes calldata decryptionProof
228
224
  ) public virtual {
229
- FHE.checkSignatures(requestId, signatures);
230
-
231
- euint64 requestHandle = _requestHandles[requestId];
232
- require(FHE.isInitialized(requestHandle), ConfidentialFungibleTokenInvalidGatewayRequest(requestId));
233
- emit AmountDisclosed(requestHandle, amount);
234
-
235
- _requestHandles[requestId] = euint64.wrap(0);
225
+ FHE.checkSignatures(requestId, cleartexts, decryptionProof);
226
+ euint64 requestHandle = euint64.wrap(FHE.loadRequestedHandles(requestId)[0]);
227
+ emit AmountDisclosed(requestHandle, abi.decode(cleartexts, (uint64)));
236
228
  }
237
229
 
238
230
  function _setOperator(address holder, address operator, uint48 until) internal virtual {
@@ -241,18 +233,18 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
241
233
  }
242
234
 
243
235
  function _mint(address to, euint64 amount) internal returns (euint64 transferred) {
244
- require(to != address(0), ConfidentialFungibleTokenInvalidReceiver(address(0)));
236
+ require(to != address(0), ERC7984InvalidReceiver(address(0)));
245
237
  return _update(address(0), to, amount);
246
238
  }
247
239
 
248
240
  function _burn(address from, euint64 amount) internal returns (euint64 transferred) {
249
- require(from != address(0), ConfidentialFungibleTokenInvalidSender(address(0)));
241
+ require(from != address(0), ERC7984InvalidSender(address(0)));
250
242
  return _update(from, address(0), amount);
251
243
  }
252
244
 
253
245
  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)));
246
+ require(from != address(0), ERC7984InvalidSender(address(0)));
247
+ require(to != address(0), ERC7984InvalidReceiver(address(0)));
256
248
  return _update(from, to, amount);
257
249
  }
258
250
 
@@ -266,14 +258,11 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
266
258
  euint64 sent = _transfer(from, to, amount);
267
259
 
268
260
  // Perform callback
269
- transferred = FHE.select(
270
- ConfidentialFungibleTokenUtils.checkOnTransferReceived(msg.sender, from, to, sent, data),
271
- sent,
272
- FHE.asEuint64(0)
273
- );
261
+ ebool success = ERC7984Utils.checkOnTransferReceived(msg.sender, from, to, sent, data);
274
262
 
275
- // Refund if success fails. refund should never fail
276
- _update(to, from, FHE.sub(sent, transferred));
263
+ // Try to refund if callback fails
264
+ euint64 refund = _update(to, from, FHE.select(success, FHE.asEuint64(0), sent));
265
+ transferred = FHE.sub(sent, refund);
277
266
  }
278
267
 
279
268
  function _update(address from, address to, euint64 amount) internal virtual returns (euint64 transferred) {
@@ -281,13 +270,13 @@ abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
281
270
  euint64 ptr;
282
271
 
283
272
  if (from == address(0)) {
284
- (success, ptr) = TFHESafeMath.tryIncrease(_totalSupply, amount);
273
+ (success, ptr) = FHESafeMath.tryIncrease(_totalSupply, amount);
285
274
  FHE.allowThis(ptr);
286
275
  _totalSupply = ptr;
287
276
  } else {
288
277
  euint64 fromBalance = _balances[from];
289
- require(FHE.isInitialized(fromBalance), ConfidentialFungibleTokenZeroBalance(from));
290
- (success, ptr) = TFHESafeMath.tryDecrease(fromBalance, amount);
278
+ require(FHE.isInitialized(fromBalance), ERC7984ZeroBalance(from));
279
+ (success, ptr) = FHESafeMath.tryDecrease(fromBalance, amount);
291
280
  FHE.allowThis(ptr);
292
281
  FHE.allow(ptr, from);
293
282
  _balances[from] = ptr;
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.2.0-rc.2) (token/extensions/ConfidentialFungibleTokenERC20Wrapper.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.3.0-rc.0) (token/ERC7984/extensions/ERC7984ERC20Wrapper.sol)
3
3
 
4
4
  pragma solidity ^0.8.27;
5
5
 
@@ -9,14 +9,17 @@ 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
+ *
19
+ * WARNING: Minting assumes the full amount of the underlying token transfer has been received, hence some non-standard
20
+ * tokens such as fee-on-transfer or other deflationary-type tokens are not supported by this wrapper.
18
21
  */
19
- abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleToken, IERC1363Receiver {
22
+ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC1363Receiver {
20
23
  IERC20 private immutable _underlying;
21
24
  uint8 private immutable _decimals;
22
25
  uint256 private immutable _rate;
@@ -38,7 +41,7 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
38
41
  }
39
42
  }
40
43
 
41
- /// @inheritdoc ConfidentialFungibleToken
44
+ /// @inheritdoc ERC7984
42
45
  function decimals() public view virtual override returns (uint8) {
43
46
  return _decimals;
44
47
  }
@@ -68,16 +71,16 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
68
71
  bytes calldata data
69
72
  ) public virtual returns (bytes4) {
70
73
  // check caller is the token contract
71
- require(address(underlying()) == msg.sender, ConfidentialFungibleTokenUnauthorizedCaller(msg.sender));
72
-
73
- // transfer excess back to the sender
74
- uint256 excess = amount % rate();
75
- if (excess > 0) SafeERC20.safeTransfer(underlying(), from, excess);
74
+ require(address(underlying()) == msg.sender, ERC7984UnauthorizedCaller(msg.sender));
76
75
 
77
76
  // mint confidential token
78
77
  address to = data.length < 20 ? from : address(bytes20(data));
79
78
  _mint(to, FHE.asEuint64(SafeCast.toUint64(amount / rate())));
80
79
 
80
+ // transfer excess back to the sender
81
+ uint256 excess = amount % rate();
82
+ if (excess > 0) SafeERC20.safeTransfer(underlying(), from, excess);
83
+
81
84
  // return magic value
82
85
  return IERC1363Receiver.onTransferReceived.selector;
83
86
  }
@@ -104,10 +107,7 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
104
107
  * NOTE: The caller *must* already be approved by ACL for the given `amount`.
105
108
  */
106
109
  function unwrap(address from, address to, euint64 amount) public virtual {
107
- require(
108
- FHE.isAllowed(amount, msg.sender),
109
- ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
110
- );
110
+ require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
111
111
  _unwrap(from, to, amount);
112
112
  }
113
113
 
@@ -125,24 +125,24 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
125
125
  }
126
126
 
127
127
  /**
128
- * @dev Called by the fhEVM gateway with the decrypted amount `amount` for a request id `requestId`.
129
- * Fills unwrap requests.
128
+ * @dev Fills an unwrap request for a given request id related to a decrypted unwrap amount.
130
129
  */
131
- function finalizeUnwrap(uint256 requestID, uint64 amount, bytes[] memory signatures) public virtual {
132
- FHE.checkSignatures(requestID, signatures);
130
+ function finalizeUnwrap(
131
+ uint256 requestID,
132
+ bytes calldata cleartexts,
133
+ bytes calldata decryptionProof
134
+ ) public virtual {
135
+ FHE.checkSignatures(requestID, cleartexts, decryptionProof);
133
136
  address to = _receivers[requestID];
134
- require(to != address(0), ConfidentialFungibleTokenInvalidGatewayRequest(requestID));
137
+ require(to != address(0), ERC7984InvalidGatewayRequest(requestID));
135
138
  delete _receivers[requestID];
136
139
 
137
- SafeERC20.safeTransfer(underlying(), to, amount * rate());
140
+ SafeERC20.safeTransfer(underlying(), to, abi.decode(cleartexts, (uint64)) * rate());
138
141
  }
139
142
 
140
143
  function _unwrap(address from, address to, euint64 amount) internal virtual {
141
- require(to != address(0), ConfidentialFungibleTokenInvalidReceiver(to));
142
- require(
143
- from == msg.sender || isOperator(from, msg.sender),
144
- ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender)
145
- );
144
+ require(to != address(0), ERC7984InvalidReceiver(to));
145
+ require(from == msg.sender || isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
146
146
 
147
147
  // try to burn, see how much we actually got
148
148
  euint64 burntAmount = _burn(from, amount);
@@ -156,6 +156,15 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
156
156
  _receivers[requestID] = to;
157
157
  }
158
158
 
159
+ /**
160
+ * @dev Returns the default number of decimals of the underlying ERC-20 token that is being wrapped.
161
+ * Used as a default fallback when {_tryGetAssetDecimals} fails to fetch decimals of the underlying
162
+ * ERC-20 token.
163
+ */
164
+ function _fallbackUnderlyingDecimals() internal pure virtual returns (uint8) {
165
+ return 18;
166
+ }
167
+
159
168
  /**
160
169
  * @dev Returns the maximum number that will be used for {decimals} by the wrapper.
161
170
  */
@@ -170,6 +179,6 @@ abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleT
170
179
  if (success && encodedDecimals.length == 32) {
171
180
  return abi.decode(encodedDecimals, (uint8));
172
181
  }
173
- return 18;
182
+ return _fallbackUnderlyingDecimals();
174
183
  }
175
184
  }
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Confidential Contracts (last updated v0.3.0-rc.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. Up to {confidentialBalanceOf}.
35
+ function confidentialAvailable(address account) public virtual returns (euint64) {
36
+ (ebool success, euint64 unfrozen) = FHESafeMath.tryDecrease(
37
+ confidentialBalanceOf(account),
38
+ confidentialFrozen(account)
39
+ );
40
+ return FHE.select(success, unfrozen, FHE.asEuint64(0));
41
+ }
42
+
43
+ /// @dev Internal function to freeze a confidential amount of tokens for an account.
44
+ function _setConfidentialFrozen(address account, euint64 encryptedAmount) internal virtual {
45
+ FHE.allowThis(encryptedAmount);
46
+ FHE.allow(encryptedAmount, account);
47
+ _frozenBalances[account] = encryptedAmount;
48
+ emit TokensFrozen(account, encryptedAmount);
49
+ }
50
+
51
+ /**
52
+ * @dev See {ERC7984-_update}.
53
+ *
54
+ * The `from` account must have sufficient unfrozen balance,
55
+ * otherwise 0 tokens are transferred.
56
+ * The default freezing behavior can be changed (for a pass-through for instance) by overriding
57
+ * {confidentialAvailable}.
58
+ */
59
+ function _update(address from, address to, euint64 encryptedAmount) internal virtual override returns (euint64) {
60
+ if (from != address(0)) {
61
+ euint64 unfrozen = confidentialAvailable(from);
62
+ encryptedAmount = FHE.select(FHE.le(encryptedAmount, unfrozen), encryptedAmount, FHE.asEuint64(0));
63
+ }
64
+ return super._update(from, to, encryptedAmount);
65
+ }
66
+ }
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Confidential Contracts (last updated v0.3.0-rc.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 => 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
+ }