Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add a flashloan and make contract callable by anyone #14

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ simple-arbitrage
================
This repository contains a simple, mechanical system for discovering, evaluating, rating, and submitting arbitrage opportunities to the Flashbots bundle endpoint. This script is very unlikely to be profitable, as many users have access to it, and it is targeting well-known Ethereum opportunities.

simple-arbitrage uses Flashloans to source liquidity to execute on arbitrage opportunities it finds. It attempts to capture them and sends any profits made, net of fees, back to the message sender. We have deployed a contract that anyone can submit trades to here:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please update the link to this execution contract or send us its address.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1


You do not need any Ethereum to use this contract.

We hope you will use this repository as an example of how to integrate Flashbots into your own Flashbot searcher (bot). For more information, see the [Flashbots Searcher FAQ](https://github.com/flashbots/pm/blob/main/guides/searcher-onboarding.md)

Environment Variables
Expand All @@ -15,15 +19,12 @@ Environment Variables
Usage
======================
1. Generate a new bot wallet address and extract the private key into a raw 32-byte format.
2. Deploy the included BundleExecutor.sol to Ethereum, from a secured account, with the address of the newly created wallet as the constructor argument
3. Transfer WETH to the newly deployed BundleExecutor

_It is important to keep both the bot wallet private key and bundleExecutor owner private key secure. The bot wallet attempts to not lose WETH inside an arbitrage, but a malicious user would be able to drain the contract._
2. Run simple-arbitage using the new private key from step (1) as an environment varaible.

```
$ npm install
$ PRIVATE_KEY=__PRIVATE_KEY_FROM_ABOVE__ \
BUNDLE_EXECUTOR_ADDRESS=__DEPLOYED_ADDRESS_FROM_ABOVE__ \
$ ETHEREUM_RPC_URL=__ETHEREUM_RPC_URL_FROM_ABOVE__ \
PRIVATE_KEY=__PRIVATE_KEY_FROM_ABOVE__ \
FLASHBOTS_RELAY_SIGNING_KEY=__RANDOM_ETHEREUM_PRIVATE_KEY__ \
npm run start
```
114 changes: 66 additions & 48 deletions contracts/BundleExecutor.sol
Original file line number Diff line number Diff line change
@@ -1,81 +1,99 @@
//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 {
address private immutable owner;
address private immutable executor;
IWETH private constant WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
contract FlashBotsMultiCallFL is FlashLoanReceiverBase {
using SafeMath for uint256;
address public constant WETH_address = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove executor?

IWETH private constant WETH = IWETH(WETH_address);

modifier onlyExecutor() {
require(msg.sender == executor);
_;
constructor(ILendingPoolAddressesProvider _addressProvider) FlashLoanReceiverBase(_addressProvider) public payable {
WETH.approve(address(LENDING_POOL), uint(-1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting! you don't move them back yourself, aave takes them back from you?

}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
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 aaveDebt = amounts[0].add(premiums[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess since there's no assets held in the contract, you don't need to limit msg.sender to aave?

uniswapWethFLParams(amounts[0], params, aaveDebt);

constructor(address _executor) public payable {
owner = msg.sender;
executor = _executor;
if (msg.value > 0) {
WETH.deposit{value: msg.value}();
}
return true;
}

receive() external payable {
function flashloan(uint256 amountToBorrow, bytes memory _params) external {
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 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 uniswapWethFLParams(uint256 _amountToFirstMarket, bytes memory _params, uint256 aaveDebt) internal {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just like the first one, anyone can perform arbitrary calls, so the "owner" here isn't really that empowered. more a convenience than anything

(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 - aaveDebt - _ethAmountToCoinbase;

require(_profit >= 0);

WETH.withdraw(_ethAmountToCoinbase + _profit);
block.coinbase.transfer(_ethAmountToCoinbase);
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);
return _result;
}

receive() external payable {
}
}
81 changes: 81 additions & 0 deletions contracts/BundleExecutorNoFlashloan.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
18 changes: 18 additions & 0 deletions contracts/FlashLoanReceiverBase.sol
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading