@@ -3,8 +3,9 @@ pragma solidity =0.8.15;
3
3
4
4
import {TransferLib} from "@src/lib/TransferLib.sol " ;
5
5
6
- import {IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
6
+ import {IERC20 , SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
7
7
import {IWETH9} from "@uni-periphery/interfaces/external/IWETH9.sol " ;
8
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol " ;
8
9
import {IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol " ;
9
10
import {INFTXVaultV2} from "@src/v2/interfaces/INFTXVaultV2.sol " ;
10
11
import {INFTXVaultV3} from "@src/interfaces/INFTXVaultV3.sol " ;
@@ -22,16 +23,16 @@ import {INonfungiblePositionManager} from "@uni-periphery/interfaces/INonfungibl
22
23
* @notice Migrates positions from NFTX v2 to v3
23
24
* @dev This Zap must be excluded from vault fees in both NFTX v2 & v3.
24
25
*/
25
- contract MigratorZap {
26
+ contract MigratorZap is Ownable {
27
+ using SafeERC20 for IERC20 ;
28
+
26
29
struct SushiToNFTXAMMParams {
27
30
// Sushiswap pool address for vTokenV2 <> WETH pair
28
31
address sushiPair;
29
32
// LP balance to withdraw from sushiswap
30
33
uint256 lpAmount;
31
34
// Vault address in NFTX v2
32
35
address vTokenV2;
33
- // NFT tokenIds to redeem from v2 vault
34
- uint256 [] idsToRedeem;
35
36
// If underlying vault NFT is ERC1155
36
37
bool is1155;
37
38
// Encoded permit signature for sushiPair
@@ -56,6 +57,7 @@ contract MigratorZap {
56
57
uint256 private constant DEADLINE =
57
58
0xf000000000000000000000000000000000000000000000000000000000000000 ;
58
59
uint256 private constant DUST_THRESHOLD = 0.005 ether ;
60
+ uint256 private constant V2_VTOKEN_DUST = 100 ; // 100 wei
59
61
60
62
IWETH9 public immutable WETH;
61
63
INFTXVaultFactoryV2 public immutable v2NFTXFactory;
@@ -126,7 +128,6 @@ contract MigratorZap {
126
128
(vTokenV3, vTokenV3Balance, wethReceived) = _v2ToV3Vault (
127
129
params.vTokenV2,
128
130
vTokenV2Balance,
129
- params.idsToRedeem,
130
131
params.vaultIdV3,
131
132
params.is1155,
132
133
0 // passing zero here as `positionManager.mint` takes this into account via `amount0Min` or `amount1Min`
@@ -194,7 +195,6 @@ contract MigratorZap {
194
195
function v2InventoryToXNFT (
195
196
uint256 vaultIdV2 ,
196
197
uint256 shares ,
197
- uint256 [] calldata idsToRedeem ,
198
198
bool is1155 ,
199
199
uint256 vaultIdV3 ,
200
200
uint256 minWethToReceive
@@ -206,14 +206,38 @@ contract MigratorZap {
206
206
address vTokenV2 = v2NFTXFactory.vault (vaultIdV2);
207
207
uint256 vTokenV2Balance = IERC20 (vTokenV2).balanceOf (address (this ));
208
208
209
+ // to account for rounding down when withdrawing xTokens to vTokens
210
+ uint256 numNftsRedeemable = vTokenV2Balance / 1 ether ;
211
+ uint256 numNftsRedeemableAfterDust = (vTokenV2Balance +
212
+ V2_VTOKEN_DUST) / 1 ether ;
213
+
214
+ if (numNftsRedeemableAfterDust > numNftsRedeemable) {
215
+ // having few wei more of vTokens (100 wei at max) would result in redeeming one whole more NFT
216
+ uint256 vTokensToBuy = numNftsRedeemableAfterDust *
217
+ 1 ether -
218
+ vTokenV2Balance;
219
+ // swapping ETH from this contract to get `vTokensToBuy`
220
+ address [] memory path = new address [](2 );
221
+ path[0 ] = address (WETH);
222
+ path[1 ] = vTokenV2;
223
+ sushiRouter.swapETHForExactTokens {value: 1_000_000_000 }(
224
+ vTokensToBuy,
225
+ path,
226
+ address (this ),
227
+ block .timestamp
228
+ );
229
+
230
+ // update var to the latest balance
231
+ vTokenV2Balance = IERC20 (vTokenV2).balanceOf (address (this ));
232
+ }
233
+
209
234
(
210
235
address vTokenV3 ,
211
236
uint256 vTokenV3Balance ,
212
237
uint256 wethReceived
213
238
) = _v2ToV3Vault (
214
239
vTokenV2,
215
240
vTokenV2Balance,
216
- idsToRedeem,
217
241
vaultIdV3,
218
242
is1155,
219
243
minWethToReceive
@@ -244,7 +268,6 @@ contract MigratorZap {
244
268
function v2VaultToXNFT (
245
269
address vTokenV2 ,
246
270
uint256 vTokenV2Balance ,
247
- uint256 [] calldata idsToRedeem ,
248
271
bool is1155 ,
249
272
uint256 vaultIdV3 ,
250
273
uint256 minWethToReceive
@@ -262,7 +285,6 @@ contract MigratorZap {
262
285
) = _v2ToV3Vault (
263
286
vTokenV2,
264
287
vTokenV2Balance,
265
- idsToRedeem,
266
288
vaultIdV3,
267
289
is1155,
268
290
minWethToReceive
@@ -286,6 +308,20 @@ contract MigratorZap {
286
308
);
287
309
}
288
310
311
+ // =============================================================
312
+ // ONLY OWNER WRITE
313
+ // =============================================================
314
+
315
+ function rescueTokens (address token ) external onlyOwner {
316
+ if (token != address (0 )) {
317
+ uint256 balance = IERC20 (token).balanceOf (address (this ));
318
+ IERC20 (token).safeTransfer (msg .sender , balance);
319
+ } else {
320
+ uint256 balance = address (this ).balance;
321
+ TransferLib.transferETH (msg .sender , balance);
322
+ }
323
+ }
324
+
289
325
// =============================================================
290
326
// INTERNAL HELPERS
291
327
// =============================================================
@@ -337,7 +373,6 @@ contract MigratorZap {
337
373
function _v2ToV3Vault (
338
374
address vTokenV2 ,
339
375
uint256 vTokenV2Balance ,
340
- uint256 [] calldata idsToRedeem ,
341
376
uint256 vaultIdV3 ,
342
377
bool is1155 ,
343
378
uint256 minWethToReceive
@@ -351,7 +386,8 @@ contract MigratorZap {
351
386
{
352
387
vTokenV3 = v3NFTXFactory.vault (vaultIdV3);
353
388
354
- // redeem v2 vTokens. Directly transferring to the v3 vault
389
+ // random redeem v2 vTokens. Directly transferring to the v3 vault
390
+ uint256 [] memory idsToRedeem;
355
391
uint256 [] memory idsRedeemed = INFTXVaultV2 (vTokenV2).redeemTo (
356
392
vTokenV2Balance / 1 ether,
357
393
idsToRedeem,
@@ -410,4 +446,7 @@ contract MigratorZap {
410
446
address (this )
411
447
);
412
448
}
449
+
450
+ // To fund the zap with ETH to swap for missing vTokens during v2 redeem
451
+ receive () external payable {}
413
452
}
0 commit comments