├── .env.template ├── .gitignore ├── README.md ├── app ├── main.ts ├── types.ts └── utils.ts ├── console.log ├── contracts ├── FlashSwap.sol ├── interfaces │ ├── IERC20.sol │ ├── IUniswapV2Callee.sol │ ├── IUniswapV2ERC20.sol │ ├── IUniswapV2Factory.sol │ ├── IUniswapV2Pair.sol │ ├── IUniswapV2Router01.sol │ ├── IUniswapV2Router02.sol │ └── IWETH.sol ├── libraries │ ├── SafeMath.sol │ └── UniswapV2Library.sol └── test │ └── TToken.sol ├── hardhat.config.ts ├── package.json ├── scripts ├── 0_create_new_wallet.ts └── 1_deploy_flashswap.ts ├── test └── FlashSwap.ts └── tsconfig.json /.env.template: -------------------------------------------------------------------------------- 1 | # Using this template create a new '.env' file and fill all private data there! 2 | 3 | MAINNET_PRIVATE_KEY= 4 | 5 | # This can be any other testnet except Kovan 6 | KOVAN_PRIVATE_KEY= 7 | KOVAN_ADDRESS= 8 | 9 | INFURA_PROJECT_ID= 10 | INFURA_PROJECT_SECRET= 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | /cache 3 | /artifacts 4 | /typechain 5 | .secret 6 | 7 | # dependencies 8 | /node_modules 9 | /.pnp 10 | .pnp.js 11 | 12 | # testing 13 | /coverage 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | package-lock.json 29 | 30 | *.env 31 | *.lock 32 | *.gitignore 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | FlashSwap is a smartcontract to make flashswaps with Uniswap. This mechanism allows you to make a swap if you only have enough money just to pay the fee. 4 | 5 | * [Core concept](https://uniswap.org/docs/v2/core-concepts/flash-swaps/) 6 | * [Detailed description](https://uniswap.org/docs/v2/smart-contract-integration/using-flash-swaps/) 7 | 8 | ## Contract 9 | 10 | This contract takes addresses of the UniswapRouter and UnswapFactory during deploy, and provides method startFlashLoan to run flashswaps 11 | 12 | ### Constructor 13 | 14 | ```solidity 15 | constructor(address factory, address router) public; 16 | ``` 17 | 18 | You can use your own Router and Factory or get actual address for default networks below: 19 | 20 | UniswapV2Router02: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 21 | 22 | UniswapV2Factory: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f 23 | 24 | ### startFlashLoan 25 | 26 | ```solidity 27 | function startFlashLoan(uint amountIn, address[] memory path, address baseToken) external; 28 | ``` 29 | 30 | This method borrows tokens from uniswapPair, makes swap with Router, returns loans back and sends balance after deduction to the `msg.sender`. 31 | 32 | * `amountIn` — amount of tokens that will be borrowed. 33 | * `path` — an array of token addresses between which the swap will be performed in turn. First and last addresses **must** be the same to make a looped swap. Contract saves it to a private attribute `_path` to read it in the method `uniswapV2Call` that will be called next. 34 | You can pass it using `data` argument instead of an attribute, if you need it. `data` is a `bytes` type therefore if you want to pass addresses as an argument you need a library to convert list of addresses to bytes and vice versa. You can read about this [here](https://ethereum.stackexchange.com/a/90801). 35 | * `baseToken` — an address of a token that creates a liquidity containing pair with `path[0]` token. This token **must not** be in a `path` array, because UniswapPair doesn't allow to call swap method while some other swap is incomplete. 36 | 37 | ### Dependencies 38 | 39 | Install dependencies of the package.json: 40 | 41 | ```bash 42 | npm i -D 43 | ``` 44 | 45 | ### Create config 46 | 47 | Use _.env_ as a local config to set private options manually: 48 | 49 | **Don't commit it to git! (keep it secret)** 50 | 51 | ```bash 52 | cp .env.template .env 53 | ``` 54 | 55 | You should also register at [Infura](https://infura.io/) and create new project there. 56 | 57 | After that set your Infura Project ID (from project settings) to _.env_ 58 | 59 | ### Scripts 60 | 61 | To run any script enter: 62 | 63 | ```bash 64 | npx hardhat run path/to/script.ts --network network_name 65 | ``` 66 | 67 | #### Generate a wallet 68 | 69 | Use the script to generate a new wallet with it's own private key and address: 70 | 71 | ```bash 72 | npx hardhat run scripts/0_create_new_wallet.ts 73 | ``` 74 | 75 | Copy generated private key (or your own private key) to _.env_ config on the corresponding line. 76 | 77 | Add some ETH to address of this wallet. For this you can use any _faucet_ for your network. For example [faucet.kovan.network](https://faucet.kovan.network) 78 | 79 | #### Deploy the FlashSwap contract 80 | 81 | A script to deploy the FlashSwap contract and to get it's address: 82 | 83 | ```bash 84 | npx hardhat run scripts/1_deploy_flashswap.ts --network kovan 85 | ``` 86 | 87 | ### Run tests 88 | 89 | Hardhat allows you to execute tests in it's own network, but due to the fact that you need to interact with Uniswap, you should run it in a network where the Uniswap is. For example - Kovan. 90 | 91 | ```bash 92 | npx hardhat test test/FlashSwap.ts --network kovan 93 | ``` 94 | -------------------------------------------------------------------------------- /app/main.ts: -------------------------------------------------------------------------------- 1 | import { config as dotEnvConfig } from "dotenv"; 2 | dotEnvConfig(); 3 | 4 | 5 | // For debug and testing: npx hardhat run app/main.ts --network kovan 6 | // For production: npx hardhat run app/main.ts --network mainnet???? 7 | 8 | 9 | import fs from "fs"; 10 | import glob from "glob"; 11 | import { ethers, Signer, ContractFactory, Contract, BigNumber, providers, Wallet } from "ethers"; 12 | const { formatEther, parseEther } = ethers.utils; 13 | import type { TransactionResponse, TransactionReceipt, Log } from "@ethersproject/abstract-provider"; 14 | import type { TransactionReceiptWithEvents, ContractData } from "./types"; 15 | 16 | 17 | const addresses = { 18 | // addresses from Uniswap docs 19 | ROUTER: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 20 | FACTORY: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f', 21 | WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 22 | 23 | ERC20: process.env.SHIT_COIN_ADDRESS || "", 24 | 25 | } 26 | 27 | 28 | async function main() { 29 | console.log(1); 30 | } 31 | 32 | main() 33 | .then(() => process.exit(0)) 34 | .catch(error => { 35 | console.error(error); 36 | process.exit(1); 37 | }); 38 | -------------------------------------------------------------------------------- /app/types.ts: -------------------------------------------------------------------------------- 1 | import type { TransactionResponse, TransactionReceipt, Log } from "@ethersproject/abstract-provider"; 2 | 3 | export interface Event extends Log { 4 | event: string; 5 | args: Array; 6 | } 7 | 8 | export interface TransactionReceiptWithEvents extends TransactionReceipt { 9 | events?: Array; 10 | } 11 | 12 | export interface ContractFactory { 13 | attach(address: string): ContractType; 14 | deploy(...args: any[]): ContractType; 15 | } 16 | 17 | export interface ContractData { 18 | _format: string, 19 | abi: Array, 20 | contractName: string, 21 | bytecode: string, 22 | sourceName: string, 23 | deployedBytecode: string, 24 | linkReferences: object, 25 | deployedLinkReferences: object, 26 | } 27 | 28 | export interface CoinData { 29 | id: string, 30 | symbol: string, 31 | name: string, 32 | } 33 | 34 | // export { TransactionReceipt, TransactionResponse }; 35 | -------------------------------------------------------------------------------- /app/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import glob from "glob"; 3 | import { Signer, ContractFactory } from "ethers"; 4 | import type { ContractData } from "./types"; 5 | 6 | // Get a ContractFactory by a contract name from ./artifacts dir 7 | export function getContractFactory(name: string, signer: Signer): ContractFactory { 8 | // Find all .json files with such name in ./artifacts 9 | const files: string[] = glob.sync(`./artifacts/**/${name}.json`); 10 | // Throw an exception if the number of files found isn't 1 11 | if (files.length == 0) throw `Contract ${name}.sol not found`; 12 | // Get the first path 13 | const path: string = files[0]; 14 | // Read the file from path 15 | const file: Buffer = fs.readFileSync(path); 16 | // Parse the Buffer to ContractData 17 | const data: ContractData = JSON.parse(file.toString()); 18 | // Load ContractFactory from the ContractData 19 | const factory: ContractFactory = new ContractFactory(data.abi, data.bytecode, signer); 20 | return factory; 21 | } 22 | -------------------------------------------------------------------------------- /console.log: -------------------------------------------------------------------------------- 1 | Contract { 2 | interface: Interface { 3 | fragments: [ [ConstructorFragment], [FunctionFragment], [FunctionFragment] ], 4 | _abiCoder: AbiCoder { coerceFunc: null }, 5 | functions: { 6 | 'startFlashLoan(uint256,address[],address)': [FunctionFragment], 7 | 'uniswapV2Call(address,uint256,uint256,bytes)': [FunctionFragment] 8 | }, 9 | errors: {}, 10 | events: {}, 11 | structs: {}, 12 | deploy: ConstructorFragment { 13 | name: null, 14 | type: 'constructor', 15 | inputs: [Array], 16 | payable: false, 17 | stateMutability: 'nonpayable', 18 | gas: null, 19 | _isFragment: true 20 | }, 21 | _isInterface: true 22 | }, 23 | provider: EthersProviderWrapper { 24 | _isProvider: true, 25 | _events: [], 26 | _emitted: { 27 | block: -2, 28 | 't:0x61d42d766f2395ea1533fac90ef28c41bee33e63596627d69d98fc1472a7792e': 24686432, 29 | 't:0xbeb2defd989a74be638067174d211f22cf677d73f2e5bb4f3d50cda38abef57c': 24686433, 30 | 't:0x1d10a823772df73e9221fa4d0920ec36101b412e35d98bd476261b741bc2eaf1': 24686434, 31 | 't:0xded8d30f226c91629d44f070a5b0fa58c955bba84f93a1383c7addea489a2d1d': 24686435, 32 | 't:0x57b119be1c5fa12bb2cacfc29c8433b557bb3f52e00a66151050f463398d5347': 24686444 33 | }, 34 | formatter: Formatter { formats: [Object] }, 35 | anyNetwork: false, 36 | _networkPromise: Promise { [Object] }, 37 | _maxInternalBlockNumber: 24686432, 38 | _lastBlockNumber: -2, 39 | _pollingInterval: 4000, 40 | _fastQueryDate: 1620295200795, 41 | connection: { url: 'http://localhost:8545' }, 42 | _nextId: 42, 43 | _hardhatProvider: BackwardsCompatibilityProviderAdapter { 44 | _wrapped: [EGRDataCollectionProvider], 45 | _provider: [EGRDataCollectionProvider], 46 | sendAsync: [Function: bound sendAsync], 47 | send: [Function: bound send], 48 | _sendJsonRpcRequest: [Function: bound _sendJsonRpcRequest] AsyncFunction 49 | }, 50 | _network: { chainId: 31337, name: 'unknown' }, 51 | _internalBlockNumber: Promise { [Object] }, 52 | _fastBlockNumber: 24686432, 53 | _fastBlockNumberPromise: Promise { 24686432 } 54 | }, 55 | signer: SignerWithAddress { 56 | _isSigner: true, 57 | address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', 58 | _signer: JsonRpcSigner { 59 | _isSigner: true, 60 | provider: [EthersProviderWrapper], 61 | _address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', 62 | _index: null 63 | }, 64 | provider: EthersProviderWrapper { 65 | _isProvider: true, 66 | _events: [], 67 | _emitted: [Object], 68 | formatter: [Formatter], 69 | anyNetwork: false, 70 | _networkPromise: [Promise], 71 | _maxInternalBlockNumber: 24686432, 72 | _lastBlockNumber: -2, 73 | _pollingInterval: 4000, 74 | _fastQueryDate: 1620295200795, 75 | connection: [Object], 76 | _nextId: 42, 77 | _hardhatProvider: [BackwardsCompatibilityProviderAdapter], 78 | _network: [Object], 79 | _internalBlockNumber: [Promise], 80 | _fastBlockNumber: 24686432, 81 | _fastBlockNumberPromise: [Promise] 82 | } 83 | }, 84 | callStatic: { 85 | 'startFlashLoan(uint256,address[],address)': [Function (anonymous)], 86 | 'uniswapV2Call(address,uint256,uint256,bytes)': [Function (anonymous)], 87 | startFlashLoan: [Function (anonymous)], 88 | uniswapV2Call: [Function (anonymous)] 89 | }, 90 | estimateGas: { 91 | 'startFlashLoan(uint256,address[],address)': [Function (anonymous)], 92 | 'uniswapV2Call(address,uint256,uint256,bytes)': [Function (anonymous)], 93 | startFlashLoan: [Function (anonymous)], 94 | uniswapV2Call: [Function (anonymous)] 95 | }, 96 | functions: { 97 | 'startFlashLoan(uint256,address[],address)': [Function (anonymous)], 98 | 'uniswapV2Call(address,uint256,uint256,bytes)': [Function (anonymous)], 99 | startFlashLoan: [Function (anonymous)], 100 | uniswapV2Call: [Function (anonymous)] 101 | }, 102 | populateTransaction: { 103 | 'startFlashLoan(uint256,address[],address)': [Function (anonymous)], 104 | 'uniswapV2Call(address,uint256,uint256,bytes)': [Function (anonymous)], 105 | startFlashLoan: [Function (anonymous)], 106 | uniswapV2Call: [Function (anonymous)] 107 | }, 108 | filters: {}, 109 | _runningEvents: {}, 110 | _wrappedEmits: {}, 111 | address: '0xDCA98B7C113496Fdc59C654a01571B8A3B427d2B', 112 | resolvedAddress: Promise { '0xDCA98B7C113496Fdc59C654a01571B8A3B427d2B' }, 113 | 'startFlashLoan(uint256,address[],address)': [Function (anonymous)], 114 | 'uniswapV2Call(address,uint256,uint256,bytes)': [Function (anonymous)], 115 | startFlashLoan: [Function (anonymous)], 116 | uniswapV2Call: [Function (anonymous)], 117 | deployTransaction: { 118 | hash: '0x57b119be1c5fa12bb2cacfc29c8433b557bb3f52e00a66151050f463398d5347', 119 | type: 0, 120 | accessList: null, 121 | blockHash: '0x6d5070f632dff5de618c2a59fb6b74bda03828380dd1c7181f05b261fb492af0', 122 | blockNumber: 24686444, 123 | transactionIndex: 0, 124 | confirmations: 1, 125 | from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', 126 | gasPrice: BigNumber { _hex: '0x01dcd65000', _isBigNumber: true }, 127 | gasLimit: BigNumber { _hex: '0x0d0282', _isBigNumber: true }, 128 | to: null, 129 | value: BigNumber { _hex: '0x00', _isBigNumber: true }, 130 | nonce: 1391, 131 | data: '0x60c060405234801561001057600080fd5b50604051610ef9380380610ef98339818101604052604081101561003357600080fd5b5080516020909101516001600160601b0319606092831b8116608052911b1660a05260805160601c60a05160601c610e716100886000398061030d52806103ec52508061021f52806106e55250610e716000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806310d1e85c1461003b578063f629ae1b146100c7575b600080fd5b6100c56004803603608081101561005157600080fd5b6001600160a01b038235169160208101359160408201359190810190608081016060820135600160201b81111561008757600080fd5b82018360208201111561009957600080fd5b803590602001918460018302840111600160201b831117156100ba57600080fd5b509092509050610145565b005b6100c5600480360360608110156100dd57600080fd5b81359190810190604081016020820135600160201b8111156100fe57600080fd5b82018360208201111561011057600080fd5b803590602001918460208302840111600160201b8311171561013157600080fd5b9193509150356001600160a01b0316610606565b6000806000336001600160a01b0316630dfe16816040518163ffffffff1660e01b815260040160206040518083038186803b15801561018357600080fd5b505afa158015610197573d6000803e3d6000fd5b505050506040513d60208110156101ad57600080fd5b50516040805163d21220a760e01b81529051919250600091339163d21220a7916004808301926020929190829003018186803b1580156101ec57600080fd5b505afa158015610200573d6000803e3d6000fd5b505050506040513d602081101561021657600080fd5b505190506102457f00000000000000000000000000000000000000000000000000000000000000008383610a1f565b6001600160a01b0316336001600160a01b0316146102945760405162461bcd60e51b8152600401808060200182810382526023815260200180610d7c6023913960400191505060405180910390fd5b87158061029f575086155b6102da5760405162461bcd60e51b8152600401808060200182810382526033815260200180610e096033913960400191505060405180910390fd5b87156102e657816102e8565b805b935087156102f657876102f8565b865b92505050816001600160a01b031663095ea7b37f0000000000000000000000000000000000000000000000000000000000000000836040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b15801561037357600080fd5b505af1158015610387573d6000803e3d6000fd5b505050506040513d602081101561039d57600080fd5b50506040516338ed173960e01b81526004810182815260248201839052306064830181905261025842016084840181905260a0604485019081526000805460a487018190526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016966338ed1739968996879694959094939192909160c4909101908690801561045d57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161043f575b50509650505050505050600060405180830381600087803b15801561048157600080fd5b505af1158015610495573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260208110156104be57600080fd5b8101908080516040519392919084600160201b8211156104dd57600080fd5b9083019060208201858111156104f257600080fd5b82518660208202830111600160201b8211171561050e57600080fd5b82525081516020918201928201910280838360005b8381101561053b578181015183820152602001610523565b5050505090500160405250505050600061057760016105716103e561056b6103e887610ada90919063ffffffff16565b90610b43565b90610ba7565b9050826001600160a01b031663a9059cbb33836040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b1580156105d057600080fd5b505af11580156105e4573d6000803e3d6000fd5b505050506040513d60208110156105fa57600080fd5b50505050505050505050565b60038210156106465760405162461bcd60e51b8152600401808060200182810382526033815260200180610d9f6033913960400191505060405180910390fd5b8282600019810181811061065657fe5b905060200201356001600160a01b03166001600160a01b03168383600081811061067c57fe5b905060200201356001600160a01b03166001600160a01b0316146106d15760405162461bcd60e51b8152600401808060200182810382526037815260200180610dd26037913960400191505060405180910390fd5b6106dd60008484610cd4565b5060006107277f00000000000000000000000000000000000000000000000000000000000000008585600081811061071157fe5b905060200201356001600160a01b031684610a1f565b90506000816001600160a01b0316630dfe16816040518163ffffffff1660e01b815260040160206040518083038186803b15801561076457600080fd5b505afa158015610778573d6000803e3d6000fd5b505050506040513d602081101561078e57600080fd5b5051905060006001600160a01b038216868683816107a857fe5b905060200201356001600160a01b03166001600160a01b0316146107cd5760006107cf565b865b90506000826001600160a01b0316878760008181106107ea57fe5b905060200201356001600160a01b03166001600160a01b03161461080e5787610811565b60005b9050836001600160a01b031663022c0d9f83833060405180604001604052806003815260200162616e7960e81b8152506040518563ffffffff1660e01b815260040180858152602001848152602001836001600160a01b0316815260200180602001828103825283818151815260200191508051906020019080838360005b838110156108a8578181015183820152602001610890565b50505050905090810190601f1680156108d55780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b1580156108f757600080fd5b505af115801561090b573d6000803e3d6000fd5b505050508686600081811061091c57fe5b905060200201356001600160a01b03166001600160a01b031663a9059cbb338989600081811061094857fe5b905060200201356001600160a01b03166001600160a01b03166370a08231306040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156109a457600080fd5b505afa1580156109b8573d6000803e3d6000fd5b505050506040513d60208110156109ce57600080fd5b5051604080516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091525160448083019260209291908290030181600087803b1580156105d057600080fd5b6000806000610a2e8585610bf6565b604080516001600160601b0319606094851b811660208084019190915293851b81166034830152825160288184030181526048830184528051908501206001600160f81b031960688401529a90941b9093166069840152607d8301989098527f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f609d808401919091528851808403909101815260bd909201909752805196019590952095945050505050565b6000811580610af557505080820282828281610af257fe5b04145b610b3d576040805162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6d756c2d6f766572666c6f7760601b604482015290519081900360640190fd5b92915050565b6000808311610b96576040805162461bcd60e51b815260206004820152601a602482015279536166654d6174683a206469766973696f6e206279207a65726f60301b604482015290519081900360640190fd5b818381610b9f57fe5b049392505050565b80820182811015610b3d576040805162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6164642d6f766572666c6f7760601b604482015290519081900360640190fd5b600080826001600160a01b0316846001600160a01b03161415610c4a5760405162461bcd60e51b8152600401808060200182810382526025815260200180610d576025913960400191505060405180910390fd5b826001600160a01b0316846001600160a01b031610610c6a578284610c6d565b83835b90925090506001600160a01b038216610ccd576040805162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f414444524553530000604482015290519081900360640190fd5b9250929050565b828054828255906000526020600020908101928215610d27579160200282015b82811115610d275781546001600160a01b0319166001600160a01b03843516178255602090920191600190910190610cf4565b50610d33929150610d37565b5090565b5b80821115610d335780546001600160a01b0319168155600101610d3856fe556e697377617056324c6962726172793a204944454e544943414c5f414444524553534553466c617368537761703a206d73672e73656e646572206973206e6f7420612070616972466c617368537761703a204c656e677468206f66207468697320706174682068617320746f206265206174206c656173742033466c617368537761703a20466972737420616e64206c61737420746f6b656e73206d757374206265207468652073616d6520746f6b656e466c617368537761703a20416d6f756e74206f6e65206f662074686520746f6b656e7320646f65736e277420657175616c2030a2646970667358221220a4165bcff834835649dd71c0c58323bc84853ffb0fb29516ff2ee8d6039e4d9b64736f6c634300060c00330000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d', 132 | r: '0xbe1fb1351227ec8cc731f3a3e76d091cd3686be3f49714e56a6eb452336ba88a', 133 | s: '0x50b6ab2adadf1e5f0107365707683fefc23f3af8a0c4c4a47d5bb37bd611ec0c', 134 | v: 62710, 135 | creates: '0xDCA98B7C113496Fdc59C654a01571B8A3B427d2B', 136 | chainId: 31337, 137 | wait: [Function (anonymous)] 138 | }, 139 | _deployedPromise: Promise { [Circular *1] } 140 | } 141 | { 142 | hash: '0x7f2c2032faa37990a5c446efebd68905f7d9338b91217f57567339d59e19ff9b', 143 | type: 0, 144 | accessList: null, 145 | blockHash: '0xb4a5994e588d224e93643dd978a46b913e3faa6445b7a57f797cc334cc3b8b9d', 146 | blockNumber: 24686449, 147 | transactionIndex: 0, 148 | confirmations: 1, 149 | from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', 150 | gasPrice: BigNumber { _hex: '0x01dcd65000', _isBigNumber: true }, 151 | gasLimit: BigNumber { _hex: '0x091d10', _isBigNumber: true }, 152 | to: '0xDCA98B7C113496Fdc59C654a01571B8A3B427d2B', 153 | value: BigNumber { _hex: '0x00', _isBigNumber: true }, 154 | nonce: 1396, 155 | data: '0xf629ae1b000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000002388d1b9d8e958528643ef6046c638879e21029400000000000000000000000000000000000000000000000000000000000000040000000000000000000000008b9f1b018f5965e050dbf5080d223c8dc70b60d0000000000000000000000000e888c1acd85730bd9f63762e9bbbaa71c817edbe0000000000000000000000001ae62c8d38892edbb8ae2793b54a24ff942bfe220000000000000000000000008b9f1b018f5965e050dbf5080d223c8dc70b60d0', 156 | r: '0x16fc9f5120fd4a53cbf93ff8ea54264d88360b4939b7a9efee3a67d78e50241c', 157 | s: '0x3471ce4cd25b36c2b857244c3f4e828b8a32f9063ec980c97af37d63f3b60fdd', 158 | v: 62710, 159 | creates: null, 160 | chainId: 31337, 161 | wait: [Function (anonymous)] 162 | } 163 | ✓ start_flash_swap 164 | 165 | ·--------------------------------|---------------------------|-----------|-----------------------------· 166 | | Solc version: 0.6.12 · Optimizer enabled: true · Runs: 1 · Block limit: 12450000 gas │ 167 | ·································|···························|···········|······························ 168 | | Methods │ 169 | ··············|··················|·············|·············|···········|···············|·············· 170 | | Contract · Method · Min · Max · Avg · # calls · eur (avg) │ 171 | ··············|··················|·············|·············|···········|···············|·············· 172 | | FlashSwap · startFlashLoan · - · - · 525881 · 1 · - │ 173 | ··············|··················|·············|·············|···········|···············|·············· 174 | | TToken · approve · - · - · 46055 · 4 · - │ 175 | ··············|··················|·············|·············|···········|···············|·············· 176 | | TToken · mint · - · - · 70614 · 4 · - │ 177 | ··············|··················|·············|·············|···········|···············|·············· 178 | | Deployments · · % of limit · │ 179 | ·································|·············|·············|···········|···············|·············· 180 | | FlashSwap · - · - · 852610 · 6.8 % · - │ 181 | ·································|·············|·············|···········|···············|·············· 182 | | TToken · 652892 · 652916 · 652904 · 5.2 % · - │ 183 | ·--------------------------------|-------------|-------------|-----------|---------------|-------------· -------------------------------------------------------------------------------- /contracts/FlashSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.6; 4 | 5 | import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol'; 6 | 7 | import './interfaces/IUniswapV2Pair.sol'; 8 | import './libraries/UniswapV2Library.sol'; 9 | import './libraries/SafeMath.sol'; 10 | import './interfaces/IUniswapV2Factory.sol'; 11 | import './interfaces/IUniswapV2Router02.sol'; 12 | import './interfaces/IERC20.sol'; 13 | 14 | contract FlashSwap is IUniswapV2Callee { 15 | using SafeMath for uint; 16 | 17 | address private immutable _factory; 18 | address private immutable _router; 19 | address[] private _path; 20 | 21 | constructor(address factoryAddr, address routerAddr) public { 22 | _factoryAddr = factoryAddr; 23 | _routerAddr = routerAddr; 24 | } 25 | 26 | function startFlashLoan(uint amountIn, address[] calldata path, address baseToken) external { 27 | // `path` must not include `baseToken` address 28 | require(path.length >= 3, "FlashSwap: Length of this path has to be at least 3"); 29 | require(path[0] == path[path.length - 1], "FlashSwap: First and last tokens must be the same token"); 30 | 31 | // Save the path to use it in uniswapV2Call 32 | _path = path; 33 | 34 | address pair = UniswapV2Library.pairFor(_factory, path[0], baseToken); 35 | address token0 = IUniswapV2Pair(pair).token0(); 36 | 37 | uint amount0 = path[0] == token0 ? amountIn : 0; 38 | uint amount1 = path[0] == token0 ? 0 : amountIn; 39 | 40 | // Make flashswap 41 | IUniswapV2Pair(pair).swap( 42 | amount0, 43 | amount1, 44 | address(this), 45 | bytes("any") // random `data` to trigger flash-swap 46 | ); 47 | // Send all profit to this sender 48 | IERC20(path[0]).transfer(msg.sender, IERC20(path[0]).balanceOf(address(this))); 49 | } 50 | 51 | function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external override { 52 | address rootToken; 53 | uint amounIn; 54 | { 55 | address token0 = IUniswapV2Pair(msg.sender).token0(); 56 | address token1 = IUniswapV2Pair(msg.sender).token1(); 57 | // Necassary to check that this msg.sender is a pair 58 | require(msg.sender == UniswapV2Library.pairFor(_factory, token0, token1), "FlashSwap: msg.sender is not a pair"); 59 | // One of the amounts has to equal 0 because we need only one token to make a flashswap 60 | // UniswapPair already checks that at least one greather than 0 61 | require(amount0 == 0 || amount1 == 0, "FlashSwap: Amount one of the tokens doesn't equal 0"); 62 | // Use token1 and amount1 if amount0 == 0 else use token0 and amount0 63 | rootToken = amount0 == 0 ? token1 : token0; 64 | amounIn = amount0 == 0 ? amount1 : amount0; 65 | } 66 | IERC20(rootToken).approve(_router, amounIn); 67 | IUniswapV2Router02(_router).swapExactTokensForTokens( 68 | amounIn, 69 | amounIn, // Amount that will return back has to be greater than start amount 70 | _path, 71 | address(this), 72 | now + 10 minutes 73 | ); 74 | // Calculate amountOut that will return to this pair. 75 | // amountOut = amounIn / 0.997 (+ 1 to avoid error) 76 | uint amountOut = amounIn.mul(1000).div(997).add(1); 77 | 78 | // Return tokens with fee back 79 | IERC20(rootToken).transfer(msg.sender, amountOut); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.6; 4 | 5 | /** 6 | * @dev Interface of the ERC20 standard as defined in the EIP. 7 | */ 8 | interface IERC20 { 9 | /** 10 | * @dev Returns the amount of tokens in existence. 11 | */ 12 | function totalSupply() external view returns (uint256); 13 | 14 | /** 15 | * @dev Returns the amount of tokens owned by `account`. 16 | */ 17 | function balanceOf(address account) external view returns (uint256); 18 | 19 | /** 20 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 21 | * 22 | * Returns a boolean value indicating whether the operation succeeded. 23 | * 24 | * Emits a {Transfer} event. 25 | */ 26 | function transfer(address recipient, uint256 amount) external returns (bool); 27 | 28 | /** 29 | * @dev Returns the remaining number of tokens that `spender` will be 30 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 31 | * zero by default. 32 | * 33 | * This value changes when {approve} or {transferFrom} are called. 34 | */ 35 | function allowance(address owner, address spender) external view returns (uint256); 36 | 37 | /** 38 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 39 | * 40 | * Returns a boolean value indicating whether the operation succeeded. 41 | * 42 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 43 | * that someone may use both the old and the new allowance by unfortunate 44 | * transaction ordering. One possible solution to mitigate this race 45 | * condition is to first reduce the spender's allowance to 0 and set the 46 | * desired value afterwards: 47 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 48 | * 49 | * Emits an {Approval} event. 50 | */ 51 | function approve(address spender, uint256 amount) external returns (bool); 52 | 53 | /** 54 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 55 | * allowance mechanism. `amount` is then deducted from the caller's 56 | * allowance. 57 | * 58 | * Returns a boolean value indicating whether the operation succeeded. 59 | * 60 | * Emits a {Transfer} event. 61 | */ 62 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 63 | 64 | /** 65 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 66 | * another (`to`). 67 | * 68 | * Note that `value` may be zero. 69 | */ 70 | event Transfer(address indexed from, address indexed to, uint256 value); 71 | 72 | /** 73 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 74 | * a call to {approve}. `value` is the new allowance. 75 | */ 76 | event Approval(address indexed owner, address indexed spender, uint256 value); 77 | } -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV2Callee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.6; 4 | 5 | interface IUniswapV2Callee { 6 | function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; 7 | } -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV2ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | 4 | pragma solidity ^0.6.6; 5 | 6 | interface IUniswapV2ERC20 { 7 | event Approval(address indexed owner, address indexed spender, uint value); 8 | event Transfer(address indexed from, address indexed to, uint value); 9 | 10 | function name() external pure returns (string memory); 11 | function symbol() external pure returns (string memory); 12 | function decimals() external pure returns (uint8); 13 | function totalSupply() external view returns (uint); 14 | function balanceOf(address owner) external view returns (uint); 15 | function allowance(address owner, address spender) external view returns (uint); 16 | 17 | function approve(address spender, uint value) external returns (bool); 18 | function transfer(address to, uint value) external returns (bool); 19 | function transferFrom(address from, address to, uint value) external returns (bool); 20 | 21 | function DOMAIN_SEPARATOR() external view returns (bytes32); 22 | function PERMIT_TYPEHASH() external pure returns (bytes32); 23 | function nonces(address owner) external view returns (uint); 24 | 25 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 26 | } -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | 4 | pragma solidity ^0.6.6; 5 | 6 | interface IUniswapV2Factory { 7 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 8 | 9 | function feeTo() external view returns (address); 10 | function feeToSetter() external view returns (address); 11 | 12 | function getPair(address tokenA, address tokenB) external view returns (address pair); 13 | function allPairs(uint) external view returns (address pair); 14 | function allPairsLength() external view returns (uint); 15 | 16 | function createPair(address tokenA, address tokenB) external returns (address pair); 17 | 18 | function setFeeTo(address) external; 19 | function setFeeToSetter(address) external; 20 | } -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.6; 4 | 5 | interface IUniswapV2Pair { 6 | event Approval(address indexed owner, address indexed spender, uint value); 7 | event Transfer(address indexed from, address indexed to, uint value); 8 | 9 | function name() external pure returns (string memory); 10 | function symbol() external pure returns (string memory); 11 | function decimals() external pure returns (uint8); 12 | function totalSupply() external view returns (uint); 13 | function balanceOf(address owner) external view returns (uint); 14 | function allowance(address owner, address spender) external view returns (uint); 15 | 16 | function approve(address spender, uint value) external returns (bool); 17 | function transfer(address to, uint value) external returns (bool); 18 | function transferFrom(address from, address to, uint value) external returns (bool); 19 | 20 | function DOMAIN_SEPARATOR() external view returns (bytes32); 21 | function PERMIT_TYPEHASH() external pure returns (bytes32); 22 | function nonces(address owner) external view returns (uint); 23 | 24 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 25 | 26 | event Mint(address indexed sender, uint amount0, uint amount1); 27 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 28 | event Swap( 29 | address indexed sender, 30 | uint amount0In, 31 | uint amount1In, 32 | uint amount0Out, 33 | uint amount1Out, 34 | address indexed to 35 | ); 36 | event Sync(uint112 reserve0, uint112 reserve1); 37 | 38 | function MINIMUM_LIQUIDITY() external pure returns (uint); 39 | function factory() external view returns (address); 40 | function token0() external view returns (address); 41 | function token1() external view returns (address); 42 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 43 | function price0CumulativeLast() external view returns (uint); 44 | function price1CumulativeLast() external view returns (uint); 45 | function kLast() external view returns (uint); 46 | 47 | function mint(address to) external returns (uint liquidity); 48 | function burn(address to) external returns (uint amount0, uint amount1); 49 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 50 | function skim(address to) external; 51 | function sync() external; 52 | 53 | function initialize(address, address) external; 54 | } -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV2Router01.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | 4 | pragma solidity ^0.6.6; 5 | 6 | interface IUniswapV2Router01 { 7 | function factory() external pure returns (address); 8 | function WETH() external pure returns (address); 9 | 10 | function addLiquidity( 11 | address tokenA, 12 | address tokenB, 13 | uint amountADesired, 14 | uint amountBDesired, 15 | uint amountAMin, 16 | uint amountBMin, 17 | address to, 18 | uint deadline 19 | ) external returns (uint amountA, uint amountB, uint liquidity); 20 | function addLiquidityETH( 21 | address token, 22 | uint amountTokenDesired, 23 | uint amountTokenMin, 24 | uint amountETHMin, 25 | address to, 26 | uint deadline 27 | ) external payable returns (uint amountToken, uint amountETH, uint liquidity); 28 | function removeLiquidity( 29 | address tokenA, 30 | address tokenB, 31 | uint liquidity, 32 | uint amountAMin, 33 | uint amountBMin, 34 | address to, 35 | uint deadline 36 | ) external returns (uint amountA, uint amountB); 37 | function removeLiquidityETH( 38 | address token, 39 | uint liquidity, 40 | uint amountTokenMin, 41 | uint amountETHMin, 42 | address to, 43 | uint deadline 44 | ) external returns (uint amountToken, uint amountETH); 45 | function removeLiquidityWithPermit( 46 | address tokenA, 47 | address tokenB, 48 | uint liquidity, 49 | uint amountAMin, 50 | uint amountBMin, 51 | address to, 52 | uint deadline, 53 | bool approveMax, uint8 v, bytes32 r, bytes32 s 54 | ) external returns (uint amountA, uint amountB); 55 | function removeLiquidityETHWithPermit( 56 | address token, 57 | uint liquidity, 58 | uint amountTokenMin, 59 | uint amountETHMin, 60 | address to, 61 | uint deadline, 62 | bool approveMax, uint8 v, bytes32 r, bytes32 s 63 | ) external returns (uint amountToken, uint amountETH); 64 | function swapExactTokensForTokens( 65 | uint amountIn, 66 | uint amountOutMin, 67 | address[] calldata path, 68 | address to, 69 | uint deadline 70 | ) external returns (uint[] memory amounts); 71 | function swapTokensForExactTokens( 72 | uint amountOut, 73 | uint amountInMax, 74 | address[] calldata path, 75 | address to, 76 | uint deadline 77 | ) external returns (uint[] memory amounts); 78 | function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) 79 | external 80 | payable 81 | returns (uint[] memory amounts); 82 | function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) 83 | external 84 | returns (uint[] memory amounts); 85 | function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) 86 | external 87 | returns (uint[] memory amounts); 88 | function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) 89 | external 90 | payable 91 | returns (uint[] memory amounts); 92 | 93 | function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); 94 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); 95 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); 96 | function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); 97 | function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); 98 | } -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV2Router02.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | 4 | pragma solidity ^0.6.6; 5 | 6 | import './IUniswapV2Router01.sol'; 7 | 8 | interface IUniswapV2Router02 is IUniswapV2Router01 { 9 | function removeLiquidityETHSupportingFeeOnTransferTokens( 10 | address token, 11 | uint liquidity, 12 | uint amountTokenMin, 13 | uint amountETHMin, 14 | address to, 15 | uint deadline 16 | ) external returns (uint amountETH); 17 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 18 | address token, 19 | uint liquidity, 20 | uint amountTokenMin, 21 | uint amountETHMin, 22 | address to, 23 | uint deadline, 24 | bool approveMax, uint8 v, bytes32 r, bytes32 s 25 | ) external returns (uint amountETH); 26 | 27 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 28 | uint amountIn, 29 | uint amountOutMin, 30 | address[] calldata path, 31 | address to, 32 | uint deadline 33 | ) external; 34 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 35 | uint amountOutMin, 36 | address[] calldata path, 37 | address to, 38 | uint deadline 39 | ) external payable; 40 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 41 | uint amountIn, 42 | uint amountOutMin, 43 | address[] calldata path, 44 | address to, 45 | uint deadline 46 | ) external; 47 | } -------------------------------------------------------------------------------- /contracts/interfaces/IWETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | 4 | pragma solidity ^0.6.6; 5 | 6 | interface IWETH { 7 | function deposit() external payable; 8 | function transfer(address to, uint value) external returns (bool); 9 | function withdraw(uint) external; 10 | } -------------------------------------------------------------------------------- /contracts/libraries/SafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.6; 4 | 5 | library SafeMath { 6 | function add(uint x, uint y) internal pure returns (uint z) { 7 | require((z = x + y) >= x, 'ds-math-add-overflow'); 8 | } 9 | 10 | function sub(uint x, uint y) internal pure returns (uint z) { 11 | require((z = x - y) <= x, 'ds-math-sub-underflow'); 12 | } 13 | 14 | function mul(uint x, uint y) internal pure returns (uint z) { 15 | require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow'); 16 | } 17 | 18 | function div(uint x, uint y) internal pure returns (uint z) { 19 | require(x > 0, "SafeMath: division by zero"); 20 | z = x / y; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/libraries/UniswapV2Library.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | 4 | pragma solidity ^0.6.6; 5 | 6 | import '../interfaces/IUniswapV2Callee.sol'; 7 | import '../interfaces/IUniswapV2Pair.sol'; 8 | 9 | import "./SafeMath.sol"; 10 | 11 | library UniswapV2Library { 12 | using SafeMath for uint; 13 | 14 | // returns sorted token addresses, used to handle return values from pairs sorted in this order 15 | function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { 16 | require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES'); 17 | (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 18 | require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS'); 19 | } 20 | 21 | // calculates the CREATE2 address for a pair without making any external calls 22 | function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) { 23 | (address token0, address token1) = sortTokens(tokenA, tokenB); 24 | pair = address(uint(keccak256(abi.encodePacked( 25 | hex'ff', 26 | factory, 27 | keccak256(abi.encodePacked(token0, token1)), 28 | hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash 29 | )))); 30 | } 31 | 32 | // fetches and sorts the reserves for a pair 33 | function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) { 34 | (address token0,) = sortTokens(tokenA, tokenB); 35 | (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); 36 | (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); 37 | } 38 | 39 | // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset 40 | function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) { 41 | require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT'); 42 | require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); 43 | amountB = amountA.mul(reserveB) / reserveA; 44 | } 45 | 46 | // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset 47 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) { 48 | require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT'); 49 | require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); 50 | uint amountInWithFee = amountIn.mul(997); 51 | uint numerator = amountInWithFee.mul(reserveOut); 52 | uint denominator = reserveIn.mul(1000).add(amountInWithFee); 53 | amountOut = numerator / denominator; 54 | } 55 | 56 | // given an output amount of an asset and pair reserves, returns a required input amount of the other asset 57 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) { 58 | require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT'); 59 | require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); 60 | uint numerator = reserveIn.mul(amountOut).mul(1000); 61 | uint denominator = reserveOut.sub(amountOut).mul(997); 62 | amountIn = (numerator / denominator).add(1); 63 | } 64 | 65 | // performs chained getAmountOut calculations on any number of pairs 66 | function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) { 67 | require(path.length >= 2, 'UniswapV2Library: INVALID_PATH'); 68 | amounts = new uint[](path.length); 69 | amounts[0] = amountIn; 70 | for (uint i; i < path.length - 1; i++) { 71 | (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]); 72 | amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut); 73 | } 74 | } 75 | 76 | // performs chained getAmountIn calculations on any number of pairs 77 | function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) { 78 | require(path.length >= 2, 'UniswapV2Library: INVALID_PATH'); 79 | amounts = new uint[](path.length); 80 | amounts[amounts.length - 1] = amountOut; 81 | for (uint i = path.length - 1; i > 0; i--) { 82 | (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]); 83 | amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/test/TToken.sol: -------------------------------------------------------------------------------- 1 | // This program is free software: you can redistribute it and/or modify 2 | // it under the terms of the GNU General Public License as published by 3 | // the Free Software Foundation, either version 3 of the License, or 4 | // (at your option) any later version. 5 | 6 | // This program is distributed in the hope that it will be useful, 7 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | // GNU General Public License for more details. 10 | 11 | // You should have received a copy of the GNU General Public License 12 | // along with this program. If not, see . 13 | 14 | // SPDX-License-Identifier: MIT 15 | 16 | pragma solidity ^0.6.6; 17 | 18 | // Test Token 19 | 20 | contract TToken { 21 | 22 | string private _name; 23 | string private _symbol; 24 | uint8 private _decimals; 25 | 26 | address private _owner; 27 | 28 | uint internal _totalSupply; 29 | 30 | mapping(address => uint) private _balance; 31 | mapping(address => mapping(address=>uint)) private _allowance; 32 | 33 | modifier _onlyOwner_() { 34 | require(msg.sender == _owner, "ERR_NOT_OWNER"); 35 | _; 36 | } 37 | 38 | event Approval(address indexed src, address indexed dst, uint amt); 39 | event Transfer(address indexed src, address indexed dst, uint amt); 40 | 41 | // Math 42 | function add(uint a, uint b) internal pure returns (uint c) { 43 | require((c = a + b) >= a); 44 | } 45 | function sub(uint a, uint b) internal pure returns (uint c) { 46 | require((c = a - b) <= a); 47 | } 48 | 49 | constructor( 50 | string memory name, 51 | string memory symbol, 52 | uint8 decimals 53 | ) public { 54 | _name = name; 55 | _symbol = symbol; 56 | _decimals = decimals; 57 | _owner = msg.sender; 58 | } 59 | 60 | function name() public view returns (string memory) { 61 | return _name; 62 | } 63 | 64 | function symbol() public view returns (string memory) { 65 | return _symbol; 66 | } 67 | 68 | function decimals() public view returns(uint8) { 69 | return _decimals; 70 | } 71 | 72 | function _move(address src, address dst, uint amt) internal { 73 | require(_balance[src] >= amt, "ERR_INSUFFICIENT_BAL"); 74 | _balance[src] = sub(_balance[src], amt); 75 | _balance[dst] = add(_balance[dst], amt); 76 | emit Transfer(src, dst, amt); 77 | } 78 | 79 | function _push(address to, uint amt) internal { 80 | _move(address(this), to, amt); 81 | } 82 | 83 | function _pull(address from, uint amt) internal { 84 | _move(from, address(this), amt); 85 | } 86 | 87 | function _mint(address dst, uint amt) internal { 88 | _balance[dst] = add(_balance[dst], amt); 89 | _totalSupply = add(_totalSupply, amt); 90 | emit Transfer(address(0), dst, amt); 91 | } 92 | 93 | function allowance(address src, address dst) external view returns (uint) { 94 | return _allowance[src][dst]; 95 | } 96 | 97 | function balanceOf(address whom) external view returns (uint) { 98 | return _balance[whom]; 99 | } 100 | 101 | function totalSupply() public view returns (uint) { 102 | return _totalSupply; 103 | } 104 | 105 | function approve(address dst, uint amt) external returns (bool) { 106 | _allowance[msg.sender][dst] = amt; 107 | emit Approval(msg.sender, dst, amt); 108 | return true; 109 | } 110 | 111 | function mint(address dst, uint256 amt) public _onlyOwner_ returns (bool) { 112 | _mint(dst, amt); 113 | return true; 114 | } 115 | 116 | // function burn(uint amt) public returns (bool) { 117 | // require(_balance[address(this)] >= amt, "ERR_INSUFFICIENT_BAL"); 118 | // _balance[address(this)] = sub(_balance[address(this)], amt); 119 | // _totalSupply = sub(_totalSupply, amt); 120 | // emit Transfer(address(this), address(0), amt); 121 | // return true; 122 | // } 123 | 124 | function _burn(address src, uint amt) private returns (bool) { 125 | require(_balance[src] >= amt, "ERR_INSUFFICIENT_BAL"); 126 | _balance[src] = sub(_balance[src], amt); 127 | _totalSupply = sub(_totalSupply, amt); 128 | emit Transfer(src, address(0), amt); 129 | return true; 130 | } 131 | 132 | function burn(uint256 amt) public virtual _onlyOwner_ { 133 | _burn(msg.sender, amt); 134 | } 135 | 136 | function transfer(address dst, uint amt) external returns (bool) { 137 | _move(msg.sender, dst, amt); 138 | return true; 139 | } 140 | 141 | function transferFrom(address src, address dst, uint amt) external returns (bool) { 142 | require(msg.sender == src || amt <= _allowance[src][msg.sender], "ERR_BTOKEN_BAD_CALLER"); 143 | _move(src, dst, amt); 144 | if (msg.sender != src && _allowance[src][msg.sender] != uint256(-1)) { 145 | _allowance[src][msg.sender] = sub(_allowance[src][msg.sender], amt); 146 | emit Approval(msg.sender, dst, _allowance[src][msg.sender]); 147 | } 148 | return true; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { config as dotEnvConfig } from "dotenv"; 2 | dotEnvConfig(); 3 | 4 | import { HardhatUserConfig } from "hardhat/types"; 5 | 6 | import "@nomiclabs/hardhat-waffle"; 7 | import "@nomiclabs/hardhat-etherscan"; 8 | import "hardhat-gas-reporter"; 9 | import "hardhat-contract-sizer"; 10 | // TODO: reenable solidity-coverage when it works 11 | // import "solidity-coverage"; 12 | 13 | // Add some .env individual variables 14 | const INFURA_PROJECT_ID = process.env.INFURA_PROJECT_ID; 15 | const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY; 16 | 17 | const MAINNET_PRIVATE_KEY = process.env.MAINNET_PRIVATE_KEY; 18 | const BSC_MAINNET_PRIVATE_KEY = process.env.BSC_MAINNET_PRIVATE_KEY; 19 | const BSC_TESTNET_PRIVATE_KEY = process.env.BSC_TESTNET_PRIVATE_KEY; 20 | const KOVAN_PRIVATE_KEY = process.env.KOVAN_PRIVATE_KEY; 21 | const RINKEBY_PRIVATE_KEY = process.env.RINKEBY_PRIVATE_KEY; 22 | 23 | const config: HardhatUserConfig = { 24 | defaultNetwork: "hardhat", 25 | solidity: { 26 | compilers: [ 27 | { 28 | version: "0.6.6", 29 | settings: { 30 | optimizer: {runs: 1, enabled: true}, 31 | }, 32 | }, 33 | ], 34 | }, 35 | networks: { 36 | hardhat: {}, 37 | localhost: {}, 38 | mainnet: { 39 | url: `https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`, 40 | accounts: MAINNET_PRIVATE_KEY ? [MAINNET_PRIVATE_KEY] : [], 41 | chainId: 1, 42 | }, 43 | kovan: { 44 | url: `https://kovan.infura.io/v3/${INFURA_PROJECT_ID}`, 45 | accounts: KOVAN_PRIVATE_KEY ? [KOVAN_PRIVATE_KEY] : [], 46 | chainId: 42, 47 | }, 48 | rinkeby: { 49 | url: `https://rinkeby.infura.io/v3/${INFURA_PROJECT_ID}`, 50 | accounts: RINKEBY_PRIVATE_KEY ? [RINKEBY_PRIVATE_KEY] : [], 51 | }, 52 | bsc_mainnet: { 53 | url: "https://bsc-dataseed.binance.org/", 54 | accounts: BSC_MAINNET_PRIVATE_KEY ? [BSC_MAINNET_PRIVATE_KEY] : [], 55 | chainId: 56, 56 | }, 57 | bsc_testnet: { 58 | url: "https://data-seed-prebsc-1-s1.binance.org:8545", 59 | accounts: BSC_TESTNET_PRIVATE_KEY ? [BSC_TESTNET_PRIVATE_KEY] : [], 60 | chainId: 97, 61 | }, 62 | coverage: { 63 | url: "http://127.0.0.1:8555", // Coverage launches its own ganache-cli client 64 | }, 65 | }, 66 | mocha: { 67 | timeout: 20000000, 68 | }, 69 | paths: { 70 | sources: "./contracts/", 71 | tests: "./test/", 72 | }, 73 | }; 74 | 75 | export default config; 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "market", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "build": "npm run clean && npm run compile", 7 | "clean": "npx hardhat clean", 8 | "compile": "npx hardhat compile", 9 | "test": "npx hardhat test", 10 | "coverage": "npm run build && npx hardhat coverage --temp artifacts --network coverage" 11 | }, 12 | "license": "MIT", 13 | "dependencies": { 14 | "@ethersproject/abstract-provider": "^5.1.0", 15 | "@ethersproject/constants": "^5.1.0", 16 | "@openzeppelin/contracts": "^3.4.1", 17 | "fs": "*", 18 | "glob": "^7.1.6" 19 | }, 20 | "devDependencies": { 21 | "@nomiclabs/hardhat-ethers": "^2.0.2", 22 | "@nomiclabs/hardhat-etherscan": "^2.1.1", 23 | "@nomiclabs/hardhat-waffle": "^2.0.1", 24 | "@types/chai": "^4.2.15", 25 | "@types/chai-as-promised": "^7.1.3", 26 | "@types/glob": "^7.1.3", 27 | "@types/mocha": "^8.2.2", 28 | "@types/node": "^14.14.36", 29 | "@uniswap/lib": "^4.0.1-alpha", 30 | "@uniswap/v2-core": "^1.0.1", 31 | "@uniswap/v2-periphery": "^1.1.0-beta.0", 32 | "axios": "^0.21.1", 33 | "chai": "^4.3.4", 34 | "chai-as-promised": "^7.1.1", 35 | "dotenv": "^8.2.0", 36 | "ethereum-waffle": "^3.3.0", 37 | "ethers": "^5.1.4", 38 | "ganache-cli": "^6.12.2", 39 | "hardhat": "^2.2.1", 40 | "hardhat-contract-sizer": "^2.0.3", 41 | "hardhat-gas-reporter": "^1.0.4", 42 | "ts-generator": "^0.1.1", 43 | "ts-node": "^9.1.1", 44 | "typescript": "^4.2.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /scripts/0_create_new_wallet.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from "ethers"; 2 | 3 | // This is script is used to create a new wallet and get its address 4 | 5 | function main() { 6 | const wallet: Wallet = Wallet.createRandom(); 7 | console.log(`New wallet private key is ${wallet.privateKey}`); 8 | console.log(`New wallet public address is ${wallet.address}`); 9 | } 10 | 11 | main(); 12 | -------------------------------------------------------------------------------- /scripts/1_deploy_flashswap.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { Contract, ContractFactory } from "ethers"; 3 | 4 | /* 5 | 6 | This script deploys FlashSwap on mainnet or testnet 7 | If you want to use this FlashSwap - deploy it via: npx hardhat run scripts/1_deploy_flashswap.ts --network YOUR NETWORK NAME HERE 8 | 9 | */ 10 | 11 | // It is constants for default networks 12 | const UNISWAP_ROUTER: string = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"; 13 | const UNISWAP_FACTORY: string = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"; 14 | 15 | async function main(): Promise { 16 | const FlashSwap: ContractFactory = await ethers.getContractFactory("FlashSwap"); 17 | const flashSwap: Contract = await FlashSwap.deploy(UNISWAP_FACTORY, UNISWAP_ROUTER); 18 | await flashSwap.deployed(); 19 | console.log("FlashSwap deployed successfully. Address:", flashSwap.address); 20 | } 21 | 22 | main() 23 | .then(() => process.exit(0)) 24 | .catch(error => { 25 | console.error(error); 26 | process.exit(1); 27 | }); 28 | -------------------------------------------------------------------------------- /test/FlashSwap.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { Signer, Contract, ContractFactory, BigNumber } from "ethers"; 3 | import chai from "chai"; 4 | import { solidity } from "ethereum-waffle"; 5 | import { AddressZero } from "@ethersproject/constants"; 6 | import { getContractFactory } from "../app/utils"; 7 | import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 8 | import type { TransactionResponse, TransactionReceipt, Log } from "@ethersproject/abstract-provider"; 9 | import type { TransactionReceiptWithEvents } from "../app/types"; 10 | 11 | chai.use(solidity); 12 | const { expect } = chai; 13 | const { formatEther, parseEther } = ethers.utils; 14 | 15 | // These are constants for default networks 16 | const UNISWAP_ROUTER: string = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"; 17 | const UNISWAP_FACTORY: string = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"; 18 | 19 | describe("Start Testing...(this might take a while)", async () => { 20 | let accounts: SignerWithAddress[]; 21 | let owner: SignerWithAddress; 22 | 23 | // Contract factories 24 | let TToken: ContractFactory; 25 | let FlashSwap: ContractFactory; 26 | let UniswapFactory: ContractFactory; 27 | let UniswapRouter: ContractFactory; 28 | let UniswapPair: ContractFactory; 29 | 30 | // Contracts that will be deployed by contact factories 31 | let weth: Contract; 32 | let dai: Contract; 33 | let usdt: Contract; 34 | let btc: Contract; 35 | let uniswapFactory: Contract; 36 | let uniswapRouter: Contract; 37 | 38 | before(async () => { 39 | accounts = await ethers.getSigners(); 40 | owner = accounts[0]; 41 | 42 | // Create contract factories for the owner of the wallet 43 | FlashSwap = getContractFactory("FlashSwap", owner); 44 | TToken = getContractFactory("TToken", owner); 45 | UniswapFactory = getContractFactory("IUniswapV2Factory", owner); 46 | UniswapRouter = getContractFactory("IUniswapV2Router02", owner); 47 | UniswapPair = getContractFactory("IUniswapV2Pair", owner); 48 | }); 49 | 50 | // Before each test-block we deploy contracts using previously created factories 51 | beforeEach(async () => { 52 | weth = await TToken.deploy("WETH", "WETH", 18); 53 | dai = await TToken.deploy("DAI", "DAI", 18); 54 | usdt = await TToken.deploy("USDT", "USDT", 18); 55 | btc = await TToken.deploy("BTC", "BTC", 18); 56 | 57 | 58 | uniswapRouter = UniswapRouter.attach(UNISWAP_ROUTER); 59 | uniswapFactory = UniswapFactory.attach(UNISWAP_FACTORY); 60 | 61 | // Wait for all tokens to be deployed 62 | await weth.deployed(); 63 | await dai.deployed(); 64 | await usdt.deployed(); 65 | await btc.deployed(); 66 | 67 | // Mint some amount of each token to the owner 68 | await weth.mint(owner.address, parseEther("1000")); 69 | await dai.mint(owner.address, parseEther("4000")); 70 | await usdt.mint(owner.address, parseEther("2500")); 71 | await btc.mint(owner.address, parseEther("2000")); 72 | 73 | // Approve the ability of router to remove some liquidity from the wallet of the one calling this method (owner) 74 | await weth.approve(uniswapRouter.address, parseEther("1000")); 75 | await dai.approve(uniswapRouter.address, parseEther("4000")); 76 | await usdt.approve(uniswapRouter.address, parseEther("2500")); 77 | await btc.approve(uniswapRouter.address, parseEther("2000")); 78 | }); 79 | 80 | it("Initializing FlashSwap...", async () => { 81 | // Deploy fresh contract 82 | const flashSwap: Contract = await FlashSwap.deploy(uniswapFactory.address, uniswapRouter.address); 83 | await flashSwap.deployed(); 84 | 85 | 86 | // Add liquidity to different contracts in different ratios 87 | const addTxLoan: TransactionResponse = await uniswapRouter.addLiquidity(btc.address, usdt.address, parseEther("1000"), parseEther("1000"), 0, 0, owner.address, Date.now() + 60000); // 1/1 88 | await addTxLoan.wait(); 89 | const addTxWeth: TransactionResponse = await uniswapRouter.addLiquidity(dai.address, weth.address, parseEther("1000"), parseEther("1000"), 0, 0, owner.address, Date.now() + 60000); // 1/1 90 | await addTxWeth.wait(); 91 | const addTxUsdt: TransactionResponse = await uniswapRouter.addLiquidity(dai.address, usdt.address, parseEther("1000"), parseEther("1500"), 0, 0, owner.address, Date.now() + 60000); // 2/3 92 | await addTxUsdt.wait(); 93 | const addTxBtc: TransactionResponse = await uniswapRouter.addLiquidity(dai.address, btc.address, parseEther("1000"), parseEther("1000"), 0, 0, owner.address, Date.now() + 60000); // 1/1 94 | await addTxBtc.wait(); 95 | 96 | // Form a path of all addresses 97 | // NOTE that it MUST be looped (dai -.....- dai) 98 | const path: string[] = [dai.address, usdt.address, btc.address, dai.address]; 99 | 100 | // Run main function 101 | console.log(await flashSwap.startFlashLoan(parseEther("0.1"), path, weth.address)); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true, 9 | "lib": [ 10 | "ES2020.Promise" 11 | ] 12 | }, 13 | "include": ["./scripts", "./test", "./app"], 14 | "files": ["./hardhat.config.ts"] 15 | } 16 | --------------------------------------------------------------------------------