Skip to content

Commit babf850

Browse files
committed
Implement Router.removeLiquidity; fix Pair.burn
1 parent de742fb commit babf850

File tree

7 files changed

+193
-21
lines changed

7 files changed

+193
-21
lines changed

src/ZuniswapV2Library.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ library ZuniswapV2Library {
5555
hex"ff",
5656
factoryAddress,
5757
keccak256(abi.encodePacked(token0, token1)),
58-
hex"1bbc6ed5991f4d700d9a3e43d868dc3e166c1af54c8182b9ca61a5ad97a00c88"
58+
hex"2684a50f28848d213d7f0069a81d79fb2a020912072a8c8f6100cfaafef33857"
5959
)
6060
)
6161
)

src/ZuniswapV2Pair.sol

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ contract ZuniswapV2Pair is ERC20, Math {
3535
uint256 public price0CumulativeLast;
3636
uint256 public price1CumulativeLast;
3737

38-
event Burn(address indexed sender, uint256 amount0, uint256 amount1);
38+
event Burn(address indexed sender, uint256 amount0, uint256 amount1, address to);
3939
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
4040
event Sync(uint256 reserve0, uint256 reserve1);
4141
event Swap(
@@ -81,28 +81,28 @@ contract ZuniswapV2Pair is ERC20, Math {
8181
emit Mint(to, amount0, amount1);
8282
}
8383

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

89-
uint256 amount0 = (liquidity * balance0) / totalSupply;
90-
uint256 amount1 = (liquidity * balance1) / totalSupply;
89+
amount0 = (liquidity * balance0) / totalSupply;
90+
amount1 = (liquidity * balance1) / totalSupply;
9191

92-
if (amount0 <= 0 || amount1 <= 0) revert InsufficientLiquidityBurned();
92+
if (amount0 == 0 || amount1 == 0) revert InsufficientLiquidityBurned();
9393

94-
_burn(msg.sender, liquidity);
94+
_burn(address(this), liquidity);
9595

96-
_safeTransfer(token0, msg.sender, amount0);
97-
_safeTransfer(token1, msg.sender, amount1);
96+
_safeTransfer(token0, to, amount0);
97+
_safeTransfer(token1, to, amount1);
9898

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

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

105-
emit Burn(msg.sender, amount0, amount1);
105+
emit Burn(msg.sender, amount0, amount1, to);
106106
}
107107

108108
function swap(

src/ZuniswapV2Router.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ contract ZuniswapV2Router {
5454
liquidity = IZuniswapV2Pair(pairAddress).mint(to);
5555
}
5656

57+
function removeLiquidity(
58+
address tokenA,
59+
address tokenB,
60+
uint256 liquidity,
61+
uint256 amountAMin,
62+
uint256 amountBMin,
63+
address to
64+
) public returns (uint256 amountA, uint256 amountB) {
65+
address pair = ZuniswapV2Library.pairFor(address(factory), tokenA, tokenB);
66+
IZuniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity);
67+
(amountA, amountB) = IZuniswapV2Pair(pair).burn(to);
68+
if (amountA < amountAMin) revert InsufficientAAmount();
69+
if (amountA < amountBMin) revert InsufficientBAmount();
70+
}
71+
5772
//
5873
//
5974
//

src/interfaces/IZuniswapV2Pair.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ interface IZuniswapV2Pair {
1313
);
1414

1515
function mint(address) external returns (uint256);
16+
17+
function burn(address) external returns (uint256, uint256);
18+
19+
function transferFrom(
20+
address,
21+
address,
22+
uint256
23+
) external returns (bool);
1624
}

src/test/ZuniswapV2Library.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,6 @@ contract ZuniswapV2LibraryTest is DSTest {
8585
address(tokenA)
8686
);
8787

88-
assertEq(pairAddress, 0x043Db4182caE8f22c76D4970278cA512AA8b0c19);
88+
assertEq(pairAddress, 0x8BbD00dFF82468090E7D720E9fB3a6529C73Ff9e);
8989
}
9090
}

src/test/ZuniswapV2Pair.t.sol

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ contract ZuniswapV2PairTest is DSTest {
170170

171171
pair.mint(address(this));
172172

173-
pair.burn();
173+
uint256 liquidity = pair.balanceOf(address(this));
174+
pair.transfer(address(pair), liquidity);
175+
pair.burn(address(this));
174176

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

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

193-
pair.burn();
195+
uint256 liquidity = pair.balanceOf(address(this));
196+
pair.transfer(address(pair), liquidity);
197+
pair.burn(address(this));
194198

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

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

220-
pair.burn();
224+
uint256 liquidity = pair.balanceOf(address(this));
225+
pair.transfer(address(pair), liquidity);
226+
pair.burn(address(this));
221227

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

229-
testUser.withdrawLiquidity(address(pair));
235+
testUser.removeLiquidity(address(pair));
230236

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

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

254260
vm.prank(address(0xdeadbeef));
255261
vm.expectRevert(encodeError("InsufficientLiquidityBurned()"));
256-
pair.burn();
262+
pair.burn(address(this));
257263
}
258264

259265
function testReservesPacking() public {
@@ -483,7 +489,11 @@ contract TestUser {
483489
ZuniswapV2Pair(pairAddress_).mint(address(this));
484490
}
485491

486-
function withdrawLiquidity(address pairAddress_) public {
487-
ZuniswapV2Pair(pairAddress_).burn();
492+
function removeLiquidity(
493+
address pairAddress_
494+
) public {
495+
uint256 liquidity = ERC20(pairAddress_).balanceOf(address(this));
496+
ERC20(pairAddress_).transfer(pairAddress_, liquidity);
497+
ZuniswapV2Pair(pairAddress_).burn(address(this));
488498
}
489499
}

src/test/ZuniswapV2Router.t.sol

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ contract ZuniswapV2RouterTest is DSTest {
5454
);
5555

5656
address pairAddress = factory.pairs(address(tokenA), address(tokenB));
57-
assertEq(pairAddress, 0xCB304901c533168e091bA95A5297E39e882FDF26);
57+
assertEq(pairAddress, 0x1200107eD3bbF12Ef4Ac648b9a36F8be48f3b0D7);
5858
}
5959

