Skip to content

Commit c69ab1d

Browse files
committed
create V3MigrateSwap
1 parent 886088e commit c69ab1d

File tree

14 files changed

+317
-0
lines changed

14 files changed

+317
-0
lines changed

deploy/V3MigrateSwap.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { HardhatRuntimeEnvironment, Network } from "hardhat/types";
2+
import { DeployFunction } from "hardhat-deploy/types";
3+
import { deployV3MigrateSwap } from "./modules/V3MigrateSwap";
4+
5+
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
6+
const { v3MigrateSwap } = await deployV3MigrateSwap({
7+
hre,
8+
});
9+
};
10+
export default func;
11+
func.tags = ["V3MigrateSwap"];
12+
func.dependencies = [];

deploy/modules/V3MigrateSwap.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { HardhatRuntimeEnvironment } from "hardhat/types";
2+
import { getConfig } from "../utils";
3+
4+
export const deployV3MigrateSwap = async ({
5+
hre,
6+
}: {
7+
hre: HardhatRuntimeEnvironment;
8+
}) => {
9+
const { deploy, deployer } = await getConfig(hre);
10+
11+
const v3MigrateSwap = await deploy("V3MigrateSwap", {
12+
from: deployer,
13+
args: [],
14+
log: true,
15+
});
16+
17+
return {
18+
v3MigrateSwap: v3MigrateSwap.address,
19+
};
20+
};

script/genAddressesJson.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const deploymentsList = [
2020
"SwapRouter",
2121
"TickLens",
2222
"UniswapV3FactoryUpgradeable",
23+
"V3MigrateSwap",
2324
];
2425
const deployConfigKeysList = [
2526
"nftxUniversalRouter",

script/genREADME.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const contractURLs = {
3636
UniswapV3FactoryUpgradeable:
3737
"./src/uniswap/v3-core/UniswapV3FactoryUpgradeable.sol",
3838
UniswapV3Staker: "https://github.com/Uniswap/v3-staker",
39+
V3MigrateSwap: "./src/V3MigrateSwap.sol",
3940
WETH: "https://vscode.blockscan.com/ethereum/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
4041
};
4142

src/V3MigrateSwap.sol

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
// inheriting
5+
import {Pausable} from "@src/custom/Pausable.sol";
6+
7+
// interfaces
8+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
9+
10+
/**
11+
* @title V3 Migrate Swap
12+
* @author @apoorvlathey
13+
*
14+
* @notice Swap v2 -> v3 NFTX vault tokens 1:1
15+
*/
16+
contract V3MigrateSwap is Pausable {
17+
// constants
18+
uint256 constant SWAP_LOCK_ID = 0;
19+
20+
// storage
21+
// v2 vault token => v3 address
22+
mapping(address => address) public v2ToV3VToken;
23+
24+
// events
25+
event Swapped(address v2VToken, uint256 amount);
26+
event V2ToV3MappingSet(address v2VToken, address v3VToken);
27+
28+
// errors
29+
error SwapNotEnabledForVault();
30+
31+
// =============================================================
32+
// PUBLIC / EXTERNAL WRITE
33+
// =============================================================
34+
35+
function swap(address v2VToken, uint256 amount) external {
36+
onlyOwnerIfPaused(SWAP_LOCK_ID);
37+
38+
address v3VToken = v2ToV3VToken[v2VToken];
39+
if (v3VToken == address(0)) revert SwapNotEnabledForVault();
40+
41+
// pull v2 tokens to this contract
42+
IERC20(v2VToken).transferFrom(msg.sender, address(this), amount);
43+
44+
// transfer equal v3 tokens to the caller
45+
IERC20(v3VToken).transfer(msg.sender, amount);
46+
47+
emit Swapped(v2VToken, amount);
48+
}
49+
50+
// =============================================================
51+
// ONLY OWNER WRITE
52+
// =============================================================
53+
54+
function setV2ToV3Mapping(
55+
address v2VToken,
56+
address v3VToken
57+
) external onlyOwner {
58+
v2ToV3VToken[v2VToken] = v3VToken;
59+
emit V2ToV3MappingSet(v2VToken, v3VToken);
60+
}
61+
62+
function rescueTokens(IERC20 token) external onlyOwner {
63+
uint256 balance = token.balanceOf(address(this));
64+
token.transfer(msg.sender, balance);
65+
}
66+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.0;
3+
4+
import {V3MigrateSwap} from "@src/V3MigrateSwap.sol";
5+
import {MockERC20} from "@mocks/MockERC20.sol";
6+
7+
import {NewTestBase} from "@test/NewTestBase.sol";
8+
9+
contract V3MigrateSwap_Unit_Test is NewTestBase {
10+
V3MigrateSwap v3MigrateSwap;
11+
MockERC20 v2VToken;
12+
MockERC20 v3VToken;
13+
14+
function setUp() public virtual override {
15+
super.setUp();
16+
17+
switchPrank(users.owner);
18+
v3MigrateSwap = new V3MigrateSwap();
19+
v2VToken = new MockERC20(1_000 ether);
20+
v3VToken = new MockERC20(1_000 ether);
21+
switchPrank(users.alice);
22+
}
23+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.0;
3+
4+
import {V3MigrateSwap} from "@src/V3MigrateSwap.sol";
5+
6+
import {V3MigrateSwap_Unit_Test} from "../V3MigrateSwap.t.sol";
7+
8+
contract V3MigrateSwap_Init_Unit_Test is V3MigrateSwap_Unit_Test {
9+
function test_ShouldSetTheOwner() external {
10+
// it should set the owner
11+
assertEq(v3MigrateSwap.owner(), users.owner);
12+
}
13+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
init.t.sol
2+
└── it should set the owner
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
rescueTokens.t.sol
2+
├── when the caller is not the owner
3+
│ └── it should revert
4+
└── when the caller is the owner
5+
└── it should send all the tokens to the caller
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.0;
3+
4+
import {V3MigrateSwap} from "@src/V3MigrateSwap.sol";
5+
6+
import {V3MigrateSwap_Unit_Test} from "../V3MigrateSwap.t.sol";
7+
8+
contract V3MigrateSwap_rescueTokens_Unit_Test is V3MigrateSwap_Unit_Test {
9+
function test_RevertWhen_TheCallerIsNotTheOwner() external {
10+
// it should revert
11+
vm.expectRevert(OWNABLE_NOT_OWNER_ERROR);
12+
v3MigrateSwap.rescueTokens(v2VToken);
13+
}
14+
15+
function test_WhenTheCallerIsTheOwner(uint256 amount) external {
16+
switchPrank(users.owner);
17+
18+
// transfer vTokens to the contract
19+
amount = bound(amount, 1, 1_000 ether);
20+
v2VToken.transfer(address(v3MigrateSwap), amount);
21+
22+
uint256 preV2VTokenBalance = v2VToken.balanceOf(users.owner);
23+
v3MigrateSwap.rescueTokens(v2VToken);
24+
uint256 postV2VTokenBalance = v2VToken.balanceOf(users.owner);
25+
26+
// it should send all the tokens to the caller
27+
assertEq(postV2VTokenBalance - preV2VTokenBalance, amount);
28+
assertEq(v2VToken.balanceOf(address(v3MigrateSwap)), 0);
29+
}
30+
}

0 commit comments

Comments
 (0)