├── .env.example ├── .gitignore ├── README.md ├── contracts ├── FlashloanTaker.sol ├── IERC20.sol ├── Migrations.sol └── dydx │ ├── DyDx.sol │ └── DyDxFlashLoan.sol ├── flat.sh ├── migrations ├── 1_initial_migration.js └── 2_deploy_FlashloanTaker.js ├── package-lock.json ├── package.json └── truffle-config.js /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | # in Gwei 3 | GAS_PRICE=20 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | build 4 | FlashloanTaker.flat.sol -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DyDx flashloan tutorial 2 | 3 | Example repo on how to take a flash loan with DyDx from ( https://etherscan.io/address/0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e ) 4 | 5 | ## Install 6 | 1. `npm i` 7 | 8 | ## Deploy 9 | 10 | ## To play in remix 11 | 1. `npm run flat` 12 | 2. Copy content from the `FlashloanTaker.flat.sol` file to the Remix. 13 | 3. Remove the second `pragma experimental ABIEncoderV2;` 14 | 4. Deploy contact with tiny amount of token at the time of deployment 15 | 16 | ## using truffle migrations 17 | 1. `cp .env.example .env` 18 | 2. Specify `PRIVATE_KEY` and `GAS_PRICE` in the `.env` file 19 | 3. `npx truffle migrate --network mainnet --reset` 20 | 4. Send a tiny amount of the token you want to borrow to the smart contract. E.g. if you borrow WETH the `FlashloanTaker` has to have 1 wei (one wrapped wei) on the balance. 21 | 22 | ## Other flashloan providers 23 | 1. [aave](https://github.com/aave/flashloan-box/blob/master/contracts/Flashloan.sol) 24 | 25 | ## Use cases 26 | 1. Refinancing. Refinance your interest to a lower interest in another lending protocol. Taking flash loan from Aave in Dai, closing a CDP by sending the Dai to the CDP, taking the ETH which was as a collateral to Compound (whichever has better interest rate for borrowers) 27 | 2. Closing CDP. The same as above but just closing the CDP without any DAI. 28 | 3. [Arbitrage](https://etherscan.io/tx/0x7b67d25e479c363c3bed6fe03fc15e7279a30f9857771b7b2539612c3dc6cf30). -------------------------------------------------------------------------------- /contracts/FlashloanTaker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./dydx/DyDxFlashLoan.sol"; 5 | import "./IERC20.sol"; 6 | 7 | 8 | contract FlashloanTaker is DyDxFlashLoan { 9 | uint256 public loan; 10 | 11 | constructor() public payable { 12 | (bool success, ) = WETH.call.value(msg.value)(""); 13 | require(success, "fail to get weth"); 14 | } 15 | 16 | function getFlashloan(address flashToken, uint256 flashAmount) external { 17 | uint256 balanceBefore = IERC20(flashToken).balanceOf(address(this)); 18 | bytes memory data = abi.encode(flashToken, flashAmount, balanceBefore); 19 | flashloan(flashToken, flashAmount, data); // execution goes to `callFunction` 20 | 21 | // and this point we have succefully paid the dept 22 | } 23 | 24 | function callFunction( 25 | address, /* sender */ 26 | Info calldata, /* accountInfo */ 27 | bytes calldata data 28 | ) external onlyPool { 29 | (address flashToken, uint256 flashAmount, uint256 balanceBefore) = abi 30 | .decode(data, (address, uint256, uint256)); 31 | uint256 balanceAfter = IERC20(flashToken).balanceOf(address(this)); 32 | require( 33 | balanceAfter - balanceBefore == flashAmount, 34 | "contract did not get the loan" 35 | ); 36 | loan = balanceAfter; 37 | 38 | // do whatever you want with the money 39 | // the dept will be automatically withdrawn from this contract at the end of execution 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 6 | * the optional functions; to access them see `ERC20Detailed`. 7 | */ 8 | interface IERC20 { 9 | function balanceOf(address account) external view returns (uint256); 10 | 11 | function approve(address spender, uint256 amount) external returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.7.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/dydx/DyDx.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | pragma experimental ABIEncoderV2; 3 | 4 | interface Structs { 5 | struct Val { 6 | uint256 value; 7 | } 8 | 9 | enum ActionType { 10 | Deposit, // supply tokens 11 | Withdraw, // borrow tokens 12 | Transfer, // transfer balance between accounts 13 | Buy, // buy an amount of some token (externally) 14 | Sell, // sell an amount of some token (externally) 15 | Trade, // trade tokens against another account 16 | Liquidate, // liquidate an undercollateralized or expiring account 17 | Vaporize, // use excess tokens to zero-out a completely negative account 18 | Call // send arbitrary data to an address 19 | } 20 | 21 | enum AssetDenomination { 22 | Wei // the amount is denominated in wei 23 | } 24 | 25 | enum AssetReference { 26 | Delta // the amount is given as a delta from the current value 27 | } 28 | 29 | struct AssetAmount { 30 | bool sign; // true if positive 31 | AssetDenomination denomination; 32 | AssetReference ref; 33 | uint256 value; 34 | } 35 | 36 | struct ActionArgs { 37 | ActionType actionType; 38 | uint256 accountId; 39 | AssetAmount amount; 40 | uint256 primaryMarketId; 41 | uint256 secondaryMarketId; 42 | address otherAddress; 43 | uint256 otherAccountId; 44 | bytes data; 45 | } 46 | 47 | struct Info { 48 | address owner; // The address that owns the account 49 | uint256 number; // A nonce that allows a single address to control many accounts 50 | } 51 | 52 | struct Wei { 53 | bool sign; // true if positive 54 | uint256 value; 55 | } 56 | } 57 | 58 | contract DyDxPool is Structs { 59 | function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory); 60 | function operate(Info[] memory, ActionArgs[] memory) public; 61 | } -------------------------------------------------------------------------------- /contracts/dydx/DyDxFlashLoan.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./DyDx.sol"; 4 | import "../IERC20.sol"; 5 | 6 | 7 | contract DyDxFlashLoan is Structs { 8 | DyDxPool pool = DyDxPool(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); 9 | 10 | address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 11 | address public SAI = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; 12 | address public USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 13 | address public DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 14 | mapping(address => uint256) public currencies; 15 | 16 | constructor() public { 17 | currencies[WETH] = 1; 18 | currencies[SAI] = 2; 19 | currencies[USDC] = 3; 20 | currencies[DAI] = 4; 21 | } 22 | 23 | modifier onlyPool() { 24 | require( 25 | msg.sender == address(pool), 26 | "FlashLoan: could be called by DyDx pool only" 27 | ); 28 | _; 29 | } 30 | 31 | function tokenToMarketId(address token) public view returns (uint256) { 32 | uint256 marketId = currencies[token]; 33 | require(marketId != 0, "FlashLoan: Unsupported token"); 34 | return marketId - 1; 35 | } 36 | 37 | // the DyDx will call `callFunction(address sender, Info memory accountInfo, bytes memory data) public` after during `operate` call 38 | function flashloan(address token, uint256 amount, bytes memory data) 39 | internal 40 | { 41 | IERC20(token).approve(address(pool), amount + 1); 42 | Info[] memory infos = new Info[](1); 43 | ActionArgs[] memory args = new ActionArgs[](3); 44 | 45 | infos[0] = Info(address(this), 0); 46 | 47 | AssetAmount memory wamt = AssetAmount( 48 | false, 49 | AssetDenomination.Wei, 50 | AssetReference.Delta, 51 | amount 52 | ); 53 | ActionArgs memory withdraw; 54 | withdraw.actionType = ActionType.Withdraw; 55 | withdraw.accountId = 0; 56 | withdraw.amount = wamt; 57 | withdraw.primaryMarketId = tokenToMarketId(token); 58 | withdraw.otherAddress = address(this); 59 | 60 | args[0] = withdraw; 61 | 62 | ActionArgs memory call; 63 | call.actionType = ActionType.Call; 64 | call.accountId = 0; 65 | call.otherAddress = address(this); 66 | call.data = data; 67 | 68 | args[1] = call; 69 | 70 | ActionArgs memory deposit; 71 | AssetAmount memory damt = AssetAmount( 72 | true, 73 | AssetDenomination.Wei, 74 | AssetReference.Delta, 75 | amount + 1 76 | ); 77 | deposit.actionType = ActionType.Deposit; 78 | deposit.accountId = 0; 79 | deposit.amount = damt; 80 | deposit.primaryMarketId = tokenToMarketId(token); 81 | deposit.otherAddress = address(this); 82 | 83 | args[2] = deposit; 84 | 85 | pool.operate(infos, args); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /flat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | node node_modules/truffle-flattener/index.js contracts/FlashloanTaker.sol > FlashloanTaker.flat.sol -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | if(deployer.network === 'mainnet') { 5 | return 6 | } 7 | deployer.deploy(Migrations); 8 | }; 9 | -------------------------------------------------------------------------------- /migrations/2_deploy_FlashloanTaker.js: -------------------------------------------------------------------------------- 1 | const FlashloanTaker = artifacts.require("FlashloanTaker"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(FlashloanTaker, { value: '10' }); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flashloan-tutorial", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "flat": "./flat.sh" 8 | }, 9 | "author": "https://peppersec.com", 10 | "license": "MIT", 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@truffle/hdwallet-provider": "^1.0.35", 14 | "dotenv": "^8.2.0", 15 | "truffle": "^5.1.26", 16 | "truffle-flattener": "^1.4.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const HDWalletProvider = require('@truffle/hdwallet-provider') 3 | const utils = require('web3-utils') 4 | // const infuraKey = "fj4jll3k....."; 5 | // 6 | // const fs = require('fs'); 7 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 8 | 9 | module.exports = { 10 | /** 11 | * Networks define how you connect to your ethereum client and let you set the 12 | * defaults web3 uses to send transactions. If you don't specify one truffle 13 | * will spin up a development blockchain for you on port 9545 when you 14 | * run `develop` or `test`. You can ask a truffle command to use a specific 15 | * network from the command line, e.g 16 | * 17 | * $ truffle test --network 18 | */ 19 | 20 | networks: { 21 | // Useful for testing. The `development` name is special - truffle uses it by default 22 | // if it's defined here and no other network is specified at the command line. 23 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 24 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 25 | // options below to some value. 26 | 27 | development: { 28 | host: '127.0.0.1', // Localhost (default: none) 29 | port: 8545, // Standard Ethereum port (default: none) 30 | network_id: '*', // Any network (default: none) 31 | }, 32 | 33 | // Another network with more advanced options... 34 | // advanced: { 35 | // port: 8777, // Custom port 36 | // network_id: 1342, // Custom network 37 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 38 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 39 | // from:
, // Account to send txs from (default: accounts[0]) 40 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 41 | // }, 42 | 43 | // Useful for deploying to a public network. 44 | // NB: It's important to wrap the provider as a function. 45 | kovan: { 46 | provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'https://kovan.infura.io/v3/da3717f25f824cc1baa32d812386d93f'), 47 | network_id: 42, 48 | gas: 4000000, 49 | gasPrice: utils.toWei(process.env.GAS_PRICE, 'gwei'), 50 | // confirmations: 0, 51 | // timeoutBlocks: 200, 52 | skipDryRun: true 53 | }, 54 | mainnet: { 55 | provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'https://mainnet.infura.io/v3/da3717f25f824cc1baa32d812386d93f'), 56 | network_id: 1, 57 | gas: 4000000, 58 | gasPrice: utils.toWei(process.env.GAS_PRICE || 21, 'gwei'), 59 | nonce: 3230, 60 | // confirmations: 0, 61 | // timeoutBlocks: 200, 62 | skipDryRun: true 63 | }, 64 | 65 | // Useful for private networks 66 | // private: { 67 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 68 | // network_id: 2111, // This network is yours, in the cloud. 69 | // production: true // Treats this network as if it was a public net. (default: false) 70 | // } 71 | }, 72 | 73 | // Set default mocha options here, use special reporters etc. 74 | mocha: { 75 | // timeout: 100000 76 | }, 77 | 78 | // Configure your compilers 79 | compilers: { 80 | solc: { 81 | version: '0.5.17', // Fetch exact version from solc-bin (default: truffle's version) 82 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 83 | settings: { // See the solidity docs for advice about optimization and evmVersion 84 | optimizer: { 85 | enabled: true, 86 | runs: 200 87 | }, 88 | // evmVersion: "byzantium" 89 | } 90 | } 91 | } 92 | } --------------------------------------------------------------------------------