@openzeppelin/confidential-contracts 0.3.1 → 0.4.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 (36) hide show
  1. package/README.md +36 -7
  2. package/build/contracts/BatcherConfidential.json +544 -0
  3. package/build/contracts/CheckpointsConfidential.json +2 -2
  4. package/build/contracts/ERC7984.json +16 -0
  5. package/build/contracts/ERC7984ERC20Wrapper.json +93 -9
  6. package/build/contracts/ERC7984Freezable.json +16 -0
  7. package/build/contracts/ERC7984ObserverAccess.json +16 -0
  8. package/build/contracts/ERC7984Omnibus.json +16 -0
  9. package/build/contracts/ERC7984Restricted.json +35 -19
  10. package/build/contracts/ERC7984Rwa.json +35 -19
  11. package/build/contracts/ERC7984Utils.json +2 -2
  12. package/build/contracts/ERC7984Votes.json +32 -0
  13. package/build/contracts/FHESafeMath.json +2 -2
  14. package/build/contracts/HandleAccessManager.json +16 -0
  15. package/build/contracts/IERC7984ERC20Wrapper.json +659 -0
  16. package/build/contracts/IERC7984Rwa.json +19 -19
  17. package/build/contracts/VestingWalletConfidentialFactory.json +16 -0
  18. package/build/contracts/VotesConfidential.json +16 -0
  19. package/finance/BatcherConfidential.sol +450 -0
  20. package/finance/VestingWalletConfidential.sol +3 -3
  21. package/governance/utils/VotesConfidential.sol +5 -4
  22. package/interfaces/IERC7984ERC20Wrapper.sol +62 -0
  23. package/interfaces/IERC7984Receiver.sol +4 -2
  24. package/interfaces/IERC7984Rwa.sol +2 -2
  25. package/package.json +4 -4
  26. package/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol +81 -55
  27. package/token/ERC7984/extensions/ERC7984Freezable.sol +4 -5
  28. package/token/ERC7984/extensions/ERC7984ObserverAccess.sol +3 -3
  29. package/token/ERC7984/extensions/ERC7984Restricted.sol +8 -8
  30. package/token/ERC7984/extensions/ERC7984Rwa.sol +5 -7
  31. package/token/ERC7984/extensions/ERC7984Votes.sol +2 -2
  32. package/utils/FHESafeMath.sol +2 -2
  33. package/utils/HandleAccessManager.sol +8 -7
  34. package/utils/structs/CheckpointsConfidential.sol +2 -2
  35. package/build/contracts/Checkpoints.json +0 -16
  36. package/utils/structs/temporary-Checkpoints.sol +0 -835
@@ -24,6 +24,22 @@
24
24
  "name": "InsufficientBalance",
25
25
  "type": "error"
26
26
  },
