-
Notifications
You must be signed in to change notification settings - Fork 9
/
EBTCToken.sol
358 lines (298 loc) · 13.4 KB
/
EBTCToken.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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "./Interfaces/IEBTCToken.sol";
import "./Dependencies/AuthNoOwner.sol";
import "./Dependencies/PermitNonce.sol";
/*
*
* Based upon OpenZeppelin's ERC20 contract:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
*
* and their EIP2612 (ERC20Permit / ERC712) functionality:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol
*
*
* --- Functionality added specific to the EBTCToken ---
*
* 1) Transfer protection: blocklist of addresses that are invalid recipients (i.e. core Ebtc contracts) in external transfer() and transferFrom() calls.
* The purpose is to protect users from losing tokens by mistakenly sending EBTC directly to a Liquity.
* core contract, when they should rather call the right function.
*/
contract EBTCToken is IEBTCToken, AuthNoOwner, PermitNonce {
uint256 private _totalSupply;
string internal constant _NAME = "EBTC Stablecoin";
string internal constant _SYMBOL = "EBTC";
string internal constant _VERSION = "1";
uint8 internal constant _DECIMALS = 18;
// --- Data for EIP2612 ---
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 private constant _PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
// keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
bytes32 private constant _TYPE_HASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;
bytes32 private immutable _HASHED_NAME;
bytes32 private immutable _HASHED_VERSION;
// User data for EBTC token
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
// --- Addresses ---
address public immutable cdpManagerAddress;
address public immutable borrowerOperationsAddress;
/// @param _cdpManagerAddress Address of the CDP Manager
/// @param _borrowerOperationsAddress Address of the Borrower Operations
/// @param _authorityAddress Address of the authority for the contract
constructor(
address _cdpManagerAddress,
address _borrowerOperationsAddress,
address _authorityAddress
) {
_initializeAuthority(_authorityAddress);
cdpManagerAddress = _cdpManagerAddress;
borrowerOperationsAddress = _borrowerOperationsAddress;
bytes32 hashedName = keccak256(bytes(_NAME));
bytes32 hashedVersion = keccak256(bytes(_VERSION));
_HASHED_NAME = hashedName;
_HASHED_VERSION = hashedVersion;
_CACHED_CHAIN_ID = _chainID();
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, hashedName, hashedVersion);
}
/// @notice Mint new tokens
/// @dev Internal system function - only callable by BorrowerOperations or CDPManager
/// @dev Governance can also expand the list of approved minters to enable other systems to mint tokens
/// @param _account The address to receive the newly minted tokens
/// @param _amount The amount of tokens to mint
function mint(address _account, uint256 _amount) external override {
_requireCallerIsBOorCdpMOrAuth();
_mint(_account, _amount);
}
/// @notice Burn existing tokens
/// @dev Internal system function - only callable by BorrowerOperations or CDPManager
/// @dev Governance can also expand the list of approved burners to enable other systems to burn tokens
/// @param _account The address to burn tokens from
/// @param _amount The amount of tokens to burn
function burn(address _account, uint256 _amount) external override {
_requireCallerIsBOorCdpMOrAuth();
_burn(_account, _amount);
}
/// @notice Burn existing tokens from caller
/// @dev Internal system function - only callable by BorrowerOperations or CDPManager
/// @dev Governance can also expand the list of approved burners to enable other systems to burn tokens
/// @param _amount The amount of tokens to burn
function burn(uint256 _amount) external {
_requireCallerIsBOorCdpMOrAuth();
_burn(msg.sender, _amount);
}
// --- External functions ---
function totalSupply() external view override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) external view override returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) external override returns (bool) {
_requireValidRecipient(recipient);
_transfer(msg.sender, recipient, amount);
return true;
}
function allowance(address owner, address spender) external view override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) external override returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint256 amount
) external override returns (bool) {
_requireValidRecipient(recipient);
_transfer(sender, recipient, amount);
uint256 cachedAllowance = _allowances[sender][msg.sender];
if (cachedAllowance != type(uint256).max) {
require(cachedAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, msg.sender, cachedAllowance - amount);
}
}
return true;
}
function increaseAllowance(
address spender,
uint256 addedValue
) external override returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
return true;
}
function decreaseAllowance(
address spender,
uint256 subtractedValue
) external override returns (bool) {
uint256 cachedAllowances = _allowances[msg.sender][spender];
require(cachedAllowances >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(msg.sender, spender, cachedAllowances - subtractedValue);
}
return true;
}
// --- EIP 2612 Functionality (https://eips.ethereum.org/EIPS/eip-2612) ---
/// @notice This function returns the domain separator for current chain
/// @return EIP712 compatible Domain definition
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return domainSeparator();
}
/// @notice This function returns the domain separator for current chain
/// @return EIP712 compatible Domain definition
function domainSeparator() public view override returns (bytes32) {
if (_chainID() == _CACHED_CHAIN_ID) {
return _CACHED_DOMAIN_SEPARATOR;
} else {
return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
}
}
/// @notice This function approve given amount for specified owner and spender
/// @notice by verifying the validity of given deadline and signature parameters (v, r, s).
/// @param owner The token owner
/// @param spender The consumer to which owner want to grant approval
/// @param amount The token expenditure budget to be set
/// @param deadline The permit valid deadline
/// @param v The v part of signature from owner
/// @param r The r part of signature from owner
/// @param s The s part of signature from owner
function permit(
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external override {
require(deadline >= block.timestamp, "EBTC: expired deadline");
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
domainSeparator(),
keccak256(
abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner]++, deadline)
)
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress == owner, "EBTC: invalid signature");
_approve(owner, spender, amount);
}
/// @dev Return current nonce for specified owner fOR EIP-2612 compatibility
/// @param owner The address whose nonce to be queried
function nonces(address owner) external view override(IERC2612, PermitNonce) returns (uint256) {
return _nonces[owner];
}
// --- Internal operations ---
function _chainID() private view returns (uint256) {
return block.chainid;
}
function _buildDomainSeparator(
bytes32 typeHash,
bytes32 name,
bytes32 version
) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));
}
// --- Internal operations ---
// Warning: sanity checks (for sender and recipient) should have been done before calling these internal functions
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "EBTCToken: zero sender!");
require(recipient != address(0), "EBTCToken: zero recipient!");
uint256 cachedSenderBalances = _balances[sender];
require(cachedSenderBalances >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
// Safe because of the check above
_balances[sender] = cachedSenderBalances - amount;
}
_balances[recipient] = _balances[recipient] + amount;
emit Transfer(sender, recipient, amount);
}
function _mint(address account, uint256 amount) internal {
require(account != address(0), "EBTCToken: mint to zero recipient!");
_totalSupply = _totalSupply + amount;
_balances[account] = _balances[account] + amount;
emit Transfer(address(0), account, amount);
}
function _burn(address account, uint256 amount) internal {
require(account != address(0), "EBTCToken: burn from zero account!");
uint256 cachedBalance = _balances[account];
require(cachedBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
// Safe because of the check above
_balances[account] = cachedBalance - amount;
}
_totalSupply = _totalSupply - amount;
emit Transfer(account, address(0), amount);
}
function _approve(address owner, address spender, uint256 amount) internal {
require(owner != address(0), "EBTCToken: zero approve owner!");
require(spender != address(0), "EBTCToken: zero approve spender!");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
// --- 'require' functions ---
function _requireValidRecipient(address _recipient) internal view {
require(
_recipient != address(0) && _recipient != address(this),
"EBTC: Cannot transfer tokens directly to the EBTC token contract or the zero address"
);
require(
_recipient != cdpManagerAddress && _recipient != borrowerOperationsAddress,
"EBTC: Cannot transfer tokens directly to the CdpManager or BorrowerOps"
);
}
function _requireCallerIsBorrowerOperations() internal view {
require(
msg.sender == borrowerOperationsAddress,
"EBTCToken: Caller is not BorrowerOperations"
);
}
/// @dev authority check last to short-circuit in the case of use by usual immutable addresses
function _requireCallerIsBOorCdpMOrAuth() internal view {
require(
msg.sender == borrowerOperationsAddress ||
msg.sender == cdpManagerAddress ||
isAuthorized(msg.sender, msg.sig),
"EBTC: Caller is neither BorrowerOperations nor CdpManager nor authorized"
);
}
function _requireCallerIsCdpM() internal view {
require(msg.sender == cdpManagerAddress, "EBTC: Caller is not CdpManager");
}
// --- Optional functions ---
/// @notice Returns the name of the token
/// @return Name of the token
function name() external pure override returns (string memory) {
return _NAME;
}
/// @notice Returns the symbol of the token
/// @return Symbol of the token
function symbol() external pure override returns (string memory) {
return _SYMBOL;
}
/// @notice Returns the number of decimals used to represent token amounts
/// @return Number of decimals used by the token
function decimals() external pure override returns (uint8) {
return _DECIMALS;
}
/// @notice Returns the version of the token
/// @return Version of the token
function version() external pure override returns (string memory) {
return _VERSION;
}
/// @notice Returns the type hash used for permit() function as per EIP-2612
/// @return EIP-2612 permit type hash
function permitTypeHash() external pure override returns (bytes32) {
return _PERMIT_TYPEHASH;
}
}