-
Notifications
You must be signed in to change notification settings - Fork 0
/
VYToken.sol
254 lines (218 loc) · 12.6 KB
/
VYToken.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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.13;
import "erc3156/contracts/interfaces/IERC3156FlashBorrower.sol";
import "erc3156/contracts/interfaces/IERC3156FlashLender.sol";
import "@yield-protocol/utils-v2/src/token/ERC20Permit.sol";
import "@yield-protocol/utils-v2/src/token/SafeERC20Namer.sol";
import "@yield-protocol/utils-v2/src/access/AccessControl.sol";
import "@yield-protocol/utils-v2/src/utils/Math.sol";
import "@yield-protocol/utils-v2/src/utils/Cast.sol";
import "../interfaces/IJoin.sol";
import "../interfaces/IOracle.sol";
import "../constants/Constants.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
contract VYToken is IERC3156FlashLender, UUPSUpgradeable, AccessControl, ERC20Permit, Constants {
using Math for uint256;
using Cast for uint256;
event FlashFeeFactorSet(uint256 indexed fee);
event Redeemed(address indexed holder, address indexed receiver, uint256 principalAmount, uint256 underlyingAmount);
bool public initialized;
bytes32 internal constant FLASH_LOAN_RETURN = keccak256("ERC3156FlashBorrower.onFlashLoan");
uint256 constant FLASH_LOANS_DISABLED = type(uint256).max;
uint256 public flashFeeFactor = FLASH_LOANS_DISABLED; // Fee on flash loans, as a percentage in fixed point with 18 decimals. Flash loans disabled by default by overflow from `flashFee`.
IOracle public immutable oracle; // Oracle for the savings rate.
IJoin public immutable join; // Source of redemption funds.
address public immutable underlying;
bytes6 public immutable underlyingId; // Needed to access the oracle
constructor(
bytes6 underlyingId_,
IOracle oracle_, // Underlying vs its interest-bearing version
IJoin join_,
string memory name,
string memory symbol
) ERC20Permit(name, symbol, SafeERC20Namer.tokenDecimals(address(IJoin(join_).asset()))) {
// The join asset is this vyToken's underlying, from which we inherit the decimals
underlyingId = underlyingId_;
join = join_;
underlying = address(IJoin(join_).asset());
oracle = oracle_;
// See https://medium.com/immunefi/wormhole-uninitialized-proxy-bugfix-review-90250c41a43a
initialized = true; // Lock the implementation contract
_revokeRole(ROOT, msg.sender); // Remove the deployer's ROOT role
}
/// @dev Give the ROOT role and create a LOCK role with itself as the admin role and no members.
/// Calling setRoleAdmin(msg.sig, LOCK) means no one can grant that msg.sig role anymore.
function initialize (address root_, string memory name_, string memory symbol_, uint8 decimals_) public {
require(!initialized, "Already initialized");
initialized = true; // On an uninitialized contract, no governance functions can be executed, because no one has permission to do so
_grantRole(ROOT, root_); // Grant ROOT
_setRoleAdmin(LOCK, LOCK); // Create the LOCK role by setting itself as its own admin, creating an independent role tree
flashFeeFactor = FLASH_LOANS_DISABLED; // Flash loans disabled by default
name = name_;
symbol = symbol_;
decimals = decimals_;
}
/// @dev Allow to set a new implementation
function _authorizeUpgrade(address newImplementation) internal override auth {}
/// @dev Set the flash loan fee factor
function setFlashFeeFactor(uint256 flashFeeFactor_) external auth {
flashFeeFactor = flashFeeFactor_;
emit FlashFeeFactorSet(flashFeeFactor_);
}
///@dev Converts the amount of the principal to the underlying
function convertToUnderlying(uint256 principalAmount) external returns (uint256 underlyingAmount) {
return _convertToUnderlying(principalAmount);
}
///@dev Converts the amount of the principal to the underlying
function _convertToUnderlying(uint256 principalAmount) internal returns (uint256 underlyingAmount) {
(uint256 chi, ) = oracle.get(underlyingId, CHI, 0); // The value returned is an accumulator, it doesn't need an input amount
return principalAmount.wmul(chi);
}
///@dev Converts the amount of the underlying to the principal
function convertToPrincipal(uint256 underlyingAmount) external returns (uint256 principalAmount) {
return _convertToPrincipal(underlyingAmount);
}
///@dev Converts the amount of the underlying to the principal
function _convertToPrincipal(uint256 underlyingAmount) internal returns (uint256 princpalAmount) {
(uint256 chi, ) = oracle.get(underlyingId, CHI, 0); // The value returned is an accumulator, it doesn't need an input amount
return underlyingAmount.wdivup(chi);
}
///@dev returns the maximum redeemable amount for the address holder in terms of the principal
function maxRedeem(address holder) external view returns (uint256 maxPrincipalAmount) {
return _balanceOf[holder];
}
///@dev returns the amount of underlying redeemable in terms of the principal
function previewRedeem(uint256 principalAmount) external returns (uint256 underlyingAmount) {
return _convertToUnderlying(principalAmount);
}
/// @dev Burn vyToken for an amount of principal that increases according to `chi`
/// If `amount` is 0, the contract will redeem instead the vyToken balance of this contract. Useful for batches.
function redeem(uint256 principalAmount, address receiver, address holder) external returns (uint256 underlyingAmount) {
principalAmount = (principalAmount == 0) ? _balanceOf[address(this)] : principalAmount;
_burn(holder, principalAmount);
underlyingAmount = _convertToUnderlying(principalAmount);
join.exit(receiver, underlyingAmount.u128());
emit Redeemed(holder, receiver, principalAmount, underlyingAmount);
}
/// @dev Burn vyToken for an amount of principal that increases according to `chi`
/// If `amount` is 0, the contract will redeem instead the vyToken balance of this contract. Useful for batches.
function redeem(address receiver, uint256 principalAmount) external returns (uint256 underlyingAmount) {
principalAmount = (principalAmount == 0) ? _balanceOf[address(this)] : principalAmount;
_burn(msg.sender, principalAmount);
underlyingAmount = _convertToUnderlying(principalAmount);
join.exit(receiver, underlyingAmount.u128());
emit Redeemed(msg.sender, receiver, principalAmount, underlyingAmount);
}
///@dev returns the maximum withdrawable amount for the address holder in terms of the underlying
function maxWithdraw(address holder) external returns (uint256 maxUnderlyingAmount) {
return _convertToUnderlying(_balanceOf[holder]);
}
///@dev returns the amount of the principal withdrawable in terms of the underlying
function previewWithdraw(uint256 underlyingAmount) external returns (uint256 principalAmount) {
return _convertToPrincipal(underlyingAmount);
}
/// @dev Burn vyToken for an amount of underlying that increases according to `chi`
/// If `amount` is 0, the contract will redeem instead the vyToken balance of this contract. Useful for batches.
function withdraw(uint256 underlyingAmount, address receiver, address holder) external returns (uint256 principalAmount) {
principalAmount = (underlyingAmount == 0) ? _balanceOf[address(this)] : _convertToPrincipal(underlyingAmount);
_burn(holder, principalAmount);
underlyingAmount = _convertToUnderlying(principalAmount);
join.exit(receiver, underlyingAmount.u128());
emit Redeemed(holder, receiver, principalAmount, underlyingAmount);
}
/// @dev Mint vyTokens.
function mint(address receiver, uint256 principalAmount) external auth {
join.join(msg.sender, _convertToUnderlying(principalAmount).u128());
_mint(receiver, principalAmount);
}
///@dev returns the maximum mintable amount for the address holder in terms of the principal
function maxMint(address) external view returns (uint256 maxPrincipalAmount) {
return type(uint256).max - _totalSupply;
}
///@dev returns the amount of the principal mintable in terms of the underlying
function previewMint(uint256 principalAmount) external returns (uint256 underlyingAmount) {
return _convertToUnderlying(principalAmount.u128());
}
/// @dev Mint vyTokens.
function deposit(address receiver, uint256 underlyingAmount) external auth {
join.join(msg.sender, underlyingAmount.u128());
_mint(receiver, _convertToPrincipal(underlyingAmount));
}
///@dev returns the maximum depositable amount for the address holder in terms of the underlying
function maxDeposit(address) external returns (uint256 maxUnderlyingAmount) {
return _convertToUnderlying(type(uint256).max - _totalSupply);
}
///@dev returns the amount of the underlying depositable in terms of the principal
function previewDeposit(uint256 underlyingAmount) external returns (uint256 principalAmount) {
return _convertToPrincipal(underlyingAmount.u128());
}
/// @dev Burn vyTokens.
/// Any tokens locked in this contract will be burned first and subtracted from the amount to burn from the user's wallet.
/// This feature allows someone to transfer vyToken to this contract to enable a `burn`, potentially saving the cost of `approve` or `permit`.
function _burn(address holder, uint256 principalAmount) internal override returns (bool) {
// First use any tokens locked in this contract
uint256 available = _balanceOf[address(this)];
if (available >= principalAmount) {
return super._burn(address(this), principalAmount);
} else {
if (available > 0) super._burn(address(this), available);
unchecked {
_decreaseAllowance(holder, principalAmount - available);
}
unchecked {
return super._burn(holder, principalAmount - available);
}
}
}
/**
* @dev From ERC-3156. The amount of currency available to be lended.
* @param token The loan currency. It must be a VYToken contract.
* @return The amount of `token` that can be borrowed.
*/
function maxFlashLoan(address token) external view returns (uint256) {
return token == address(this) ? type(uint256).max - _totalSupply : 0;
}
/**
* @dev From ERC-3156. The fee to be charged for a given loan.
* @param token The loan currency. It must be the asset.
* @param principalAmount The amount of tokens lent.
* @return The amount of `token` to be charged for the loan, on top of the returned principal.
*/
function flashFee(address token, uint256 principalAmount) external view returns (uint256) {
require(token == address(this), "Unsupported currency");
return _flashFee(principalAmount);
}
/**
* @dev The fee to be charged for a given loan.
* @param principalAmount The amount of tokens lent.
* @return The amount of `token` to be charged for the loan, on top of the returned principal.
*/
function _flashFee(uint256 principalAmount) internal view returns (uint256) {
return principalAmount.wmul(flashFeeFactor);
}
/**
* @dev From ERC-3156. Loan `amount` vyDai to `receiver`, which needs to return them plus fee to this contract within the same transaction.
* Note that if the initiator and the borrower are the same address, no approval is needed for this contract to take the principal + fee from the borrower.
* If the borrower transfers the principal + fee to this contract, they will be burnt here instead of pulled from the borrower.
* @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface.
* @param token The loan currency. Must be a vyDai contract.
* @param principalAmount The amount of tokens lent.
* @param data A data parameter to be passed on to the `receiver` for any custom use.
*/
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 principalAmount,
bytes memory data
) external returns (bool) {
require(token == address(this), "Unsupported currency");
_mint(address(receiver), principalAmount);
uint128 fee = _flashFee(principalAmount).u128();
require(
receiver.onFlashLoan(msg.sender, token, principalAmount, fee, data) == FLASH_LOAN_RETURN,
"Non-compliant borrower"
);
_burn(address(receiver), principalAmount + fee);
return true;
}
}