27
+ {
28
+ "inputs": [
29
+ {
30
+ "internalType": "bytes32",
31
+ "name": "handle",
32
+ "type": "bytes32"
33
+ },
34
+ {
35
+ "internalType": "address",
36
+ "name": "sender",
37
+ "type": "address"
38
+ }
39
+ ],
40
+ "name": "SenderNotAllowedToUseHandle",
41
+ "type": "error"
42
+ },
27
43
  {
28
44
  "anonymous": false,
29
45
  "inputs": [
@@ -56,6 +56,22 @@
56
56
  "name": "ERC6372InconsistentClock",
57
57
  "type": "error"
58
58
  },
59
+ {
60
+ "inputs": [
61
+ {
62
+ "internalType": "bytes32",
63
+ "name": "handle",
64
+ "type": "bytes32"
65
+ },
66
+ {
67
+ "internalType": "address",
68
+ "name": "account",
69
+ "type": "address"
70
+ }
71
+ ],
72
+ "name": "HandleAccessManagerNotAllowed",
73
+ "type": "error"
74
+ },
59
75
  {
60
76
  "inputs": [
61
77
  {
@@ -0,0 +1,450 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Confidential Contracts (last updated v0.4.0) (finance/BatcherConfidential.sol)
3
+
4
+ pragma solidity ^0.8.27;
5
+
6
+ import {FHE, externalEuint64, euint64, ebool, euint128} from "@fhevm/solidity/lib/FHE.sol";
7
+ import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
8
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
9
+ import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
10
+ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
11
+ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
12
+ import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
13
+ import {IERC7984ERC20Wrapper} from "./../interfaces/IERC7984ERC20Wrapper.sol";
14
+ import {IERC7984Receiver} from "./../interfaces/IERC7984Receiver.sol";
15
+ import {FHESafeMath} from "./../utils/FHESafeMath.sol";
16
+
17
+ /**
18
+ * @dev `BatcherConfidential` is a batching primitive that enables routing between two {ERC7984ERC20Wrapper} contracts
19
+ * via a non-confidential route. Users deposit {fromToken} into the batcher and receive {toToken} in exchange. Deposits are
20
+ * made by using `ERC7984` transfer and call functions such as {ERC7984-confidentialTransferAndCall}.
21
+ *
22
+ * Developers must implement the virtual function {_executeRoute} to perform the batch's route. This function is called
23
+ * once the batch deposits are unwrapped into the underlying tokens. The function should swap the underlying {fromToken} for
24
+ * underlying {toToken}. If an issue is encountered, the function should return {ExecuteOutcome.Cancel} to cancel the batch.
25
+ *
26
+ * Developers must also implement the virtual function {routeDescription} to provide a human readable description of the batch's route.
27
+ *
28
+ * Claim outputs are rounded down. This may result in small deposits being rounded down to 0 if the exchange rate is less than 1:1.
29
+ * {toToken} dust from rounding down will accumulate in the batcher over time.
30
+ *
31
+ * NOTE: The batcher does not support {ERC7984ERC20Wrapper} contracts prior to v0.4.0.
32
+ *
33
+ * NOTE: The batcher could be used to maintain confidentiality of deposits--by default there are no confidentiality guarantees.
34
+ * If desired, developers should consider restricting certain functions to increase confidentiality.
35
+ *
36
+ * WARNING: The {toToken} and {fromToken} must be carefully inspected to ensure proper capacity is maintained. If {toToken} or
37
+ * {fromToken} are filled--resulting in denial of service--batches could get bricked. The batcher would be unable to wrap
38
+ * underlying tokens into {toToken}. Further, if {fromToken} is also filled, cancellation would also fail on rewrap.
39
+ */
40
+ abstract contract BatcherConfidential is ReentrancyGuardTransient, IERC7984Receiver {
41
+ /// @dev Enum representing the lifecycle state of a batch.
42
+ enum BatchState {
43
+ Pending, // Batch is active and accepting deposits (batchId == currentBatchId)
44
+ Dispatched, // Batch has been dispatched but not yet finalized
45
+ Finalized, // Batch is complete, users can claim their tokens
46
+ Canceled // Batch is canceled, users can claim their refund
47
+ }
48
+
49
+ /// @dev Enum representing the outcome of a route execution in {_executeRoute}.
50
+ enum ExecuteOutcome {
51
+ Complete, // Route execution is complete. Full balance of underlying {toToken} is assigned to the batch.
52
+ Partial, // Route execution is incomplete and will be called again. Intermediate steps *must* not result in underlying {toToken} being transferred into the batcher.
53
+ Cancel // Route execution failed. Batch is canceled. Underlying {fromToken} is rewrapped.
54
+ }
55
+
56
+ struct Batch {
57
+ euint64 totalDeposits;
58
+ bytes32 unwrapRequestId;
59
+ uint64 exchangeRate;
60
+ bool canceled;
61
+ mapping(address => euint64) deposits;
62
+ }
63
+
64
+ IERC7984ERC20Wrapper private immutable _fromToken;
65
+ IERC7984ERC20Wrapper private immutable _toToken;
66
+ mapping(uint256 => Batch) private _batches;
67
+ uint256 private _currentBatchId;
68
+
69
+ /// @dev Emitted when a batch with id `batchId` is dispatched via {dispatchBatch}.
70
+ event BatchDispatched(uint256 indexed batchId);
71
+
72
+ /// @dev Emitted when a batch with id `batchId` is canceled.
73
+ event BatchCanceled(uint256 indexed batchId);
74
+
75
+ /// @dev Emitted when a batch with id `batchId` is finalized with an exchange rate of `exchangeRate`.
76
+ event BatchFinalized(uint256 indexed batchId, uint64 exchangeRate);
77
+
78
+ /// @dev Emitted when an `account` joins a batch with id `batchId` with a deposit of `amount`.
79
+ event Joined(uint256 indexed batchId, address indexed account, euint64 amount);
80
+
81
+ /// @dev Emitted when an `account` claims their `amount` from batch with id `batchId`.
82
+ event Claimed(uint256 indexed batchId, address indexed account, euint64 amount);
83
+
84
+ /// @dev Emitted when an `account` quits a batch with id `batchId`.
85
+ event Quit(uint256 indexed batchId, address indexed account, euint64 amount);
86
+
87
+ /// @dev The `batchId` does not exist. Batch IDs start at 1 and must be less than or equal to {currentBatchId}.
88
+ error BatchNonexistent(uint256 batchId);
89
+
90
+ /// @dev The `account` has a zero deposits in batch `batchId`.
91
+ error ZeroDeposits(uint256 batchId, address account);
92
+
93
+ /**
94
+ * @dev The batch `batchId` is in the state `current`, which is invalid for the operation.
95
+ * The `expectedStates` is a bitmap encoding the expected/allowed states for the operation.
96
+ *
97
+ * See {_encodeStateBitmap}.
98
+ */
99
+ error BatchUnexpectedState(uint256 batchId, BatchState current, bytes32 expectedStates);
100
+
101
+ /**
102
+ * @dev Thrown when the given exchange rate is invalid. The exchange rate must be non-zero and the wrapped
103
+ * amount of {toToken} must be less than or equal to `type(uint64).max`.
104
+ */
105
+ error InvalidExchangeRate(uint256 batchId, uint256 totalDeposits, uint64 exchangeRate);
106
+
107
+ /// @dev The caller is not authorized to call this function.
108
+ error Unauthorized();
109
+
110
+ /// @dev The given `token` does not support `IERC7984ERC20Wrapper` via `ERC165`.
111
+ error InvalidWrapperToken(address token);
112
+
113
+ constructor(IERC7984ERC20Wrapper fromToken_, IERC7984ERC20Wrapper toToken_) {
114
+ require(
115
+ ERC165Checker.supportsInterface(address(fromToken_), type(IERC7984ERC20Wrapper).interfaceId),
116
+ InvalidWrapperToken(address(fromToken_))
117
+ );
118
+ require(
119
+ ERC165Checker.supportsInterface(address(toToken_), type(IERC7984ERC20Wrapper).interfaceId),
120
+ InvalidWrapperToken(address(toToken_))
121
+ );
122
+
123
+ _fromToken = fromToken_;
124
+ _toToken = toToken_;
125
+ _currentBatchId = 1;
126
+
127
+ SafeERC20.forceApprove(IERC20(fromToken().underlying()), address(fromToken()), type(uint256).max);
128
+ SafeERC20.forceApprove(IERC20(toToken().underlying()), address(toToken()), type(uint256).max);
129
+ }
130
+
131
+ /**
132
+ * @dev Claim the `toToken` corresponding to `account`'s deposit in batch with id `batchId`.
133
+ *
134
+ * NOTE: This function is not gated and can be called by anyone. Claims could be frontrun.
135
+ */
136
+ function claim(uint256 batchId, address account) public virtual nonReentrant returns (euint64) {
137
+ return _claim(batchId, account);
138
+ }
139
+
140
+ /**
141
+ * @dev Quit the batch with id `batchId`. Entire deposit is returned to the user.
142
+ * This can only be called if the batch has not yet been dispatched or if the batch was canceled.
143
+ *
144
+ * NOTE: Developers should consider adding additional restrictions to this function
145
+ * if maintaining confidentiality of deposits is critical to the application.
146
+ *
147
+ * WARNING: {dispatchBatch} may fail if an incompatible version of {ERC7984ERC20Wrapper} is used.
148
+ * This function must be unrestricted in cases where batch dispatching fails.
149
+ */
150
+ function quit(uint256 batchId) public virtual nonReentrant returns (euint64) {
151
+ _validateStateBitmap(batchId, _encodeStateBitmap(BatchState.Pending) | _encodeStateBitmap(BatchState.Canceled));
152
+
153
+ euint64 deposit = deposits(batchId, msg.sender);
154
+ require(FHE.isInitialized(deposit), ZeroDeposits(batchId, msg.sender));
155
+
156
+ euint64 totalDeposits_ = totalDeposits(batchId);
157
+
158
+ FHE.allowTransient(deposit, address(fromToken()));
159
+ euint64 sent = fromToken().confidentialTransfer(msg.sender, deposit);
160
+ euint64 newTotalDeposits = FHE.sub(totalDeposits_, sent);
161
+ euint64 newDeposit = FHE.sub(deposit, sent);
162
+
163
+ FHE.allowThis(newTotalDeposits);
164
+ FHE.allowThis(newDeposit);
165
+ FHE.allow(newDeposit, msg.sender);
166
+
167
+ _batches[batchId].totalDeposits = newTotalDeposits;
168
+ _batches[batchId].deposits[msg.sender] = newDeposit;
169
+
170
+ emit Quit(batchId, msg.sender, sent);
171
+
172
+ return sent;
173
+ }
174
+
175
+ /**
176
+ * @dev Permissionless function to dispatch the current batch. Increments the {currentBatchId}.
177
+ *
178
+ * NOTE: Developers should consider adding additional restrictions to this function
179
+ * if maintaining confidentiality of deposits is critical to the application.
180
+ */
181
+ function dispatchBatch() public virtual {
182
+ uint256 batchId = _getAndIncreaseBatchId();
183
+
184
+ euint64 amountToUnwrap = totalDeposits(batchId);
185
+ FHE.allowTransient(amountToUnwrap, address(fromToken()));
186
+ _batches[batchId].unwrapRequestId = fromToken().unwrap(
187
+ address(this),
188
+ address(this),
189
+ externalEuint64.wrap(euint64.unwrap(amountToUnwrap)),
190
+ ""
191
+ );
192
+
193
+ emit BatchDispatched(batchId);
194
+ }
195
+
196
+ /**
197
+ * @dev Dispatch batch callback callable by anyone. This function finalizes the unwrap of {fromToken}
198
+ * and calls {_executeRoute} to perform the batch's route. If `_executeRoute` returns `ExecuteOutcome.Partial`,
199
+ * this function should be called again with the same `batchId`, `unwrapAmountCleartext`, and `decryptionProof`.
200
+ */
201
+ function dispatchBatchCallback(
202
+ uint256 batchId,
203
+ uint64 unwrapAmountCleartext,
204
+ bytes calldata decryptionProof
205
+ ) public virtual nonReentrant {
206
+ _validateStateBitmap(batchId, _encodeStateBitmap(BatchState.Dispatched));
207
+
208
+ bytes32 unwrapRequestId_ = unwrapRequestId(batchId);
209
+ // finalize unwrap call will fail if already called by this contract or by anyone else
210
+ try IERC7984ERC20Wrapper(fromToken()).finalizeUnwrap(unwrapRequestId_, unwrapAmountCleartext, decryptionProof) {
211
+ // No need to validate input since `finalizeUnwrap` request succeeded
212
+ } catch {
213
+ // Must validate input since `finalizeUnwrap` request failed
214
+ bytes32[] memory handles = new bytes32[](1);
215
+ handles[0] = euint64.unwrap(fromToken().unwrapAmount(unwrapRequestId_));
216
+ FHE.checkSignatures(handles, abi.encode(unwrapAmountCleartext), decryptionProof);
217
+ }
218
+
219
+ ExecuteOutcome outcome;
220
+ if (unwrapAmountCleartext == 0) {
221
+ outcome = ExecuteOutcome.Cancel;
222
+ } else {
223
+ outcome = _executeRoute(batchId, unwrapAmountCleartext);
224
+ }
225
+
226
+ if (outcome == ExecuteOutcome.Complete) {
227
+ uint256 swappedAmount = IERC20(toToken().underlying()).balanceOf(address(this));
228
+
229
+ // If wrapper is full, this reverts. Will brick batcher.
230
+ // If output is less than toToken().rate() batch can never be finalized.
231
+ // Any dust left after (amount % toToken().rate()) goes to the next batch.
232
+ toToken().wrap(address(this), swappedAmount);
233
+
234
+ uint256 wrappedAmount = swappedAmount / toToken().rate();
235
+ uint64 exchangeRate_ = SafeCast.toUint64(
236
+ Math.mulDiv(wrappedAmount, uint256(10) ** exchangeRateDecimals(), unwrapAmountCleartext)
237
+ );
238
+
239
+ // Ensure valid exchange rate: not 0 and will not overflow when calculating user outputs
240
+ require(
241
+ exchangeRate_ != 0 && wrappedAmount <= type(uint64).max,
242
+ InvalidExchangeRate(batchId, unwrapAmountCleartext, exchangeRate_)
243
+ );
244
+ _batches[batchId].exchangeRate = exchangeRate_;
245
+
246
+ emit BatchFinalized(batchId, exchangeRate_);
247
+ } else if (outcome == ExecuteOutcome.Cancel) {
248
+ // rewrap tokens so that users can quit and receive their original deposit back.
249
+ // This assumes that the unwrap was successful and that the batch has not executed any route logic.
250
+ fromToken().wrap(address(this), unwrapAmountCleartext * fromToken().rate());
251
+ _batches[batchId].canceled = true;
252
+
253
+ emit BatchCanceled(batchId);
254
+ }
255
+ }
256
+
257
+ /**
258
+ * @dev See {IERC7984Receiver-onConfidentialTransferReceived}.
259
+ *
260
+ * Deposit {fromToken} into the current batch.
261
+ *
262
+ * NOTE: See {_claim} to understand how the {toToken} amount is calculated. Claim amounts are rounded down. Small
263
+ * deposits may be rounded down to 0 if the exchange rate is less than 1:1.
264
+ */
265
+ function onConfidentialTransferReceived(
266
+ address,
267
+ address from,
268
+ euint64 amount,
269
+ bytes calldata
270
+ ) external returns (ebool) {
271
+ require(msg.sender == address(fromToken()), Unauthorized());
272
+ ebool success = FHE.gt(_join(from, amount), FHE.asEuint64(0));
273
+ FHE.allowTransient(success, msg.sender);
274
+ return success;
275
+ }
276
+
277
+ /// @dev Batcher from token. Users deposit this token in exchange for {toToken}.
278
+ function fromToken() public view virtual returns (IERC7984ERC20Wrapper) {
279
+ return _fromToken;
280
+ }
281
+
282
+ /// @dev Batcher to token. Users receive this token in exchange for their {fromToken} deposits.
283
+ function toToken() public view virtual returns (IERC7984ERC20Wrapper) {
284
+ return _toToken;
285
+ }
286
+
287
+ /// @dev The ongoing batch id. New deposits join this batch.
288
+ function currentBatchId() public view virtual returns (uint256) {
289
+ return _currentBatchId;
290
+ }
291
+
292
+ /// @dev The unwrap request id for a batch with id `batchId`.
293
+ function unwrapRequestId(uint256 batchId) public view virtual returns (bytes32) {
294
+ return _batches[batchId].unwrapRequestId;
295
+ }
296
+
297
+ /// @dev The total deposits made in batch with id `batchId`.
298
+ function totalDeposits(uint256 batchId) public view virtual returns (euint64) {
299
+ return _batches[batchId].totalDeposits;
300
+ }
301
+
302
+ /// @dev The deposits made by `account` in batch with id `batchId`.
303
+ function deposits(uint256 batchId, address account) public view virtual returns (euint64) {
304
+ return _batches[batchId].deposits[account];
305
+ }
306
+
307
+ /// @dev The exchange rate set for batch with id `batchId`.
308
+ function exchangeRate(uint256 batchId) public view virtual returns (uint64) {
309
+ return _batches[batchId].exchangeRate;
310
+ }
311
+
312
+ /// @dev The number of decimals of precision for the exchange rate.
313
+ function exchangeRateDecimals() public pure virtual returns (uint8) {
314
+ return 6;
315
+ }
316
+
317
+ /// @dev Human readable description of what the batcher does.
318
+ function routeDescription() public pure virtual returns (string memory);
319
+
320
+ /// @dev Returns the current state of a batch. Reverts if the batch does not exist.
321
+ function batchState(uint256 batchId) public view virtual returns (BatchState) {
322
+ if (_batches[batchId].canceled) {
323
+ return BatchState.Canceled;
324
+ }
325
+ if (exchangeRate(batchId) != 0) {
326
+ return BatchState.Finalized;
327
+ }
328
+ if (unwrapRequestId(batchId) != 0) {
329
+ return BatchState.Dispatched;
330
+ }
331
+ if (batchId == currentBatchId()) {
332
+ return BatchState.Pending;
333
+ }
334
+
335
+ revert BatchNonexistent(batchId);
336
+ }
337
+
338
+ /**
339
+ * @dev Claims `toToken` for `account`'s deposit in batch with id `batchId`. Tokens are always
340
+ * sent to `account`, enabling third-party relayers to claim on behalf of depositors.
341
+ */
342
+ function _claim(uint256 batchId, address account) internal virtual returns (euint64) {
343
+ _validateStateBitmap(batchId, _encodeStateBitmap(BatchState.Finalized));
344
+
345
+ euint64 deposit = deposits(batchId, account);
346
+ require(FHE.isInitialized(deposit), ZeroDeposits(batchId, account));
347
+
348
+ // Overflow is not possible on mul since `type(uint64).max ** 2 < type(uint128).max`.
349
+ // Given that the output of the entire batch must fit in uint64, individual user outputs must also fit.
350
+ euint64 amountToSend = FHE.asEuint64(
351
+ FHE.div(FHE.mul(FHE.asEuint128(deposit), exchangeRate(batchId)), uint128(10) ** exchangeRateDecimals())
352
+ );
353
+ FHE.allowTransient(amountToSend, address(toToken()));
354
+
355
+ euint64 amountTransferred = toToken().confidentialTransfer(account, amountToSend);
356
+
357
+ ebool transferSuccess = FHE.ne(amountTransferred, FHE.asEuint64(0));
358
+ euint64 newDeposit = FHE.select(transferSuccess, FHE.asEuint64(0), deposit);
359
+
360
+ FHE.allowThis(newDeposit);
361
+ FHE.allow(newDeposit, account);
362
+ _batches[batchId].deposits[account] = newDeposit;
363
+
364
+ emit Claimed(batchId, account, amountTransferred);
365
+
366
+ return amountTransferred;
367
+ }
368
+
369
+ /**
370
+ * @dev Joins a batch with amount `amount` on behalf of `to`. Does not do any transfers in.
371
+ * Returns the amount joined with.
372
+ */
373
+ function _join(address to, euint64 amount) internal virtual returns (euint64) {
374
+ uint256 batchId = currentBatchId();
375
+
376
+ (ebool success, euint64 newTotalDeposits) = FHESafeMath.tryIncrease(totalDeposits(batchId), amount);
377
+ euint64 joinedAmount = FHE.select(success, amount, FHE.asEuint64(0));
378
+ euint64 newDeposits = FHE.add(deposits(batchId, to), joinedAmount);
379
+
380
+ FHE.allowThis(newTotalDeposits);
381
+ FHE.allowThis(newDeposits);
382
+ FHE.allow(newDeposits, to);
383
+ FHE.allow(joinedAmount, to);
384
+
385
+ _batches[batchId].totalDeposits = newTotalDeposits;
386
+ _batches[batchId].deposits[to] = newDeposits;
387
+
388
+ emit Joined(batchId, to, joinedAmount);
389
+
390
+ return joinedAmount;
391
+ }
392
+
393
+ /**
394
+ * @dev Function which is executed by {dispatchBatchCallback} after validation and unwrap finalization. The parameter
395
+ * `amount` is the plaintext amount of the `fromToken` which were unwrapped--to attain the underlying tokens received,
396
+ * evaluate `amount * fromToken().rate()`. This function should swap the underlying {fromToken} for underlying {toToken}.
397
+ *
398
+ * This function returns an {ExecuteOutcome} enum indicating the new state of the batch. If the route execution is complete,
399
+ * the balance of the underlying {toToken} is wrapped and the exchange rate is set.
400
+ *
401
+ * NOTE: {dispatchBatchCallback} (and in turn {_executeRoute}) can be repeatedly called until the route execution is complete.
402
+ * If a multi-step route is necessary, intermediate steps should return `ExecuteOutcome.Partial`. Intermediate steps *must* not
403
+ * result in underlying {toToken} being transferred into the batcher.
404
+ *
405
+ * [WARNING]
406
+ * ====
407
+ * This function must eventually return `ExecuteOutcome.Complete` or `ExecuteOutcome.Cancel`. Failure to do so results
408
+ * in user deposits being locked indefinitely.
409
+ *
410
+ * Additionally, the following must hold:
411
+ *
412
+ * - `swappedAmount >= ceil(unwrapAmountCleartext / 10 ** exchangeRateDecimals()) * toToken().rate()` (the exchange rate must not be 0)
413
+ * - `swappedAmount \<= type(uint64).max * toToken().rate()` (the wrapped amount of {toToken} must fit in `uint64`)
414
+ * ====
415
+ */
416
+ function _executeRoute(uint256 batchId, uint256 amount) internal virtual returns (ExecuteOutcome);
417
+
418
+ /**
419
+ * @dev Check that the current state of a batch matches the requirements described by the `allowedStates` bitmap.
420
+ * This bitmap should be built using `_encodeStateBitmap`.
421
+ *
422
+ * If requirements are not met, reverts with a {BatchUnexpectedState} error.
423
+ */
424
+ function _validateStateBitmap(uint256 batchId, bytes32 allowedStates) internal view returns (BatchState) {
425
+ BatchState currentState = batchState(batchId);
426
+ if (_encodeStateBitmap(currentState) & allowedStates == bytes32(0)) {
427
+ revert BatchUnexpectedState(batchId, currentState, allowedStates);
428
+ }
429
+ return currentState;
430
+ }
431
+
432
+ /// @dev Gets the current batch id and increments it.
433
+ function _getAndIncreaseBatchId() internal virtual returns (uint256) {
434
+ return _currentBatchId++;
435
+ }
436
+
437
+ /**
438
+ * @dev Encodes a `BatchState` into a `bytes32` representation where each bit enabled corresponds to
439
+ * the underlying position in the `BatchState` enum. For example:
440
+ *
441
+ * 0x000...1000
442
+ * ^--- Canceled
443
+ * ^-- Finalized
444
+ * ^- Dispatched
445
+ * ^ Pending
446
+ */
447
+ function _encodeStateBitmap(BatchState batchState_) internal pure returns (bytes32) {
448
+ return bytes32(1 << uint8(batchState_));
449
+ }
450
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.3.0) (finance/VestingWalletConfidential.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.4.0) (finance/VestingWalletConfidential.sol)
3
3
  pragma solidity ^0.8.24;
4
4
 
5
5
  import {FHE, ebool, euint64, euint128} from "@fhevm/solidity/lib/FHE.sol";
@@ -21,7 +21,7 @@ import {IERC7984} from "./../interfaces/IERC7984.sol";
21
21
  * NOTE: Since the wallet is `Ownable`, and ownership can be transferred, it is possible to sell unvested tokens.
22
22
  *
23
23
  * NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make
24
- * sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended.
24
+ * sure to account for the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended.
25
25
  *
26
26
  * Confidential vesting wallet contracts can be deployed (as clones) using the {VestingWalletConfidentialFactory}.
27
27
  */
@@ -81,7 +81,7 @@ abstract contract VestingWalletConfidential is OwnableUpgradeable, ReentrancyGua
81
81
  FHE.allowTransient(amount, token);
82
82
  euint64 amountSent = IERC7984(token).confidentialTransfer(owner(), amount);
83
83
 
84
- // This could overflow if the total supply is resent `type(uint128).max/type(uint64).max` times. This is an accepted risk.
84
+ // This could overflow if the total supply is re-sent `type(uint128).max/type(uint64).max` times. This is an accepted risk.
85
85
  euint128 newReleasedAmount = FHE.add(released(token), amountSent);
86
86
  FHE.allow(newReleasedAmount, owner());
87
87
  FHE.allowThis(newReleasedAmount);
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.2.0) (governance/utils/VotesConfidential.sol)
3
- pragma solidity ^0.8.24;
2
+ // OpenZeppelin Confidential Contracts (last updated v0.4.0) (governance/utils/VotesConfidential.sol)
3
+
4
+ pragma solidity ^0.8.26;
4
5
 
5
6
  import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
6
7
  import {IERC6372} from "@openzeppelin/contracts/interfaces/IERC6372.sol";
@@ -14,7 +15,7 @@ import {CheckpointsConfidential} from "./../../utils/structs/CheckpointsConfiden
14
15
 
15
16
  /**
16
17
  * @dev A confidential votes contract tracking confidential voting power of accounts over time.
17
- * It features vote delegation to delegators.
18
+ * It features vote delegation to delegatees.
18
19
 
19
20
  * This contract keeps a history (checkpoints) of each account's confidential vote power. Confidential
20
21
  * voting power can be delegated either by calling the {delegate} function directly, or by providing
@@ -23,7 +24,7 @@ import {CheckpointsConfidential} from "./../../utils/structs/CheckpointsConfiden
23
24
  * allowed to access them. Ensure that {HandleAccessManager-_validateHandleAllowance} is implemented properly, allowing all
24
25
  * necessary addresses to access voting power handles.
25
26
  *
26
- * By default, voting units does not account for voting power. This makes transfers of underlying
27
+ * By default, transfer of voting units does not account for voting power. This makes transfers of
27
28
  * voting units cheaper. The downside is that it requires users to delegate to themselves in order
28
29
  * to activate checkpoints and have their voting power tracked.
29
30
  */
@@ -0,0 +1,62 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Confidential Contracts (last updated v0.4.0) (interfaces/IERC7984ERC20Wrapper.sol)
3
+
4
+ pragma solidity ^0.8.24;
5
+
6
+ import {externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
7
+ import {IERC7984} from "./IERC7984.sol";
8
+
9
+ /// @dev Interface for ERC7984ERC20Wrapper contract.
10
+ interface IERC7984ERC20Wrapper is IERC7984 {
11
+ /// @dev Emitted when an unwrap request is made for a given `receiver`, `unwrapRequestId`, and `amount`.
12
+ event UnwrapRequested(address indexed receiver, bytes32 indexed unwrapRequestId, euint64 amount);
13
+
14
+ /// @dev Emitted when an unwrap request is finalized for a given `receiver`, `unwrapRequestId`, `encryptedAmount`, and `cleartextAmount`.
15
+ event UnwrapFinalized(
16
+ address indexed receiver,
17
+ bytes32 indexed unwrapRequestId,
18
+ euint64 encryptedAmount,
19
+ uint64 cleartextAmount
20
+ );
21
+
22
+ /**
23
+ * @dev Wraps `amount` of the underlying token into a confidential token and sends it to `to`.
24
+ *
25
+ * Returns amount of wrapped token sent.
26
+ */
27
+ function wrap(address to, uint256 amount) external returns (euint64);
28
+
29
+ /**
30
+ * @dev Unwraps tokens from `from` and sends the underlying tokens to `to`. The caller must be `from`
31
+ * or be an approved operator for `from`.
32
+ *
33
+ * Returns the unwrap request id.
34
+ *
35
+ * NOTE: The returned unwrap request id must never be zero.
36
+ */
37
+ function unwrap(
38
+ address from,
39
+ address to,
40
+ externalEuint64 encryptedAmount,
41
+ bytes calldata inputProof
42
+ ) external returns (bytes32);
43
+
44
+ /// @dev Returns the address of the underlying ERC-20 token that is being wrapped.
45
+ function underlying() external view returns (address);
46
+
47
+ /// @dev Finalizes an unwrap request identified by `unwrapRequestId` with the given `unwrapAmountCleartext` and `decryptionProof`.
48
+ function finalizeUnwrap(
49
+ bytes32 unwrapRequestId,
50
+ uint64 unwrapAmountCleartext,
51
+ bytes calldata decryptionProof
52
+ ) external;
53
+
54
+ /**
55
+ * @dev Returns the rate at which the underlying token is converted to the wrapped token.
56
+ * For example, if the `rate` is 1000, then 1000 units of the underlying token equal 1 unit of the wrapped token.
57
+ */
58
+ function rate() external view returns (uint256);
59
+
60
+ /// @dev Returns the amount of wrapper tokens that were unwrapped for a given `unwrapRequestId`.
61
+ function unwrapAmount(bytes32 unwrapRequestId) external view returns (euint64);
62
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.3.0) (interfaces/IERC7984Receiver.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.4.0) (interfaces/IERC7984Receiver.sol)
3
3
  pragma solidity ^0.8.24;
4
4
 
5
5
  import {ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
@@ -8,7 +8,9 @@ import {ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
8
8
  interface IERC7984Receiver {
9
9
  /**
10
10
  * @dev Called upon receiving a confidential token transfer. Returns an encrypted boolean indicating success
11
- * of the callback. If false is returned, the transfer must be reversed.
11
+ * of the callback. If false is returned, the token contract will attempt to refund the transfer.
12
+ *
13
+ * WARNING: Do not manually refund the transfer AND return false, as this can lead to double refunds.
12
14
  */
13
15
  function onConfidentialTransferReceived(
14
16
  address operator,
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.3.0) (interfaces/IERC7984Rwa.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.4.0) (interfaces/IERC7984Rwa.sol)
3
3
  pragma solidity ^0.8.24;
4
4
 
5
5
  import {externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
@@ -10,7 +10,7 @@ interface IERC7984Rwa is IERC7984 {
10
10
  /// @dev Returns true if the contract is paused, false otherwise.
11
11
  function paused() external view returns (bool);
12
12
  /// @dev Returns whether an account is allowed to interact with the token.
13
- function isUserAllowed(address account) external view returns (bool);
13
+ function canTransact(address account) external view returns (bool);
14
14
  /// @dev Returns the confidential frozen balance of an account.
15
15
  function confidentialFrozen(address account) external view returns (euint64);
16
16
  /// @dev Returns the confidential available (unfrozen) balance of an account. Up to {IERC7984-confidentialBalanceOf}.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openzeppelin/confidential-contracts",
3
3
  "description": "Smart Contract library for use with confidential coprocessors",
4
- "version": "0.3.1",
4
+ "version": "0.4.0",
5
5
  "files": [
6
6
  "**/*.sol",
7
7
  "/build/contracts/*.json",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "homepage": "https://openzeppelin.com/contracts/",
34
34
  "peerDependencies": {
35
- "@fhevm/solidity": "0.9.1",
36
- "@openzeppelin/contracts": "^5.4.0",
37
- "@openzeppelin/contracts-upgradeable": "^5.4.0"
35
+ "@fhevm/solidity": "0.11.1",
36
+ "@openzeppelin/contracts": "^5.6.1",
37
+ "@openzeppelin/contracts-upgradeable": "^5.6.1"
38
38
  }
39
39
  }