Skip to content

Commit

Permalink
feat: init
Browse files Browse the repository at this point in the history
  • Loading branch information
Akagi201 committed Aug 14, 2024
1 parent a91cea8 commit 9c2e97f
Show file tree
Hide file tree
Showing 26 changed files with 2,829 additions and 5 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,3 @@ jobs:
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
4 changes: 3 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
forge-std/=lib/forge-std/src/
forge-std/=lib/forge-std/src/
@uniswap/lib/contracts/libraries/=lib/uniswap-lib/src/
@uniswap/v2-core/contracts/=lib/uniswap-v2-core/src/
44 changes: 44 additions & 0 deletions src/UniswapV2Migrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@uniswap/lib/contracts/libraries/TransferHelper.sol";

import "./interfaces/IUniswapV2Migrator.sol";
import "./interfaces/V1/IUniswapV1Factory.sol";
import "./interfaces/V1/IUniswapV1Exchange.sol";
import "./interfaces/IUniswapV2Router01.sol";
import "./interfaces/IERC20.sol";

contract UniswapV2Migrator is IUniswapV2Migrator {
IUniswapV1Factory immutable factoryV1;
IUniswapV2Router01 immutable router;

constructor(address _factoryV1, address _router) {
factoryV1 = IUniswapV1Factory(_factoryV1);
router = IUniswapV2Router01(_router);
}

// needs to accept ETH from any v1 exchange and the router. ideally this could be enforced, as in the router,
// but it's not possible because it requires a call to the v1 factory, which takes too much gas
receive() external payable {}

function migrate(address token, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline)
external
override
{
IUniswapV1Exchange exchangeV1 = IUniswapV1Exchange(factoryV1.getExchange(token));
uint256 liquidityV1 = exchangeV1.balanceOf(msg.sender);
require(exchangeV1.transferFrom(msg.sender, address(this), liquidityV1), "TRANSFER_FROM_FAILED");
(uint256 amountETHV1, uint256 amountTokenV1) = exchangeV1.removeLiquidity(liquidityV1, 1, 1, type(uint256).max);
TransferHelper.safeApprove(token, address(router), amountTokenV1);
(uint256 amountTokenV2, uint256 amountETHV2,) =
router.addLiquidityETH{value: amountETHV1}(token, amountTokenV1, amountTokenMin, amountETHMin, to, deadline);
if (amountTokenV1 > amountTokenV2) {
TransferHelper.safeApprove(token, address(router), 0); // be a good blockchain citizen, reset allowance to 0
TransferHelper.safeTransfer(token, msg.sender, amountTokenV1 - amountTokenV2);
} else if (amountETHV1 > amountETHV2) {
// addLiquidityETH guarantees that all of amountETHV1 or amountTokenV1 will be used, hence this else is safe
TransferHelper.safeTransferETH(msg.sender, amountETHV1 - amountETHV2);
}
}
}
323 changes: 323 additions & 0 deletions src/UniswapV2Router01.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import "@uniswap/lib/contracts/libraries/TransferHelper.sol";

import "./libraries/UniswapV2Library.sol";
import "./interfaces/IUniswapV2Router01.sol";
import "./interfaces/IERC20.sol";
import "./interfaces/IWETH.sol";

contract UniswapV2Router01 is IUniswapV2Router01 {
address public immutable override factory;
address public immutable override WETH;

modifier ensure(uint256 deadline) {
require(deadline >= block.timestamp, "UniswapV2Router: EXPIRED");
_;
}

constructor(address _factory, address _WETH) {
factory = _factory;
WETH = _WETH;
}

receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}

// **** ADD LIQUIDITY ****
function _addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin
) private returns (uint256 amountA, uint256 amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
(uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint256 amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, "UniswapV2Router: INSUFFICIENT_B_AMOUNT");
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint256 amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, "UniswapV2Router: INSUFFICIENT_A_AMOUNT");
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}

function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external override ensure(deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to);
}

function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external payable override ensure(deadline) returns (uint256 amountToken, uint256 amountETH, uint256 liquidity) {
(amountToken, amountETH) =
_addLiquidity(token, WETH, amountTokenDesired, msg.value, amountTokenMin, amountETHMin);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
assert(IWETH(WETH).transfer(pair, amountETH));
liquidity = IUniswapV2Pair(pair).mint(to);
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH); // refund dust eth, if any
}

// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) public override ensure(deadline) returns (uint256 amountA, uint256 amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
(uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(to);
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, "UniswapV2Router: INSUFFICIENT_A_AMOUNT");
require(amountB >= amountBMin, "UniswapV2Router: INSUFFICIENT_B_AMOUNT");
}

function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) public override ensure(deadline) returns (uint256 amountToken, uint256 amountETH) {
(amountToken, amountETH) =
removeLiquidity(token, WETH, liquidity, amountTokenMin, amountETHMin, address(this), deadline);
TransferHelper.safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}

function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external override returns (uint256 amountA, uint256 amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
uint256 value = approveMax ? type(uint256).max : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
}

function removeLiquidityETHWithPermit(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external override returns (uint256 amountToken, uint256 amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint256 value = approveMax ? type(uint256).max : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
}

// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint256[] memory amounts, address[] memory path, address _to) private {
for (uint256 i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint256 amountOut = amounts[i + 1];
(uint256 amount0Out, uint256 amount1Out) =
input == token0 ? (uint256(0), amountOut) : (amountOut, uint256(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}

function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external override ensure(deadline) returns (uint256[] memory amounts) {
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT");
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}

function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external override ensure(deadline) returns (uint256[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, "UniswapV2Router: EXCESSIVE_INPUT_AMOUNT");
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}

function swapExactETHForTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline)
external
payable
override
ensure(deadline)
returns (uint256[] memory amounts)
{
require(path[0] == WETH, "UniswapV2Router: INVALID_PATH");
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT");
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}

function swapTokensForExactETH(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external override ensure(deadline) returns (uint256[] memory amounts) {
require(path[path.length - 1] == WETH, "UniswapV2Router: INVALID_PATH");
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, "UniswapV2Router: EXCESSIVE_INPUT_AMOUNT");
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}

function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external override ensure(deadline) returns (uint256[] memory amounts) {
require(path[path.length - 1] == WETH, "UniswapV2Router: INVALID_PATH");
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT");
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}

function swapETHForExactTokens(uint256 amountOut, address[] calldata path, address to, uint256 deadline)
external
payable
override
ensure(deadline)
returns (uint256[] memory amounts)
{
require(path[0] == WETH, "UniswapV2Router: INVALID_PATH");
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= msg.value, "UniswapV2Router: EXCESSIVE_INPUT_AMOUNT");
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]); // refund dust eth, if any
}

function quote(uint256 amountA, uint256 reserveA, uint256 reserveB)
public
pure
override
returns (uint256 amountB)
{
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}

function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
public
pure
override
returns (uint256 amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}

function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut)
public
pure
override
returns (uint256 amountIn)
{
return UniswapV2Library.getAmountOut(amountOut, reserveIn, reserveOut);
}

function getAmountsOut(uint256 amountIn, address[] memory path)
public
view
override
returns (uint256[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}

function getAmountsIn(uint256 amountOut, address[] memory path)
public
view
override
returns (uint256[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
Loading

0 comments on commit 9c2e97f

Please # to comment.