@openzeppelin/confidential-contracts 0.2.0-rc.1
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.
- package/README.md +24 -0
- package/build/contracts/Checkpoints.json +16 -0
- package/build/contracts/CheckpointsConfidential.json +16 -0
- package/build/contracts/ConfidentialFungibleToken.json +614 -0
- package/build/contracts/ConfidentialFungibleTokenERC20Wrapper.json +793 -0
- package/build/contracts/ConfidentialFungibleTokenUtils.json +10 -0
- package/build/contracts/ConfidentialFungibleTokenVotes.json +1002 -0
- package/build/contracts/ERC7821WithExecutor.json +145 -0
- package/build/contracts/IConfidentialFungibleToken.json +458 -0
- package/build/contracts/IConfidentialFungibleTokenReceiver.json +45 -0
- package/build/contracts/TFHESafeMath.json +10 -0
- package/build/contracts/VestingWalletCliffConfidential.json +275 -0
- package/build/contracts/VestingWalletCliffExecutorConfidential.json +424 -0
- package/build/contracts/VestingWalletCliffExecutorConfidentialFactory.json +290 -0
- package/build/contracts/VestingWalletConfidential.json +246 -0
- package/build/contracts/VotesConfidential.json +412 -0
- package/finance/ERC7821WithExecutor.sol +46 -0
- package/finance/VestingWalletCliffConfidential.sol +62 -0
- package/finance/VestingWalletCliffExecutorConfidentialFactory.sol +203 -0
- package/finance/VestingWalletConfidential.sol +130 -0
- package/governance/utils/VotesConfidential.sol +202 -0
- package/interfaces/IConfidentialFungibleToken.sol +135 -0
- package/interfaces/IConfidentialFungibleTokenReceiver.sol +19 -0
- package/package.json +39 -0
- package/token/ConfidentialFungibleToken.sol +314 -0
- package/token/extensions/ConfidentialFungibleTokenERC20Wrapper.sol +175 -0
- package/token/extensions/ConfidentialFungibleTokenVotes.sol +29 -0
- package/token/utils/ConfidentialFungibleTokenUtils.sol +46 -0
- package/utils/TFHESafeMath.sol +37 -0
- package/utils/structs/CheckpointsConfidential.sol +193 -0
- package/utils/structs/temporary-Checkpoints.sol +835 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openzeppelin/confidential-contracts",
|
|
3
|
+
"description": "Smart Contract library for use with confidential coprocessors",
|
|
4
|
+
"version": "0.2.0-rc.1",
|
|
5
|
+
"files": [
|
|
6
|
+
"**/*.sol",
|
|
7
|
+
"/build/contracts/*.json",
|
|
8
|
+
"!/mocks/**/*"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"prepack": "bash ../scripts/prepack.sh",
|
|
12
|
+
"prepare-docs": "cd ..; npm run prepare-docs"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/OpenZeppelin/openzeppelin-confidential-contracts.git"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"solidity",
|
|
20
|
+
"ethereum",
|
|
21
|
+
"smart",
|
|
22
|
+
"contracts",
|
|
23
|
+
"security",
|
|
24
|
+
"zeppelin",
|
|
25
|
+
"confidential",
|
|
26
|
+
"fhe"
|
|
27
|
+
],
|
|
28
|
+
"author": "OpenZeppelin Community <maintainers@openzeppelin.org>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://openzeppelin.com/contracts/",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@fhevm/solidity": "0.7.0",
|
|
36
|
+
"@openzeppelin/contracts": "^5.3.0",
|
|
37
|
+
"@openzeppelin/contracts-upgradeable": "^5.3.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Confidential Contracts (last updated v0.2.0-rc.1) (token/ConfidentialFungibleToken.sol)
|
|
3
|
+
pragma solidity ^0.8.27;
|
|
4
|
+
|
|
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";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @dev Reference implementation for {IConfidentialFungibleToken}.
|
|
12
|
+
*
|
|
13
|
+
* This contract implements a fungible token where balances and transfers are encrypted using the Zama fhEVM,
|
|
14
|
+
* providing confidentiality to users. Token amounts are stored as encrypted, unsigned integers (`euint64`)
|
|
15
|
+
* that can only be decrypted by authorized parties.
|
|
16
|
+
*
|
|
17
|
+
* Key features:
|
|
18
|
+
*
|
|
19
|
+
* - All balances are encrypted
|
|
20
|
+
* - Transfers happen without revealing amounts
|
|
21
|
+
* - Support for operators (delegated transfer capabilities with time bounds)
|
|
22
|
+
* - Transfer and call pattern
|
|
23
|
+
* - Safe overflow/underflow handling for FHE operations
|
|
24
|
+
*/
|
|
25
|
+
abstract contract ConfidentialFungibleToken is IConfidentialFungibleToken {
|
|
26
|
+
mapping(address holder => euint64) private _balances;
|
|
27
|
+
mapping(address holder => mapping(address spender => uint48)) private _operators;
|
|
28
|
+
mapping(uint256 requestId => euint64 encryptedAmount) private _requestHandles;
|
|
29
|
+
euint64 private _totalSupply;
|
|
30
|
+
string private _name;
|
|
31
|
+
string private _symbol;
|
|
32
|
+
string private _tokenURI;
|
|
33
|
+
|
|
34
|
+
/// @dev The given receiver `receiver` is invalid for transfers.
|
|
35
|
+
error ConfidentialFungibleTokenInvalidReceiver(address receiver);
|
|
36
|
+
|
|
37
|
+
/// @dev The given sender `sender` is invalid for transfers.
|
|
38
|
+
error ConfidentialFungibleTokenInvalidSender(address sender);
|
|
39
|
+
|
|
40
|
+
/// @dev The given holder `holder` is not authorized to spend on behalf of `spender`.
|
|
41
|
+
error ConfidentialFungibleTokenUnauthorizedSpender(address holder, address spender);
|
|
42
|
+
|
|
43
|
+
/// @dev The holder `holder` is trying to send tokens but has a balance of 0.
|
|
44
|
+
error ConfidentialFungibleTokenZeroBalance(address holder);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @dev The caller `user` does not have access to the encrypted amount `amount`.
|
|
48
|
+
*
|
|
49
|
+
* NOTE: Try using the equivalent transfer function with an input proof.
|
|
50
|
+
*/
|
|
51
|
+
error ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(euint64 amount, address user);
|
|
52
|
+
|
|
53
|
+
/// @dev The given caller `caller` is not authorized for the current operation.
|
|
54
|
+
error ConfidentialFungibleTokenUnauthorizedCaller(address caller);
|
|
55
|
+
|
|
56
|
+
/// @dev The given gateway request ID `requestId` is invalid.
|
|
57
|
+
error ConfidentialFungibleTokenInvalidGatewayRequest(uint256 requestId);
|
|
58
|
+
|
|
59
|
+
constructor(string memory name_, string memory symbol_, string memory tokenURI_) {
|
|
60
|
+
_name = name_;
|
|
61
|
+
_symbol = symbol_;
|
|
62
|
+
_tokenURI = tokenURI_;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
66
|
+
function name() public view virtual returns (string memory) {
|
|
67
|
+
return _name;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
71
|
+
function symbol() public view virtual returns (string memory) {
|
|
72
|
+
return _symbol;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
76
|
+
function decimals() public view virtual returns (uint8) {
|
|
77
|
+
return 6;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
81
|
+
function tokenURI() public view virtual returns (string memory) {
|
|
82
|
+
return _tokenURI;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
86
|
+
function confidentialTotalSupply() public view virtual returns (euint64) {
|
|
87
|
+
return _totalSupply;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
91
|
+
function confidentialBalanceOf(address account) public view virtual returns (euint64) {
|
|
92
|
+
return _balances[account];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
96
|
+
function isOperator(address holder, address spender) public view virtual returns (bool) {
|
|
97
|
+
return holder == spender || block.timestamp <= _operators[holder][spender];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
101
|
+
function setOperator(address operator, uint48 until) public virtual {
|
|
102
|
+
_setOperator(msg.sender, operator, until);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
106
|
+
function confidentialTransfer(
|
|
107
|
+
address to,
|
|
108
|
+
externalEuint64 encryptedAmount,
|
|
109
|
+
bytes calldata inputProof
|
|
110
|
+
) public virtual returns (euint64) {
|
|
111
|
+
return _transfer(msg.sender, to, FHE.fromExternal(encryptedAmount, inputProof));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
115
|
+
function confidentialTransfer(address to, euint64 amount) public virtual returns (euint64) {
|
|
116
|
+
require(
|
|
117
|
+
FHE.isAllowed(amount, msg.sender),
|
|
118
|
+
ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
|
|
119
|
+
);
|
|
120
|
+
return _transfer(msg.sender, to, amount);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
124
|
+
function confidentialTransferFrom(
|
|
125
|
+
address from,
|
|
126
|
+
address to,
|
|
127
|
+
externalEuint64 encryptedAmount,
|
|
128
|
+
bytes calldata inputProof
|
|
129
|
+
) public virtual returns (euint64 transferred) {
|
|
130
|
+
require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
|
|
131
|
+
transferred = _transfer(from, to, FHE.fromExternal(encryptedAmount, inputProof));
|
|
132
|
+
FHE.allowTransient(transferred, msg.sender);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
136
|
+
function confidentialTransferFrom(
|
|
137
|
+
address from,
|
|
138
|
+
address to,
|
|
139
|
+
euint64 amount
|
|
140
|
+
) 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));
|
|
146
|
+
transferred = _transfer(from, to, amount);
|
|
147
|
+
FHE.allowTransient(transferred, msg.sender);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
151
|
+
function confidentialTransferAndCall(
|
|
152
|
+
address to,
|
|
153
|
+
externalEuint64 encryptedAmount,
|
|
154
|
+
bytes calldata inputProof,
|
|
155
|
+
bytes calldata data
|
|
156
|
+
) public virtual returns (euint64 transferred) {
|
|
157
|
+
transferred = _transferAndCall(msg.sender, to, FHE.fromExternal(encryptedAmount, inputProof), data);
|
|
158
|
+
FHE.allowTransient(transferred, msg.sender);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
162
|
+
function confidentialTransferAndCall(
|
|
163
|
+
address to,
|
|
164
|
+
euint64 amount,
|
|
165
|
+
bytes calldata data
|
|
166
|
+
) public virtual returns (euint64 transferred) {
|
|
167
|
+
require(
|
|
168
|
+
FHE.isAllowed(amount, msg.sender),
|
|
169
|
+
ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
|
|
170
|
+
);
|
|
171
|
+
transferred = _transferAndCall(msg.sender, to, amount, data);
|
|
172
|
+
FHE.allowTransient(transferred, msg.sender);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
176
|
+
function confidentialTransferFromAndCall(
|
|
177
|
+
address from,
|
|
178
|
+
address to,
|
|
179
|
+
externalEuint64 encryptedAmount,
|
|
180
|
+
bytes calldata inputProof,
|
|
181
|
+
bytes calldata data
|
|
182
|
+
) public virtual returns (euint64 transferred) {
|
|
183
|
+
require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
|
|
184
|
+
transferred = _transferAndCall(from, to, FHE.fromExternal(encryptedAmount, inputProof), data);
|
|
185
|
+
FHE.allowTransient(transferred, msg.sender);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// @inheritdoc IConfidentialFungibleToken
|
|
189
|
+
function confidentialTransferFromAndCall(
|
|
190
|
+
address from,
|
|
191
|
+
address to,
|
|
192
|
+
euint64 amount,
|
|
193
|
+
bytes calldata data
|
|
194
|
+
) public virtual returns (euint64 transferred) {
|
|
195
|
+
require(
|
|
196
|
+
FHE.isAllowed(amount, msg.sender),
|
|
197
|
+
ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
|
|
198
|
+
);
|
|
199
|
+
require(isOperator(from, msg.sender), ConfidentialFungibleTokenUnauthorizedSpender(from, msg.sender));
|
|
200
|
+
transferred = _transferAndCall(from, to, amount, data);
|
|
201
|
+
FHE.allowTransient(transferred, msg.sender);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @dev Discloses an encrypted amount `encryptedAmount` publicly via an {IConfidentialFungibleToken-AmountDisclosed}
|
|
206
|
+
* event. The caller and this contract must be authorized to use the encrypted amount on the ACL.
|
|
207
|
+
*
|
|
208
|
+
* NOTE: This is an asynchronous operation where the actual decryption happens off-chain and
|
|
209
|
+
* {finalizeDiscloseEncryptedAmount} is called with the result.
|
|
210
|
+
*/
|
|
211
|
+
function discloseEncryptedAmount(euint64 encryptedAmount) public virtual {
|
|
212
|
+
require(
|
|
213
|
+
FHE.isAllowed(encryptedAmount, msg.sender) && FHE.isAllowed(encryptedAmount, address(this)),
|
|
214
|
+
ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(encryptedAmount, msg.sender)
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
bytes32[] memory cts = new bytes32[](1);
|
|
218
|
+
cts[0] = euint64.unwrap(encryptedAmount);
|
|
219
|
+
uint256 requestID = FHE.requestDecryption(cts, this.finalizeDiscloseEncryptedAmount.selector);
|
|
220
|
+
_requestHandles[requestID] = encryptedAmount;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// @dev May only be called by the gateway contract. Finalizes a disclose encrypted amount request.
|
|
224
|
+
function finalizeDiscloseEncryptedAmount(
|
|
225
|
+
uint256 requestId,
|
|
226
|
+
uint64 amount,
|
|
227
|
+
bytes[] memory signatures
|
|
228
|
+
) 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);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function _setOperator(address holder, address operator, uint48 until) internal virtual {
|
|
239
|
+
_operators[holder][operator] = until;
|
|
240
|
+
emit OperatorSet(holder, operator, until);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function _mint(address to, euint64 amount) internal returns (euint64 transferred) {
|
|
244
|
+
require(to != address(0), ConfidentialFungibleTokenInvalidReceiver(address(0)));
|
|
245
|
+
return _update(address(0), to, amount);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function _burn(address from, euint64 amount) internal returns (euint64 transferred) {
|
|
249
|
+
require(from != address(0), ConfidentialFungibleTokenInvalidSender(address(0)));
|
|
250
|
+
return _update(from, address(0), amount);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
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)));
|
|
256
|
+
return _update(from, to, amount);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function _transferAndCall(
|
|
260
|
+
address from,
|
|
261
|
+
address to,
|
|
262
|
+
euint64 amount,
|
|
263
|
+
bytes calldata data
|
|
264
|
+
) internal returns (euint64 transferred) {
|
|
265
|
+
// Try to transfer amount + replace input with actually transferred amount.
|
|
266
|
+
euint64 sent = _transfer(from, to, amount);
|
|
267
|
+
|
|
268
|
+
// Perform callback
|
|
269
|
+
transferred = FHE.select(
|
|
270
|
+
ConfidentialFungibleTokenUtils.checkOnTransferReceived(msg.sender, from, to, sent, data),
|
|
271
|
+
sent,
|
|
272
|
+
FHE.asEuint64(0)
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// Refund if success fails. refund should never fail
|
|
276
|
+
_update(to, from, FHE.sub(sent, transferred));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function _update(address from, address to, euint64 amount) internal virtual returns (euint64 transferred) {
|
|
280
|
+
ebool success;
|
|
281
|
+
euint64 ptr;
|
|
282
|
+
|
|
283
|
+
if (from == address(0)) {
|
|
284
|
+
(success, ptr) = TFHESafeMath.tryIncrease(_totalSupply, amount);
|
|
285
|
+
FHE.allowThis(ptr);
|
|
286
|
+
_totalSupply = ptr;
|
|
287
|
+
} else {
|
|
288
|
+
euint64 fromBalance = _balances[from];
|
|
289
|
+
require(FHE.isInitialized(fromBalance), ConfidentialFungibleTokenZeroBalance(from));
|
|
290
|
+
(success, ptr) = TFHESafeMath.tryDecrease(fromBalance, amount);
|
|
291
|
+
FHE.allowThis(ptr);
|
|
292
|
+
FHE.allow(ptr, from);
|
|
293
|
+
_balances[from] = ptr;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
transferred = FHE.select(success, amount, FHE.asEuint64(0));
|
|
297
|
+
|
|
298
|
+
if (to == address(0)) {
|
|
299
|
+
ptr = FHE.sub(_totalSupply, transferred);
|
|
300
|
+
FHE.allowThis(ptr);
|
|
301
|
+
_totalSupply = ptr;
|
|
302
|
+
} else {
|
|
303
|
+
ptr = FHE.add(_balances[to], transferred);
|
|
304
|
+
FHE.allowThis(ptr);
|
|
305
|
+
FHE.allow(ptr, to);
|
|
306
|
+
_balances[to] = ptr;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (from != address(0)) FHE.allow(transferred, from);
|
|
310
|
+
if (to != address(0)) FHE.allow(transferred, to);
|
|
311
|
+
FHE.allowThis(transferred);
|
|
312
|
+
emit ConfidentialTransfer(from, to, transferred);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Confidential Contracts (last updated v0.2.0-rc.1) (token/extensions/ConfidentialFungibleTokenERC20Wrapper.sol)
|
|
3
|
+
|
|
4
|
+
pragma solidity ^0.8.27;
|
|
5
|
+
|
|
6
|
+
import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
|
|
7
|
+
import {IERC1363Receiver} from "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol";
|
|
8
|
+
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
|
9
|
+
import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
|
|
10
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
11
|
+
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
|
|
12
|
+
import {ConfidentialFungibleToken} from "./../ConfidentialFungibleToken.sol";
|
|
13
|
+
|
|
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
|
|
17
|
+
* which allows users to transfer `ERC1363` tokens directly to the wrapper with a callback to wrap the tokens.
|
|
18
|
+
*/
|
|
19
|
+
abstract contract ConfidentialFungibleTokenERC20Wrapper is ConfidentialFungibleToken, IERC1363Receiver {
|
|
20
|
+
IERC20 private immutable _underlying;
|
|
21
|
+
uint8 private immutable _decimals;
|
|
22
|
+
uint256 private immutable _rate;
|
|
23
|
+
|
|
24
|
+
/// @dev Mapping from gateway decryption request ID to the address that will receive the tokens
|
|
25
|
+
mapping(uint256 decryptionRequest => address) private _receivers;
|
|
26
|
+
|
|
27
|
+
constructor(IERC20 underlying_) {
|
|
28
|
+
_underlying = underlying_;
|
|
29
|
+
|
|
30
|
+
uint8 tokenDecimals = _tryGetAssetDecimals(underlying_);
|
|
31
|
+
uint8 maxDecimals = _maxDecimals();
|
|
32
|
+
if (tokenDecimals > maxDecimals) {
|
|
33
|
+
_decimals = maxDecimals;
|
|
34
|
+
_rate = 10 ** (tokenDecimals - maxDecimals);
|
|
35
|
+
} else {
|
|
36
|
+
_decimals = tokenDecimals;
|
|
37
|
+
_rate = 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// @inheritdoc ConfidentialFungibleToken
|
|
42
|
+
function decimals() public view virtual override returns (uint8) {
|
|
43
|
+
return _decimals;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @dev Returns the rate at which the underlying token is converted to the wrapped token.
|
|
48
|
+
* For example, if the `rate` is 1000, then 1000 units of the underlying token equal 1 unit of the wrapped token.
|
|
49
|
+
*/
|
|
50
|
+
function rate() public view virtual returns (uint256) {
|
|
51
|
+
return _rate;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// @dev Returns the address of the underlying ERC-20 token that is being wrapped.
|
|
55
|
+
function underlying() public view returns (IERC20) {
|
|
56
|
+
return _underlying;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @dev `ERC1363` callback function which wraps tokens to the address specified in `data` or
|
|
61
|
+
* the address `from` (if no address is specified in `data`). This function refunds any excess tokens
|
|
62
|
+
* sent beyond the nearest multiple of {rate}. See {wrap} from more details on wrapping tokens.
|
|
63
|
+
*/
|
|
64
|
+
function onTransferReceived(
|
|
65
|
+
address /*operator*/,
|
|
66
|
+
address from,
|
|
67
|
+
uint256 amount,
|
|
68
|
+
bytes calldata data
|
|
69
|
+
) public virtual returns (bytes4) {
|
|
70
|
+
// 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);
|
|
76
|
+
|
|
77
|
+
// mint confidential token
|
|
78
|
+
address to = data.length < 20 ? from : address(bytes20(data));
|
|
79
|
+
_mint(to, FHE.asEuint64(SafeCast.toUint64(amount / rate())));
|
|
80
|
+
|
|
81
|
+
// return magic value
|
|
82
|
+
return IERC1363Receiver.onTransferReceived.selector;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @dev Wraps amount `amount` of the underlying token into a confidential token and sends it to
|
|
87
|
+
* `to`. Tokens are exchanged at a fixed rate specified by {rate} such that `amount / rate()` confidential
|
|
88
|
+
* tokens are sent. Amount transferred in is rounded down to the nearest multiple of {rate}.
|
|
89
|
+
*/
|
|
90
|
+
function wrap(address to, uint256 amount) public virtual {
|
|
91
|
+
// take ownership of the tokens
|
|
92
|
+
SafeERC20.safeTransferFrom(underlying(), msg.sender, address(this), amount - (amount % rate()));
|
|
93
|
+
|
|
94
|
+
// mint confidential token
|
|
95
|
+
_mint(to, FHE.asEuint64(SafeCast.toUint64(amount / rate())));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @dev Unwraps tokens from `from` and sends the underlying tokens to `to`. The caller must be `from`
|
|
100
|
+
* or be an approved operator for `from`. `amount * rate()` underlying tokens are sent to `to`.
|
|
101
|
+
*
|
|
102
|
+
* NOTE: This is an asynchronous function and waits for decryption to be completed off-chain before disbursing
|
|
103
|
+
* tokens.
|
|
104
|
+
* NOTE: The caller *must* already be approved by ACL for the given `amount`.
|
|
105
|
+
*/
|
|
106
|
+
function unwrap(address from, address to, euint64 amount) public virtual {
|
|
107
|
+
require(
|
|
108
|
+
FHE.isAllowed(amount, msg.sender),
|
|
109
|
+
ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, msg.sender)
|
|
110
|
+
);
|
|
111
|
+
_unwrap(from, to, amount);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @dev Variant of {unwrap} that passes an `inputProof` which approves the caller for the `encryptedAmount`
|
|
116
|
+
* in the ACL.
|
|
117
|
+
*/
|
|
118
|
+
function unwrap(
|
|
119
|
+
address from,
|
|
120
|
+
address to,
|
|
121
|
+
externalEuint64 encryptedAmount,
|
|
122
|
+
bytes calldata inputProof
|
|
123
|
+
) public virtual {
|
|
124
|
+
_unwrap(from, to, FHE.fromExternal(encryptedAmount, inputProof));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @dev Called by the fhEVM gateway with the decrypted amount `amount` for a request id `requestId`.
|
|
129
|
+
* Fills unwrap requests.
|
|
130
|
+
*/
|
|
131
|
+
function finalizeUnwrap(uint256 requestID, uint64 amount, bytes[] memory signatures) public virtual {
|
|
132
|
+
FHE.checkSignatures(requestID, signatures);
|
|
133
|
+
address to = _receivers[requestID];
|
|
134
|
+
require(to != address(0), ConfidentialFungibleTokenInvalidGatewayRequest(requestID));
|
|
135
|
+
delete _receivers[requestID];
|
|
136
|
+
|
|
137
|
+
SafeERC20.safeTransfer(underlying(), to, amount * rate());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
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
|
+
);
|
|
146
|
+
|
|
147
|
+
// try to burn, see how much we actually got
|
|
148
|
+
euint64 burntAmount = _burn(from, amount);
|
|
149
|
+
|
|
150
|
+
// decrypt that burntAmount
|
|
151
|
+
bytes32[] memory cts = new bytes32[](1);
|
|
152
|
+
cts[0] = euint64.unwrap(burntAmount);
|
|
153
|
+
uint256 requestID = FHE.requestDecryption(cts, this.finalizeUnwrap.selector);
|
|
154
|
+
|
|
155
|
+
// register who is getting the tokens
|
|
156
|
+
_receivers[requestID] = to;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @dev Returns the maximum number that will be used for {decimals} by the wrapper.
|
|
161
|
+
*/
|
|
162
|
+
function _maxDecimals() internal pure virtual returns (uint8) {
|
|
163
|
+
return 6;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function _tryGetAssetDecimals(IERC20 asset_) private view returns (uint8 assetDecimals) {
|
|
167
|
+
(bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
|
|
168
|
+
abi.encodeCall(IERC20Metadata.decimals, ())
|
|
169
|
+
);
|
|
170
|
+
if (success && encodedDecimals.length == 32) {
|
|
171
|
+
return abi.decode(encodedDecimals, (uint8));
|
|
172
|
+
}
|
|
173
|
+
return 18;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Confidential Contracts (last updated v0.2.0-rc.1) (token/extensions/ConfidentialFungibleTokenVotes.sol)
|
|
3
|
+
pragma solidity ^0.8.27;
|
|
4
|
+
|
|
5
|
+
import {euint64} from "@fhevm/solidity/lib/FHE.sol";
|
|
6
|
+
import {VotesConfidential} from "./../../governance/utils/VotesConfidential.sol";
|
|
7
|
+
import {ConfidentialFungibleToken} from "./../ConfidentialFungibleToken.sol";
|
|
8
|
+
|
|
9
|
+
abstract contract ConfidentialFungibleTokenVotes is ConfidentialFungibleToken, VotesConfidential {
|
|
10
|
+
function confidentialTotalSupply()
|
|
11
|
+
public
|
|
12
|
+
view
|
|
13
|
+
virtual
|
|
14
|
+
override(VotesConfidential, ConfidentialFungibleToken)
|
|
15
|
+
returns (euint64)
|
|
16
|
+
{
|
|
17
|
+
return super.confidentialTotalSupply();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function _update(address from, address to, euint64 amount) internal virtual override returns (euint64 transferred) {
|
|
21
|
+
transferred = super._update(from, to, amount);
|
|
22
|
+
|
|
23
|
+
_transferVotingUnits(from, to, transferred);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _getVotingUnits(address account) internal view virtual override returns (euint64) {
|
|
27
|
+
return confidentialBalanceOf(account);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Confidential Contracts (last updated v0.2.0-rc.1) (token/utils/ConfidentialFungibleTokenUtils.sol)
|
|
3
|
+
pragma solidity ^0.8.24;
|
|
4
|
+
|
|
5
|
+
import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
|
|
6
|
+
|
|
7
|
+
import {IConfidentialFungibleTokenReceiver} from "../../interfaces/IConfidentialFungibleTokenReceiver.sol";
|
|
8
|
+
import {ConfidentialFungibleToken} from "../ConfidentialFungibleToken.sol";
|
|
9
|
+
|
|
10
|
+
/// @dev Library that provides common {ConfidentialFungibleToken} utility functions.
|
|
11
|
+
library ConfidentialFungibleTokenUtils {
|
|
12
|
+
/**
|
|
13
|
+
* @dev Performs a transfer callback to the recipient of the transfer `to`. Should be invoked
|
|
14
|
+
* after all transfers "withCallback" on a {ConfidentialFungibleToken}.
|
|
15
|
+
*
|
|
16
|
+
* The transfer callback is not invoked on the recipient if the recipient has no code (i.e. is an EOA). If the
|
|
17
|
+
* recipient has non-zero code, it must implement
|
|
18
|
+
* {IConfidentialFungibleTokenReceiver-onConfidentialTransferReceived} and return an `ebool` indicating
|
|
19
|
+
* whether the transfer was accepted or not. If the `ebool` is `false`, the transfer will be reversed.
|
|
20
|
+
*/
|
|
21
|
+
function checkOnTransferReceived(
|
|
22
|
+
address operator,
|
|
23
|
+
address from,
|
|
24
|
+
address to,
|
|
25
|
+
euint64 amount,
|
|
26
|
+
bytes calldata data
|
|
27
|
+
) internal returns (ebool) {
|
|
28
|
+
if (to.code.length > 0) {
|
|
29
|
+
try
|
|
30
|
+
IConfidentialFungibleTokenReceiver(to).onConfidentialTransferReceived(operator, from, amount, data)
|
|
31
|
+
returns (ebool retval) {
|
|
32
|
+
return retval;
|
|
33
|
+
} catch (bytes memory reason) {
|
|
34
|
+
if (reason.length == 0) {
|
|
35
|
+
revert ConfidentialFungibleToken.ConfidentialFungibleTokenInvalidReceiver(to);
|
|
36
|
+
} else {
|
|
37
|
+
assembly ("memory-safe") {
|
|
38
|
+
revert(add(32, reason), mload(reason))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
return FHE.asEbool(true);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Confidential Contracts (last updated v0.2.0-rc.1) (utils/TFHESafeMath.sol)
|
|
3
|
+
pragma solidity ^0.8.24;
|
|
4
|
+
|
|
5
|
+
import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @dev Library providing safe arithmetic operations for encrypted values
|
|
9
|
+
* to handle potential overflows in FHE operations.
|
|
10
|
+
*/
|
|
11
|
+
library TFHESafeMath {
|
|
12
|
+
/**
|
|
13
|
+
* @dev Try to increase the encrypted value `oldValue` by `delta`. If the operation is successful,
|
|
14
|
+
* `success` will be true and `updated` will be the new value. Otherwise, `success` will be false
|
|
15
|
+
* and `updated` will be the original value.
|
|
16
|
+
*/
|
|
17
|
+
function tryIncrease(euint64 oldValue, euint64 delta) internal returns (ebool success, euint64 updated) {
|
|
18
|
+
if (!FHE.isInitialized(oldValue)) {
|
|
19
|
+
success = FHE.asEbool(true);
|
|
20
|
+
updated = delta;
|
|
21
|
+
} else {
|
|
22
|
+
euint64 newValue = FHE.add(oldValue, delta);
|
|
23
|
+
success = FHE.ge(newValue, oldValue);
|
|
24
|
+
updated = FHE.select(success, newValue, oldValue);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @dev Try to decrease the encrypted value `oldValue` by `delta`. If the operation is successful,
|
|
30
|
+
* `success` will be true and `updated` will be the new value. Otherwise, `success` will be false
|
|
31
|
+
* and `updated` will be the original value.
|
|
32
|
+
*/
|
|
33
|
+
function tryDecrease(euint64 oldValue, euint64 delta) internal returns (ebool success, euint64 updated) {
|
|
34
|
+
success = FHE.ge(oldValue, delta);
|
|
35
|
+
updated = FHE.select(success, FHE.sub(oldValue, delta), oldValue);
|
|
36
|
+
}
|
|
37
|
+
}
|