Skip to content

Commit

Permalink
Implement Router.removeLiquidity; fix Pair.burn
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeiwan committed Apr 12, 2022
1 parent de742fb commit babf850
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/ZuniswapV2Library.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ library ZuniswapV2Library {
hex"ff",
factoryAddress,
keccak256(abi.encodePacked(token0, token1)),
hex"1bbc6ed5991f4d700d9a3e43d868dc3e166c1af54c8182b9ca61a5ad97a00c88"
hex"2684a50f28848d213d7f0069a81d79fb2a020912072a8c8f6100cfaafef33857"
)
)
)
Expand Down
20 changes: 10 additions & 10 deletions src/ZuniswapV2Pair.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ contract ZuniswapV2Pair is ERC20, Math {
uint256 public price0CumulativeLast;
uint256 public price1CumulativeLast;

event Burn(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(address indexed sender, uint256 amount0, uint256 amount1, address to);
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Sync(uint256 reserve0, uint256 reserve1);
event Swap(
Expand Down Expand Up @@ -81,28 +81,28 @@ contract ZuniswapV2Pair is ERC20, Math {
emit Mint(to, amount0, amount1);
}

function burn() public {
function burn(address to) public returns (uint256 amount0, uint256 amount1) {
uint256 balance0 = IERC20(token0).balanceOf(address(this));
uint256 balance1 = IERC20(token1).balanceOf(address(this));
uint256 liquidity = balanceOf[msg.sender];
uint256 liquidity = balanceOf[address(this)];

uint256 amount0 = (liquidity * balance0) / totalSupply;
uint256 amount1 = (liquidity * balance1) / totalSupply;
amount0 = (liquidity * balance0) / totalSupply;
amount1 = (liquidity * balance1) / totalSupply;

if (amount0 <= 0 || amount1 <= 0) revert InsufficientLiquidityBurned();
if (amount0 == 0 || amount1 == 0) revert InsufficientLiquidityBurned();

_burn(msg.sender, liquidity);
_burn(address(this), liquidity);

_safeTransfer(token0, msg.sender, amount0);
_safeTransfer(token1, msg.sender, amount1);
_safeTransfer(token0, to, amount0);
_safeTransfer(token1, to, amount1);

balance0 = IERC20(token0).balanceOf(address(this));
balance1 = IERC20(token1).balanceOf(address(this));

(uint112 reserve0_, uint112 reserve1_, ) = getReserves();
_update(balance0, balance1, reserve0_, reserve1_);

emit Burn(msg.sender, amount0, amount1);
emit Burn(msg.sender, amount0, amount1, to);
}

function swap(
Expand Down
15 changes: 15 additions & 0 deletions src/ZuniswapV2Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ contract ZuniswapV2Router {
liquidity = IZuniswapV2Pair(pairAddress).mint(to);
}

function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to
) public returns (uint256 amountA, uint256 amountB) {
address pair = ZuniswapV2Library.pairFor(address(factory), tokenA, tokenB);
IZuniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity);
(amountA, amountB) = IZuniswapV2Pair(pair).burn(to);
if (amountA < amountAMin) revert InsufficientAAmount();
if (amountA < amountBMin) revert InsufficientBAmount();
}

//
//
//
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/IZuniswapV2Pair.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,12 @@ interface IZuniswapV2Pair {
);

function mint(address) external returns (uint256);

function burn(address) external returns (uint256, uint256);

function transferFrom(
address,
address,
uint256
) external returns (bool);
}
2 changes: 1 addition & 1 deletion src/test/ZuniswapV2Library.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,6 @@ contract ZuniswapV2LibraryTest is DSTest {
address(tokenA)
);

assertEq(pairAddress, 0x043Db4182caE8f22c76D4970278cA512AA8b0c19);
assertEq(pairAddress, 0x8BbD00dFF82468090E7D720E9fB3a6529C73Ff9e);
}
}
26 changes: 18 additions & 8 deletions src/test/ZuniswapV2Pair.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ contract ZuniswapV2PairTest is DSTest {

pair.mint(address(this));

pair.burn();
uint256 liquidity = pair.balanceOf(address(this));
pair.transfer(address(pair), liquidity);
pair.burn(address(this));

assertEq(pair.balanceOf(address(this)), 0);
assertReserves(1000, 1000);
Expand All @@ -190,7 +192,9 @@ contract ZuniswapV2PairTest is DSTest {

pair.mint(address(this)); // + 1 LP

pair.burn();
uint256 liquidity = pair.balanceOf(address(this));
pair.transfer(address(pair), liquidity);
pair.burn(address(this));

assertEq(pair.balanceOf(address(this)), 0);
assertReserves(1500, 1000);
Expand All @@ -217,7 +221,9 @@ contract ZuniswapV2PairTest is DSTest {

pair.mint(address(this)); // + 1 LP

pair.burn();
uint256 liquidity = pair.balanceOf(address(this));
pair.transfer(address(pair), liquidity);
pair.burn(address(this));

// this user is penalized for providing unbalanced liquidity
assertEq(pair.balanceOf(address(this)), 0);
Expand All @@ -226,7 +232,7 @@ contract ZuniswapV2PairTest is DSTest {
assertEq(token0.balanceOf(address(this)), 10 ether - 0.5 ether);
assertEq(token1.balanceOf(address(this)), 10 ether);

testUser.withdrawLiquidity(address(pair));
testUser.removeLiquidity(address(pair));

// testUser receives the amount collected from this user
assertEq(pair.balanceOf(address(testUser)), 0);
Expand All @@ -242,7 +248,7 @@ contract ZuniswapV2PairTest is DSTest {
function testBurnZeroTotalSupply() public {
// 0x12; If you divide or modulo by zero.
vm.expectRevert(encodeError("Panic(uint256)", 0x12));
pair.burn();
pair.burn(address(this));
}

function testBurnZeroLiquidity() public {
Expand All @@ -253,7 +259,7 @@ contract ZuniswapV2PairTest is DSTest {

vm.prank(address(0xdeadbeef));
vm.expectRevert(encodeError("InsufficientLiquidityBurned()"));
pair.burn();
pair.burn(address(this));
}

function testReservesPacking() public {
Expand Down Expand Up @@ -483,7 +489,11 @@ contract TestUser {
ZuniswapV2Pair(pairAddress_).mint(address(this));
}

function withdrawLiquidity(address pairAddress_) public {
ZuniswapV2Pair(pairAddress_).burn();
function removeLiquidity(
address pairAddress_
) public {
uint256 liquidity = ERC20(pairAddress_).balanceOf(address(this));
ERC20(pairAddress_).transfer(pairAddress_, liquidity);
ZuniswapV2Pair(pairAddress_).burn(address(this));
}
}
141 changes: 140 additions & 1 deletion src/test/ZuniswapV2Router.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ contract ZuniswapV2RouterTest is DSTest {
);

address pairAddress = factory.pairs(address(tokenA), address(tokenB));
assertEq(pairAddress, 0xCB304901c533168e091bA95A5297E39e882FDF26);
assertEq(pairAddress, 0x1200107eD3bbF12Ef4Ac648b9a36F8be48f3b0D7);
}

function testAddLiquidityNoPair() public {
Expand Down Expand Up @@ -215,4 +215,143 @@ contract ZuniswapV2RouterTest is DSTest {
assertEq(amountB, 0.9 ether);
assertEq(liquidity, 1272792206135785543);
}

function testRemoveLiquidity() public {
tokenA.approve(address(router), 1 ether);
tokenB.approve(address(router), 1 ether);

router.addLiquidity(
address(tokenA),
address(tokenB),
1 ether,
1 ether,
1 ether,
1 ether,
address(this)
);

address pairAddress = factory.pairs(address(tokenA), address(tokenB));
ZuniswapV2Pair pair = ZuniswapV2Pair(pairAddress);
uint256 liquidity = pair.balanceOf(address(this));

pair.approve(address(router), liquidity);

router.removeLiquidity(
address(tokenA),
address(tokenB),
liquidity,
1 ether - 1000,
1 ether - 1000,
address(this)
);

(uint256 reserve0, uint256 reserve1,) = pair.getReserves();
assertEq(reserve0, 1000);
assertEq(reserve1, 1000);
assertEq(pair.balanceOf(address(this)), 0);
assertEq(pair.totalSupply(), 1000);
assertEq(tokenA.balanceOf(address(this)), 20 ether - 1000);
assertEq(tokenB.balanceOf(address(this)), 20 ether - 1000);
}

function testRemoveLiquidityPartially() public {
tokenA.approve(address(router), 1 ether);
tokenB.approve(address(router), 1 ether);

router.addLiquidity(
address(tokenA),
address(tokenB),
1 ether,
1 ether,
1 ether,
1 ether,
address(this)
);

address pairAddress = factory.pairs(address(tokenA), address(tokenB));
ZuniswapV2Pair pair = ZuniswapV2Pair(pairAddress);
uint256 liquidity = pair.balanceOf(address(this));

liquidity = (liquidity * 3) / 10;
pair.approve(address(router), liquidity);

router.removeLiquidity(
address(tokenA),
address(tokenB),
liquidity,
0.3 ether - 300,
0.3 ether - 300,
address(this)
);

(uint256 reserve0, uint256 reserve1,) = pair.getReserves();
assertEq(reserve0, 0.7 ether + 300);
assertEq(reserve1, 0.7 ether + 300);
assertEq(pair.balanceOf(address(this)), 0.7 ether - 700);
assertEq(pair.totalSupply(), 0.7 ether + 300);
assertEq(tokenA.balanceOf(address(this)), 20 ether - 0.7 ether - 300);
assertEq(tokenB.balanceOf(address(this)), 20 ether - 0.7 ether - 300);
}

function testRemoveLiquidityInsufficientAAmount() public {
tokenA.approve(address(router), 1 ether);
tokenB.approve(address(router), 1 ether);

router.addLiquidity(
address(tokenA),
address(tokenB),
1 ether,
1 ether,
1 ether,
1 ether,
address(this)
);

address pairAddress = factory.pairs(address(tokenA), address(tokenB));
ZuniswapV2Pair pair = ZuniswapV2Pair(pairAddress);
uint256 liquidity = pair.balanceOf(address(this));

pair.approve(address(router), liquidity);

vm.expectRevert(encodeError("InsufficientAAmount()"));
router.removeLiquidity(
address(tokenA),
address(tokenB),
liquidity,
1 ether,
1 ether - 1000,
address(this)
);
}

function testRemoveLiquidityInsufficientBAmount() public {
tokenA.approve(address(router), 1 ether);
tokenB.approve(address(router), 1 ether);

router.addLiquidity(
address(tokenA),
address(tokenB),
1 ether,
1 ether,
1 ether,
1 ether,
address(this)
);

address pairAddress = factory.pairs(address(tokenA), address(tokenB));
ZuniswapV2Pair pair = ZuniswapV2Pair(pairAddress);
uint256 liquidity = pair.balanceOf(address(this));

pair.approve(address(router), liquidity);

vm.expectRevert(encodeError("InsufficientBAmount()"));
router.removeLiquidity(
address(tokenA),
address(tokenB),
liquidity,
1 ether - 1000,
1 ether,
address(this)
);
}
}

0 comments on commit babf850

Please sign in to comment.