-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathUniswapV3SpellIntegrationOp.sol
More file actions
280 lines (237 loc) · 8.93 KB
/
UniswapV3SpellIntegrationOp.sol
File metadata and controls
280 lines (237 loc) · 8.93 KB
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
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.16;
import 'OpenZeppelin/openzeppelin-contracts@4.7.3/contracts/token/ERC20/IERC20.sol';
import 'OpenZeppelin/openzeppelin-contracts@4.7.3/contracts/token/ERC20/utils/SafeERC20.sol';
import 'OpenZeppelin/openzeppelin-contracts@4.7.3/contracts/token/ERC20/extensions/IERC20Metadata.sol';
import '../BaseIntegration.sol';
import '../utils/HomoraMath.sol';
import '../../interfaces/uniswapv3/IUniswapV3Factory.sol';
import '../../interfaces/uniswapv3/IUniswapV3Pool.sol';
import '../../interfaces/uniswapv3/IUniswapV3PositionManager.sol';
import '../../interfaces/homorav2/banks/IBankOP.sol';
import '../../interfaces/homorav2/wrappers/IWUniswapV3Position.sol';
import '../../interfaces/homorav2/spells/IUniswapV3Spell.sol';
import 'forge-std/console2.sol';
contract UniswapV3SpellIntegrationOp is BaseIntegration {
using SafeERC20 for IERC20;
using HomoraMath for uint;
IBankOP bank; // homora bank
IUniswapV3Factory factory; // uniswap v3 factory
IUniswapV3PositionManager npm; // uniswap v3 position manager
constructor(
IBankOP _bank,
IUniswapV3Factory _factory,
IUniswapV3PositionManager _npm
) {
bank = _bank;
factory = _factory;
npm = _npm;
}
function openPosition(IUniswapV3Spell _spell, IUniswapV3Spell.OpenPositionParams memory _params)
external
returns (uint positionId)
{
// approve tokens
ensureApprove(_params.token0, address(bank));
ensureApprove(_params.token1, address(bank));
// transfer tokens from user
IERC20(_params.token0).safeTransferFrom(msg.sender, address(this), _params.amt0User);
IERC20(_params.token1).safeTransferFrom(msg.sender, address(this), _params.amt1User);
bytes memory executeData = abi.encodeWithSelector(_spell.openPosition.selector, _params);
// (0 is reserved for opening new position)
positionId = bank.execute(0, address(_spell), executeData);
doRefundETH();
doRefund(_params.token0);
doRefund(_params.token1);
}
function increasePosition(
uint _positionId,
IUniswapV3Spell _spell,
IUniswapV3Spell.AddLiquidityParams memory _params
) external {
(, address collateralTokenAddress, uint collateralTokenId, ) = bank.getPositionInfo(
_positionId
);
IWUniswapV3Position wrapper = IWUniswapV3Position(collateralTokenAddress);
IWUniswapV3Position.PositionInfo memory posInfo = wrapper.getPositionInfoFromTokenId(
collateralTokenId
);
// approve tokens
ensureApprove(posInfo.token0, address(bank));
ensureApprove(posInfo.token1, address(bank));
// transfer tokens from user
IERC20(posInfo.token0).safeTransferFrom(msg.sender, address(this), _params.amt0User);
IERC20(posInfo.token1).safeTransferFrom(msg.sender, address(this), _params.amt1User);
bytes memory executeData = abi.encodeWithSelector(_spell.addLiquidity.selector, _params);
bank.execute(_positionId, address(_spell), executeData);
doRefundETH();
doRefund(posInfo.token0);
doRefund(posInfo.token1);
}
function reducePosition(
uint _positionId,
IUniswapV3Spell _spell,
IUniswapV3Spell.RemoveLiquidityParams memory _params
) external {
(, address collateralTokenAddress, uint collateralTokenId, ) = bank.getPositionInfo(
_positionId
);
IWUniswapV3Position wrapper = IWUniswapV3Position(collateralTokenAddress);
IWUniswapV3Position.PositionInfo memory posInfo = wrapper.getPositionInfoFromTokenId(
collateralTokenId
);
bytes memory executeData = abi.encodeWithSelector(_spell.removeLiquidity.selector, _params);
bank.execute(_positionId, address(_spell), executeData);
doRefundETH();
doRefund(posInfo.token0);
doRefund(posInfo.token1);
}
function harvestFee(
uint _positionId,
IUniswapV3Spell _spell,
bool _convertWETH
) external {
bytes memory executeData = abi.encodeWithSelector(_spell.harvest.selector, _convertWETH);
bank.execute(_positionId, address(_spell), executeData);
// query position info from position id
(, address collateralTokenAddress, uint collateralTokenId, ) = bank.getPositionInfo(
_positionId
);
IWUniswapV3Position wrapper = IWUniswapV3Position(collateralTokenAddress);
IWUniswapV3Position.PositionInfo memory posInfo = wrapper.getPositionInfoFromTokenId(
collateralTokenId
);
doRefundETH();
doRefund(posInfo.token0);
doRefund(posInfo.token1);
}
function closePosition(
uint _positionId,
IUniswapV3Spell _spell,
IUniswapV3Spell.ClosePositionParams memory _params
) external {
(, address collateralTokenAddress, uint collateralId, ) = bank.getPositionInfo(_positionId);
IWUniswapV3Position wrapper = IWUniswapV3Position(collateralTokenAddress);
IWUniswapV3Position.PositionInfo memory posInfo = wrapper.getPositionInfoFromTokenId(
collateralId
);
bytes memory executeData = abi.encodeWithSelector(_spell.closePosition.selector, _params);
bank.execute(_positionId, address(_spell), executeData);
doRefundETH();
doRefund(posInfo.token0);
doRefund(posInfo.token1);
}
function reinvest(
uint _positionId,
IUniswapV3Spell _spell,
IUniswapV3Spell.ReinvestParams memory _params
) external {
(, address collateralTokenAddress, uint collateralId, ) = bank.getPositionInfo(_positionId);
IWUniswapV3Position wrapper = IWUniswapV3Position(collateralTokenAddress);
IWUniswapV3Position.PositionInfo memory posInfo = wrapper.getPositionInfoFromTokenId(
collateralId
);
bytes memory executeData = abi.encodeWithSelector(_spell.reinvest.selector, _params);
bank.execute(_positionId, address(_spell), executeData);
doRefundETH();
doRefund(posInfo.token0);
doRefund(posInfo.token1);
}
function getPendingFees(uint _positionId) external view returns (uint feeAmt0, uint feeAmt1) {
uint collateralTokenId;
uint collateralAmount;
address collateralTokenAddress;
uint feeGrowthInside0LastX128;
uint feeGrowthInside1LastX128;
IWUniswapV3Position.PositionInfo memory posInfo;
IWUniswapV3Position wrapper;
{
// query position info from position id
(, collateralTokenAddress, collateralTokenId, collateralAmount) = bank.getPositionInfo(
_positionId
);
wrapper = IWUniswapV3Position(collateralTokenAddress);
(, , , , , , , , feeGrowthInside0LastX128, feeGrowthInside1LastX128, , ) = npm.positions(
collateralTokenId
);
posInfo = wrapper.getPositionInfoFromTokenId(collateralTokenId);
}
IUniswapV3Pool pool = IUniswapV3Pool(
factory.getPool(posInfo.token0, posInfo.token1, posInfo.fee)
);
(, int24 curTick, , , , , ) = pool.slot0();
(uint128 liquidity, , , uint128 tokensOwed0, uint128 tokensOwed1) = pool.positions(
_getPositionID(address(npm), posInfo.tickLower, posInfo.tickUpper)
);
feeAmt0 =
_computeFeesEarned(
pool,
true,
feeGrowthInside0LastX128,
curTick,
posInfo.tickLower,
posInfo.tickUpper,
liquidity
) +
tokensOwed0;
feeAmt1 =
_computeFeesEarned(
pool,
false,
feeGrowthInside1LastX128,
curTick,
posInfo.tickLower,
posInfo.tickUpper,
liquidity
) +
tokensOwed1;
}
function _getPositionID(
address _owner,
int24 _lowerTick,
int24 _upperTick
) internal pure returns (bytes32 positionId) {
return keccak256(abi.encodePacked(_owner, _lowerTick, _upperTick));
}
// ref: from arrakis finance: https://github.com/ArrakisFinance/vault-v1-core/blob/main/contracts/ArrakisVaultV1.sol
function _computeFeesEarned(
IUniswapV3Pool _pool,
bool _isZero,
uint _feeGrowthInsideLast,
int24 _tick,
int24 _lowerTick,
int24 _upperTick,
uint128 _liquidity
) internal view returns (uint fee) {
uint feeGrowthOutsideLower;
uint feeGrowthOutsideUpper;
uint feeGrowthGlobal;
if (_isZero) {
feeGrowthGlobal = _pool.feeGrowthGlobal0X128();
(, , feeGrowthOutsideLower, , , , , ) = _pool.ticks(_lowerTick);
(, , feeGrowthOutsideUpper, , , , , ) = _pool.ticks(_upperTick);
} else {
feeGrowthGlobal = _pool.feeGrowthGlobal1X128();
(, , , feeGrowthOutsideLower, , , , ) = _pool.ticks(_lowerTick);
(, , , feeGrowthOutsideUpper, , , , ) = _pool.ticks(_upperTick);
}
unchecked {
// calculate fee growth below
uint feeGrowthBelow;
if (_tick >= _lowerTick) {
feeGrowthBelow = feeGrowthOutsideLower;
} else {
feeGrowthBelow = feeGrowthGlobal - feeGrowthOutsideLower;
}
// calculate fee growth above
uint feeGrowthAbove;
if (_tick < _upperTick) {
feeGrowthAbove = feeGrowthOutsideUpper;
} else {
feeGrowthAbove = feeGrowthGlobal - feeGrowthOutsideUpper;
}
uint feeGrowthInside = feeGrowthGlobal - feeGrowthBelow - feeGrowthAbove;
fee = (_liquidity * (feeGrowthInside - _feeGrowthInsideLast)) / 2**128;
}
}
}