-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathAbstractVault.sol
383 lines (321 loc) · 13.6 KB
/
AbstractVault.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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC4626Vault } from "../interfaces/IERC4626Vault.sol";
import { VaultManagerRole } from "../shared/VaultManagerRole.sol";
import { InitializableToken } from "../tokens/InitializableToken.sol";
/**
* @title Abstract ERC-4626 Vault.
* @author mStable
* @notice See the following for the full EIP-4626 specification https://eips.ethereum.org/EIPS/eip-4626.
* Connects to the mStable Nexus to get modules and roles like the `Governor` and `Liquidator`.
* Creates the `VaultManager` role.
*
* The `totalAssets`, `_beforeWithdrawHook` and `_afterDepositHook` functions need to be implemented.
*
* @dev VERSION: 1.0
* DATE: 2022-02-10
*
* The constructor of implementing contracts need to call the following:
* - VaultManagerRole(_nexus)
* - AbstractVault(_assetArg)
*
* The `initialize` function of implementing contracts need to call the following:
* - InitializableToken._initialize(_name, _symbol, decimals)
* - VaultManagerRole._initialize(_vaultManager)
* - AbstractVault._initialize(_assetToBurn)
*/
abstract contract AbstractVault is IERC4626Vault, InitializableToken, VaultManagerRole {
using SafeERC20 for IERC20;
/// @notice Address of the vault's underlying asset token.
IERC20 internal immutable _asset;
/**
* @param _assetArg Address of the vault's underlying asset.
*/
constructor(address _assetArg) {
require(_assetArg != address(0), "Asset is zero");
_asset = IERC20(_assetArg);
}
/**
* @param _assetToBurn amount of assets that will be deposited and corresponding shares locked permanently
* @dev This is to prevent against loss of precision and frontrunning the user deposits by sandwitch attack. Should be a non-trivial amount
*/
function _initialize(uint256 _assetToBurn) internal virtual {
if (_assetToBurn > 0) {
// deposit the assets and transfer shares to the vault to lock permanently
_deposit(_assetToBurn, address(this));
}
}
/*///////////////////////////////////////////////////////////////
DEPOSIT/MINT
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver)
external
virtual
override
whenNotPaused
returns (uint256 shares)
{
shares = _deposit(assets, receiver);
}
function _deposit(uint256 assets, address receiver) internal virtual returns (uint256 shares) {
shares = _previewDeposit(assets);
_transferAndMint(assets, shares, receiver, true);
}
function previewDeposit(uint256 assets) external view override returns (uint256 shares) {
shares = _previewDeposit(assets);
}
function _previewDeposit(uint256 assets) internal view virtual returns (uint256 shares) {
shares = _convertToShares(assets, false);
}
function maxDeposit(address caller) external view override returns (uint256 maxAssets) {
maxAssets = _maxDeposit(caller);
}
function _maxDeposit(address) internal view virtual returns (uint256 maxAssets) {
if (paused()) {
return 0;
}
maxAssets = type(uint256).max;
}
function mint(uint256 shares, address receiver)
external
virtual
override
whenNotPaused
returns (uint256 assets)
{
assets = _mint(shares, receiver);
}
function _mint(uint256 shares, address receiver) internal virtual returns (uint256 assets) {
assets = _previewMint(shares);
_transferAndMint(assets, shares, receiver, false);
}
function previewMint(uint256 shares) external view override returns (uint256 assets) {
assets = _previewMint(shares);
}
function _previewMint(uint256 shares) internal view virtual returns (uint256 assets) {
assets = _convertToAssets(shares, true);
}
function maxMint(address owner) external view override returns (uint256 maxShares) {
maxShares = _maxMint(owner);
}
function _maxMint(address) internal view virtual returns (uint256 maxShares) {
if (paused()) {
return 0;
}
maxShares = type(uint256).max;
}
/*///////////////////////////////////////////////////////////////
INTERNAL DEPSOIT/MINT
//////////////////////////////////////////////////////////////*/
function _transferAndMint(
uint256 assets,
uint256 shares,
address receiver,
bool fromDeposit
) internal virtual {
_asset.safeTransferFrom(msg.sender, address(this), assets);
_afterDepositHook(assets, shares, receiver, fromDeposit);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
/*///////////////////////////////////////////////////////////////
WITHDRAW/REDEEM
//////////////////////////////////////////////////////////////*/
function withdraw(
uint256 assets,
address receiver,
address owner
) external virtual override whenNotPaused returns (uint256 shares) {
shares = _withdraw(assets, receiver, owner);
}
function _withdraw(
uint256 assets,
address receiver,
address owner
) internal virtual returns (uint256 shares) {
shares = _previewWithdraw(assets);
_burnTransfer(assets, shares, receiver, owner, false);
}
function previewWithdraw(uint256 assets) external view override returns (uint256 shares) {
shares = _previewWithdraw(assets);
}
function _previewWithdraw(uint256 assets) internal view virtual returns (uint256 shares) {
shares = _convertToShares(assets, true);
}
function maxWithdraw(address owner) external view override returns (uint256 maxAssets) {
maxAssets = _maxWithdraw(owner);
}
function _maxWithdraw(address owner) internal view virtual returns (uint256 maxAssets) {
if (paused()) {
return 0;
}
maxAssets = _previewRedeem(balanceOf(owner));
}
function redeem(
uint256 shares,
address receiver,
address owner
) external virtual override whenNotPaused returns (uint256 assets) {
assets = _redeem(shares, receiver, owner);
}
function _redeem(
uint256 shares,
address receiver,
address owner
) internal virtual returns (uint256 assets) {
assets = _previewRedeem(shares);
_burnTransfer(assets, shares, receiver, owner, true);
}
function previewRedeem(uint256 shares) external view override returns (uint256 assets) {
assets = _previewRedeem(shares);
}
function _previewRedeem(uint256 shares) internal view virtual returns (uint256 assets) {
assets = _convertToAssets(shares, false);
}
function maxRedeem(address owner) external view override returns (uint256 maxShares) {
maxShares = _maxRedeem(owner);
}
function _maxRedeem(address owner) internal view virtual returns (uint256 maxShares) {
if (paused()) {
return 0;
}
maxShares = balanceOf(owner);
}
/*///////////////////////////////////////////////////////////////
INTERNAL WITHDRAW/REDEEM
//////////////////////////////////////////////////////////////*/
function _burnTransfer(
uint256 assets,
uint256 shares,
address receiver,
address owner,
bool fromRedeem
) internal virtual {
// If caller is not the owner of the shares
uint256 allowed = allowance(owner, msg.sender);
if (msg.sender != owner && allowed != type(uint256).max) {
require(shares <= allowed, "Amount exceeds allowance");
_approve(owner, msg.sender, allowed - shares);
}
_beforeWithdrawHook(assets, shares, owner, fromRedeem);
_burn(owner, shares);
_asset.safeTransfer(receiver, assets);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}
/*///////////////////////////////////////////////////////////////
EXTENRAL ASSETS
//////////////////////////////////////////////////////////////*/
function asset() external view virtual override returns (address assetTokenAddress) {
assetTokenAddress = address(_asset);
}
/**
* @notice It should include any compounding that occurs from yield. It must be inclusive of any fees that are charged against assets in the Vault. It must not revert.
*
* Returns the total amount of the underlying asset that is “managed” by vault.
*/
function totalAssets() public view virtual override returns (uint256 totalManagedAssets);
/*///////////////////////////////////////////////////////////////
CONVERTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice The amount of assets that the Vault would exchange for the amount of shares provided, in an ideal scenario where all the conditions are met.
* @param shares The amount of vault shares to be converted to the underlying assets.
* @return assets The amount of underlying assets converted from the vault shares.
*/
function convertToAssets(uint256 shares)
external
view
virtual
override
returns (uint256 assets)
{
assets = _convertToAssets(shares, false);
}
/// @param shares The amount of vault shares to be converted to the underlying assets.
/// @param isRoundUp bool to indicate round up the assets
/// @dev isRoundUp is used to round-up the assets amount for mint and previewMint
function _convertToAssets(uint256 shares, bool isRoundUp) internal view virtual returns (uint256 assets) {
uint256 totalShares = totalSupply();
if (totalShares == 0) {
assets = shares; // 1:1 value of shares and assets
} else {
uint256 totalAssetsMem = totalAssets();
assets = (shares * totalAssetsMem) / totalShares;
// Round Up if needed
if(isRoundUp && mulmod(shares, totalAssetsMem, totalShares) > 0) {
assets += 1;
}
}
}
/**
* @notice The amount of shares that the Vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met.
* @param assets The amount of underlying assets to be convert to vault shares.
* @return shares The amount of vault shares converted from the underlying assets.
*/
function convertToShares(uint256 assets)
external
view
virtual
override
returns (uint256 shares)
{
shares = _convertToShares(assets, false);
}
/// @param assets The amount of underlying assets to be convert to vault shares.
/// @param isRoundUp bool to indicate round up the shares
/// @dev isRoundUp is used to round-up the shares amount for withdraw and previewWithdraw
function _convertToShares(uint256 assets, bool isRoundUp) internal view virtual returns (uint256 shares) {
uint256 totalShares = totalSupply();
if (totalShares == 0) {
shares = assets; // 1:1 value of shares and assets
} else {
uint256 totalAssetsMem = totalAssets();
shares = (assets * totalShares) / totalAssetsMem;
// Round Up if needed
if (isRoundUp && mulmod(assets, totalShares, totalAssetsMem) > 0) {
shares += 1;
}
}
}
/*///////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
/**
* Called be the `deposit` and `mint` functions after the assets have been transferred into the vault
* but before shares are minted.
* Typically, the hook implementation deposits the assets into the underlying vaults or platforms.
*
* @dev the shares returned from `totalSupply` and `balanceOf` have not yet been updated with the minted shares.
* The assets returned from `totalAssets` and `assetsOf` are typically updated as part of the `_afterDepositHook` hook but it depends on the implementation.
*
* If an vault is implementing multiple vault capabilities, the `_afterDepositHook` function that updates the assets amounts should be executed last.
*
* @param assets the amount of underlying assets to be transferred to the vault.
* @param shares the amount of vault shares to be minted.
* @param receiver the account that is receiving the minted shares.
*/
function _afterDepositHook(
uint256 assets,
uint256 shares,
address receiver,
bool fromDeposit
) internal virtual {}
/**
* Called be the `withdraw` and `redeem` functions before
* the assets have been transferred from the vault to the receiver
* and before the owner's shares are burnt.
* Typically, the hook implementation withdraws the assets from the underlying vaults or platforms.
*
* @param assets the amount of underlying assets to be withdrawn from the vault.
* @param shares the amount of vault shares to be burnt.
* @param owner the account that owns the shares that are being burnt.
*/
function _beforeWithdrawHook(
uint256 assets,
uint256 shares,
address owner,
bool fromRedeem
) internal virtual {}
}