@openzeppelin/confidential-contracts 0.4.1 → 0.5.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/build/contracts/BatcherConfidential.json +16 -0
  2. package/build/contracts/CheckpointsConfidential.json +2 -2
  3. package/build/contracts/ERC7984.json +6 -28
  4. package/build/contracts/ERC7984BalanceCapHookModule.json +242 -0
  5. package/build/contracts/ERC7984ERC20Wrapper.json +6 -28
  6. package/build/contracts/ERC7984Freezable.json +6 -28
  7. package/build/contracts/ERC7984HolderCapHookModule.json +256 -0
  8. package/build/contracts/ERC7984HookModule.json +165 -0
  9. package/build/contracts/ERC7984Hooked.json +827 -0
  10. package/build/contracts/ERC7984ObserverAccess.json +6 -28
  11. package/build/contracts/ERC7984Omnibus.json +6 -28
  12. package/build/contracts/ERC7984Restricted.json +6 -28
  13. package/build/contracts/ERC7984Rwa.json +61 -29
  14. package/build/contracts/ERC7984Utils.json +2 -2
  15. package/build/contracts/ERC7984Votes.json +6 -28
  16. package/build/contracts/FHESafeMath.json +2 -2
  17. package/build/contracts/IERC7984HookModule.json +138 -0
  18. package/build/contracts/IERC7984Rwa.json +87 -0
  19. package/finance/BatcherConfidential.sol +20 -5
  20. package/governance/utils/VotesConfidential.sol +2 -2
  21. package/interfaces/IERC7984HookModule.sol +32 -0
  22. package/interfaces/IERC7984Receiver.sol +3 -1
  23. package/interfaces/IERC7984Rwa.sol +28 -1
  24. package/package.json +1 -1
  25. package/token/ERC7984/ERC7984.sol +43 -29
  26. package/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol +3 -3
  27. package/token/ERC7984/extensions/ERC7984Freezable.sol +3 -7
  28. package/token/ERC7984/extensions/ERC7984Hooked.sol +157 -0
  29. package/token/ERC7984/extensions/ERC7984Restricted.sol +3 -3
  30. package/token/ERC7984/extensions/ERC7984Rwa.sol +83 -28
  31. package/token/ERC7984/utils/ERC7984BalanceCapHookModule.sol +87 -0
  32. package/token/ERC7984/utils/ERC7984HolderCapHookModule.sol +141 -0
  33. package/token/ERC7984/utils/ERC7984HookModule.sol +131 -0
  34. package/utils/FHESafeMath.sol +26 -1
  35. package/utils/HandleAccessManager.sol +5 -3
  36. package/utils/structs/CheckpointsConfidential.sol +1 -2
@@ -72,6 +72,31 @@
72
72
  "name": "OperatorSet",
73
73
  "type": "event"
74
74
  },
