Created
December 31, 2021 01:25
-
-
Save nch7/f14ca8321ea84234dacaed62df4e9371 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.7+commit.e28d00a7.js&optimize=false&runs=200&gist=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//SPDX-License-Identifier: UNLICENSED | |
pragma solidity 0.8.7; | |
import "./interfaces/IUniswapV2Pair.sol"; | |
import "./interfaces/IERC20.sol"; | |
import "./interfaces/IWETH.sol"; | |
import { FlashLoanReceiverBase } from "./interfaces/FlashLoanReceiverBase.sol"; | |
import "./interfaces/ILendingPoolAddressesProvider.sol"; | |
import "./interfaces/ILendingPool.sol"; | |
contract Swapper is FlashLoanReceiverBase { | |
uint256 MAX_INT = 2**96 - 1; | |
event TransactionInputEvent(Transaction transaction); | |
address owner; | |
address public baseToken; | |
bool useFlashLoan; | |
struct TransactionSide { | |
address token; | |
uint256 amount; | |
} | |
event Log(string message); | |
event LogMemoryString(string message); | |
uint256 minBalance; | |
struct Transaction { | |
address token0; | |
address token1; | |
uint256 amount0In; | |
uint256 amount1In; | |
uint256 amount0Out; | |
uint256 amount1Out; | |
address pool; | |
int16 dexType; | |
} | |
ILendingPool lendingPool; | |
constructor(address _baseToken, ILendingPoolAddressesProvider _addressProvider, uint256 _minBalance, bool _useFlashLoan) FlashLoanReceiverBase(_addressProvider) { | |
owner = msg.sender; | |
baseToken = _baseToken; | |
useFlashLoan = _useFlashLoan; | |
if (useFlashLoan) { | |
lendingPool = ILendingPool(_addressProvider.getLendingPool()); | |
} | |
minBalance = _minBalance; | |
} | |
receive() external payable { | |
} | |
function approve(address token, address pool, uint256 amount) external { | |
uint256 allowance = IERC20(token).allowance(address(this), pool); | |
if (allowance == 0) { | |
IERC20(token).approve(pool, MAX_INT); | |
} else { | |
if (allowance < amount) { | |
IERC20(token).approve(pool, 0); | |
IERC20(token).approve(pool, MAX_INT); | |
} | |
} | |
} | |
function approveForce(address token, address pool, uint256 amount) external { | |
IERC20(token).approve(pool, amount); | |
} | |
function getAllowance(address token, address pool) external view returns (uint256) { | |
return IERC20(token).allowance(address(this), pool); | |
} | |
function sendERC20(address token, address pool, uint256 amount) external { | |
this.approve(token, pool, amount); | |
IERC20(token).transfer(pool, amount); | |
} | |
function uniswapV2_executeSwap(address pool, uint256 amount0Out, uint256 amount1Out, address destination) external { | |
IUniswapV2Pair(pool).swap(amount0Out, amount1Out, destination, new bytes(0)); | |
} | |
struct LogSwapData { | |
address pool; | |
uint256 amount0Out; | |
uint256 amount1Out; | |
address destination; | |
} | |
function executeOperation( | |
address[] calldata assets, | |
uint256[] calldata amounts, | |
uint256[] calldata premiums, | |
address initiator, | |
bytes calldata params | |
) | |
external | |
override | |
returns (bool) | |
{ | |
require(initiator == address(this), "Swapper: II"); | |
(Transaction[] memory transactions, uint256 minerBribe, uint256 startBalance, uint256 transactionFee) = abi.decode(params, (Transaction[], uint256, uint256, uint256)); | |
this.executeArbitrage(transactions, minerBribe); | |
// Approve the LendingPool contract allowance to *pull* the owed amount | |
uint amountOwing = amounts[0] + premiums[0]; | |
IERC20(assets[0]).approve(address(LENDING_POOL), amountOwing); | |
this.checkProfit(startBalance, minerBribe, transactionFee, amountOwing); | |
this.sendERC20(baseToken, owner, this.getBalance() - (minBalance + amountOwing)); | |
return true; | |
} | |
function updateMinBalance(uint256 _minBalance) external { | |
require(msg.sender == owner, "Swapper: NOWNR"); | |
minBalance = _minBalance; | |
} | |
function takeFlashLoan(address token, uint256 flashLoanAmount, bytes memory data) public { | |
address[] memory addresses = new address[](1); | |
addresses[0] = token; | |
uint256[] memory amounts = new uint256[](1); | |
amounts[0] = flashLoanAmount; | |
uint256[] memory modes = new uint256[](1); | |
modes[0] = 0; | |
lendingPool.flashLoan( | |
address(this), | |
addresses, | |
amounts, | |
modes, | |
address(this), | |
data, | |
0 | |
); | |
} | |
function arbitrage(Transaction[] memory transactions, uint256 minerBribe, uint256 transactionFee) external { | |
uint256 startBalance = this.getBalance(); | |
bool isFlashloan = false; | |
require(startBalance > 0, "Swapper: no balance"); | |
if (useFlashLoan && startBalance < transactions[0].amount0In + transactions[0].amount1In) { | |
isFlashloan = true; | |
} | |
if (isFlashloan) { | |
try this.takeFlashLoan(baseToken, (transactions[0].amount0In + transactions[0].amount1In) - startBalance, abi.encode(transactions, minerBribe, startBalance, transactionFee)) { | |
} catch Error(string memory reason) { | |
require(false, string(abi.encodePacked("Swapper: TFLF - ", reason))); | |
} catch { | |
require(false, "Swapper: TFLE"); | |
} | |
} else { | |
require(startBalance >= transactions[0].amount0In + transactions[0].amount1In, "Swapper: not enough balance for first transaction"); | |
this.executeArbitrage(transactions, minerBribe); | |
this.checkProfit(startBalance, minerBribe, transactionFee, 0); | |
this.sendERC20(baseToken, owner, this.getBalance() - minBalance); | |
} | |
} | |
function executeArbitrage(Transaction[] memory transactions, uint256 minerBribe) public { | |
for (uint256 index = 0; index < transactions.length; index++) { | |
if (transactions[index].dexType == 0) { | |
bool zeroForOne = transactions[index].amount0In > 0 ? true : false; | |
if (index == 0) { | |
try this.sendERC20(zeroForOne ? transactions[index].token0 : transactions[index].token1, transactions[index].pool, transactions[index].amount0In + transactions[index].amount1In) { | |
} catch Error(string memory reason) { | |
require(false, string(abi.encodePacked("Swapper: SEF - ", reason))); | |
} catch { | |
require(false, "Swapper: SEA"); | |
} | |
} | |
uint256 nextPoolBalanceBeforeSending = 0; | |
uint256 nextPoolBalanceAfterSending = 0; | |
if (index < transactions.length -1) { | |
nextPoolBalanceBeforeSending = this.getERC20BalanceOf(transactions[index + 1].amount0In > 0 ? transactions[index + 1].token0 : transactions[index + 1].token1, transactions[index+1].pool); | |
} | |
try this.uniswapV2_executeSwap( | |
transactions[index].pool, | |
transactions[index].amount0Out, | |
transactions[index].amount1Out, | |
index == transactions.length - 1 ? address(this) : transactions[index+1].pool | |
) { | |
} catch Error(string memory reason) { | |
require(false, string(abi.encodePacked("Swapper: EFE - ", reason))); | |
} catch { | |
require(false, "Swapper: EFA"); | |
} | |
if (index < transactions.length -1) { | |
nextPoolBalanceAfterSending = this.getERC20BalanceOf(transactions[index + 1].amount0In > 0 ? transactions[index + 1].token0 : transactions[index + 1].token1, transactions[index+1].pool); | |
if (nextPoolBalanceAfterSending - nextPoolBalanceBeforeSending != transactions[index + 1].amount0In + transactions[index + 1].amount1In) { | |
for (uint256 j=index + 1; j<transactions.length; j++) { | |
uint256 nextAmountIn = transactions[index + 1].amount0In + transactions[index + 1].amount1In; | |
transactions[j].amount0Out = this.adjustAmount(transactions[j].amount0Out, nextPoolBalanceAfterSending - nextPoolBalanceBeforeSending, nextAmountIn); | |
transactions[j].amount1Out = this.adjustAmount(transactions[j].amount1Out, nextPoolBalanceAfterSending - nextPoolBalanceBeforeSending, nextAmountIn); | |
transactions[j].amount0In = this.adjustAmount(transactions[j].amount0In, nextPoolBalanceAfterSending - nextPoolBalanceBeforeSending, nextAmountIn); | |
transactions[j].amount1In = this.adjustAmount(transactions[j].amount1In, nextPoolBalanceAfterSending - nextPoolBalanceBeforeSending, nextAmountIn); | |
} | |
} | |
} | |
} | |
} | |
if (minerBribe > 0) { | |
IWETH(baseToken).withdraw(minerBribe); | |
block.coinbase.transfer(minerBribe); | |
} | |
} | |
function checkProfit(uint256 startBalance, uint256 minerBribe, uint256 transactionFee, uint256 amountOwing) view public { | |
uint256 endBalance = this.getBalance(); | |
uint256 revenue = endBalance - startBalance; | |
uint256 costOfRevenue = minerBribe + transactionFee + amountOwing; | |
require(revenue >= costOfRevenue, "NP-3"); | |
} | |
function adjustAmount(uint256 amount, uint256 numerator, uint256 denominator) external pure returns(uint256) { | |
if (amount == 0) { | |
return 0; | |
} | |
return (amount * (numerator * 1000 / denominator))/1000 - 1; | |
} | |
function cashoutETH(address _to, uint256 _amount) public payable { | |
require(owner == _to, 'X'); | |
payable(_to).transfer(_amount); | |
} | |
function cashout() external { | |
require(owner == msg.sender, 'X'); | |
this.cashoutETH(owner, this.getETHBalance()); | |
this.sendERC20(baseToken, owner, this.getBalance()); | |
} | |
function getBalance() external view returns (uint256) { | |
return IERC20(baseToken).balanceOf(address(this)); | |
} | |
function getERC20Balance(address token) external view returns (uint256) { | |
return this.getERC20BalanceOf(token, address(this)); | |
} | |
function getERC20BalanceOf(address token, address balanceOf) external view returns (uint256) { | |
return IERC20(token).balanceOf(balanceOf); | |
} | |
function getETHBalance() external view returns (uint256) { | |
return address(this).balance; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment