This repository has been archived by the owner on May 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
BaseStrategyVault.sol
237 lines (199 loc) · 9.38 KB
/
BaseStrategyVault.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
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.17;
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {Token, TokenType} from "../../global/Types.sol";
import {Deployments} from "../../global/Deployments.sol";
import {Constants} from "../../global/Constants.sol";
import {IStrategyVault} from "../../../interfaces/notional/IStrategyVault.sol";
import {NotionalProxy} from "../../../interfaces/notional/NotionalProxy.sol";
import {ITradingModule, Trade} from "../../../interfaces/trading/ITradingModule.sol";
import {IERC20} from "../../../interfaces/IERC20.sol";
import {TokenUtils} from "../../utils/TokenUtils.sol";
import {TradeHandler} from "../../trading/TradeHandler.sol";
import {nProxy} from "../../proxy/nProxy.sol";
abstract contract BaseStrategyVault is Initializable, IStrategyVault, AccessControlUpgradeable {
using TokenUtils for IERC20;
using TradeHandler for Trade;
bytes32 public constant EMERGENCY_EXIT_ROLE = keccak256("EMERGENCY_EXIT_ROLE");
bytes32 public constant REWARD_REINVESTMENT_ROLE = keccak256("REWARD_REINVESTMENT_ROLE");
bytes32 public constant STATIC_SLIPPAGE_TRADING_ROLE = keccak256("STATIC_SLIPPAGE_TRADING_ROLE");
/// @notice Hardcoded on the implementation contract during deployment
NotionalProxy public immutable NOTIONAL;
ITradingModule public immutable TRADING_MODULE;
uint8 constant internal INTERNAL_TOKEN_DECIMALS = 8;
// Borrowing Currency ID the vault is configured with
uint16 private _BORROW_CURRENCY_ID;
// True if the underlying is ETH
bool private _UNDERLYING_IS_ETH;
// Address of the underlying token
IERC20 private _UNDERLYING_TOKEN;
// NOTE: end of first storage slot here
// Name of the vault
string private _NAME;
/**************************************************************************/
/* Global Modifiers, Constructor and Initializer */
/**************************************************************************/
modifier onlyNotional() {
require(msg.sender == address(NOTIONAL), "Unauthorized");
_;
}
modifier onlyNotionalOwner() {
require(msg.sender == address(NOTIONAL.owner()), "Unauthorized");
_;
}
/// @notice Set the NOTIONAL address on deployment
constructor(NotionalProxy notional_, ITradingModule tradingModule_) initializer {
// Make sure we are using the correct Deployments lib
uint256 chainId = 42161;
//assembly { chainId := chainid() }
require(Deployments.CHAIN_ID == chainId);
NOTIONAL = notional_;
TRADING_MODULE = tradingModule_;
}
/// @notice Override this method and revert if the contract should not receive ETH.
/// Upgradeable proxies must have this implemented on the proxy for transfer calls
/// succeed (use nProxy for this).
receive() external virtual payable {
// Allow ETH transfers to succeed
}
/// @notice All strategy vaults MUST implement 8 decimal precision
function decimals() public override pure returns (uint8) {
return INTERNAL_TOKEN_DECIMALS;
}
function name() external override view returns (string memory) {
return _NAME;
}
function strategy() external virtual view returns (bytes4);
function _borrowCurrencyId() internal view returns (uint16) {
return _BORROW_CURRENCY_ID;
}
function _underlyingToken() internal view returns (IERC20) {
return _UNDERLYING_TOKEN;
}
function _isUnderlyingETH() internal view returns (bool) {
return _UNDERLYING_IS_ETH;
}
/// @notice Can only be called once during initialization
function __INIT_VAULT(
string memory name_,
uint16 borrowCurrencyId_
) internal onlyInitializing {
_NAME = name_;
_BORROW_CURRENCY_ID = borrowCurrencyId_;
address underlyingAddress = _getNotionalUnderlyingToken(borrowCurrencyId_);
_UNDERLYING_TOKEN = IERC20(underlyingAddress);
_UNDERLYING_IS_ETH = underlyingAddress == address(0);
_setupRole(DEFAULT_ADMIN_ROLE, NOTIONAL.owner());
}
function _getNotionalUnderlyingToken(uint16 currencyId) internal view returns (address) {
(Token memory assetToken, Token memory underlyingToken) = NOTIONAL.getCurrency(currencyId);
return assetToken.tokenType == TokenType.NonMintable ?
assetToken.tokenAddress : underlyingToken.tokenAddress;
}
/// @notice Can be used to delegate call to the TradingModule's implementation in order to execute
/// a trade.
function _executeTrade(
uint16 dexId,
Trade memory trade
) internal returns (uint256 amountSold, uint256 amountBought) {
return trade._executeTrade(dexId, TRADING_MODULE);
}
/**************************************************************************/
/* Virtual Methods Requiring Implementation */
/**************************************************************************/
function convertStrategyToUnderlying(
address account,
uint256 vaultShares,
uint256 maturity
) public view virtual returns (int256 underlyingValue);
function getExchangeRate(uint256 maturity) external virtual view returns (int256);
// Vaults need to implement these two methods
function _depositFromNotional(
address account,
uint256 deposit,
uint256 maturity,
bytes calldata data
) internal virtual returns (uint256 vaultSharesMinted);
function _redeemFromNotional(
address account,
uint256 vaultShares,
uint256 maturity,
bytes calldata data
) internal virtual returns (uint256 tokensFromRedeem);
function _convertVaultSharesToPrimeMaturity(
address /* account */,
uint256 /* vaultShares */,
uint256 /* maturity */
) internal virtual returns (uint256 /* primeVaultShares */) {
revert();
}
function _checkReentrancyContext() internal virtual;
/**************************************************************************/
/* Default External Method Implementations */
/**************************************************************************/
function depositFromNotional(
address account,
uint256 deposit,
uint256 maturity,
bytes calldata data
) external payable onlyNotional returns (uint256 vaultSharesMinted) {
return _depositFromNotional(account, deposit, maturity, data);
}
function redeemFromNotional(
address account,
address receiver,
uint256 vaultShares,
uint256 maturity,
uint256 underlyingToRepayDebt,
bytes calldata data
) external onlyNotional returns (uint256 transferToReceiver) {
uint256 borrowedCurrencyAmount = _redeemFromNotional(account, vaultShares, maturity, data);
uint256 transferToNotional;
if (account == address(this) || borrowedCurrencyAmount <= underlyingToRepayDebt) {
// It may be the case that insufficient tokens were redeemed to repay the debt. If this
// happens the Notional will attempt to recover the shortfall from the account directly.
// This can happen if an account wants to reduce their leverage by paying off debt but
// does not want to sell strategy tokens to do so.
// The other situation would be that the vault is calling redemption to deleverage or
// settle. In that case all tokens go back to Notional.
transferToNotional = borrowedCurrencyAmount;
} else {
transferToNotional = underlyingToRepayDebt;
unchecked { transferToReceiver = borrowedCurrencyAmount - underlyingToRepayDebt; }
}
if (_UNDERLYING_IS_ETH) {
if (transferToReceiver > 0) payable(receiver).transfer(transferToReceiver);
if (transferToNotional > 0) payable(address(NOTIONAL)).transfer(transferToNotional);
} else {
if (transferToReceiver > 0) _UNDERLYING_TOKEN.checkTransfer(receiver, transferToReceiver);
if (transferToNotional > 0) _UNDERLYING_TOKEN.checkTransfer(address(NOTIONAL), transferToNotional);
}
}
function convertVaultSharesToPrimeMaturity(
address account,
uint256 vaultShares,
uint256 maturity
) external onlyNotional returns (uint256 primeVaultShares) {
require(maturity != Constants.PRIME_CASH_VAULT_MATURITY);
return _convertVaultSharesToPrimeMaturity(account, vaultShares, maturity);
}
function deleverageAccount(
address account,
address vault,
address liquidator,
uint16 currencyIndex,
int256 depositUnderlyingInternal
) external payable returns (uint256 vaultSharesFromLiquidation, int256 depositAmountPrimeCash) {
require(msg.sender == liquidator);
_checkReentrancyContext();
return NOTIONAL.deleverageAccount{value: msg.value}(
account, vault, liquidator, currencyIndex, depositUnderlyingInternal
);
}
function _canUseStaticSlippage() internal view returns (bool) {
return hasRole(STATIC_SLIPPAGE_TRADING_ROLE, msg.sender);
}
// Storage gap for future potential upgrades
uint256[45] private __gap;
}