-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
2,829 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,7 +39,3 @@ jobs: | |
forge build --sizes | ||
id: build | ||
|
||
- name: Run Forge tests | ||
run: | | ||
forge test -vvv | ||
id: test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.