This repository has been archived by the owner on Nov 5, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
/
BasicSpell.sol
359 lines (319 loc) · 13 KB
/
BasicSpell.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
// SPDX-License-Identifier: MIT
/*
██████╗ ██╗ ██╗ ██╗███████╗██████╗ ███████╗██████╗ ██████╗ ██╗ ██╗
██╔══██╗██║ ██║ ██║██╔════╝██╔══██╗██╔════╝██╔══██╗██╔══██╗╚██╗ ██╔╝
██████╔╝██║ ██║ ██║█████╗ ██████╔╝█████╗ ██████╔╝██████╔╝ ╚████╔╝
██╔══██╗██║ ██║ ██║██╔══╝ ██╔══██╗██╔══╝ ██╔══██╗██╔══██╗ ╚██╔╝
██████╔╝███████╗╚██████╔╝███████╗██████╔╝███████╗██║ ██║██║ ██║ ██║
╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
*/
pragma solidity 0.8.16;
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "../utils/BlueBerryConst.sol" as Constants;
import "../utils/BlueBerryErrors.sol" as Errors;
import "../utils/EnsureApprove.sol";
import "../utils/ERC1155NaiveReceiver.sol";
import "../interfaces/IBank.sol";
import "../interfaces/IWERC20.sol";
/**
* @title BasicSpell
* @author BlueberryProtocol
* @notice BasicSpell is the abstract contract that other spells utilize
*/
abstract contract BasicSpell is
ERC1155NaiveReceiver,
OwnableUpgradeable,
EnsureApprove
{
using SafeERC20Upgradeable for IERC20Upgradeable;
struct Strategy {
address vault;
uint256 maxPositionSize;
}
/**
* @param collToken Collateral Token address to deposit (e.g USDC)
* @param collAmount Amount of user's collateral (e.g USDC)
* @param borrowToken Address of token to borrow
* @param borrowAmount Amount to borrow from Bank
* @param farmingPoolId Farming Pool ID
*/
struct OpenPosParam {
uint256 strategyId;
address collToken;
uint256 collAmount;
address borrowToken;
uint256 borrowAmount;
uint256 farmingPoolId;
}
/**
* @param strategyId Strategy ID
* @param collToken Isolated collateral token address
* @param borrowToken Token address of debt
* @param amountPosRemove Amount of position to withdraw
* @param amountRepay Amount of debt to repay
* @param amountShareWithdraw Amount of isolated collaterals to withdraw
*/
struct ClosePosParam {
uint256 strategyId;
address collToken;
address borrowToken;
uint256 amountRepay;
uint256 amountPosRemove;
uint256 amountShareWithdraw;
uint256 sellSlippage;
uint160 sqrtRatioLimit;
}
IBank public bank;
IWERC20 public werc20;
address public WETH;
/// @dev strategyId => vault
Strategy[] public strategies;
/// @dev strategyId => collateral token => maxLTV
mapping(uint256 => mapping(address => uint256)) public maxLTV; // base 1e4
event StrategyAdded(uint256 strategyId, address vault, uint256 maxPosSize);
event StrategyMaxPosSizeUpdated(uint256 strategyId, uint256 maxPosSize);
event CollateralsMaxLTVSet(
uint256 strategyId,
address[] collaterals,
uint256[] maxLTVs
);
modifier existingStrategy(uint256 strategyId) {
if (strategyId >= strategies.length)
revert Errors.STRATEGY_NOT_EXIST(address(this), strategyId);
_;
}
modifier existingCollateral(uint256 strategyId, address col) {
if (maxLTV[strategyId][col] == 0)
revert Errors.COLLATERAL_NOT_EXIST(strategyId, col);
_;
}
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __BasicSpell_init(
IBank bank_,
address werc20_,
address weth_
) internal onlyInitializing {
if (
address(bank_) == address(0) ||
address(werc20_) == address(0) ||
address(weth_) == address(0)
) revert Errors.ZERO_ADDRESS();
__Ownable_init();
bank = bank_;
werc20 = IWERC20(werc20_);
WETH = weth_;
IWERC20(werc20_).setApprovalForAll(address(bank_), true);
}
/**
* @notice Add strategy to the spell
* @param vault Address of vault for given strategy
* @param maxPosSize, USD price of maximum position size for given strategy, based 1e18
*/
function _addStrategy(address vault, uint256 maxPosSize) internal {
if (vault == address(0)) revert Errors.ZERO_ADDRESS();
if (maxPosSize == 0) revert Errors.ZERO_AMOUNT();
strategies.push(Strategy({vault: vault, maxPositionSize: maxPosSize}));
emit StrategyAdded(strategies.length - 1, vault, maxPosSize);
}
/**
* @notice Set maxPosSize of existing strategy
* @param strategyId Strategy ID
* @param maxPosSize New maxPosSize to set
*/
function setMaxPosSize(
uint256 strategyId,
uint256 maxPosSize
) external existingStrategy(strategyId) onlyOwner {
if (maxPosSize == 0) revert Errors.ZERO_AMOUNT();
strategies[strategyId].maxPositionSize = maxPosSize;
emit StrategyMaxPosSizeUpdated(strategyId, maxPosSize);
}
/**
* @notice Set maxLTV values of collaterals for given strategy
* @dev Only owner can set maxLTVs of collaterals
* @param strategyId Strategy ID
* @param collaterals Array of collateral token addresses
* @param maxLTVs Array of maxLTV to set
*/
function setCollateralsMaxLTVs(
uint256 strategyId,
address[] memory collaterals,
uint256[] memory maxLTVs
) external existingStrategy(strategyId) onlyOwner {
if (collaterals.length != maxLTVs.length || collaterals.length == 0)
revert Errors.INPUT_ARRAY_MISMATCH();
for (uint256 i = 0; i < collaterals.length; i++) {
if (collaterals[i] == address(0)) revert Errors.ZERO_ADDRESS();
if (maxLTVs[i] == 0) revert Errors.ZERO_AMOUNT();
maxLTV[strategyId][collaterals[i]] = maxLTVs[i];
}
emit CollateralsMaxLTVSet(strategyId, collaterals, maxLTVs);
}
/**
* @notice Validate whether current position is in maxLTV
* @dev Only check current pos in execution and revert when it exceeds maxLTV
* @param strategyId Strategy ID to check
*/
function _validateMaxLTV(uint256 strategyId) internal view {
uint positionId = bank.POSITION_ID();
IBank.Position memory pos = bank.getPositionInfo(positionId);
uint256 debtValue = bank.getDebtValue(positionId);
uint uValue = bank.getIsolatedCollateralValue(positionId);
if (
debtValue >
(uValue * maxLTV[strategyId][pos.underlyingToken]) /
Constants.DENOMINATOR
) revert Errors.EXCEED_MAX_LTV();
}
function _validateMaxPosSize(uint256 strategyId) internal view {
Strategy memory strategy = strategies[strategyId];
IERC20Upgradeable lpToken = IERC20Upgradeable(strategy.vault);
uint256 lpBalance = lpToken.balanceOf(address(this));
uint256 lpPrice = bank.oracle().getPrice(address(lpToken));
uint256 curPosSize = (lpPrice * lpBalance) /
10 ** IERC20MetadataUpgradeable(address(lpToken)).decimals();
if (curPosSize > strategy.maxPositionSize)
revert Errors.EXCEED_MAX_POS_SIZE(strategyId);
}
/**
* @dev Refund tokens from spell to current bank executor
* @param token The token to perform the refund action.
*/
function _doRefund(address token) internal {
uint256 balance = IERC20Upgradeable(token).balanceOf(address(this));
if (balance > 0) {
IERC20Upgradeable(token).safeTransfer(bank.EXECUTOR(), balance);
}
}
/**
* @dev Cut rewards fee
* @param token The rewards token to cut fee.
* @return left Remaining amount of reward token after fee cut
*/
function _doCutRewardsFee(address token) internal returns (uint256 left) {
uint256 rewardsBalance = IERC20Upgradeable(token).balanceOf(
address(this)
);
if (rewardsBalance > 0) {
_ensureApprove(token, address(bank.feeManager()), rewardsBalance);
left = bank.feeManager().doCutRewardsFee(token, rewardsBalance);
}
}
/**
* @dev Cut rewards fee and refund rewards tokens from spell to the current bank executor
* @param token The token to perform the refund action.
*/
function _doRefundRewards(address token) internal {
_doCutRewardsFee(token);
_doRefund(token);
}
/**
* @dev Deposit isolated collaterals to the bank
* @param token The token address of isolated collateral
* @param amount The amount to token to lend
*/
function _doLend(address token, uint256 amount) internal {
if (amount > 0) {
bank.lend(token, amount);
}
}
/**
* @dev Withdraw isolated collaterals from the bank
* @param token The token address of isolated collateral
* @param amount The amount of tokens to withdraw
*/
function _doWithdraw(address token, uint256 amount) internal {
if (amount > 0) {
bank.withdrawLend(token, amount);
}
}
/**
* @notice Internal call to borrow tokens from the bank on behalf of the current executor.
* @param token The token to borrow from the bank.
* @param amount The amount to borrow.
* @return borrowedAmount The amount of borrowed tokens
*/
function _doBorrow(
address token,
uint256 amount
) internal returns (uint256 borrowedAmount) {
if (amount > 0) {
borrowedAmount = bank.borrow(token, amount);
}
}
/// @dev Internal call to repay tokens to the bank on behalf of the current executor.
/// @param token The token to repay to the bank.
/// @param amount The amount to repay.
function _doRepay(address token, uint256 amount) internal {
if (amount > 0) {
_ensureApprove(token, address(bank), amount);
bank.repay(token, amount);
}
}
/// @dev Internal call to put collateral tokens in the bank.
/// @param token The token to put in the bank.
/// @param amount The amount to put in the bank.
function _doPutCollateral(address token, uint256 amount) internal {
if (amount > 0) {
_ensureApprove(token, address(werc20), amount);
werc20.mint(token, amount);
bank.putCollateral(
address(werc20),
uint256(uint160(token)),
amount
);
}
}
/// @dev Internal call to take collateral tokens from the bank.
/// @param token The token to take back.
/// @param amount The amount to take back.
function _doTakeCollateral(address token, uint256 amount) internal {
if (amount > 0) {
amount = bank.takeCollateral(amount);
werc20.burn(token, amount);
}
}
/**
* @notice Increase isolated collateral to support the position
* @param token Isolated collateral token address
* @param amount Amount of token to increase position
*/
function increasePosition(address token, uint256 amount) external {
// 1. Deposit isolated collaterals on Blueberry Money Market
_doLend(token, amount);
}
/**
* @dev Reduce isolated collateral of position
* @param collToken Isolated collateral token address
* @param collShareAmount Amount of Isolated collateral
*/
function reducePosition(
uint256 strategyId,
address collToken,
uint256 collShareAmount
) external {
// Validate strategy id
IBank.Position memory pos = bank.getCurrentPositionInfo();
address unwrappedCollToken = IERC20Wrapper(pos.collToken)
.getUnderlyingToken(pos.collId);
if (strategies[strategyId].vault != unwrappedCollToken)
revert Errors.INCORRECT_STRATEGY_ID(strategyId);
_doWithdraw(collToken, collShareAmount);
_doRefund(collToken);
_validateMaxLTV(strategyId);
}
/// @dev Fallback function. Can only receive ETH from WETH contract.
receive() external payable {
if (msg.sender != WETH) revert Errors.NOT_FROM_WETH(msg.sender);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[45] private __gap;
}