75
+ {
76
+ "anonymous": false,
77
+ "inputs": [
78
+ {
79
+ "indexed": true,
80
+ "internalType": "address",
81
+ "name": "lostAccount",
82
+ "type": "address"
83
+ },
84
+ {
85
+ "indexed": true,
86
+ "internalType": "address",
87
+ "name": "newAccount",
88
+ "type": "address"
89
+ },
90
+ {
91
+ "indexed": false,
92
+ "internalType": "euint64",
93
+ "name": "amount",
94
+ "type": "bytes32"
95
+ }
96
+ ],
97
+ "name": "TokensRecovered",
98
+ "type": "event"
99
+ },
75
100
  {
76
101
  "inputs": [
77
102
  {
@@ -621,6 +646,44 @@
621
646
  "stateMutability": "nonpayable",
622
647
  "type": "function"
623
648
  },
649
+ {
650
+ "inputs": [
651
+ {
652
+ "internalType": "address",
653
+ "name": "account",
654
+ "type": "address"
655
+ }
656
+ ],
657
+ "name": "isAdmin",
658
+ "outputs": [
659
+ {
660
+ "internalType": "bool",
661
+ "name": "",
662
+ "type": "bool"
663
+ }
664
+ ],
665
+ "stateMutability": "view",
666
+ "type": "function"
667
+ },
668
+ {
669
+ "inputs": [
670
+ {
671
+ "internalType": "address",
672
+ "name": "account",
673
+ "type": "address"
674
+ }
675
+ ],
676
+ "name": "isAgent",
677
+ "outputs": [
678
+ {
679
+ "internalType": "bool",
680
+ "name": "",
681
+ "type": "bool"
682
+ }
683
+ ],
684
+ "stateMutability": "view",
685
+ "type": "function"
686
+ },
624
687
  {
625
688
  "inputs": [
626
689
  {
@@ -678,6 +741,30 @@
678
741
  "stateMutability": "view",
679
742
  "type": "function"
680
743
  },
744
+ {
745
+ "inputs": [
746
+ {
747
+ "internalType": "address",
748
+ "name": "lostAccount",
749
+ "type": "address"
750
+ },
751
+ {
752
+ "internalType": "address",
753
+ "name": "newAccount",
754
+ "type": "address"
755
+ }
756
+ ],
757
+ "name": "recoverAddress",
758
+ "outputs": [
759
+ {
760
+ "internalType": "euint64",
761
+ "name": "",
762
+ "type": "bytes32"
763
+ }
764
+ ],
765
+ "stateMutability": "nonpayable",
766
+ "type": "function"
767
+ },
681
768
  {
682
769
  "inputs": [
683
770
  {
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.4.1) (finance/BatcherConfidential.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.5.0) (finance/BatcherConfidential.sol)
3
3
 
4
4
  pragma solidity ^0.8.27;
5
5
 
6
- import {FHE, externalEuint64, euint64, ebool, euint128} from "@fhevm/solidity/lib/FHE.sol";
6
+ import {FHE, externalEuint64, euint64, ebool} from "@fhevm/solidity/lib/FHE.sol";
7
7
  import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
8
8
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
9
9
  import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
@@ -16,8 +16,8 @@ import {FHESafeMath} from "./../utils/FHESafeMath.sol";
16
16
 
17
17
  /**
18
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}.
19
+ * (with distinct underlying tokens) via a non-confidential route. Users deposit {fromToken} into the batcher and receive
20
+ * {toToken} in exchange. Deposits are made by using `ERC7984` transfer and call functions such as {ERC7984-confidentialTransferAndCall}.
21
21
  *
22
22
  * Developers must implement the virtual function {_executeRoute} to perform the batch's route. This function is called
23
23
  * once the batch deposits are unwrapped into the underlying tokens. The function should swap the underlying {fromToken} for
@@ -110,6 +110,12 @@ abstract contract BatcherConfidential is ReentrancyGuardTransient, IERC7984Recei
110
110
  /// @dev The given `token` does not support `IERC7984ERC20Wrapper` via `ERC165`.
111
111
  error InvalidWrapperToken(address token);
112
112
 
113
+ /// @dev The underlying wrapper tokens are the same.
114
+ error DuplicateUnderlyingTokens();
115
+
116
+ /// @dev Intermediate steps must not result in underlying {toToken} being transferred to or from the batcher.
117
+ error IntermediateStepToTokenBalanceChanged(uint256 batchId);
118
+
113
119
  constructor(IERC7984ERC20Wrapper fromToken_, IERC7984ERC20Wrapper toToken_) {
114
120
  require(
115
121
  ERC165Checker.supportsInterface(address(fromToken_), type(IERC7984ERC20Wrapper).interfaceId),
@@ -119,6 +125,7 @@ abstract contract BatcherConfidential is ReentrancyGuardTransient, IERC7984Recei
119
125
  ERC165Checker.supportsInterface(address(toToken_), type(IERC7984ERC20Wrapper).interfaceId),
120
126
  InvalidWrapperToken(address(toToken_))
121
127
  );
128
+ require(fromToken_.underlying() != toToken_.underlying(), DuplicateUnderlyingTokens());
122
129
 
123
130
  _fromToken = fromToken_;
124
131
  _toToken = toToken_;
@@ -216,10 +223,13 @@ abstract contract BatcherConfidential is ReentrancyGuardTransient, IERC7984Recei
216
223
  FHE.checkSignatures(handles, abi.encode(unwrapAmountCleartext), decryptionProof);
217
224
  }
218
225
 
226
+ uint256 beforeUnderlyingToTokenBalance;
227
+
219
228
  ExecuteOutcome outcome;
220
229
  if (unwrapAmountCleartext == 0) {
221
230
  outcome = ExecuteOutcome.Cancel;
222
231
  } else {
232
+ beforeUnderlyingToTokenBalance = IERC20(toToken().underlying()).balanceOf(address(this));
223
233
  outcome = _executeRoute(batchId, unwrapAmountCleartext);
224
234
  }
225
235
 
@@ -251,6 +261,11 @@ abstract contract BatcherConfidential is ReentrancyGuardTransient, IERC7984Recei
251
261
  _batches[batchId].canceled = true;
252
262
 
253
263
  emit BatchCanceled(batchId);
264
+ } else if (outcome == ExecuteOutcome.Partial) {
265
+ require(
266
+ IERC20(toToken().underlying()).balanceOf(address(this)) == beforeUnderlyingToTokenBalance,
267
+ IntermediateStepToTokenBalanceChanged(batchId)
268
+ );
254
269
  }
255
270
  }
256
271
 
@@ -401,7 +416,7 @@ abstract contract BatcherConfidential is ReentrancyGuardTransient, IERC7984Recei
401
416
  *
402
417
  * NOTE: {dispatchBatchCallback} (and in turn {_executeRoute}) can be repeatedly called until the route execution is complete.
403
418
  * If a multi-step route is necessary, intermediate steps should return `ExecuteOutcome.Partial`. Intermediate steps *must* not
404
- * result in underlying {toToken} being transferred into the batcher.
419
+ * result in underlying {toToken} being transferred to or from the batcher.
405
420
  *
406
421
  * [WARNING]
407
422
  * ====
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.4.0) (governance/utils/VotesConfidential.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.5.0) (governance/utils/VotesConfidential.sol)
3
3
 
4
4
  pragma solidity ^0.8.26;
5
5
 
6
- import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
6
+ import {FHE, euint64} from "@fhevm/solidity/lib/FHE.sol";
7
7
  import {IERC6372} from "@openzeppelin/contracts/interfaces/IERC6372.sol";
8
8
  import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
9
9
  import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
@@ -0,0 +1,32 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Confidential Contracts (last updated v0.5.0) (interfaces/IERC7984HookModule.sol)
3
+
4
+ pragma solidity ^0.8.24;
5
+
6
+ import {euint64, ebool} from "@fhevm/solidity/lib/FHE.sol";
7
+ import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
8
+
9
+ /// @dev Interface for an ERC-7984 hook module.
10
+ interface IERC7984HookModule is IERC165 {
11
+ /// @dev Optionally emitted by a module to indicate the result of its validation (pre-transfer) hook.
12
+ event ERC7984HookModuleResult(
13
+ address indexed token,
14
+ address indexed from,
15
+ address indexed to,
16
+ euint64 encryptedAmount,
17
+ ebool result,
18
+ bytes32 context
19
+ );
20
+
21
+ /**
22
+ * @dev Hook that runs before a transfer. Should not mutate token state. Module is already
23
+ * granted transient access to `encryptedAmount`.
24
+ */
25
+ function preTransfer(address from, address to, euint64 encryptedAmount) external returns (ebool);
26
+
27
+ /// @dev Performs operation after transfer.
28
+ function postTransfer(address from, address to, euint64 encryptedAmount) external;
29
+
30
+ /// @dev Performs operations after installation.
31
+ function onInstall(bytes calldata initData) external;
32
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.4.0) (interfaces/IERC7984Receiver.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.5.0) (interfaces/IERC7984Receiver.sol)
3
3
  pragma solidity ^0.8.24;
4
4
 
5
5
  import {ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
@@ -10,6 +10,8 @@ interface IERC7984Receiver {
10
10
  * @dev Called upon receiving a confidential token transfer. Returns an encrypted boolean indicating success
11
11
  * of the callback. If false is returned, the token contract will attempt to refund the transfer.
12
12
  *
13
+ * NOTE: The calling contract (token) must be granted ACL allowance to read the confidential return value.
14
+ *
13
15
  * WARNING: Do not manually refund the transfer AND return false, as this can lead to double refunds.
14
16
  */
15
17
  function onConfidentialTransferReceived(
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.4.0) (interfaces/IERC7984Rwa.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.5.0) (interfaces/IERC7984Rwa.sol)
3
3
  pragma solidity ^0.8.24;
4
4
 
5
5
  import {externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
@@ -7,46 +7,69 @@ import {IERC7984} from "./IERC7984.sol";
7
7
 
8
8
  /// @dev Interface for confidential RWA contracts.
9
9
  interface IERC7984Rwa is IERC7984 {
10
+ /// @dev Emitted when the balance `amount` of `lostAccount` is recovered to `newAccount`.
11
+ event TokensRecovered(address indexed lostAccount, address indexed newAccount, euint64 amount);
12
+
10
13
  /// @dev Returns true if the contract is paused, false otherwise.
11
14
  function paused() external view returns (bool);
15
+
16
+ /// @dev Returns true if has admin role, false otherwise.
17
+ function isAdmin(address account) external view returns (bool);
18
+
19
+ /// @dev Returns true if agent, false otherwise.
20
+ function isAgent(address account) external view returns (bool);
21
+
12
22
  /// @dev Returns whether an account is allowed to interact with the token.
13
23
  function canTransact(address account) external view returns (bool);
24
+
14
25
  /// @dev Returns the confidential frozen balance of an account.
15
26
  function confidentialFrozen(address account) external view returns (euint64);
27
+
16
28
  /// @dev Returns the confidential available (unfrozen) balance of an account. Up to {IERC7984-confidentialBalanceOf}.
17
29
  function confidentialAvailable(address account) external returns (euint64);
30
+
18
31
  /// @dev Pauses contract.
19
32
  function pause() external;
33
+
20
34
  /// @dev Unpauses contract.
21
35
  function unpause() external;
36
+
22
37
  /// @dev Blocks a user account.
23
38
  function blockUser(address account) external;
39
+
24
40
  /// @dev Unblocks a user account.
25
41
  function unblockUser(address account) external;
42
+
26
43
  /// @dev Sets confidential amount of token for an account as frozen with proof.
27
44
  function setConfidentialFrozen(
28
45
  address account,
29
46
  externalEuint64 encryptedAmount,
30
47
  bytes calldata inputProof
31
48
  ) external;
49
+
32
50
  /// @dev Sets confidential amount of token for an account as frozen.
33
51
  function setConfidentialFrozen(address account, euint64 encryptedAmount) external;
52
+
34
53
  /// @dev Mints confidential amount of tokens to account with proof.
35
54
  function confidentialMint(
36
55
  address to,
37
56
  externalEuint64 encryptedAmount,
38
57
  bytes calldata inputProof
39
58
  ) external returns (euint64);
59
+
40
60
  /// @dev Mints confidential amount of tokens to account.
41
61
  function confidentialMint(address to, euint64 encryptedAmount) external returns (euint64);
62
+
42
63
  /// @dev Burns confidential amount of tokens from account with proof.
43
64
  function confidentialBurn(
44
65
  address account,
45
66
  externalEuint64 encryptedAmount,
46
67
  bytes calldata inputProof
47
68
  ) external returns (euint64);
69
+
48
70
  /// @dev Burns confidential amount of tokens from account.
49
71
  function confidentialBurn(address account, euint64 encryptedAmount) external returns (euint64);
72
+
50
73
  /// @dev Forces transfer of confidential amount of tokens from account to account with proof by skipping compliance checks.
51
74
  function forceConfidentialTransferFrom(
52
75
  address from,
@@ -54,10 +77,14 @@ interface IERC7984Rwa is IERC7984 {
54
77
  externalEuint64 encryptedAmount,
55
78
  bytes calldata inputProof
56
79
  ) external returns (euint64);
80
+
57
81
  /// @dev Forces transfer of confidential amount of tokens from account to account by skipping compliance checks.
58
82
  function forceConfidentialTransferFrom(
59
83
  address from,
60
84
  address to,
61
85
  euint64 encryptedAmount
62
86
  ) external returns (euint64);
87
+
88
+ /// @dev Recovers the address of a lost account to a new account.
89
+ function recoverAddress(address lostAccount, address newAccount) external returns (euint64);
63
90
  }
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.4.1",
4
+ "version": "0.5.0",
5
5
  "files": [
6
6
  "**/*.sol",
7
7
  "/build/contracts/*.json",
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.3.0) (token/ERC7984/ERC7984.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.5.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";
@@ -44,9 +44,6 @@ abstract contract ERC7984 is IERC7984, ERC165 {
44
44
  /// @dev The given holder `holder` is not authorized to spend on behalf of `spender`.
45
45
  error ERC7984UnauthorizedSpender(address holder, address spender);
46
46
 
47
- /// @dev The holder `holder` is trying to send tokens but has a balance of 0.
48
- error ERC7984ZeroBalance(address holder);
49
-
50
47
  /**
51
48
  * @dev The caller `user` does not have access to the encrypted amount `amount`.
52
49
  *
@@ -57,9 +54,6 @@ abstract contract ERC7984 is IERC7984, ERC165 {
57
54
  /// @dev The given caller `caller` is not authorized for the current operation.
58
55
  error ERC7984UnauthorizedCaller(address caller);
59
56
 
60
- /// @dev The given gateway request ID `requestId` is invalid.
61
- error ERC7984InvalidGatewayRequest(uint256 requestId);
62
-
63
57
  constructor(string memory name_, string memory symbol_, string memory contractURI_) {
64
58
  _name = name_;
65
59
  _symbol = symbol_;
@@ -106,7 +100,10 @@ abstract contract ERC7984 is IERC7984, ERC165 {
106
100
  return holder == spender || block.timestamp <= _operators[holder][spender];
107
101
  }
108
102
 
109
- /// @inheritdoc IERC7984
103
+ /**
104
+ * @dev See {IERC7984-setOperator}. Operators are given ACL allowance (ability to decrypt) for the transferred amount
105
+ * of transfers they initiate.
106
+ */
110
107
  function setOperator(address operator, uint48 until) public virtual {
111
108
  _setOperator(msg.sender, operator, until);
112
109
  }
@@ -132,22 +129,20 @@ abstract contract ERC7984 is IERC7984, ERC165 {
132
129
  address to,
133
130
  externalEuint64 encryptedAmount,
134
131
  bytes calldata inputProof
135
- ) public virtual returns (euint64 transferred) {
132
+ ) public virtual returns (euint64) {
136
133
  require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
137
- transferred = _transfer(from, to, FHE.fromExternal(encryptedAmount, inputProof));
134
+ euint64 transferred = _transfer(from, to, FHE.fromExternal(encryptedAmount, inputProof));
138
135
  FHE.allowTransient(transferred, msg.sender);
136
+ return transferred;
139
137
  }
140
138
 
141
139
  /// @inheritdoc IERC7984
142
- function confidentialTransferFrom(
143
- address from,
144
- address to,
145
- euint64 amount
146
- ) public virtual returns (euint64 transferred) {
140
+ function confidentialTransferFrom(address from, address to, euint64 amount) public virtual returns (euint64) {
147
141
  require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
148
142
  require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
149
- transferred = _transfer(from, to, amount);
143
+ euint64 transferred = _transfer(from, to, amount);
150
144
  FHE.allowTransient(transferred, msg.sender);
145
+ return transferred;
151
146
  }
152
147
 
153
148
  /// @inheritdoc IERC7984
@@ -156,9 +151,8 @@ abstract contract ERC7984 is IERC7984, ERC165 {
156
151
  externalEuint64 encryptedAmount,
157
152
  bytes calldata inputProof,
158
153
  bytes calldata data
159
- ) public virtual returns (euint64 transferred) {
160
- transferred = _transferAndCall(msg.sender, to, FHE.fromExternal(encryptedAmount, inputProof), data);
161
- FHE.allowTransient(transferred, msg.sender);
154
+ ) public virtual returns (euint64) {
155
+ return _transferAndCall(msg.sender, to, FHE.fromExternal(encryptedAmount, inputProof), data);
162
156
  }
163
157
 
164
158
  /// @inheritdoc IERC7984
@@ -166,10 +160,9 @@ abstract contract ERC7984 is IERC7984, ERC165 {
166
160
  address to,
167
161
  euint64 amount,
168
162
  bytes calldata data
169
- ) public virtual returns (euint64 transferred) {
163
+ ) public virtual returns (euint64) {
170
164
  require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
171
- transferred = _transferAndCall(msg.sender, to, amount, data);
172
- FHE.allowTransient(transferred, msg.sender);
165
+ return _transferAndCall(msg.sender, to, amount, data);
173
166
  }
174
167
 
175
168
  /// @inheritdoc IERC7984
@@ -179,10 +172,9 @@ abstract contract ERC7984 is IERC7984, ERC165 {
179
172
  externalEuint64 encryptedAmount,
180
173
  bytes calldata inputProof,
181
174
  bytes calldata data
182
- ) public virtual returns (euint64 transferred) {
175
+ ) public virtual returns (euint64) {
183
176
  require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
184
- transferred = _transferAndCall(from, to, FHE.fromExternal(encryptedAmount, inputProof), data);
185
- FHE.allowTransient(transferred, msg.sender);
177
+ return _transferAndCall(from, to, FHE.fromExternal(encryptedAmount, inputProof), data);
186
178
  }
187
179
 
188
180
  /// @inheritdoc IERC7984
@@ -191,11 +183,10 @@ abstract contract ERC7984 is IERC7984, ERC165 {
191
183
  address to,
192
184
  euint64 amount,
193
185
  bytes calldata data
194
- ) public virtual returns (euint64 transferred) {
186
+ ) public virtual returns (euint64) {
195
187
  require(FHE.isAllowed(amount, msg.sender), ERC7984UnauthorizedUseOfEncryptedAmount(amount, msg.sender));
196
188
  require(isOperator(from, msg.sender), ERC7984UnauthorizedSpender(from, msg.sender));
197
- transferred = _transferAndCall(from, to, amount, data);
198
- FHE.allowTransient(transferred, msg.sender);
189
+ return _transferAndCall(from, to, amount, data);
199
190
  }
200
191
 
201
192
  /**
@@ -255,6 +246,25 @@ abstract contract ERC7984 is IERC7984, ERC165 {
255
246
  return _update(from, to, amount);
256
247
  }
257
248
 
249
+ /**
250
+ * @dev Transfers the given amount of tokens from `from` to `to` and calls the `onConfidentialTransferReceived`
251
+ * function on the recipient.
252
+ *
253
+ * The token contract initiates a second transfer refunding the tokens from the recipient to the sender--the
254
+ * amount is 0 if the callback succeeds, otherwise the amount is the amount that was transferred.
255
+ *
256
+ * The returned `transferred` amount is a fresh ciphertext computed as `sent - refund`
257
+ * and `msg.sender` only receives a transient FHE allowance for it. This value is generally
258
+ * intended to be processed only in the same transaction. Event observers see `sent` and `refund` individually.
259
+ *
260
+ * WARNING: The refund triggered when {IERC7984Receiver-onConfidentialTransferReceived} returns an encrypted
261
+ * false is best-effort only. A receiver that transfers, burns, or otherwise reduces its balance during
262
+ * the hook can still return false, in which case the refund transfers zero tokens. The sender's tokens
263
+ * end up with the recipient rather than being refunded.
264
+ *
265
+ * WARNING: Refunds are subject to the same validation flow as a normal transfer--they may fail for a variety of
266
+ * reasons (such as failed hook validation in {ERC7984Hooked}). In these cases, the tokens do not return to the sender.
267
+ */
258
268
  function _transferAndCall(
259
269
  address from,
260
270
  address to,
@@ -270,8 +280,13 @@ abstract contract ERC7984 is IERC7984, ERC165 {
270
280
  // Try to refund if callback fails
271
281
  euint64 refund = _update(to, from, FHE.select(success, FHE.asEuint64(0), sent));
272
282
  transferred = FHE.sub(sent, refund);
283
+ FHE.allowTransient(transferred, msg.sender);
273
284
  }
274
285
 
286
+ /**
287
+ * @dev Safely moves up to `amount` from `from` to `to`, or mints/burns if `from`/`to` is the zero address.
288
+ * Emits a {ConfidentialTransfer} event with the successfully transferred amount.
289
+ */
275
290
  function _update(address from, address to, euint64 amount) internal virtual returns (euint64 transferred) {
276
291
  ebool success;
277
292
  euint64 ptr;
@@ -282,7 +297,6 @@ abstract contract ERC7984 is IERC7984, ERC165 {
282
297
  _totalSupply = ptr;
283
298
  } else {
284
299
  euint64 fromBalance = _balances[from];
285
- require(FHE.isInitialized(fromBalance), ERC7984ZeroBalance(from));
286
300
  (success, ptr) = FHESafeMath.tryDecrease(fromBalance, amount);
287
301
  FHE.allowThis(ptr);
288
302
  FHE.allow(ptr, from);
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.4.0) (token/ERC7984/extensions/ERC7984ERC20Wrapper.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.5.0) (token/ERC7984/extensions/ERC7984ERC20Wrapper.sol)
3
3
 
4
4
  pragma solidity ^0.8.27;
5
5
 
@@ -180,8 +180,8 @@ abstract contract ERC7984ERC20Wrapper is ERC7984, IERC7984ERC20Wrapper, IERC1363
180
180
  }
181
181
 
182
182
  /**
183
- * @dev Get the address that has a pending unwrap request for the given `unwrapAmount`. Returns `address(0)` if no pending
184
- * unwrap request for the amount `unwrapAmount` exists.
183
+ * @dev Gets the address that will receive the ERC-20 tokens associated with a pending unwrap request identified by
184
+ * `unwrapRequestId`. Returns `address(0)` if there is no pending unwrap request with id `unwrapRequestId`.
185
185
  */
186
186
  function unwrapRequester(bytes32 unwrapRequestId) public view virtual returns (address) {
187
187
  return _unwrapRequests[unwrapRequestId];
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Confidential Contracts (last updated v0.4.0) (token/ERC7984/extensions/ERC7984Freezable.sol)
2
+ // OpenZeppelin Confidential Contracts (last updated v0.5.0) (token/ERC7984/extensions/ERC7984Freezable.sol)
3
3
 
4
4
  pragma solidity ^0.8.27;
5
5
 
6
- import {FHE, ebool, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
6
+ import {FHE, euint64} from "@fhevm/solidity/lib/FHE.sol";
7
7
  import {FHESafeMath} from "../../../utils/FHESafeMath.sol";
8
8
  import {ERC7984} from "../ERC7984.sol";
9
9
 
@@ -40,11 +40,7 @@ abstract contract ERC7984Freezable is ERC7984 {
40
40
 
41
41
  /// @dev Internal function to calculate the available balance of an account. Does not give any allowances.
42
42
  function _confidentialAvailable(address account) internal virtual returns (euint64) {
43
- (ebool success, euint64 unfrozen) = FHESafeMath.tryDecrease(
44
- confidentialBalanceOf(account),
45
- confidentialFrozen(account)
46
- );
47
- return FHE.select(success, unfrozen, FHE.asEuint64(0));
43
+ return FHESafeMath.saturatingSub(confidentialBalanceOf(account), confidentialFrozen(account));
48
44
  }
49
45
 
50
46
  /// @dev Internal function to freeze a confidential amount of tokens for an account.