-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathConfidentialERC20.sol
230 lines (198 loc) · 7.86 KB
/
ConfidentialERC20.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import "fhevm/lib/TFHE.sol";
import { IERC20Errors } from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import { IConfidentialERC20 } from "./IConfidentialERC20.sol";
import { TFHEErrors } from "../../utils/TFHEErrors.sol";
/**
* @title ConfidentialERC20.
* @notice This contract implements an encrypted ERC20-like token with confidential balances using
* Zama's FHE (Fully Homomorphic Encryption) library.
* @dev It supports standard ERC20 functions such as transferring tokens, minting,
* and setting allowances, but uses encrypted data types.
* The total supply is not encrypted.
*/
abstract contract ConfidentialERC20 is IConfidentialERC20, IERC20Errors, TFHEErrors {
/// @notice Used as a placeholder in `Approval` & `Transfer` events to comply with the official EIP20.
uint256 internal constant _PLACEHOLDER = type(uint256).max;
/// @notice Total supply.
uint64 internal _totalSupply;
/// @notice Name.
string internal _name;
/// @notice Symbol.
string internal _symbol;
/// @notice A mapping from `account` address to an encrypted `balance`.
mapping(address account => euint64 balance) internal _balances;
/// @notice A mapping of the form mapping(account => mapping(spender => allowance)).
mapping(address account => mapping(address spender => euint64 allowance)) internal _allowances;
/**
* @param name_ Name of the token.
* @param symbol_ Symbol.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @notice See {IConfidentialERC20-approve}.
*/
function approve(address spender, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) {
approve(spender, TFHE.asEuint64(encryptedAmount, inputProof));
return true;
}
/**
* @notice See {IConfidentialERC20-approve}.
*/
function approve(address spender, euint64 amount) public virtual returns (bool) {
_isSenderAllowedForAmount(amount);
address owner = msg.sender;
_approve(owner, spender, amount);
emit Approval(owner, spender, _PLACEHOLDER);
return true;
}
/**
* @notice See {IConfidentialERC20-transfer}.
*/
function transfer(address to, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) {
transfer(to, TFHE.asEuint64(encryptedAmount, inputProof));
return true;
}
/**
* @notice See {IConfidentialERC20-transfer}.
*/
function transfer(address to, euint64 amount) public virtual returns (bool) {
_isSenderAllowedForAmount(amount);
/// @dev Make sure the owner has enough tokens.
ebool canTransfer = TFHE.le(amount, _balances[msg.sender]);
_transfer(msg.sender, to, amount, canTransfer);
return true;
}
/**
* @notice See {IConfidentialERC20-transferFrom}.
*/
function transferFrom(
address from,
address to,
einput encryptedAmount,
bytes calldata inputProof
) public virtual returns (bool) {
transferFrom(from, to, TFHE.asEuint64(encryptedAmount, inputProof));
return true;
}
/**
* @notice See {IConfidentialERC20-transferFrom}.
*/
function transferFrom(address from, address to, euint64 amount) public virtual returns (bool) {
_isSenderAllowedForAmount(amount);
address spender = msg.sender;
ebool isTransferable = _updateAllowance(from, spender, amount);
_transfer(from, to, amount, isTransferable);
return true;
}
/**
* @notice See {IConfidentialERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (euint64) {
return _allowance(owner, spender);
}
/**
* @notice See {IConfidentialERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (euint64) {
return _balances[account];
}
/**
* @notice See {IConfidentialERC20-decimals}.
*/
function decimals() public view virtual returns (uint8) {
return 6;
}
/**
* @notice See {IConfidentialERC20-name}.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @notice See {IConfidentialERC20-symbol}.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @notice See {IConfidentialERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint64) {
return _totalSupply;
}
function _approve(address owner, address spender, euint64 amount) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(owner);
}
if (spender == address(0)) {
revert ERC20InvalidSpender(spender);
}
_allowances[owner][spender] = amount;
TFHE.allowThis(amount);
TFHE.allow(amount, owner);
TFHE.allow(amount, spender);
}
/**
* @dev It does not incorporate any overflow check. It must be implemented
* by the function calling it.
*/
function _unsafeMint(address account, uint64 amount) internal virtual {
_unsafeMintNoEvent(account, amount);
emit Transfer(address(0), account, _PLACEHOLDER);
}
/**
* @dev It does not incorporate any overflow check. It must be implemented
* by the function calling it.
*/
function _unsafeMintNoEvent(address account, uint64 amount) internal virtual {
euint64 newBalanceAccount = TFHE.add(_balances[account], amount);
_balances[account] = newBalanceAccount;
TFHE.allowThis(newBalanceAccount);
TFHE.allow(newBalanceAccount, account);
}
function _transfer(address from, address to, euint64 amount, ebool isTransferable) internal virtual {
_transferNoEvent(from, to, amount, isTransferable);
emit Transfer(from, to, _PLACEHOLDER);
}
function _transferNoEvent(address from, address to, euint64 amount, ebool isTransferable) internal virtual {
if (from == address(0)) {
revert ERC20InvalidSender(from);
}
if (to == address(0)) {
revert ERC20InvalidReceiver(to);
}
/// @dev Add to the balance of `to` and subtract from the balance of `from`.
euint64 transferValue = TFHE.select(isTransferable, amount, TFHE.asEuint64(0));
euint64 newBalanceTo = TFHE.add(_balances[to], transferValue);
_balances[to] = newBalanceTo;
TFHE.allowThis(newBalanceTo);
TFHE.allow(newBalanceTo, to);
euint64 newBalanceFrom = TFHE.sub(_balances[from], transferValue);
_balances[from] = newBalanceFrom;
TFHE.allowThis(newBalanceFrom);
TFHE.allow(newBalanceFrom, from);
}
function _updateAllowance(address owner, address spender, euint64 amount) internal virtual returns (ebool) {
euint64 currentAllowance = _allowance(owner, spender);
/// @dev Make sure sure the allowance suffices.
ebool allowedTransfer = TFHE.le(amount, currentAllowance);
/// @dev Make sure the owner has enough tokens.
ebool canTransfer = TFHE.le(amount, _balances[owner]);
ebool isTransferable = TFHE.and(canTransfer, allowedTransfer);
_approve(owner, spender, TFHE.select(isTransferable, TFHE.sub(currentAllowance, amount), currentAllowance));
return isTransferable;
}
function _allowance(address owner, address spender) internal view virtual returns (euint64) {
return _allowances[owner][spender];
}
function _isSenderAllowedForAmount(euint64 amount) internal view virtual {
if (!TFHE.isSenderAllowed(amount)) {
revert TFHESenderNotAllowed();
}
}
}