Skip to content

Instantly share code, notes, and snippets.

@nch7
Created December 31, 2021 01:25
Show Gist options
  • Save nch7/f14ca8321ea84234dacaed62df4e9371 to your computer and use it in GitHub Desktop.
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=
//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