├── images
├── logo1.png
├── test.png
├── maths00.png
├── maths01.png
├── maths02.png
├── maths03.png
├── maths04.png
├── maths05.png
├── maths06.png
├── maths07.png
└── maths03bis.png
├── .gitignore
├── contracts
├── IUniswapV2Callee.sol
├── IERC20.sol
├── IUniswapV2Factory.sol
├── IUniswapV2Router.sol
├── UniswapV2SingleHopSwap.sol
├── UniswapV2FlashSwap.sol
├── UniswapV2MultiHopSwap.sol
├── IUniswapV2Pair.sol
└── UniswapV2Liquidity.sol
├── hardhat.config.js
├── package.json
├── test
├── config.js
└── uniswap.test.js
└── README.md
/images/logo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/logo1.png
--------------------------------------------------------------------------------
/images/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/test.png
--------------------------------------------------------------------------------
/images/maths00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths00.png
--------------------------------------------------------------------------------
/images/maths01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths01.png
--------------------------------------------------------------------------------
/images/maths02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths02.png
--------------------------------------------------------------------------------
/images/maths03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths03.png
--------------------------------------------------------------------------------
/images/maths04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths04.png
--------------------------------------------------------------------------------
/images/maths05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths05.png
--------------------------------------------------------------------------------
/images/maths06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths06.png
--------------------------------------------------------------------------------
/images/maths07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths07.png
--------------------------------------------------------------------------------
/images/maths03bis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aboudoc/Uniswap-v2/HEAD/images/maths03bis.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | coverage
4 | coverage.json
5 | typechain
6 | typechain-types
7 |
8 | # Hardhat files
9 | cache
10 | artifacts
11 |
12 |
--------------------------------------------------------------------------------
/contracts/IUniswapV2Callee.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.5.0;
2 |
3 | interface IUniswapV2Callee {
4 | function uniswapV2Call(
5 | address sender,
6 | uint amount0,
7 | uint amount1,
8 | bytes calldata data
9 | ) external;
10 | }
11 |
--------------------------------------------------------------------------------
/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("@nomicfoundation/hardhat-toolbox");
2 | require("dotenv").config();
3 |
4 | /** @type import('hardhat/config').HardhatUserConfig */
5 | module.exports = {
6 | solidity: {
7 | compilers: [{ version: "0.8.17" }, { version: "0.4.19" }],
8 | },
9 | networks: {
10 | hardhat: {
11 | forking: {
12 | url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
13 | },
14 | },
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uniswapv2",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "dotenv": "^16.0.3",
14 | "hardhat": "^2.13.0"
15 | },
16 | "devDependencies": {
17 | "@nomicfoundation/hardhat-toolbox": "^2.0.2",
18 | "prettier": "^2.8.4"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/contracts/IERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.17;
3 |
4 | interface IERC20 {
5 | function totalSupply() external view returns (uint256);
6 |
7 | function balanceOf(address _owner) external view returns (uint256);
8 |
9 | function transfer(address to, uint256 amount) external;
10 |
11 | function allowance(
12 | address from,
13 | address to,
14 | uint256 amount
15 | ) external view returns (bool);
16 |
17 | function approve(address sender, uint256 amount) external;
18 |
19 | function transferFrom(
20 | address from,
21 | address to,
22 | uint256 amount
23 | ) external;
24 |
25 | event Transfer(address indexed from, address indexed to, uint256 amount);
26 |
27 | event Approval(address indexed from, address indexed to, uint256 amount);
28 | }
29 |
--------------------------------------------------------------------------------
/contracts/IUniswapV2Factory.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.17;
3 |
4 | interface IUniswapV2Factory {
5 | event PairCreated(
6 | address indexed token0,
7 | address indexed token1,
8 | address pair,
9 | uint
10 | );
11 |
12 | function feeTo() external view returns (address);
13 |
14 | function feeToSetter() external view returns (address);
15 |
16 | function getPair(address tokenA, address tokenB)
17 | external
18 | view
19 | returns (address pair);
20 |
21 | function allPairs(uint) external view returns (address pair);
22 |
23 | function allPairsLength() external view returns (uint);
24 |
25 | function createPair(address tokenA, address tokenB)
26 | external
27 | returns (address pair);
28 |
29 | function setFeeTo(address) external;
30 |
31 | function setFeeToSetter(address) external;
32 | }
33 |
--------------------------------------------------------------------------------
/test/config.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 |
3 | const DAI = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
4 | const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
5 | const USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
6 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
7 | const WBTC = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599";
8 | const CRV = "0xD533a949740bb3306d119CC777fa900bA034cd52";
9 |
10 | const WETH_10 = "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F";
11 |
12 | const DAI_WHALE = process.env.DAI_WHALE;
13 | const USDC_WHALE = process.env.USDC_WHALE;
14 | const USDT_WHALE = process.env.USDT_WHALE;
15 | const WETH_WHALE = process.env.WETH_WHALE;
16 | const WETH_WHALE1 = process.env.WETH_WHALE1;
17 | const WBTC_WHALE = process.env.WBTC_WHALE;
18 | const WETHDAI_WHALE = process.env.WETHDAI_WHALE;
19 |
20 | module.exports = {
21 | DAI,
22 | USDC,
23 | USDT,
24 | WETH,
25 | WBTC,
26 | CRV,
27 | WETH_10,
28 | DAI_WHALE,
29 | USDC_WHALE,
30 | USDT_WHALE,
31 | WETH_WHALE,
32 | WETH_WHALE1,
33 | WBTC_WHALE,
34 | WETHDAI_WHALE,
35 | };
36 |
--------------------------------------------------------------------------------
/contracts/IUniswapV2Router.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.17;
3 |
4 | interface IUniswapV2Router {
5 | function swapExactTokensForTokens(
6 | uint amountIn,
7 | uint amountOutMin,
8 | address[] calldata path,
9 | address to,
10 | uint deadline
11 | ) external returns (uint[] memory amounts);
12 |
13 | function swapTokensForExactTokens(
14 | uint amountOut,
15 | uint amountInMax,
16 | address[] calldata path,
17 | address to,
18 | uint deadline
19 | ) external returns (uint[] memory amounts);
20 |
21 | function addLiquidity(
22 | address tokenA,
23 | address tokenB,
24 | uint amountADesired,
25 | uint amountBDesired,
26 | uint amountAMin,
27 | uint amountBMin,
28 | address to,
29 | uint deadline
30 | )
31 | external
32 | returns (
33 | uint amountA,
34 | uint amountB,
35 | uint liquidity
36 | );
37 |
38 | function removeLiquidity(
39 | address tokenA,
40 | address tokenB,
41 | uint liquidity,
42 | uint amountAMin,
43 | uint amountBMin,
44 | address to,
45 | uint deadline
46 | ) external returns (uint amountA, uint amountB);
47 | }
48 |
--------------------------------------------------------------------------------
/contracts/UniswapV2SingleHopSwap.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.17;
3 |
4 | import "./IERC20.sol";
5 | import "./IUniswapV2Router.sol";
6 |
7 | contract UniswapV2SingleHopSwap {
8 | address private constant UNISWAP_V2_ROUTER =
9 | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
10 |
11 | address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
12 | address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
13 |
14 | IUniswapV2Router private constant router =
15 | IUniswapV2Router(UNISWAP_V2_ROUTER);
16 | IERC20 private constant weth = IERC20(WETH);
17 | IERC20 private constant dai = IERC20(DAI);
18 |
19 | function swapSingleHopExactAmountIn(uint amountIn, uint amountOutMin)
20 | external
21 | {
22 | weth.transferFrom(msg.sender, address(this), amountIn);
23 | weth.approve(UNISWAP_V2_ROUTER, amountIn);
24 | address[] memory path = new address[](2);
25 | path[0] = WETH;
26 | path[1] = DAI;
27 | router.swapExactTokensForTokens(
28 | amountIn,
29 | amountOutMin,
30 | path,
31 | msg.sender,
32 | block.timestamp
33 | );
34 | }
35 |
36 | function swapSingleHopExactAmountOut(
37 | uint amountOutDesired,
38 | uint amountInMax
39 | ) external {
40 | weth.transferFrom(msg.sender, address(this), amountInMax);
41 | weth.approve(UNISWAP_V2_ROUTER, amountInMax);
42 | address[] memory path = new address[](2);
43 | path[0] = WETH;
44 | path[1] = DAI;
45 | router.swapTokensForExactTokens(
46 | amountOutDesired,
47 | amountInMax,
48 | path,
49 | msg.sender,
50 | block.timestamp
51 | );
52 | weth.transfer(msg.sender, weth.balanceOf(address(this)));
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/contracts/UniswapV2FlashSwap.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.17;
3 |
4 | import "./IERC20.sol";
5 | import "./IUniswapV2Factory.sol";
6 | import "./IUniswapV2Callee.sol";
7 | import "./IUniswapV2Pair.sol";
8 |
9 | contract UniswapV2FlashSwap is IUniswapV2Callee {
10 | event Log(string message, uint val);
11 |
12 | address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
13 | address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
14 |
15 | address private constant UNISWAP_V2_FACTORY =
16 | 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
17 |
18 | IERC20 private constant weth = IERC20(WETH);
19 | IUniswapV2Factory private constant factory =
20 | IUniswapV2Factory(UNISWAP_V2_FACTORY);
21 | IUniswapV2Pair private immutable pair;
22 |
23 | constructor() {
24 | pair = IUniswapV2Pair(factory.getPair(DAI, WETH));
25 | }
26 |
27 | function flashSwap(uint wethAmount) external {
28 | bytes memory data = abi.encode(WETH, msg.sender);
29 | pair.swap(0, wethAmount, address(this), data);
30 | }
31 |
32 | function uniswapV2Call(
33 | address sender,
34 | uint amount0,
35 | uint amount1,
36 | bytes calldata data
37 | ) external {
38 | require(msg.sender == address(pair), "not pair");
39 | require(sender == address(this), "not sender");
40 |
41 | (address tokenBorrow, address caller) = abi.decode(
42 | data,
43 | (address, address)
44 | );
45 | uint fee = ((amount1 * 3) / 997) + 1;
46 |
47 | // Arbitrage...
48 | emit Log("amount", amount1);
49 | emit Log("fee", fee);
50 | emit Log("amount to repay", fee + amount1);
51 |
52 | weth.transferFrom(caller, address(this), amount1 + fee);
53 | weth.transfer(address(pair), amount1 + fee);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/contracts/UniswapV2MultiHopSwap.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.17;
3 |
4 | import "./IERC20.sol";
5 | import "./IUniswapV2Router.sol";
6 | import "./IUniswapV2Factory.sol";
7 |
8 | contract UniswapV2MultiHopSwap {
9 | address private constant UNISWAP_V2_ROUTER =
10 | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
11 |
12 | address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
13 | address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
14 | address private constant CRV = 0xD533a949740bb3306d119CC777fa900bA034cd52;
15 |
16 | IUniswapV2Router private constant router =
17 | IUniswapV2Router(UNISWAP_V2_ROUTER);
18 | IERC20 private constant weth = IERC20(WETH);
19 | IERC20 private constant dai = IERC20(DAI);
20 | IERC20 private constant crv = IERC20(CRV);
21 |
22 | function swapMultiHopExactAmountIn(uint amountIn, uint amountOutMin)
23 | external
24 | {
25 | dai.transferFrom(msg.sender, address(this), amountIn);
26 | dai.approve(UNISWAP_V2_ROUTER, amountIn);
27 |
28 | address[] memory path = new address[](3);
29 | path[0] = DAI;
30 | path[1] = WETH;
31 | path[2] = CRV;
32 |
33 | router.swapExactTokensForTokens(
34 | amountIn,
35 | amountOutMin,
36 | path,
37 | msg.sender,
38 | block.timestamp
39 | );
40 | }
41 |
42 | function swapMultiHopExactAmountOut(uint amountOutDesired, uint amountInMax)
43 | external
44 | {
45 | dai.transferFrom(msg.sender, address(this), amountInMax);
46 | dai.approve(UNISWAP_V2_ROUTER, amountInMax);
47 |
48 | address[] memory path = new address[](3);
49 | path[0] = DAI;
50 | path[1] = WETH;
51 | path[2] = CRV;
52 |
53 | uint[] memory amounts = router.swapTokensForExactTokens(
54 | amountOutDesired,
55 | amountInMax,
56 | path,
57 | msg.sender,
58 | block.timestamp
59 | );
60 |
61 | if (amounts[0] < amountInMax) {
62 | dai.transfer(msg.sender, amountInMax - amounts[0]);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/contracts/IUniswapV2Pair.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.5.0;
2 |
3 | interface IUniswapV2Pair {
4 | event Approval(address indexed owner, address indexed spender, uint value);
5 | event Transfer(address indexed from, address indexed to, uint value);
6 |
7 | function name() external pure returns (string memory);
8 |
9 | function symbol() external pure returns (string memory);
10 |
11 | function decimals() external pure returns (uint8);
12 |
13 | function totalSupply() external view returns (uint);
14 |
15 | function balanceOf(address owner) external view returns (uint);
16 |
17 | function allowance(address owner, address spender)
18 | external
19 | view
20 | returns (uint);
21 |
22 | function approve(address spender, uint value) external returns (bool);
23 |
24 | function transfer(address to, uint value) external returns (bool);
25 |
26 | function transferFrom(
27 | address from,
28 | address to,
29 | uint value
30 | ) external returns (bool);
31 |
32 | function DOMAIN_SEPARATOR() external view returns (bytes32);
33 |
34 | function PERMIT_TYPEHASH() external pure returns (bytes32);
35 |
36 | function nonces(address owner) external view returns (uint);
37 |
38 | function permit(
39 | address owner,
40 | address spender,
41 | uint value,
42 | uint deadline,
43 | uint8 v,
44 | bytes32 r,
45 | bytes32 s
46 | ) external;
47 |
48 | event Mint(address indexed sender, uint amount0, uint amount1);
49 | event Burn(
50 | address indexed sender,
51 | uint amount0,
52 | uint amount1,
53 | address indexed to
54 | );
55 | event Swap(
56 | address indexed sender,
57 | uint amount0In,
58 | uint amount1In,
59 | uint amount0Out,
60 | uint amount1Out,
61 | address indexed to
62 | );
63 | event Sync(uint112 reserve0, uint112 reserve1);
64 |
65 | function MINIMUM_LIQUIDITY() external pure returns (uint);
66 |
67 | function factory() external view returns (address);
68 |
69 | function token0() external view returns (address);
70 |
71 | function token1() external view returns (address);
72 |
73 | function getReserves()
74 | external
75 | view
76 | returns (
77 | uint112 reserve0,
78 | uint112 reserve1,
79 | uint32 blockTimestampLast
80 | );
81 |
82 | function price0CumulativeLast() external view returns (uint);
83 |
84 | function price1CumulativeLast() external view returns (uint);
85 |
86 | function kLast() external view returns (uint);
87 |
88 | function mint(address to) external returns (uint liquidity);
89 |
90 | function burn(address to) external returns (uint amount0, uint amount1);
91 |
92 | function swap(
93 | uint amount0Out,
94 | uint amount1Out,
95 | address to,
96 | bytes calldata data
97 | ) external;
98 |
99 | function skim(address to) external;
100 |
101 | function sync() external;
102 |
103 | function initialize(address, address) external;
104 | }
105 |
--------------------------------------------------------------------------------
/contracts/UniswapV2Liquidity.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.17;
3 |
4 | import "./IUniswapV2Factory.sol";
5 | import "./IUniswapV2Router.sol";
6 | import "./IERC20.sol";
7 |
8 | contract UniswapV2Liquidity {
9 | event Log(string message, uint val);
10 |
11 | address private constant UNISWAP_V2_ROUTER =
12 | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
13 |
14 | address private constant UNISWAP_V2_FACTORY =
15 | 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
16 |
17 | address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
18 | address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
19 |
20 | IUniswapV2Router private constant router =
21 | IUniswapV2Router(UNISWAP_V2_ROUTER);
22 | IUniswapV2Factory private constant factory =
23 | IUniswapV2Factory(UNISWAP_V2_FACTORY);
24 |
25 | IERC20 private constant weth = IERC20(WETH);
26 | IERC20 private constant dai = IERC20(DAI);
27 |
28 | IERC20 private immutable pair;
29 |
30 | constructor() {
31 | pair = IERC20(factory.getPair(WETH, DAI));
32 | }
33 |
34 | function addLiquidity(uint wethAmountDesired, uint daiAmountDesired)
35 | external
36 | {
37 | weth.transferFrom(msg.sender, address(this), wethAmountDesired);
38 | dai.transferFrom(msg.sender, address(this), daiAmountDesired);
39 | weth.approve(address(router), wethAmountDesired);
40 | dai.approve(address(router), daiAmountDesired);
41 | (uint wethAmount, uint daiAmount, uint liquidity) = router.addLiquidity(
42 | WETH,
43 | DAI,
44 | wethAmountDesired,
45 | daiAmountDesired,
46 | 1,
47 | 1,
48 | msg.sender,
49 | block.timestamp
50 | );
51 | // Trying to get liquidity amount returned from the function call to fix issues met when testing
52 | // emit Log("wethAmount", wethAmount);
53 | // emit Log("daiAmount", daiAmount);
54 | emit Log("liquidity", liquidity);
55 |
56 | if (wethAmount < wethAmountDesired) {
57 | weth.transfer(msg.sender, wethAmountDesired - wethAmount);
58 | }
59 |
60 | if (daiAmount < daiAmountDesired) {
61 | dai.transfer(msg.sender, daiAmountDesired - daiAmount);
62 | }
63 | }
64 |
65 | // Trying to get liquidity
66 | function removeLiquidity() external {
67 | uint liquidity = IERC20(pair).balanceOf(msg.sender);
68 | require(liquidity > 0, "liquidity = 0");
69 | pair.transferFrom(msg.sender, address(this), liquidity);
70 | pair.approve(address(router), liquidity);
71 |
72 | (uint wethAmount, uint daiAmount) = router.removeLiquidity(
73 | WETH,
74 | DAI,
75 | liquidity,
76 | 1,
77 | 1,
78 | msg.sender,
79 | block.timestamp
80 | );
81 |
82 | emit Log("wethAmount", wethAmount);
83 | emit Log("daiAmount", daiAmount);
84 | }
85 |
86 | // function removeLiquidity(uint liquidity) external {
87 | // pair.transferFrom(msg.sender, address(this), liquidity);
88 | // pair.approve(address(router), liquidity);
89 |
90 | // router.removeLiquidity(
91 | // WETH,
92 | // DAI,
93 | // liquidity,
94 | // 1,
95 | // 1,
96 | // msg.sender,
97 | // block.timestamp
98 | // );
99 | // }
100 | }
101 |
--------------------------------------------------------------------------------
/test/uniswap.test.js:
--------------------------------------------------------------------------------
1 | // const { BigNumber } = require("@ethersproject/bignumber");
2 | const { assert, expect } = require("chai");
3 | const { ethers } = require("hardhat");
4 | const {
5 | WETH_WHALE,
6 | DAI_WHALE,
7 | WETHDAI_WHALE,
8 | WETH_WHALE1,
9 | WETH,
10 | DAI,
11 | CRV,
12 | } = require("./config.js");
13 |
14 | // Single Hop Swap ✅
15 | describe("Uniswap v2 Single Hop Swap", function () {
16 | let TestSwapContract;
17 |
18 | beforeEach(async () => {
19 | const TestSwapFactory = await ethers.getContractFactory(
20 | "UniswapV2SingleHopSwap"
21 | );
22 | TestSwapContract = await TestSwapFactory.deploy();
23 | await TestSwapContract.deployed();
24 | });
25 |
26 | it("should swap", async () => {
27 | await hre.network.provider.request({
28 | method: "hardhat_impersonateAccount",
29 | params: [WETH_WHALE],
30 | });
31 | const impersonateSigner = await ethers.getSigner(WETH_WHALE);
32 |
33 | const WETHContract = await ethers.getContractAt("IERC20", WETH);
34 |
35 | const WETHHolderBalance = await WETHContract.balanceOf(
36 | impersonateSigner.address
37 | );
38 | await WETHContract.connect(impersonateSigner).approve(
39 | TestSwapContract.address,
40 | WETHHolderBalance
41 | );
42 |
43 | const DAIContract = await ethers.getContractAt("IERC20", DAI);
44 |
45 | const DAIHolderBalance = await DAIContract.balanceOf(
46 | impersonateSigner.address
47 | );
48 | console.log(
49 | "Initial DAI Balance:",
50 | ethers.utils.formatUnits(DAIHolderBalance.toString())
51 | );
52 |
53 | console.log(
54 | "Initial WETH Balance:",
55 | ethers.utils.formatUnits(WETHHolderBalance.toString())
56 | );
57 |
58 | console.log(
59 | "-----------------------------SINGLE SWAP-----------------------------"
60 | );
61 |
62 | await TestSwapContract.connect(
63 | impersonateSigner
64 | ).swapSingleHopExactAmountIn(WETHHolderBalance, 1);
65 |
66 | const daiBalance_updated = await DAIContract.balanceOf(
67 | impersonateSigner.address
68 | );
69 | console.log(
70 | "DAI Balance after Swap:",
71 | ethers.utils.formatUnits(daiBalance_updated.toString())
72 | );
73 | const WETHHolderBalance_updated = await WETHContract.balanceOf(
74 | impersonateSigner.address
75 | );
76 | console.log(
77 | `WETH Balance after Swap: ${ethers.utils.formatUnits(
78 | WETHHolderBalance_updated.toString()
79 | )}`
80 | );
81 |
82 | assert.equal(WETHHolderBalance_updated.toString(), 0);
83 | });
84 | });
85 |
86 | // Multi Hop Swap ✅
87 | describe("Uniswap v2 Multi Hop Swap", function () {
88 | let TestMultiSwapContract;
89 |
90 | beforeEach(async () => {
91 | const TestMultiSwapFactory = await ethers.getContractFactory(
92 | "UniswapV2MultiHopSwap"
93 | );
94 | TestMultiSwapContract = await TestMultiSwapFactory.deploy();
95 | await TestMultiSwapContract.deployed();
96 | });
97 |
98 | it("should Multiswap", async () => {
99 | await hre.network.provider.request({
100 | method: "hardhat_impersonateAccount",
101 | params: [DAI_WHALE],
102 | });
103 | const impersonateSigner = await ethers.getSigner(DAI_WHALE);
104 |
105 | const DAIContract = await ethers.getContractAt("IERC20", DAI);
106 |
107 | const DAIHolderBalance = await DAIContract.balanceOf(
108 | impersonateSigner.address
109 | );
110 |
111 | await DAIContract.connect(impersonateSigner).approve(
112 | TestMultiSwapContract.address,
113 | DAIHolderBalance
114 | );
115 |
116 | const CRVContract = await ethers.getContractAt("IERC20", CRV);
117 |
118 | const CRVHolderBalance = await CRVContract.balanceOf(
119 | impersonateSigner.address
120 | );
121 |
122 | console.log(
123 | "Initial CRV Balance:",
124 | ethers.utils.formatUnits(CRVHolderBalance.toString())
125 | );
126 |
127 | console.log(
128 | "Initial DAI Balance:",
129 | ethers.utils.formatUnits(DAIHolderBalance.toString())
130 | );
131 |
132 | console.log(
133 | "------------------------------MULTI SWAP------------------------------"
134 | );
135 |
136 | await TestMultiSwapContract.connect(
137 | impersonateSigner
138 | ).swapMultiHopExactAmountIn(DAIHolderBalance, 1);
139 |
140 | const crvBalance_updated = await CRVContract.balanceOf(
141 | impersonateSigner.address
142 | );
143 | console.log(
144 | "CRV Balance after Swap:",
145 | ethers.utils.formatUnits(crvBalance_updated.toString())
146 | );
147 | const DAIHolderBalance_updated = await DAIContract.balanceOf(
148 | impersonateSigner.address
149 | );
150 |
151 | console.log(
152 | "DAI Balance after Swap:",
153 | ethers.utils.formatUnits(DAIHolderBalance_updated.toString())
154 | );
155 |
156 | assert.equal(DAIHolderBalance_updated.toString(), 0);
157 | });
158 | });
159 |
160 | // Add Liquidity ✅
161 | describe("Uniswap V2 Liquidity", function () {
162 | let TestLiquidityContract;
163 |
164 | beforeEach(async () => {
165 | const TestLiquidityFactory = await ethers.getContractFactory(
166 | "UniswapV2Liquidity"
167 | );
168 |
169 | TestLiquidityContract = await TestLiquidityFactory.deploy();
170 | await TestLiquidityContract.deployed();
171 | });
172 | it("should add liquidity", async () => {
173 | await hre.network.provider.request({
174 | method: "hardhat_impersonateAccount",
175 | params: [WETHDAI_WHALE],
176 | // WETHDAI_WHALE holds WETH, DAI and ETH
177 | });
178 | const impersonateSigner = await ethers.getSigner(WETHDAI_WHALE);
179 |
180 | const WETHContract = await ethers.getContractAt("IERC20", WETH);
181 | const DAIContract = await ethers.getContractAt("IERC20", DAI);
182 | // const UNIContract = await ethers.getContractAt("IERC20", UNI);
183 | const DAIBalanceBefore = await DAIContract.balanceOf(
184 | impersonateSigner.address
185 | );
186 | const WETHBalanceBefore = await WETHContract.balanceOf(
187 | impersonateSigner.address
188 | );
189 |
190 | await DAIContract.connect(impersonateSigner).approve(
191 | TestLiquidityContract.address,
192 | DAIBalanceBefore
193 | );
194 |
195 | await WETHContract.connect(impersonateSigner).approve(
196 | TestLiquidityContract.address,
197 | WETHBalanceBefore
198 | );
199 |
200 | console.log(
201 | `WETH Balannce before adding liquidity: ${ethers.utils.formatUnits(
202 | WETHBalanceBefore.toString()
203 | )}`
204 | );
205 | console.log(
206 | `DAI Balance before adding liquidity: ${ethers.utils.formatUnits(
207 | DAIBalanceBefore.toString()
208 | )}`
209 | );
210 |
211 | console.log(
212 | "----------------------------ADD LIQUIDITY----------------------------"
213 | );
214 |
215 | let tx = await TestLiquidityContract.connect(
216 | impersonateSigner
217 | ).addLiquidity(WETHBalanceBefore, DAIBalanceBefore);
218 |
219 | let receipt = await tx.wait();
220 | // console.log(receipt.logs);
221 | // console.log(receipt.events[0].topics.toString());
222 | // console.log(receipt.events[0].args.message.toString());
223 | // console.log(receipt.events[0].args.val.toString());
224 |
225 | const WETHBalanceAfter = await WETHContract.balanceOf(
226 | impersonateSigner.address
227 | );
228 | const DAIBalanceAfter = await DAIContract.balanceOf(
229 | impersonateSigner.address
230 | );
231 |
232 | console.log(
233 | `WETH Balance after adding Liquidity: ${ethers.utils.formatUnits(
234 | WETHBalanceAfter.toString()
235 | )}`
236 | );
237 |
238 | console.log(
239 | `DAI Balance after adding Liquidity: ${ethers.utils.formatUnits(
240 | DAIBalanceAfter.toString()
241 | )}`
242 | );
243 |
244 | assert.isBelow(
245 | WETHBalanceAfter,
246 | WETHBalanceBefore,
247 | "WETH not added to liquidity"
248 | );
249 | assert.isBelow(
250 | DAIBalanceAfter,
251 | DAIBalanceBefore,
252 | "DAI not added to liquidity"
253 | );
254 |
255 | expect(tx).to.emit(TestLiquidityContract, "Log");
256 | });
257 |
258 | it("should remove liquidity", async () => {
259 | await hre.network.provider.request({
260 | method: "hardhat_impersonateAccount",
261 | params: [WETHDAI_WHALE],
262 | // WETHDAI_WHALE holds WETH, DAI and ETH
263 | });
264 | const impersonateSigner = await ethers.getSigner(WETHDAI_WHALE);
265 |
266 | const WETHContract = await ethers.getContractAt("IERC20", WETH);
267 | const DAIContract = await ethers.getContractAt("IERC20", DAI);
268 | const DAIBalanceBefore = await DAIContract.balanceOf(
269 | impersonateSigner.address
270 | );
271 | const WETHBalanceBefore = await WETHContract.balanceOf(
272 | impersonateSigner.address
273 | );
274 | console.log(
275 | `WETH Balance before removing Liquidity: ${ethers.utils.formatUnits(
276 | WETHBalanceBefore.toString()
277 | )}`
278 | );
279 |
280 | console.log(
281 | `DAI Balance before removing Liquidity: ${ethers.utils.formatUnits(
282 | DAIBalanceBefore.toString()
283 | )}`
284 | );
285 |
286 | //❌
287 | ////////////////////////////////////////////////////
288 | ////////////////Stuck at this point////////////////
289 | ////////////////////////////////////////////////////
290 | //can't find events emited on transaction receipt,
291 | // and also the transaction was reverted
292 | // (after calling remove liquidity)
293 | // with this error: 'ds-math-sub-underflow'
294 | ////////////////////////////////////////////////////
295 | ////////////////////////////////////////////////////
296 |
297 | console.log(
298 | "----------------------------REMOVE LIQUIDITY----------------------------"
299 | );
300 |
301 | // let tx = await TestLiquidityContract.connect(
302 | // impersonateSigner
303 | // ).removeLiquidity();
304 |
305 | // let receipt = await tx.wait(1);
306 |
307 | // console.log(receipt);
308 |
309 | // const WETHBalanceAfter = await WETHContract.balanceOf(
310 | // impersonateSigner.address
311 | // );
312 | // const DAIBalanceAfter = await DAIContract.balanceOf(
313 | // impersonateSigner.address
314 | // );
315 |
316 | // console.log(
317 | // `WETH Balance after removing Liquidity: ${ethers.utils.formatUnits(
318 | // WETHBalanceAfter.toString()
319 | // )}`
320 | // );
321 |
322 | // console.log(
323 | // `DAI Balance after removing Liquidity: ${ethers.utils.formatUnits(
324 | // DAIBalanceAfter.toString()
325 | // )}`
326 | // );
327 | });
328 | });
329 |
330 | // Flash Swap ✅
331 | describe("Uniswap V2 Flash Swap", function () {
332 | let TestFlashContract;
333 |
334 | beforeEach(async () => {
335 | const TestFlashFactory = await ethers.getContractFactory(
336 | "UniswapV2FlashSwap"
337 | );
338 |
339 | TestFlashContract = await TestFlashFactory.deploy();
340 | await TestFlashContract.deployed();
341 | });
342 | it("flash swap", async () => {
343 | await hre.network.provider.request({
344 | method: "hardhat_impersonateAccount",
345 | params: [WETH_WHALE1],
346 | // WETHDAI_WHALE holds WETH, DAI and ETH
347 | });
348 |
349 | const impersonateSigner = await ethers.getSigner(WETH_WHALE1);
350 |
351 | const WETHContract = await ethers.getContractAt("IERC20", WETH);
352 |
353 | const WETHBalanceBefore = await WETHContract.balanceOf(
354 | impersonateSigner.address
355 | );
356 |
357 | console.log(
358 | `Balance of WETH before the Flash Swap: ${ethers.utils.formatUnits(
359 | WETHBalanceBefore.toString()
360 | )}`
361 | );
362 |
363 | await WETHContract.connect(impersonateSigner).approve(
364 | TestFlashContract.address,
365 | WETHBalanceBefore
366 | );
367 |
368 | console.log(
369 | "----------------------------FLASH SWAP----------------------------"
370 | );
371 | const amountFlash = ethers.utils.parseUnits("100");
372 | await TestFlashContract.connect(impersonateSigner).flashSwap(amountFlash);
373 |
374 | const WETHBalanceAfter = await WETHContract.balanceOf(
375 | impersonateSigner.address
376 | );
377 |
378 | console.log(
379 | `Balance of WETH AFTER the Flash Swap: ${ethers.utils.formatUnits(
380 | WETHBalanceAfter.toString()
381 | )}`
382 | );
383 | assert(WETHBalanceAfter < WETHBalanceBefore);
384 | });
385 | });
386 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [![Contributors][contributors-shield]][contributors-url]
4 | [![Forks][forks-shield]][forks-url]
5 | [![Stargazers][stars-shield]][stars-url]
6 | [![Issues][issues-shield]][issues-url]
7 | [![MIT License][license-shield]][license-url]
8 | [![LinkedIn][linkedin-shield]][linkedin-url]
9 |
10 |
11 |
12 |
15 |
16 |
17 |
20 | Uniswap V2
21 |
22 | Explore the docs »
23 |
24 |
25 | View Demo
26 | ·
27 | Report Bug
28 | ·
29 | Request Feature
30 |
186 |
192 |
198 |
206 |
212 |
220 |
291 |
297 |
303 |