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
/
Copy pathIchiSpell.sol
323 lines (280 loc) · 11.7 KB
/
IchiSpell.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
// SPDX-License-Identifier: MIT
/*
██████╗ ██╗ ██╗ ██╗███████╗██████╗ ███████╗██████╗ ██████╗ ██╗ ██╗
██╔══██╗██║ ██║ ██║██╔════╝██╔══██╗██╔════╝██╔══██╗██╔══██╗╚██╗ ██╔╝
██████╔╝██║ ██║ ██║█████╗ ██████╔╝█████╗ ██████╔╝██████╔╝ ╚████╔╝
██╔══██╗██║ ██║ ██║██╔══╝ ██╔══██╗██╔══╝ ██╔══██╗██╔══██╗ ╚██╔╝
██████╔╝███████╗╚██████╔╝███████╗██████╔╝███████╗██║ ██║██║ ██║ ██║
╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
*/
pragma solidity 0.8.16;
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol";
import "./BasicSpell.sol";
import "../interfaces/IWIchiFarm.sol";
import "../interfaces/ichi/IICHIVault.sol";
/**
* @title IchiSpell
* @author BlueberryProtocol
* @notice IchiSpell is the factory contract that
* defines how Blueberry Protocol interacts with Ichi Vaults
*/
contract IchiSpell is BasicSpell, IUniswapV3SwapCallback {
using SafeCast for uint256;
using SafeCast for int256;
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @dev temperory state used to store uni v3 pool when swapping on uni v3
IUniswapV3Pool private SWAP_POOL;
/// @dev address of ICHI farm wrapper
IWIchiFarm public wIchiFarm;
/// @dev address of ICHI token
address public ICHI;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
IBank bank_,
address werc20_,
address weth_,
address wichiFarm_
) external initializer {
__BasicSpell_init(bank_, werc20_, weth_);
if (wichiFarm_ == address(0)) revert Errors.ZERO_ADDRESS();
wIchiFarm = IWIchiFarm(wichiFarm_);
ICHI = address(wIchiFarm.ICHI());
wIchiFarm.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) external onlyOwner {
_addStrategy(vault, maxPosSize);
}
/**
* @notice Internal function to deposit assets on ICHI Vault
* @dev Deposit isolated underlying to Blueberry Money Market,
* Borrow tokens from Blueberry Money Market,
* Then deposit borrowed tokens on ICHI vault
*/
function _deposit(OpenPosParam calldata param) internal {
Strategy memory strategy = strategies[param.strategyId];
// 1. Deposit isolated collaterals on Blueberry Money Market
_doLend(param.collToken, param.collAmount);
// 2. Borrow specific amounts
IICHIVault vault = IICHIVault(strategy.vault);
if (
vault.token0() != param.borrowToken &&
vault.token1() != param.borrowToken
) revert Errors.INCORRECT_DEBT(param.borrowToken);
uint256 borrowBalance = _doBorrow(
param.borrowToken,
param.borrowAmount
);
// 3. Add liquidity - Deposit on ICHI Vault
bool isTokenA = vault.token0() == param.borrowToken;
_ensureApprove(param.borrowToken, address(vault), borrowBalance);
uint ichiVaultShare;
if (isTokenA) {
ichiVaultShare = vault.deposit(borrowBalance, 0, address(this));
} else {
ichiVaultShare = vault.deposit(0, borrowBalance, address(this));
}
// 4. Validate MAX LTV
_validateMaxLTV(param.strategyId);
// 5. Validate Max Pos Size
_validateMaxPosSize(param.strategyId);
}
/**
* @notice External function to deposit assets on IchiVault
*/
function openPosition(
OpenPosParam calldata param
)
external
existingStrategy(param.strategyId)
existingCollateral(param.strategyId, param.collToken)
{
// 1-5 Deposit on ichi vault
_deposit(param);
// 6. Put collateral - ICHI Vault Lp Token
address vault = strategies[param.strategyId].vault;
_doPutCollateral(
vault,
IERC20Upgradeable(vault).balanceOf(address(this))
);
}
/**
* @notice External function to deposit assets on IchiVault and farm in Ichi Farm
*/
function openPositionFarm(
OpenPosParam calldata param
)
external
existingStrategy(param.strategyId)
existingCollateral(param.strategyId, param.collToken)
{
Strategy memory strategy = strategies[param.strategyId];
address lpToken = wIchiFarm.ichiFarm().lpToken(param.farmingPoolId);
if (strategy.vault != lpToken) revert Errors.INCORRECT_LP(lpToken);
// 1-5 Deposit on ichi vault
_deposit(param);
// 6. Take out collateral and burn
{
IBank.Position memory pos = bank.getCurrentPositionInfo();
address posCollToken = pos.collToken;
uint256 collId = pos.collId;
uint256 collSize = pos.collateralSize;
if (collSize > 0) {
(uint256 decodedPid, ) = wIchiFarm.decodeId(collId);
if (param.farmingPoolId != decodedPid)
revert Errors.INCORRECT_PID(param.farmingPoolId);
if (posCollToken != address(wIchiFarm))
revert Errors.INCORRECT_COLTOKEN(posCollToken);
bank.takeCollateral(collSize);
wIchiFarm.burn(collId, collSize);
_doRefundRewards(ICHI);
}
}
// 5. Deposit on farming pool, put collateral
uint256 lpAmount = IERC20Upgradeable(lpToken).balanceOf(address(this));
_ensureApprove(lpToken, address(wIchiFarm), lpAmount);
uint256 id = wIchiFarm.mint(param.farmingPoolId, lpAmount);
bank.putCollateral(address(wIchiFarm), id, lpAmount);
}
/**
* @notice Internal function to withdraw assets from ICHI Vault
* @dev Withdraw assets from ICHI Vault,
* Swap withdrawn assets to debt token,
* Withdraw isolated collaterals from Blueberry Money Market,
* Repay Debt and refund rest to user
*/
function _withdraw(ClosePosParam calldata param) internal {
if (param.sellSlippage > bank.config().maxSlippageOfClose())
revert Errors.RATIO_TOO_HIGH(param.sellSlippage);
Strategy memory strategy = strategies[param.strategyId];
IICHIVault vault = IICHIVault(strategy.vault);
// 1. Compute repay amount if MAX_INT is supplied (max debt)
uint256 amountRepay = param.amountRepay;
if (amountRepay == type(uint256).max) {
amountRepay = bank.currentPositionDebt(bank.POSITION_ID());
}
// 2. Calculate actual amount to remove
uint256 amountPosRemove = param.amountPosRemove;
if (amountPosRemove == type(uint256).max) {
amountPosRemove = vault.balanceOf(address(this));
}
// 3. Withdraw liquidity from ICHI vault
vault.withdraw(amountPosRemove, address(this));
// 4. Swap withdrawn tokens to debt token
bool isTokenA = vault.token0() == param.borrowToken;
uint256 amountToSwap = IERC20Upgradeable(
isTokenA ? vault.token1() : vault.token0()
).balanceOf(address(this));
if (amountToSwap > 0) {
SWAP_POOL = IUniswapV3Pool(vault.pool());
uint160 deltaSqrt = (param.sqrtRatioLimit *
uint160(param.sellSlippage)) / uint160(Constants.DENOMINATOR);
SWAP_POOL.swap(
address(this),
// if withdraw token is Token0, then swap token1 -> token0 (false)
!isTokenA,
amountToSwap.toInt256(),
isTokenA
? param.sqrtRatioLimit + deltaSqrt
: param.sqrtRatioLimit - deltaSqrt, // slippaged price cap
abi.encode(address(this))
);
}
// 5. Withdraw isolated collateral from Bank
_doWithdraw(param.collToken, param.amountShareWithdraw);
// 6. Repay
_doRepay(param.borrowToken, amountRepay);
_validateMaxLTV(param.strategyId);
// 7. Refund
_doRefund(param.borrowToken);
_doRefund(param.collToken);
}
/**
* @notice External function to withdraw assets from ICHI Vault
*/
function closePosition(
ClosePosParam calldata param
)
external
existingStrategy(param.strategyId)
existingCollateral(param.strategyId, param.collToken)
{
// 1. Take out collateral
_doTakeCollateral(
strategies[param.strategyId].vault,
param.amountPosRemove
);
// 2-8. Remove liquidity
_withdraw(param);
}
function closePositionFarm(
ClosePosParam calldata param
)
external
existingStrategy(param.strategyId)
existingCollateral(param.strategyId, param.collToken)
{
address vault = strategies[param.strategyId].vault;
IBank.Position memory pos = bank.getCurrentPositionInfo();
address posCollToken = pos.collToken;
uint256 collId = pos.collId;
if (IWIchiFarm(posCollToken).getUnderlyingToken(collId) != vault)
revert Errors.INCORRECT_UNDERLYING(vault);
if (posCollToken != address(wIchiFarm))
revert Errors.INCORRECT_COLTOKEN(posCollToken);
// 1. Take out collateral
bank.takeCollateral(param.amountPosRemove);
wIchiFarm.burn(collId, param.amountPosRemove);
_doRefundRewards(ICHI);
// 2-8. Remove liquidity
_withdraw(param);
// 9. Refund ichi token
_doRefund(ICHI);
}
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external override {
if (msg.sender != address(SWAP_POOL))
revert Errors.NOT_FROM_UNIV3(msg.sender);
address payer = abi.decode(data, (address));
if (amount0Delta > 0) {
if (payer == address(this)) {
IERC20Upgradeable(SWAP_POOL.token0()).safeTransfer(
msg.sender,
amount0Delta.toUint256()
);
} else {
IERC20Upgradeable(SWAP_POOL.token0()).safeTransferFrom(
payer,
msg.sender,
amount0Delta.toUint256()
);
}
} else if (amount1Delta > 0) {
if (payer == address(this)) {
IERC20Upgradeable(SWAP_POOL.token1()).safeTransfer(
msg.sender,
amount1Delta.toUint256()
);
} else {
IERC20Upgradeable(SWAP_POOL.token1()).safeTransferFrom(
payer,
msg.sender,
amount1Delta.toUint256()
);
}
}
}
}