From 8060f6d559710a7229b9e774430ce3e4065b0d4c Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 18 Jan 2021 12:22:23 -0500 Subject: [PATCH 01/15] Add variables to "usage" and "environment variables" Specifically adding ETHEREUM_RPC_URL and BUNDLE_EXECUTOR_ADDRESS to "usage." --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf159878..e39e6b5f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Environment Variables ===================== - ETHEREUM_RPC_URL - Ethereum RPC endpoint. Can not be the same as FLASHBOTS_RPC_URL - PRIVATE_KEY - Private key for the Ethereum EOA that will be submitting Flashbots Ethereum transactions +- BUNDLE_EXECUTOR_ADDRESS - The address for the BundledExecutor.sol that you have deployed (see "Usage" below for details). - FLASHBOTS_KEY_ID / FLASHBOTS_SECRET - Flashbots submissions requires an API key. [Apply for an API key here](https://docs.google.com/forms/d/e/1FAIpQLSd4AKrS-vcfW1X-dQvkFY73HysoKfkhcd-31Tj8frDAU6D6aQ/viewform) - HEALTHCHECK_URL _[Optional]_ - Health check URL, hit only after successfully submitting a bundle. - MINER_REWARD_PERCENTAGE _[Optional, default 80]_ - 0 -> 100, what percentage of overall profitability to send to miner. @@ -22,7 +23,8 @@ _It is important to keep both the bot wallet private key and bundleExecutor owne ``` $ npm install -$ PRIVATE_KEY=__PRIVATE_KEY_FROM_ABOVE__ \ +$ ETHEREUM_RPC_URL=__ETHEREUM_RPC_URL_FROM_ABOVE__ \ +PRIVATE_KEY=__PRIVATE_KEY_FROM_ABOVE__ \ BUNDLE_EXECUTOR_ADDRESS=__DEPLOYED_ADDRESS_FROM_ABOVE__ \ FLASHBOTS_KEY_ID=__YOUR_PERSONAL_KEY_ID__ \ FLASHBOTS_SECRET=__YOUR_PERSONAL_SECRET__ \ From 85fdafb9d9f4b53f2eb2b2d5379f55cc28d8e8db Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 18 Jan 2021 12:24:57 -0500 Subject: [PATCH 02/15] Changing spacing on code to run bot --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e39e6b5f..8bb768e3 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ _It is important to keep both the bot wallet private key and bundleExecutor owne ``` $ npm install $ ETHEREUM_RPC_URL=__ETHEREUM_RPC_URL_FROM_ABOVE__ \ -PRIVATE_KEY=__PRIVATE_KEY_FROM_ABOVE__ \ + PRIVATE_KEY=__PRIVATE_KEY_FROM_ABOVE__ \ BUNDLE_EXECUTOR_ADDRESS=__DEPLOYED_ADDRESS_FROM_ABOVE__ \ FLASHBOTS_KEY_ID=__YOUR_PERSONAL_KEY_ID__ \ FLASHBOTS_SECRET=__YOUR_PERSONAL_SECRET__ \ - npm run start + npm run start ``` From 9890ff2513fb4f3a244c1a2d2ca203fb218e8ef1 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 18 Jan 2021 16:39:30 -0500 Subject: [PATCH 03/15] Adding Aave imports for flashloan --- contracts/aave/FlashLoanReceiverBase.sol | 44 +++++++++++++++++++ contracts/aave/IFlashLoanReceiver.sol | 11 +++++ contracts/aave/ILendingPool.sol | 19 ++++++++ .../aave/ILendingPoolAddressesProvider.sol | 11 +++++ 4 files changed, 85 insertions(+) create mode 100644 contracts/aave/FlashLoanReceiverBase.sol create mode 100644 contracts/aave/IFlashLoanReceiver.sol create mode 100644 contracts/aave/ILendingPool.sol create mode 100644 contracts/aave/ILendingPoolAddressesProvider.sol diff --git a/contracts/aave/FlashLoanReceiverBase.sol b/contracts/aave/FlashLoanReceiverBase.sol new file mode 100644 index 00000000..e177a2dc --- /dev/null +++ b/contracts/aave/FlashLoanReceiverBase.sol @@ -0,0 +1,44 @@ +pragma solidity ^0.6.12; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "./IFlashLoanReceiver.sol"; +import "./ILendingPoolAddressesProvider.sol"; +// import "../common/Withdrawable.sol"; + +abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { + + using SafeERC20 for IERC20; + using SafeMath for uint256; + + address constant ethAddress = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + ILendingPoolAddressesProvider public addressesProvider; + + constructor(address _addressProvider) public { + addressesProvider = ILendingPoolAddressesProvider(_addressProvider); + } + + receive() payable external {} + + function transferFundsBackToPoolInternal(address _reserve, uint256 _amount) internal { + address payable core = addressesProvider.getLendingPoolCore(); + transferInternal(core, _reserve, _amount); + } + + function transferInternal(address payable _destination, address _reserve, uint256 _amount) internal { + if(_reserve == ethAddress) { + (bool success, ) = _destination.call{value: _amount}(""); + require(success == true, "Couldn't transfer ETH"); + return; + } + IERC20(_reserve).safeTransfer(_destination, _amount); + } + + function getBalanceInternal(address _target, address _reserve) internal view returns(uint256) { + if(_reserve == ethAddress) { + return _target.balance; + } + return IERC20(_reserve).balanceOf(_target); + } +} \ No newline at end of file diff --git a/contracts/aave/IFlashLoanReceiver.sol b/contracts/aave/IFlashLoanReceiver.sol new file mode 100644 index 00000000..3bf98c13 --- /dev/null +++ b/contracts/aave/IFlashLoanReceiver.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.6.12; + +/** +* @title IFlashLoanReceiver interface +* @notice Interface for the Aave fee IFlashLoanReceiver. +* @author Aave +* @dev implement this interface to develop a flashloan-compatible flashLoanReceiver contract +**/ +interface IFlashLoanReceiver { + function executeOperation(address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external; +} \ No newline at end of file diff --git a/contracts/aave/ILendingPool.sol b/contracts/aave/ILendingPool.sol new file mode 100644 index 00000000..276eb65a --- /dev/null +++ b/contracts/aave/ILendingPool.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.6.12; + +interface ILendingPool { + function addressesProvider () external view returns ( address ); + function deposit ( address _reserve, uint256 _amount, uint16 _referralCode ) external payable; + function redeemUnderlying ( address _reserve, address _user, uint256 _amount ) external; + function borrow ( address _reserve, uint256 _amount, uint256 _interestRateMode, uint16 _referralCode ) external; + function repay ( address _reserve, uint256 _amount, address _onBehalfOf ) external payable; + function swapBorrowRateMode ( address _reserve ) external; + function rebalanceFixedBorrowRate ( address _reserve, address _user ) external; + function setUserUseReserveAsCollateral ( address _reserve, bool _useAsCollateral ) external; + function liquidationCall ( address _collateral, address _reserve, address _user, uint256 _purchaseAmount, bool _receiveAToken ) external payable; + function flashLoan ( address _receiver, address _reserve, uint256 _amount, bytes calldata _params ) external; + function getReserveConfigurationData ( address _reserve ) external view returns ( uint256 ltv, uint256 liquidationThreshold, uint256 liquidationDiscount, address interestRateStrategyAddress, bool usageAsCollateralEnabled, bool borrowingEnabled, bool fixedBorrowRateEnabled, bool isActive ); + function getReserveData ( address _reserve ) external view returns ( uint256 totalLiquidity, uint256 availableLiquidity, uint256 totalBorrowsFixed, uint256 totalBorrowsVariable, uint256 liquidityRate, uint256 variableBorrowRate, uint256 fixedBorrowRate, uint256 averageFixedBorrowRate, uint256 utilizationRate, uint256 liquidityIndex, uint256 variableBorrowIndex, address aTokenAddress, uint40 lastUpdateTimestamp ); + function getUserAccountData ( address _user ) external view returns ( uint256 totalLiquidityETH, uint256 totalCollateralETH, uint256 totalBorrowsETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor ); + function getUserReserveData ( address _reserve, address _user ) external view returns ( uint256 currentATokenBalance, uint256 currentUnderlyingBalance, uint256 currentBorrowBalance, uint256 principalBorrowBalance, uint256 borrowRateMode, uint256 borrowRate, uint256 liquidityRate, uint256 originationFee, uint256 variableBorrowIndex, uint256 lastUpdateTimestamp, bool usageAsCollateralEnabled ); + function getReserves () external view; +} diff --git a/contracts/aave/ILendingPoolAddressesProvider.sol b/contracts/aave/ILendingPoolAddressesProvider.sol new file mode 100644 index 00000000..fadfa31e --- /dev/null +++ b/contracts/aave/ILendingPoolAddressesProvider.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.6.12; + +/** + @title ILendingPoolAddressesProvider interface + @notice provides the interface to fetch the LendingPoolCore address + */ + +interface ILendingPoolAddressesProvider { + function getLendingPoolCore() external view returns (address payable); + function getLendingPool() external view returns (address); +} \ No newline at end of file From 90d59f74c5d1a2487014b2800fa020e1901285e4 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 18 Jan 2021 16:39:50 -0500 Subject: [PATCH 04/15] Adding flashloan() and executeOperations() --- contracts/BundleExecutor.sol | 37 ++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/contracts/BundleExecutor.sol b/contracts/BundleExecutor.sol index 39954c84..c511649c 100644 --- a/contracts/BundleExecutor.sol +++ b/contracts/BundleExecutor.sol @@ -3,6 +3,11 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; +// Aave flashloan imports +import "../aave/FlashLoanReceiverBase.sol"; +import "../aave/IFlashLoanReceiver.sol"; +import "../aave/ILendingPool.sol"; + interface IERC20 { event Approval(address indexed owner, address indexed spender, uint value); event Transfer(address indexed from, address indexed to, uint value); @@ -26,7 +31,7 @@ interface IWETH is IERC20 { // This contract simply calls multiple targets sequentially, ensuring WETH balance before and after -contract FlashBotsMultiCall { +contract FlashBotsMultiCall is FlashLoanReceiverBase { address private immutable owner; address private immutable executor; IWETH private constant WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); @@ -52,7 +57,35 @@ contract FlashBotsMultiCall { receive() external payable { } - function uniswapWeth(uint256 _wethAmountToFirstMarket, uint256 _ethAmountToCoinbase, address[] memory _targets, bytes[] memory _payloads) external onlyExecutor payable { + function executeOperation( + address _reserve, + uint256 _amount, + uint256 _fee, + bytes calldata _params + ) + external + override + { + require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance, was the flashLoan successful?"); + + // ARBITRAGE BEGINS + + uniswapWethParams(_amount, _params) + + // ARBITRAGE ENDS + + uint totalDebt = _amount.add(_fee); + transferFundsBackToPoolInternal(_reserve, totalDebt); + } + + function flashloan(address borrowedTokenAddress, uint256 amountToBorrow, bytes memory _params) public { + ILendingPool lendingPool = ILendingPool(addressesProvider.getLendingPool()); + lendingPool.flashLoan(address(this), borrowedTokenAddress, amountToBorrow, _params); + } + + function uniswapWethParams(uint256 _wethAmountToFirstMarket, bytes memory _params) external onlyExecutor payable { + (uint256 _ethAmountToCoinbase, address[] memory _targets, bytes[] memory _payloads) = abi.decode(_params, (uint256, address[], bytes[])) + require (_targets.length == _payloads.length); uint256 _wethBalanceBefore = WETH.balanceOf(address(this)); WETH.transfer(_targets[0], _wethAmountToFirstMarket); From 429545cc023e0a158e0e6638f55e3d3035a8a80d Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 18 Jan 2021 16:40:45 -0500 Subject: [PATCH 05/15] Changing transaction execute to use a flashloan --- src/Arbitrage.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Arbitrage.ts b/src/Arbitrage.ts index b06eab31..13ec57b9 100644 --- a/src/Arbitrage.ts +++ b/src/Arbitrage.ts @@ -1,5 +1,5 @@ import * as _ from "lodash"; -import { BigNumber, Contract, Wallet } from "ethers"; +import { BigNumber, Contract, Wallet, utils } from "ethers"; import { FlashbotsBundleProvider } from "@flashbots/ethers-provider-bundle"; import { WETH_ADDRESS } from "./addresses"; import { EthMarket } from "./EthMarket"; @@ -135,7 +135,11 @@ export class Arbitrage { const payloads: Array = [...buyCalls.data, sellCallData] console.log({targets, payloads}) const minerReward = bestCrossedMarket.profit.mul(minerRewardPercentage).div(100); - const transaction = await this.bundleExecutorContract.populateTransaction.uniswapWeth(bestCrossedMarket.volume, minerReward, targets, payloads, { + + const ethersAbiCoder = new ethers.utils.AbiCoder() + const params = ethersAbiCoder.encode(['uint256', 'address[]', 'bytes[]'], [minerReward, targets, payloads]) + + const transaction = await this.bundleExecutorContract.populateTransaction.flashloan(WETH_ADDRESS, bestCrossedMarket.volume, params, { gasPrice: BigNumber.from(0), gasLimit: BigNumber.from(1000000), }); From 4857d85aba518ade02221b5362b6f499c568b910 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Thu, 6 May 2021 08:00:17 -0400 Subject: [PATCH 06/15] Add flashloan file --- contracts/BundleExecutor.sol | 103 +++-- contracts/BundleExecutorNoFlashloan.sol | 81 ++++ contracts/FlashLoanReceiverBase.sol | 18 + contracts/Interfaces.sol | 542 ++++++++++++++++++++++++ contracts/Libraries.sol | 315 ++++++++++++++ 5 files changed, 1025 insertions(+), 34 deletions(-) create mode 100644 contracts/BundleExecutorNoFlashloan.sol create mode 100644 contracts/FlashLoanReceiverBase.sol create mode 100644 contracts/Interfaces.sol create mode 100644 contracts/Libraries.sol diff --git a/contracts/BundleExecutor.sol b/contracts/BundleExecutor.sol index 39954c84..793894f9 100644 --- a/contracts/BundleExecutor.sol +++ b/contracts/BundleExecutor.sol @@ -1,35 +1,22 @@ //SPDX-License-Identifier: UNLICENSED pragma solidity 0.6.12; - pragma experimental ABIEncoderV2; -interface IERC20 { - event Approval(address indexed owner, address indexed spender, uint value); - event Transfer(address indexed from, address indexed to, uint value); - - function name() external view returns (string memory); - function symbol() external view returns (string memory); - function decimals() external view returns (uint8); - function totalSupply() external view returns (uint); - function balanceOf(address owner) external view returns (uint); - function allowance(address owner, address spender) external view returns (uint); - - function approve(address spender, uint value) external returns (bool); - function transfer(address to, uint value) external returns (bool); - function transferFrom(address from, address to, uint value) external returns (bool); -} +import "./FlashLoanReceiverBase.sol"; +import "./Interfaces.sol"; +import "./Libraries.sol"; interface IWETH is IERC20 { function deposit() external payable; function withdraw(uint) external; } -// This contract simply calls multiple targets sequentially, ensuring WETH balance before and after - -contract FlashBotsMultiCall { +contract FlashBotsMultiCallFL is FlashLoanReceiverBase { + using SafeMath for uint256; address private immutable owner; address private immutable executor; - IWETH private constant WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + address public WETH_address = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IWETH private constant WETH = IWETH(WETH_address); modifier onlyExecutor() { require(msg.sender == executor); @@ -41,7 +28,7 @@ contract FlashBotsMultiCall { _; } - constructor(address _executor) public payable { + constructor(address _executor, ILendingPoolAddressesProvider _addressProvider) FlashLoanReceiverBase(_addressProvider) public payable { owner = msg.sender; executor = _executor; if (msg.value > 0) { @@ -49,27 +36,72 @@ contract FlashBotsMultiCall { } } - receive() external payable { + /** + This function is called after your contract has received the flash loaned amount + */ + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) + external + override + returns (bool) + { + uint amountOwing = amounts[0].add(premiums[0]); + uniswapWethFLParams(amounts[0], params, amountOwing); + WETH.approve(address(LENDING_POOL), amountOwing); + + return true; } - function uniswapWeth(uint256 _wethAmountToFirstMarket, uint256 _ethAmountToCoinbase, address[] memory _targets, bytes[] memory _payloads) external onlyExecutor payable { - require (_targets.length == _payloads.length); - uint256 _wethBalanceBefore = WETH.balanceOf(address(this)); - WETH.transfer(_targets[0], _wethAmountToFirstMarket); + function flashloan(uint256 amountToBorrow, bytes memory _params) external onlyExecutor() { + address receiverAddress = address(this); + + address[] memory assets = new address[](1); + assets[0] = WETH_address; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountToBorrow; + + uint256[] memory modes = new uint256[](1); + modes[0] = 0; + + address onBehalfOf = address(this); + uint16 referralCode = 161; + + LENDING_POOL.flashLoan( + receiverAddress, + assets, + amounts, + modes, + onBehalfOf, + _params, + referralCode + ); + } + + function uniswapWethFLParams(uint256 _amountToFirstMarket, bytes memory _params, uint256 totalAaveDebt) internal { + (uint256 _ethAmountToCoinbase, address[] memory _targets, bytes[] memory _payloads) = abi.decode(_params, (uint256, address[], bytes[])); + require(_targets.length == _payloads.length); + + WETH.transfer(_targets[0], _amountToFirstMarket); for (uint256 i = 0; i < _targets.length; i++) { (bool _success, bytes memory _response) = _targets[i].call(_payloads[i]); - require(_success); _response; + require(_success); } uint256 _wethBalanceAfter = WETH.balanceOf(address(this)); - require(_wethBalanceAfter > _wethBalanceBefore + _ethAmountToCoinbase); - if (_ethAmountToCoinbase == 0) return; - uint256 _ethBalance = address(this).balance; - if (_ethBalance < _ethAmountToCoinbase) { - WETH.withdraw(_ethAmountToCoinbase - _ethBalance); - } + uint256 _profit = _wethBalanceAfter - totalAaveDebt - _ethAmountToCoinbase; + + require(_profit > 0); + + WETH.withdraw(_ethAmountToCoinbase + _profit); block.coinbase.transfer(_ethAmountToCoinbase); + msg.sender.transfer(_profit); } function call(address payable _to, uint256 _value, bytes calldata _data) external onlyOwner payable returns (bytes memory) { @@ -78,4 +110,7 @@ contract FlashBotsMultiCall { require(_success); return _result; } -} + + receive() external payable { + } +} \ No newline at end of file diff --git a/contracts/BundleExecutorNoFlashloan.sol b/contracts/BundleExecutorNoFlashloan.sol new file mode 100644 index 00000000..39954c84 --- /dev/null +++ b/contracts/BundleExecutorNoFlashloan.sol @@ -0,0 +1,81 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.6.12; + +pragma experimental ABIEncoderV2; + +interface IERC20 { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); +} + +interface IWETH is IERC20 { + function deposit() external payable; + function withdraw(uint) external; +} + +// This contract simply calls multiple targets sequentially, ensuring WETH balance before and after + +contract FlashBotsMultiCall { + address private immutable owner; + address private immutable executor; + IWETH private constant WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + + modifier onlyExecutor() { + require(msg.sender == executor); + _; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + constructor(address _executor) public payable { + owner = msg.sender; + executor = _executor; + if (msg.value > 0) { + WETH.deposit{value: msg.value}(); + } + } + + receive() external payable { + } + + function uniswapWeth(uint256 _wethAmountToFirstMarket, uint256 _ethAmountToCoinbase, address[] memory _targets, bytes[] memory _payloads) external onlyExecutor payable { + require (_targets.length == _payloads.length); + uint256 _wethBalanceBefore = WETH.balanceOf(address(this)); + WETH.transfer(_targets[0], _wethAmountToFirstMarket); + for (uint256 i = 0; i < _targets.length; i++) { + (bool _success, bytes memory _response) = _targets[i].call(_payloads[i]); + require(_success); _response; + } + + uint256 _wethBalanceAfter = WETH.balanceOf(address(this)); + require(_wethBalanceAfter > _wethBalanceBefore + _ethAmountToCoinbase); + if (_ethAmountToCoinbase == 0) return; + + uint256 _ethBalance = address(this).balance; + if (_ethBalance < _ethAmountToCoinbase) { + WETH.withdraw(_ethAmountToCoinbase - _ethBalance); + } + block.coinbase.transfer(_ethAmountToCoinbase); + } + + function call(address payable _to, uint256 _value, bytes calldata _data) external onlyOwner payable returns (bytes memory) { + require(_to != address(0)); + (bool _success, bytes memory _result) = _to.call{value: _value}(_data); + require(_success); + return _result; + } +} diff --git a/contracts/FlashLoanReceiverBase.sol b/contracts/FlashLoanReceiverBase.sol new file mode 100644 index 00000000..85cd445e --- /dev/null +++ b/contracts/FlashLoanReceiverBase.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import "./Interfaces.sol"; +import "./Libraries.sol"; + +abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { + using SafeERC20 for IERC20; + using SafeMath for uint256; + + ILendingPoolAddressesProvider public immutable ADDRESSES_PROVIDER; + ILendingPool public immutable LENDING_POOL; + + constructor(ILendingPoolAddressesProvider provider) public { + ADDRESSES_PROVIDER = provider; + LENDING_POOL = ILendingPool(provider.getLendingPool()); + } +} \ No newline at end of file diff --git a/contracts/Interfaces.sol b/contracts/Interfaces.sol new file mode 100644 index 00000000..b4298cf8 --- /dev/null +++ b/contracts/Interfaces.sol @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import "./Libraries.sol"; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +interface IFlashLoanReceiver { + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) external returns (bool); +} + +/** + * @title LendingPoolAddressesProvider contract + * @dev Main registry of addresses part of or connected to the protocol, including permissioned roles + * - Acting also as factory of proxies and admin of those, so with right to change its implementations + * - Owned by the Aave Governance + * @author Aave + **/ +interface ILendingPoolAddressesProvider { + event LendingPoolUpdated(address indexed newAddress); + event ConfigurationAdminUpdated(address indexed newAddress); + event EmergencyAdminUpdated(address indexed newAddress); + event LendingPoolConfiguratorUpdated(address indexed newAddress); + event LendingPoolCollateralManagerUpdated(address indexed newAddress); + event PriceOracleUpdated(address indexed newAddress); + event LendingRateOracleUpdated(address indexed newAddress); + event ProxyCreated(bytes32 id, address indexed newAddress); + event AddressSet(bytes32 id, address indexed newAddress, bool hasProxy); + + function setAddress(bytes32 id, address newAddress) external; + + function setAddressAsProxy(bytes32 id, address impl) external; + + function getAddress(bytes32 id) external view returns (address); + + function getLendingPool() external view returns (address); + + function setLendingPoolImpl(address pool) external; + + function getLendingPoolConfigurator() external view returns (address); + + function setLendingPoolConfiguratorImpl(address configurator) external; + + function getLendingPoolCollateralManager() external view returns (address); + + function setLendingPoolCollateralManager(address manager) external; + + function getPoolAdmin() external view returns (address); + + function setPoolAdmin(address admin) external; + + function getEmergencyAdmin() external view returns (address); + + function setEmergencyAdmin(address admin) external; + + function getPriceOracle() external view returns (address); + + function setPriceOracle(address priceOracle) external; + + function getLendingRateOracle() external view returns (address); + + function setLendingRateOracle(address lendingRateOracle) external; +} + +interface ILendingPool { + /** + * @dev Emitted on deposit() + * @param reserve The address of the underlying asset of the reserve + * @param user The address initiating the deposit + * @param onBehalfOf The beneficiary of the deposit, receiving the aTokens + * @param amount The amount deposited + * @param referral The referral code used + **/ + event Deposit( + address indexed reserve, + address user, + address indexed onBehalfOf, + uint256 amount, + uint16 indexed referral + ); + + /** + * @dev Emitted on withdraw() + * @param reserve The address of the underlyng asset being withdrawn + * @param user The address initiating the withdrawal, owner of aTokens + * @param to Address that will receive the underlying + * @param amount The amount to be withdrawn + **/ + event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount); + + /** + * @dev Emitted on borrow() and flashLoan() when debt needs to be opened + * @param reserve The address of the underlying asset being borrowed + * @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just + * initiator of the transaction on flashLoan() + * @param onBehalfOf The address that will be getting the debt + * @param amount The amount borrowed out + * @param borrowRateMode The rate mode: 1 for Stable, 2 for Variable + * @param borrowRate The numeric rate at which the user has borrowed + * @param referral The referral code used + **/ + event Borrow( + address indexed reserve, + address user, + address indexed onBehalfOf, + uint256 amount, + uint256 borrowRateMode, + uint256 borrowRate, + uint16 indexed referral + ); + + /** + * @dev Emitted on repay() + * @param reserve The address of the underlying asset of the reserve + * @param user The beneficiary of the repayment, getting his debt reduced + * @param repayer The address of the user initiating the repay(), providing the funds + * @param amount The amount repaid + **/ + event Repay( + address indexed reserve, + address indexed user, + address indexed repayer, + uint256 amount + ); + + /** + * @dev Emitted on swapBorrowRateMode() + * @param reserve The address of the underlying asset of the reserve + * @param user The address of the user swapping his rate mode + * @param rateMode The rate mode that the user wants to swap to + **/ + event Swap(address indexed reserve, address indexed user, uint256 rateMode); + + /** + * @dev Emitted on setUserUseReserveAsCollateral() + * @param reserve The address of the underlying asset of the reserve + * @param user The address of the user enabling the usage as collateral + **/ + event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user); + + /** + * @dev Emitted on setUserUseReserveAsCollateral() + * @param reserve The address of the underlying asset of the reserve + * @param user The address of the user enabling the usage as collateral + **/ + event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user); + + /** + * @dev Emitted on rebalanceStableBorrowRate() + * @param reserve The address of the underlying asset of the reserve + * @param user The address of the user for which the rebalance has been executed + **/ + event RebalanceStableBorrowRate(address indexed reserve, address indexed user); + + /** + * @dev Emitted on flashLoan() + * @param target The address of the flash loan receiver contract + * @param initiator The address initiating the flash loan + * @param asset The address of the asset being flash borrowed + * @param amount The amount flash borrowed + * @param premium The fee flash borrowed + * @param referralCode The referral code used + **/ + event FlashLoan( + address indexed target, + address indexed initiator, + address indexed asset, + uint256 amount, + uint256 premium, + uint16 referralCode + ); + + /** + * @dev Emitted when the pause is triggered. + */ + event Paused(); + + /** + * @dev Emitted when the pause is lifted. + */ + event Unpaused(); + + /** + * @dev Emitted when a borrower is liquidated. This event is emitted by the LendingPool via + * LendingPoolCollateral manager using a DELEGATECALL + * This allows to have the events in the generated ABI for LendingPool. + * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation + * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation + * @param user The address of the borrower getting liquidated + * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover + * @param liquidatedCollateralAmount The amount of collateral received by the liiquidator + * @param liquidator The address of the liquidator + * @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants + * to receive the underlying collateral asset directly + **/ + event LiquidationCall( + address indexed collateralAsset, + address indexed debtAsset, + address indexed user, + uint256 debtToCover, + uint256 liquidatedCollateralAmount, + address liquidator, + bool receiveAToken + ); + + /** + * @dev Emitted when the state of a reserve is updated. NOTE: This event is actually declared + * in the ReserveLogic library and emitted in the updateInterestRates() function. Since the function is internal, + * the event will actually be fired by the LendingPool contract. The event is therefore replicated here so it + * gets added to the LendingPool ABI + * @param reserve The address of the underlying asset of the reserve + * @param liquidityRate The new liquidity rate + * @param stableBorrowRate The new stable borrow rate + * @param variableBorrowRate The new variable borrow rate + * @param liquidityIndex The new liquidity index + * @param variableBorrowIndex The new variable borrow index + **/ + event ReserveDataUpdated( + address indexed reserve, + uint256 liquidityRate, + uint256 stableBorrowRate, + uint256 variableBorrowRate, + uint256 liquidityIndex, + uint256 variableBorrowIndex + ); + + /** + * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. + * - E.g. User deposits 100 USDC and gets in return 100 aUSDC + * @param asset The address of the underlying asset to deposit + * @param amount The amount to be deposited + * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user + * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens + * is a different wallet + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + **/ + function deposit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) external; + + /** + * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned + * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC + * @param asset The address of the underlying asset to withdraw + * @param amount The underlying amount to be withdrawn + * - Send the value type(uint256).max in order to withdraw the whole aToken balance + * @param to Address that will receive the underlying, same as msg.sender if the user + * wants to receive it on his own wallet, or a different address if the beneficiary is a + * different wallet + **/ + function withdraw( + address asset, + uint256 amount, + address to + ) external; + + /** + * @dev Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower + * already deposited enough collateral, or he was given enough allowance by a credit delegator on the + * corresponding debt token (StableDebtToken or VariableDebtToken) + * - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet + * and 100 stable/variable debt tokens, depending on the `interestRateMode` + * @param asset The address of the underlying asset to borrow + * @param amount The amount to be borrowed + * @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + * @param onBehalfOf Address of the user who will receive the debt. Should be the address of the borrower itself + * calling the function if he wants to borrow against his own collateral, or the address of the credit delegator + * if he has been given credit delegation allowance + **/ + function borrow( + address asset, + uint256 amount, + uint256 interestRateMode, + uint16 referralCode, + address onBehalfOf + ) external; + + /** + * @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned + * - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of the `onBehalfOf` address + * @param asset The address of the borrowed underlying asset previously borrowed + * @param amount The amount to repay + * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode` + * @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable + * @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the + * user calling the function if he wants to reduce/remove his own debt, or the address of any other + * other borrower whose debt should be removed + **/ + function repay( + address asset, + uint256 amount, + uint256 rateMode, + address onBehalfOf + ) external; + + /** + * @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa + * @param asset The address of the underlying asset borrowed + * @param rateMode The rate mode that the user wants to swap to + **/ + function swapBorrowRateMode(address asset, uint256 rateMode) external; + + /** + * @dev Rebalances the stable interest rate of a user to the current stable rate defined on the reserve. + * - Users can be rebalanced if the following conditions are satisfied: + * 1. Usage ratio is above 95% + * 2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been + * borrowed at a stable rate and depositors are not earning enough + * @param asset The address of the underlying asset borrowed + * @param user The address of the user to be rebalanced + **/ + function rebalanceStableBorrowRate(address asset, address user) external; + + /** + * @dev Allows depositors to enable/disable a specific deposited asset as collateral + * @param asset The address of the underlying asset deposited + * @param useAsCollateral `true` if the user wants to use the deposit as collateral, `false` otherwise + **/ + function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external; + + /** + * @dev Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1 + * - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives + * a proportionally amount of the `collateralAsset` plus a bonus to cover market risk + * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation + * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation + * @param user The address of the borrower getting liquidated + * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover + * @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants + * to receive the underlying collateral asset directly + **/ + function liquidationCall( + address collateralAsset, + address debtAsset, + address user, + uint256 debtToCover, + bool receiveAToken + ) external; + + /** + * @dev Allows smartcontracts to access the liquidity of the pool within one transaction, + * as long as the amount taken plus a fee is returned. + * IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept into consideration. + * For further details please visit https://developers.aave.com + * @param receiverAddress The address of the contract receiving the funds, implementing the IFlashLoanReceiver interface + * @param assets The addresses of the assets being flash-borrowed + * @param amounts The amounts amounts being flash-borrowed + * @param modes Types of the debt to open if the flash loan is not returned: + * 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver + * 1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address + * 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address + * @param onBehalfOf The address that will receive the debt in the case of using on `modes` 1 or 2 + * @param params Variadic packed params to pass to the receiver as extra information + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + **/ + function flashLoan( + address receiverAddress, + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata modes, + address onBehalfOf, + bytes calldata params, + uint16 referralCode + ) external; + + /** + * @dev Returns the user account data across all the reserves + * @param user The address of the user + * @return totalCollateralETH the total collateral in ETH of the user + * @return totalDebtETH the total debt in ETH of the user + * @return availableBorrowsETH the borrowing power left of the user + * @return currentLiquidationThreshold the liquidation threshold of the user + * @return ltv the loan to value of the user + * @return healthFactor the current health factor of the user + **/ + function getUserAccountData(address user) + external + view + returns ( + uint256 totalCollateralETH, + uint256 totalDebtETH, + uint256 availableBorrowsETH, + uint256 currentLiquidationThreshold, + uint256 ltv, + uint256 healthFactor + ); + + function initReserve( + address reserve, + address aTokenAddress, + address stableDebtAddress, + address variableDebtAddress, + address interestRateStrategyAddress + ) external; + + function setReserveInterestRateStrategyAddress(address reserve, address rateStrategyAddress) + external; + + function setConfiguration(address reserve, uint256 configuration) external; + + /** + * @dev Returns the configuration of the reserve + * @param asset The address of the underlying asset of the reserve + * @return The configuration of the reserve + **/ + function getConfiguration(address asset) external view returns (DataTypes.ReserveConfigurationMap memory); + + /** + * @dev Returns the configuration of the user across all the reserves + * @param user The user address + * @return The configuration of the user + **/ + function getUserConfiguration(address user) external view returns (DataTypes.UserConfigurationMap memory); + + /** + * @dev Returns the normalized income normalized income of the reserve + * @param asset The address of the underlying asset of the reserve + * @return The reserve's normalized income + */ + function getReserveNormalizedIncome(address asset) external view returns (uint256); + + /** + * @dev Returns the normalized variable debt per unit of asset + * @param asset The address of the underlying asset of the reserve + * @return The reserve normalized variable debt + */ + function getReserveNormalizedVariableDebt(address asset) external view returns (uint256); + + /** + * @dev Returns the state and configuration of the reserve + * @param asset The address of the underlying asset of the reserve + * @return The state of the reserve + **/ + function getReserveData(address asset) external view returns (DataTypes.ReserveData memory); + + function finalizeTransfer( + address asset, + address from, + address to, + uint256 amount, + uint256 balanceFromAfter, + uint256 balanceToBefore + ) external; + + function getReservesList() external view returns (address[] memory); + + function getAddressesProvider() external view returns (ILendingPoolAddressesProvider); + + function setPause(bool val) external; + + function paused() external view returns (bool); +} \ No newline at end of file diff --git a/contracts/Libraries.sol b/contracts/Libraries.sol new file mode 100644 index 00000000..b26dc46f --- /dev/null +++ b/contracts/Libraries.sol @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import "./Interfaces.sol"; + +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, 'SafeMath: addition overflow'); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, 'SafeMath: subtraction overflow'); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, 'SafeMath: multiplication overflow'); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, 'SafeMath: division by zero'); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, 'SafeMath: modulo by zero'); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { + codehash := extcodehash(account) + } + return (codehash != accountHash && codehash != 0x0); + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, 'Address: insufficient balance'); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{value: amount}(''); + require(success, 'Address: unable to send value, recipient may have reverted'); + } +} + + + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + 'SafeERC20: approve from non-zero to non-zero allowance' + ); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function callOptionalReturn(IERC20 token, bytes memory data) private { + require(address(token).isContract(), 'SafeERC20: call to non-contract'); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, 'SafeERC20: low-level call failed'); + + if (returndata.length > 0) { + // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), 'SafeERC20: ERC20 operation did not succeed'); + } + } +} + +library DataTypes { + // refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties. + struct ReserveData { + //stores the reserve configuration + ReserveConfigurationMap configuration; + //the liquidity index. Expressed in ray + uint128 liquidityIndex; + //variable borrow index. Expressed in ray + uint128 variableBorrowIndex; + //the current supply rate. Expressed in ray + uint128 currentLiquidityRate; + //the current variable borrow rate. Expressed in ray + uint128 currentVariableBorrowRate; + //the current stable borrow rate. Expressed in ray + uint128 currentStableBorrowRate; + uint40 lastUpdateTimestamp; + //tokens addresses + address aTokenAddress; + address stableDebtTokenAddress; + address variableDebtTokenAddress; + //address of the interest rate strategy + address interestRateStrategyAddress; + //the id of the reserve. Represents the position in the list of the active reserves + uint8 id; + } + + struct ReserveConfigurationMap { + //bit 0-15: LTV + //bit 16-31: Liq. threshold + //bit 32-47: Liq. bonus + //bit 48-55: Decimals + //bit 56: Reserve is active + //bit 57: reserve is frozen + //bit 58: borrowing is enabled + //bit 59: stable rate borrowing enabled + //bit 60-63: reserved + //bit 64-79: reserve factor + uint256 data; + } + + struct UserConfigurationMap { + uint256 data; + } + + enum InterestRateMode {NONE, STABLE, VARIABLE} +} From 7b6e1676912506c86eedb7668824d9a470433dae Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Fri, 14 May 2021 19:30:55 -0400 Subject: [PATCH 07/15] Small updates to the bundleExecutor --- contracts/BundleExecutor.sol | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/contracts/BundleExecutor.sol b/contracts/BundleExecutor.sol index 793894f9..f6a43382 100644 --- a/contracts/BundleExecutor.sol +++ b/contracts/BundleExecutor.sol @@ -28,12 +28,8 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { _; } - constructor(address _executor, ILendingPoolAddressesProvider _addressProvider) FlashLoanReceiverBase(_addressProvider) public payable { + constructor(ILendingPoolAddressesProvider _addressProvider) FlashLoanReceiverBase(_addressProvider) public payable { owner = msg.sender; - executor = _executor; - if (msg.value > 0) { - WETH.deposit{value: msg.value}(); - } } /** @@ -57,7 +53,7 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { return true; } - function flashloan(uint256 amountToBorrow, bytes memory _params) external onlyExecutor() { + function flashloan(uint256 amountToBorrow, bytes memory _params) external { address receiverAddress = address(this); address[] memory assets = new address[](1); @@ -97,7 +93,7 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { uint256 _profit = _wethBalanceAfter - totalAaveDebt - _ethAmountToCoinbase; - require(_profit > 0); + require(_profit >= 0); WETH.withdraw(_ethAmountToCoinbase + _profit); block.coinbase.transfer(_ethAmountToCoinbase); From 061590ebe6dfaa32879597dfac0cb198e74b50bd Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Fri, 14 May 2021 19:32:01 -0400 Subject: [PATCH 08/15] Up volumes and min profit, encode params and call flashloan() --- src/Arbitrage.ts | 138 ++++++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 50 deletions(-) diff --git a/src/Arbitrage.ts b/src/Arbitrage.ts index 9fe83639..35817e8b 100644 --- a/src/Arbitrage.ts +++ b/src/Arbitrage.ts @@ -1,5 +1,5 @@ import * as _ from "lodash"; -import { BigNumber, Contract, Wallet } from "ethers"; +import { BigNumber, Contract, Wallet, utils } from "ethers"; import { FlashbotsBundleProvider } from "@flashbots/ethers-provider-bundle"; import { WETH_ADDRESS } from "./addresses"; import { EthMarket } from "./EthMarket"; @@ -17,17 +17,33 @@ export type MarketsByToken = { [tokenAddress: string]: Array } // TODO: implement binary search (assuming linear/exponential global maximum profitability) const TEST_VOLUMES = [ - ETHER.div(100), - ETHER.div(10), - ETHER.div(6), - ETHER.div(4), - ETHER.div(2), - ETHER.div(1), - ETHER.mul(2), + ETHER.mul(1), ETHER.mul(5), ETHER.mul(10), + ETHER.mul(15), + ETHER.mul(20), + ETHER.mul(25), + ETHER.mul(30), + ETHER.mul(35), + ETHER.mul(40), + ETHER.mul(45), + ETHER.mul(50), + ETHER.mul(75), + ETHER.mul(100), + ETHER.mul(150), + ETHER.mul(200), + ETHER.mul(250), + ETHER.mul(300), + ETHER.mul(350), + ETHER.mul(400), + ETHER.mul(450), + ETHER.mul(500), + ETHER.mul(750), + ETHER.mul(1000) ] +const flashloanFeePercentage = 9 // (0.09%) or 9/10000 + export function getBestCrossedMarket(crossedMarkets: Array[], tokenAddress: string): CrossedMarketDetails | undefined { let bestCrossedMarket: CrossedMarketDetails | undefined = undefined; for (const crossedMarket of crossedMarkets) { @@ -114,7 +130,7 @@ export class Arbitrage { } const bestCrossedMarket = getBestCrossedMarket(crossedMarkets, tokenAddress); - if (bestCrossedMarket !== undefined && bestCrossedMarket.profit.gt(ETHER.div(1000))) { + if (bestCrossedMarket !== undefined && bestCrossedMarket.profit.gt(ETHER.div(50))) { bestCrossedMarkets.push(bestCrossedMarket) } } @@ -126,7 +142,6 @@ export class Arbitrage { async takeCrossedMarkets(bestCrossedMarkets: CrossedMarketDetails[], blockNumber: number, minerRewardPercentage: number): Promise { for (const bestCrossedMarket of bestCrossedMarkets) { - console.log("Send this much WETH", bestCrossedMarket.volume.toString(), "get this much profit", bestCrossedMarket.profit.toString()) const buyCalls = await bestCrossedMarket.buyFromMarket.sellTokensToNextMarket(WETH_ADDRESS, bestCrossedMarket.volume, bestCrossedMarket.sellToMarket); const inter = bestCrossedMarket.buyFromMarket.getTokensOut(WETH_ADDRESS, bestCrossedMarket.tokenAddress, bestCrossedMarket.volume) const sellCallData = await bestCrossedMarket.sellToMarket.sellTokens(bestCrossedMarket.tokenAddress, inter, this.bundleExecutorContract.address); @@ -134,49 +149,72 @@ export class Arbitrage { const targets: Array = [...buyCalls.targets, bestCrossedMarket.sellToMarket.marketAddress] const payloads: Array = [...buyCalls.data, sellCallData] console.log({targets, payloads}) - const minerReward = bestCrossedMarket.profit.mul(minerRewardPercentage).div(100); - const transaction = await this.bundleExecutorContract.populateTransaction.uniswapWeth(bestCrossedMarket.volume, minerReward, targets, payloads, { - gasPrice: BigNumber.from(0), - gasLimit: BigNumber.from(1000000), - }); + const flashloanFee = bestCrossedMarket.volume.mul(flashloanFeePercentage).div(1000); + + + if (flashloanFee.lt(bestCrossedMarket.profit)){ + const profitMinusFee = bestCrossedMarket.profit.sub(flashloanFee); + const minerReward = profitMinusFee.mul(minerRewardPercentage).div(100); + if (minerReward.lt(profitMinusFee)){ + const profitMinusFeeMinusMinerReward = profitMinusFee.sub(minerReward) + console.log("Send this much WETH", bestCrossedMarket.volume.toString(), "get this much profit after fees", profitMinusFeeMinusMinerReward.toString()) + + const ethersAbiCoder = new utils.AbiCoder() + const flashloanParametersTypes = ['uint256', 'address[]', 'bytes[]'] + const flashloanParamtersInputs = [minerReward.toString(), targets, payloads] + const encodedParameters = ethersAbiCoder.encode(flashloanParametersTypes, flashloanParamtersInputs) + + const transaction = await this.bundleExecutorContract.populateTransaction.flashloan(bestCrossedMarket.volume, encodedParameters, { + gasPrice: BigNumber.from(0), + gasLimit: BigNumber.from(1000000), + }); + + try { + const estimateGas = await this.bundleExecutorContract.provider.estimateGas( + { + ...transaction, + from: this.executorWallet.address + }) + if (estimateGas.gt(1400000)) { + console.log("EstimateGas succeeded, but suspiciously large: " + estimateGas.toString()) + continue + } + transaction.gasLimit = estimateGas.mul(2) + } catch (e) { + console.warn(`Estimate gas failure for ${JSON.stringify(bestCrossedMarket)}`) + continue + } - try { - const estimateGas = await this.bundleExecutorContract.provider.estimateGas( - { - ...transaction, - from: this.executorWallet.address - }) - if (estimateGas.gt(1400000)) { - console.log("EstimateGas succeeded, but suspiciously large: " + estimateGas.toString()) - continue - } - transaction.gasLimit = estimateGas.mul(2) - } catch (e) { - console.warn(`Estimate gas failure for ${JSON.stringify(bestCrossedMarket)}`) - continue - } - const bundledTransactions = [ - { - signer: this.executorWallet, - transaction: transaction + const bundledTransactions = [ + { + signer: this.executorWallet, + transaction: transaction + } + ]; + console.log(bundledTransactions) + const signedBundle = await this.flashbotsProvider.signBundle(bundledTransactions) + // + const simulation = await this.flashbotsProvider.simulate(signedBundle, blockNumber + 1 ) + if ("error" in simulation || simulation.firstRevert !== undefined) { + console.log(`Simulation Error on token ${bestCrossedMarket.tokenAddress}, skipping`) + continue + } + console.log(`Submitting bundle, profit sent to miner: ${bigNumberToDecimal(simulation.coinbaseDiff)}, effective gas price: ${bigNumberToDecimal(simulation.coinbaseDiff.div(simulation.totalGasUsed), 9)} GWEI`) + const bundlePromises = _.map([blockNumber + 1, blockNumber + 2], targetBlockNumber => + this.flashbotsProvider.sendRawBundle( + signedBundle, + targetBlockNumber + )) + await Promise.all(bundlePromises) + return + + } else { + throw new Error("No arbitrage submitted to relay") } - ]; - console.log(bundledTransactions) - const signedBundle = await this.flashbotsProvider.signBundle(bundledTransactions) - // - const simulation = await this.flashbotsProvider.simulate(signedBundle, blockNumber + 1 ) - if ("error" in simulation || simulation.firstRevert !== undefined) { - console.log(`Simulation Error on token ${bestCrossedMarket.tokenAddress}, skipping`) - continue + + } else { + throw new Error("No arbitrage submitted to relay") } - console.log(`Submitting bundle, profit sent to miner: ${bigNumberToDecimal(simulation.coinbaseDiff)}, effective gas price: ${bigNumberToDecimal(simulation.coinbaseDiff.div(simulation.totalGasUsed), 9)} GWEI`) - const bundlePromises = _.map([blockNumber + 1, blockNumber + 2], targetBlockNumber => - this.flashbotsProvider.sendRawBundle( - signedBundle, - targetBlockNumber - )) - await Promise.all(bundlePromises) - return } throw new Error("No arbitrage submitted to relay") } From ccefbaea3c737ca99b50f8daca86b0eef21e244c Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 7 Jun 2021 12:47:02 -0400 Subject: [PATCH 09/15] Update variable naming for clarity, use tx.origin --- contracts/BundleExecutor.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/BundleExecutor.sol b/contracts/BundleExecutor.sol index f6a43382..ae446e88 100644 --- a/contracts/BundleExecutor.sol +++ b/contracts/BundleExecutor.sol @@ -46,9 +46,9 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { override returns (bool) { - uint amountOwing = amounts[0].add(premiums[0]); - uniswapWethFLParams(amounts[0], params, amountOwing); - WETH.approve(address(LENDING_POOL), amountOwing); + uint aaveDebt = amounts[0].add(premiums[0]); + uniswapWethFLParams(amounts[0], params, aaveDebt); + WETH.approve(address(LENDING_POOL), aaveDebt); return true; } @@ -79,7 +79,7 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { ); } - function uniswapWethFLParams(uint256 _amountToFirstMarket, bytes memory _params, uint256 totalAaveDebt) internal { + function uniswapWethFLParams(uint256 _amountToFirstMarket, bytes memory _params, uint256 aaveDebt) internal { (uint256 _ethAmountToCoinbase, address[] memory _targets, bytes[] memory _payloads) = abi.decode(_params, (uint256, address[], bytes[])); require(_targets.length == _payloads.length); @@ -91,13 +91,13 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { uint256 _wethBalanceAfter = WETH.balanceOf(address(this)); - uint256 _profit = _wethBalanceAfter - totalAaveDebt - _ethAmountToCoinbase; + uint256 _profit = _wethBalanceAfter - aaveDebt - _ethAmountToCoinbase; require(_profit >= 0); WETH.withdraw(_ethAmountToCoinbase + _profit); block.coinbase.transfer(_ethAmountToCoinbase); - msg.sender.transfer(_profit); + tx.origin.transfer(_profit); } function call(address payable _to, uint256 _value, bytes calldata _data) external onlyOwner payable returns (bytes memory) { From 57457cc46218d863e914f6cdcedd1dbb2cc99c65 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 7 Jun 2021 12:47:19 -0400 Subject: [PATCH 10/15] Update bundle executor ABI --- src/abi.ts | 175 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 142 insertions(+), 33 deletions(-) diff --git a/src/abi.ts b/src/abi.ts index 02c281a8..4fb402e1 100644 --- a/src/abi.ts +++ b/src/abi.ts @@ -20,39 +20,148 @@ export const UNISWAP_QUERY_ABI = [{ "type": "function" }] -export const BUNDLE_EXECUTOR_ABI = [{ - "inputs": [{ - "internalType": "address payable", - "name": "_to", - "type": "address" - }, {"internalType": "uint256", "name": "_value", "type": "uint256"}, { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - }], - "name": "call", - "outputs": [{"internalType": "bytes", "name": "", "type": "bytes"}], - "stateMutability": "payable", - "type": "function" -}, { - "inputs": [{"internalType": "address", "name": "_executor", "type": "address"}], - "stateMutability": "payable", - "type": "constructor" -}, { - "inputs": [{ - "internalType": "uint256", - "name": "_wethAmountToFirstMarket", - "type": "uint256" - }, {"internalType": "uint256", "name": "_ethAmountToCoinbase", "type": "uint256"}, { - "internalType": "address[]", - "name": "_targets", - "type": "address[]" - }, {"internalType": "bytes[]", "name": "_payloads", "type": "bytes[]"}], - "name": "uniswapWeth", - "outputs": [], - "stateMutability": "payable", - "type": "function" -}, {"stateMutability": "payable", "type": "receive"}] +export const BUNDLE_EXECUTOR_ABI = [ + { + "inputs": [ + { + "internalType": "contract ILendingPoolAddressesProvider", + "name": "_addressProvider", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ADDRESSES_PROVIDER", + "outputs": [ + { + "internalType": "contract ILendingPoolAddressesProvider", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LENDING_POOL", + "outputs": [ + { + "internalType": "contract ILendingPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WETH_address", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "call", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "premiums", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "params", + "type": "bytes" + } + ], + "name": "executeOperation", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountToBorrow", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_params", + "type": "bytes" + } + ], + "name": "flashloan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] export const UNISWAP_PAIR_ABI = [{ From cb9a82b93717969a6d47ab75195cb494d75418b5 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 7 Jun 2021 12:51:59 -0400 Subject: [PATCH 11/15] Remove a check that didn't make sense --- src/Arbitrage.ts | 101 ++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/src/Arbitrage.ts b/src/Arbitrage.ts index 35817e8b..d535a3c7 100644 --- a/src/Arbitrage.ts +++ b/src/Arbitrage.ts @@ -149,68 +149,63 @@ export class Arbitrage { const targets: Array = [...buyCalls.targets, bestCrossedMarket.sellToMarket.marketAddress] const payloads: Array = [...buyCalls.data, sellCallData] console.log({targets, payloads}) - const flashloanFee = bestCrossedMarket.volume.mul(flashloanFeePercentage).div(1000); - + const flashloanFee = bestCrossedMarket.volume.mul(flashloanFeePercentage).div(10000); if (flashloanFee.lt(bestCrossedMarket.profit)){ const profitMinusFee = bestCrossedMarket.profit.sub(flashloanFee); const minerReward = profitMinusFee.mul(minerRewardPercentage).div(100); - if (minerReward.lt(profitMinusFee)){ - const profitMinusFeeMinusMinerReward = profitMinusFee.sub(minerReward) - console.log("Send this much WETH", bestCrossedMarket.volume.toString(), "get this much profit after fees", profitMinusFeeMinusMinerReward.toString()) - - const ethersAbiCoder = new utils.AbiCoder() - const flashloanParametersTypes = ['uint256', 'address[]', 'bytes[]'] - const flashloanParamtersInputs = [minerReward.toString(), targets, payloads] - const encodedParameters = ethersAbiCoder.encode(flashloanParametersTypes, flashloanParamtersInputs) - - const transaction = await this.bundleExecutorContract.populateTransaction.flashloan(bestCrossedMarket.volume, encodedParameters, { - gasPrice: BigNumber.from(0), - gasLimit: BigNumber.from(1000000), - }); - - try { - const estimateGas = await this.bundleExecutorContract.provider.estimateGas( - { - ...transaction, - from: this.executorWallet.address - }) - if (estimateGas.gt(1400000)) { - console.log("EstimateGas succeeded, but suspiciously large: " + estimateGas.toString()) - continue - } - transaction.gasLimit = estimateGas.mul(2) - } catch (e) { - console.warn(`Estimate gas failure for ${JSON.stringify(bestCrossedMarket)}`) + + const profitMinusFeeMinusMinerReward = profitMinusFee.sub(minerReward) + console.log("Flashloan this much WETH", bestCrossedMarket.volume.toString(), "get this much profit after fees", profitMinusFeeMinusMinerReward.toString()) + + const ethersAbiCoder = new utils.AbiCoder() + const flashloanParametersTypes = ['uint256', 'address[]', 'bytes[]'] + const flashloanParamtersInputs = [minerReward.toString(), targets, payloads] + const encodedParameters = ethersAbiCoder.encode(flashloanParametersTypes, flashloanParamtersInputs) + + const transaction = await this.bundleExecutorContract.populateTransaction.flashloan(bestCrossedMarket.volume, encodedParameters, { + gasPrice: BigNumber.from(0), + gasLimit: BigNumber.from(1000000), + }); + + try { + const estimateGas = await this.bundleExecutorContract.provider.estimateGas( + { + ...transaction, + from: this.executorWallet.address + }) + if (estimateGas.gt(1400000)) { + console.log("EstimateGas succeeded, but suspiciously large: " + estimateGas.toString()) continue } + transaction.gasLimit = estimateGas.mul(2) + } catch (e) { + console.warn(`Estimate gas failure for ${JSON.stringify(bestCrossedMarket)}`) + continue + } - const bundledTransactions = [ - { - signer: this.executorWallet, - transaction: transaction - } - ]; - console.log(bundledTransactions) - const signedBundle = await this.flashbotsProvider.signBundle(bundledTransactions) - // - const simulation = await this.flashbotsProvider.simulate(signedBundle, blockNumber + 1 ) - if ("error" in simulation || simulation.firstRevert !== undefined) { - console.log(`Simulation Error on token ${bestCrossedMarket.tokenAddress}, skipping`) - continue + const bundledTransactions = [ + { + signer: this.executorWallet, + transaction: transaction } - console.log(`Submitting bundle, profit sent to miner: ${bigNumberToDecimal(simulation.coinbaseDiff)}, effective gas price: ${bigNumberToDecimal(simulation.coinbaseDiff.div(simulation.totalGasUsed), 9)} GWEI`) - const bundlePromises = _.map([blockNumber + 1, blockNumber + 2], targetBlockNumber => - this.flashbotsProvider.sendRawBundle( - signedBundle, - targetBlockNumber - )) - await Promise.all(bundlePromises) - return - - } else { - throw new Error("No arbitrage submitted to relay") + ]; + console.log(bundledTransactions) + const signedBundle = await this.flashbotsProvider.signBundle(bundledTransactions) + // + const simulation = await this.flashbotsProvider.simulate(signedBundle, blockNumber + 1 ) + if ("error" in simulation || simulation.firstRevert !== undefined) { + console.log(`Simulation Error on token ${bestCrossedMarket.tokenAddress}, skipping`) + continue } + console.log(`Submitting bundle, profit sent to miner: ${bigNumberToDecimal(simulation.coinbaseDiff)}, effective gas price: ${bigNumberToDecimal(simulation.coinbaseDiff.div(simulation.totalGasUsed), 9)} GWEI`) + const bundlePromises = _.map([blockNumber + 1, blockNumber + 2], targetBlockNumber => + this.flashbotsProvider.sendRawBundle( + signedBundle, + targetBlockNumber + )) + await Promise.all(bundlePromises) + return } else { throw new Error("No arbitrage submitted to relay") From 3a4ac485903ac845ed26364435b304e7a4bdb223 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Mon, 7 Jun 2021 12:55:06 -0400 Subject: [PATCH 12/15] Update to latest bundle provider --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89818763..5e280529 100644 --- a/package-lock.json +++ b/package-lock.json @@ -691,9 +691,9 @@ } }, "@flashbots/ethers-provider-bundle": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@flashbots/ethers-provider-bundle/-/ethers-provider-bundle-0.2.1.tgz", - "integrity": "sha512-GdOCINGat3kzkq0pkGkEzrsIWWngpQyFFGOPv2ewR8Uswqzl5Ho0bcYW9mIfjOQ1hH0450LZ9PTzJppgEUX4fw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@flashbots/ethers-provider-bundle/-/ethers-provider-bundle-0.3.1.tgz", + "integrity": "sha512-wCeRcwD3cYJ1jxWFy1e8bELeI9rVoFF5zRQLeu4+owAL6N7ZBwJJESJRX+jSMOTCKfldNHxOkkmgqJ+ZxX6iuA==", "requires": { "ts-node": "^9.1.0", "typescript": "^4.1.2" diff --git a/package.json b/package.json index 15063b75..bde5b539 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "ethers": "^5.0.23" }, "dependencies": { - "@flashbots/ethers-provider-bundle": "^0.2.1", + "@flashbots/ethers-provider-bundle": "^0.3.1", "lodash": "^4.17.20", "ts-node": "^9.1.0", "typescript": "^4.1.2" From edff9050c63275294edeb2fd4f902d85891812ac Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Fri, 11 Jun 2021 10:36:00 -0400 Subject: [PATCH 13/15] Infinite approval of Aave in the constructor --- contracts/BundleExecutor.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/BundleExecutor.sol b/contracts/BundleExecutor.sol index ae446e88..9e846c2e 100644 --- a/contracts/BundleExecutor.sol +++ b/contracts/BundleExecutor.sol @@ -30,6 +30,7 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { constructor(ILendingPoolAddressesProvider _addressProvider) FlashLoanReceiverBase(_addressProvider) public payable { owner = msg.sender; + WETH.approve(address(LENDING_POOL), uint(-1)); } /** @@ -48,7 +49,6 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { { uint aaveDebt = amounts[0].add(premiums[0]); uniswapWethFLParams(amounts[0], params, aaveDebt); - WETH.approve(address(LENDING_POOL), aaveDebt); return true; } @@ -109,4 +109,4 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { receive() external payable { } -} \ No newline at end of file +} From fb4309f2a4e8777ffbcef0d4ef1301573a9498e9 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Fri, 11 Jun 2021 10:37:34 -0400 Subject: [PATCH 14/15] Update BundleExecutor.sol --- contracts/BundleExecutor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/BundleExecutor.sol b/contracts/BundleExecutor.sol index 9e846c2e..a3d9aa6e 100644 --- a/contracts/BundleExecutor.sol +++ b/contracts/BundleExecutor.sol @@ -15,7 +15,7 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { using SafeMath for uint256; address private immutable owner; address private immutable executor; - address public WETH_address = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + address public constant WETH_address = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IWETH private constant WETH = IWETH(WETH_address); modifier onlyExecutor() { From 3ad421e019c02c4091244eeced28addf0c523f80 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Fri, 18 Jun 2021 14:22:26 -0400 Subject: [PATCH 15/15] Remove executor/owner --- contracts/BundleExecutor.sol | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/contracts/BundleExecutor.sol b/contracts/BundleExecutor.sol index a3d9aa6e..8610f47b 100644 --- a/contracts/BundleExecutor.sol +++ b/contracts/BundleExecutor.sol @@ -13,23 +13,10 @@ interface IWETH is IERC20 { contract FlashBotsMultiCallFL is FlashLoanReceiverBase { using SafeMath for uint256; - address private immutable owner; - address private immutable executor; address public constant WETH_address = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IWETH private constant WETH = IWETH(WETH_address); - modifier onlyExecutor() { - require(msg.sender == executor); - _; - } - - modifier onlyOwner() { - require(msg.sender == owner); - _; - } - constructor(ILendingPoolAddressesProvider _addressProvider) FlashLoanReceiverBase(_addressProvider) public payable { - owner = msg.sender; WETH.approve(address(LENDING_POOL), uint(-1)); } @@ -100,7 +87,7 @@ contract FlashBotsMultiCallFL is FlashLoanReceiverBase { tx.origin.transfer(_profit); } - function call(address payable _to, uint256 _value, bytes calldata _data) external onlyOwner payable returns (bytes memory) { + function call(address payable _to, uint256 _value, bytes calldata _data) external payable returns (bytes memory) { require(_to != address(0)); (bool _success, bytes memory _result) = _to.call{value: _value}(_data); require(_success);