6060
function testAddLiquidityNoPair() public {
@@ -215,4 +215,143 @@ contract ZuniswapV2RouterTest is DSTest {
215215
assertEq(amountB, 0.9 ether);
216216
assertEq(liquidity, 1272792206135785543);
217217
}
218+
219+
function testRemoveLiquidity() public {
220+
tokenA.approve(address(router), 1 ether);
221+
tokenB.approve(address(router), 1 ether);
222+
223+
router.addLiquidity(
224+
address(tokenA),
225+
address(tokenB),
226+
1 ether,
227+
1 ether,
228+
1 ether,
229+
1 ether,
230+
address(this)
231+
);
232+
233+
address pairAddress = factory.pairs(address(tokenA), address(tokenB));
234+
ZuniswapV2Pair pair = ZuniswapV2Pair(pairAddress);
235+
uint256 liquidity = pair.balanceOf(address(this));
236+
237+
pair.approve(address(router), liquidity);
238+
239+
router.removeLiquidity(
240+
address(tokenA),
241+
address(tokenB),
242+
liquidity,
243+
1 ether - 1000,
244+
1 ether - 1000,
245+
address(this)
246+
);
247+
248+
(uint256 reserve0, uint256 reserve1,) = pair.getReserves();
249+
assertEq(reserve0, 1000);
250+
assertEq(reserve1, 1000);
251+
assertEq(pair.balanceOf(address(this)), 0);
252+
assertEq(pair.totalSupply(), 1000);
253+
assertEq(tokenA.balanceOf(address(this)), 20 ether - 1000);
254+
assertEq(tokenB.balanceOf(address(this)), 20 ether - 1000);
255+
}
256+
257+
function testRemoveLiquidityPartially() public {
258+
tokenA.approve(address(router), 1 ether);
259+
tokenB.approve(address(router), 1 ether);
260+
261+
router.addLiquidity(
262+
address(tokenA),
263+
address(tokenB),
264+
1 ether,
265+
1 ether,
266+
1 ether,
267+
1 ether,
268+
address(this)
269+
);
270+
271+
address pairAddress = factory.pairs(address(tokenA), address(tokenB));
272+
ZuniswapV2Pair pair = ZuniswapV2Pair(pairAddress);
273+
uint256 liquidity = pair.balanceOf(address(this));
274+
275+
liquidity = (liquidity * 3) / 10;
276+
pair.approve(address(router), liquidity);
277+
278+
router.removeLiquidity(
279+
address(tokenA),
280+
address(tokenB),
281+
liquidity,
282+
0.3 ether - 300,
283+
0.3 ether - 300,
284+
address(this)
285+
);
286+
287+
(uint256 reserve0, uint256 reserve1,) = pair.getReserves();
288+
assertEq(reserve0, 0.7 ether + 300);
289+
assertEq(reserve1, 0.7 ether + 300);
290+
assertEq(pair.balanceOf(address(this)), 0.7 ether - 700);
291+
assertEq(pair.totalSupply(), 0.7 ether + 300);
292+
assertEq(tokenA.balanceOf(address(this)), 20 ether - 0.7 ether - 300);
293+
assertEq(tokenB.balanceOf(address(this)), 20 ether - 0.7 ether - 300);
294+
}
295+
296+
function testRemoveLiquidityInsufficientAAmount() public {
297+
tokenA.approve(address(router), 1 ether);
298+
tokenB.approve(address(router), 1 ether);
299+
300+
router.addLiquidity(
301+
address(tokenA),
302+
address(tokenB),
303+
1 ether,
304+
1 ether,
305+
1 ether,
306+
1 ether,
307+
address(this)
308+
);
309+
310+
address pairAddress = factory.pairs(address(tokenA), address(tokenB));
311+
ZuniswapV2Pair pair = ZuniswapV2Pair(pairAddress);
312+
uint256 liquidity = pair.balanceOf(address(this));
313+
314+
pair.approve(address(router), liquidity);
315+
316+
vm.expectRevert(encodeError("InsufficientAAmount()"));
317+
router.removeLiquidity(
318+
address(tokenA),
319+
address(tokenB),
320+
liquidity,
321+
1 ether,
322+
1 ether - 1000,
323+
address(this)
324+
);
325+
}
326+
327+
function testRemoveLiquidityInsufficientBAmount() public {
328+
tokenA.approve(address(router), 1 ether);
329+
tokenB.approve(address(router), 1 ether);
330+
331+
router.addLiquidity(
332+
address(tokenA),
333+
address(tokenB),
334+
1 ether,
335+
1 ether,
336+
1 ether,
337+
1 ether,
338+
address(this)
339+
);
340+
341+
address pairAddress = factory.pairs(address(tokenA), address(tokenB));
342+
ZuniswapV2Pair pair = ZuniswapV2Pair(pairAddress);
343+
uint256 liquidity = pair.balanceOf(address(this));
344+
345+
pair.approve(address(router), liquidity);
346+
347+
vm.expectRevert(encodeError("InsufficientBAmount()"));
348+
router.removeLiquidity(
349+
address(tokenA),
350+
address(tokenB),
351+
liquidity,
352+
1 ether - 1000,
353+
1 ether,
354+
address(this)
355+
);
356+
}
218357
}

0 commit comments

Comments
 (0)