// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
// Import ERC20 interface:
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol";
// Uniswap/Sushiswap router:
import "https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/interfaces/IUniswapV2Router02.sol";
// get this from the ABI using https://bia.is/tools/abi2solidity/
interface IBDITokenContract {
function balanceOf ( address account ) external view returns ( uint256 );
function burn ( uint256 _amount ) external;
function transfer ( address recipient, uint256 amount ) external returns ( bool );
function transferFrom ( address sender, address recipient, uint256 amount ) external returns ( bool );
}
/// @dev This interfaces defines the functions of the KeeperDAO liquidity pool
/// that our contract needs to know about. The only function we need is the
/// borrow function, which allows us to take flash loans from the liquidity
/// pool.
interface LiquidityPool {
/// @dev Borrow ETH/ERC20s from the liquidity pool. This function will (1)
/// send an amount of tokens to the `msg.sender`, (2) call
/// `msg.sender.call(_data)` from the KeeperDAO borrow proxy, and then (3)
/// check that the balance of the liquidity pool is greater than it was
/// before the borrow.
///
/// @param _token The address of the ERC20 to be borrowed. ETH can be
/// borrowed by specifying "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE".
/// @param _amount The amount of the ERC20 (or ETH) to be borrowed. At least
/// more than this amount must be returned to the liquidity pool before the
/// end of the transaction, otherwise the transaction will revert.
/// @param _data The calldata that encodes the callback to be called on the
/// `msg.sender`. This is the mechanism through which the borrower is able
/// to implement their custom keeper logic. The callback will be called from
/// the KeeperDAO borrow proxy.
function borrow(
address _token,
uint256 _amount,
bytes calldata _data
) external;
}
/// @dev
contract BasketDAOBuyAndBurn {
using SafeERC20 for IERC20;
/// @dev Owner of the contract.
address public owner;
/// @dev Address of the KeeperDAO borrow proxy. This will be the
/// `msg.sender` for calls to the `helloCallback` function.
address public borrowProxy;
/// @dev Address of the KeeperDAO liquidity pool. This is will be the
/// address to which the `helloCallback` function must return all bororwed
/// assets (and all excess profits).
address payable public liquidityPool;
/// @dev This modifier restricts the caller of a function to the owner of
/// this contract.
modifier onlyOwner {
if (msg.sender == owner) {
_;
}
}
/// @dev This modifier restricts the caller of a function to the KeeperDAO
/// borrow proxy.
modifier onlyBorrowProxy {
if (msg.sender == borrowProxy) {
_;
}
}
constructor() public payable {
owner = msg.sender;
// Approve the Uniswap/Sushiswap router to spend this contract's WETH:
IERC20 weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // WETH
// Amount is:
// 0000000000000000000000000000000000000000ffffffffffffffffffffffff
// Approval for Sushiswap:
require(weth.approve(address(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F),79228162514264337593543950335), 'weth approve failed');
require(IERC20(0xcee60cFa923170e4f8204AE08B4fA6A3F5656F3a).approve(address(0xF178C0b5Bb7e7aBF4e12A4838C7b7c5bA2C623c0),79228162514264337593543950335), 'approve failed');
// Blank approvals:
address[15] memory underlyings = [0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e,
0xc00e94Cb662C3520282E6f5717214004A7f26888,
0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F,
0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2,
0x408e41876cCCDC0F92210600ef50372656052a38,
0xdeFA4e8a7bcBA345F687a2f1456F5Edd9CE97202,
0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD,
0xba100000625a3754423978a60c9317c58a424e3D,
0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984,
0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9,
0x6B3595068778DD592e39A122f4f5a5cF09C90fE2,
0x2ba592F78dB6436527729929AAf6c908497cB200,
0x514910771AF9Ca656af840dff83E8264EcF986CA,
0xc5bDdf9843308380375a611c18B50Fb9341f502A,
0xE41d2489571d322189246DaFA5ebDe1F4699F498];
for (uint256 i = 0; i < underlyings.length; i++) {
IERC20(underlyings[i]).approve(address(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F),79228162514264337593543950335);
IERC20(underlyings[i]).approve(address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D),79228162514264337593543950335);
}
liquidityPool = payable(0x4F868C1aa37fCf307ab38D215382e88FCA6275E2); // new contract since June 2021
borrowProxy = 0x17a4C8F43cB407dD21f9885c5289E66E21bEcD9D; // new contract since June 2021
}
receive() external payable {
// Do nothing.
}
fallback() external payable { return; }
/// @dev Set the owner of this contract. This function can only be called by
/// the current owner.
///
/// @param _newOwner The new owner of this contract.
function setOwner(address _newOwner) external onlyOwner {
owner = _newOwner;
}
/// @dev Set the borrow proxy expected by this contract. This function can
/// only be called by the current owner.
///
/// @param _newBorrowProxy The new borrow proxy expected by this contract.
function setBorrowProxy(address _newBorrowProxy) external onlyOwner {
borrowProxy = _newBorrowProxy;
}
/// @dev Set the liquidity pool used by this contract. This function can
/// only be called by the current owner.
///
/// @param _newLiquidityPool The new liquidity pool used by this contract.
/// It must be a payable address, because this contract needs to be able to
/// return borrowed assets and profits to the liquidty pool.
function setLiquidityPool(address payable _newLiquidityPool) external onlyOwner {
liquidityPool = _newLiquidityPool;
}
/// Function that allows to withdraw ERC-20 tokens that are sitting on this contract to the owner address.
/// Uses safeTransfer to deal with non-standard tokens like USDT
function withdrawTokens(address _token) external onlyOwner {
IERC20(_token).safeTransfer(
msg.sender,
IERC20(_token).balanceOf(address(this))
);
}
/// Function that allows to withdraw ETH that are sitting on this contract to the owner address.
function withdrawEth(uint256 _amount) external onlyOwner {
assert(
address(this).balance >= _amount
);
assert(_amount > 0);
(bool success, ) = owner.call{value:_amount}("");
require(success, "Transfer failed.");
}
// Safety fallback, code from @kinzrec
function delegateCall(address to, bytes memory data) external payable onlyOwner {
(bool success, bytes memory retData) = to.delegatecall(data);
require(success, string(retData));
}
/// @dev The main function.
function safeFlashLoanBuyAndRedeem(uint256 _amountToBorrow, uint256 _minimalProfitability, uint256 _amountOfProfitToReturn, address[] memory underlyings, address[] memory routers) external onlyOwner returns(uint256)
{
// Check input:
require( _amountToBorrow > 0, "empty amount");
address _token = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH
// Do flash-loan:
LiquidityPool(liquidityPool).borrow(
// Address of the token we want to borrow. Using this address
// means that we want to borrow ETH.
_token,
// The amount of WEI that we will borrow. We have to return at least
// more than this amount.
_amountToBorrow,
// Encode the callback into calldata. This will be used to call a
// function on this contract.
abi.encodeWithSelector(
// Function selector of the callback function.
this.buyAndRedeemCallback.selector,
// First parameter of the callback.
_amountToBorrow,
_amountOfProfitToReturn,
underlyings,
routers
)
);
// At this point the flash-loan is paid back...
// Ensure we are left with more WETH than before
IERC20 weth = IERC20(_token);
uint256 weth_balance = weth.balanceOf(address(this));
require(weth_balance >= _minimalProfitability, 'not profitable');
// Transfer profit to owner address.
bool sent = weth.transfer(owner, weth_balance);
require(sent, "Token transfer failed");
return weth_balance;
}
function buyAndRedeemCallback(
uint256 _amountBorrowed,
uint256 _amountOfProfitToReturn,
address[] memory underlyings,
address[] memory routers
) external onlyBorrowProxy {
/// We have now borrowed a certain amount of WETH
// Pay back flash loan
IERC20 weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
//uint256 weth_balance = weth.balanceOf(address(this));
// Buy BDI on Sushiswap
// From WETH to BDI
address[] memory path = new address[](2);
path[0] = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH
path[1] = 0x0309c98B1bffA350bcb3F9fB9780970CA32a5060; // BDI
// Swap _amountBorrowed WETH to any amount of BDI (we will check that this was profitable later)
IUniswapV2Router02(address(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F)).swapExactTokensForTokens(_amountBorrowed, 0, path, address(this), block.timestamp + 15000);
uint256 bdi_balance = IBDITokenContract(0x0309c98B1bffA350bcb3F9fB9780970CA32a5060).balanceOf(address(this));
// Burn BDI into constituents
IBDITokenContract(0x0309c98B1bffA350bcb3F9fB9780970CA32a5060).burn(bdi_balance);
// Convert constituents into underlyings
// yvYFI - https://etherscan.io/address/0xE14d13d8B3b85aF791b2AADD661cDBd5E6097Db1
uint256 bal = IERC20(0xE14d13d8B3b85aF791b2AADD661cDBd5E6097Db1).balanceOf(address(this));
(0xE14d13d8B3b85aF791b2AADD661cDBd5E6097Db1).call(abi.encodeWithSignature("withdraw(uint256)",bal));
// Now we have YFI: https://etherscan.io/token/0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e
// cCOMP - 0x70e36f6BF80a52b3B46b3aF8e106CC0ed743E8e4
bal = IERC20(0x70e36f6BF80a52b3B46b3aF8e106CC0ed743E8e4).balanceOf(address(this));
(0x70e36f6BF80a52b3B46b3aF8e106CC0ed743E8e4).call(abi.encodeWithSignature("redeem(uint256)",bal));
// Now we have COMP: 0xc00e94Cb662C3520282E6f5717214004A7f26888
// yvSNX - 0xF29AE508698bDeF169B89834F76704C3B205aedf
bal = IERC20(0xF29AE508698bDeF169B89834F76704C3B205aedf).balanceOf(address(this));
(0xF29AE508698bDeF169B89834F76704C3B205aedf).call(abi.encodeWithSignature("withdraw(uint256)",bal));
// Now we have SNX: 0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F
// yvUNI: 0xFBEB78a723b8087fD2ea7Ef1afEc93d35E8Bed42
bal = IERC20(0xFBEB78a723b8087fD2ea7Ef1afEc93d35E8Bed42).balanceOf(address(this));
(0xFBEB78a723b8087fD2ea7Ef1afEc93d35E8Bed42).call(abi.encodeWithSignature("withdraw(uint256)",bal));
// Now we have UNI: 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984
// xSUSHI: 0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272
// can be directly swapped on Sushiswap, alternatively, could un-wrap First
bal = IERC20(0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272).balanceOf(address(this));
(0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272).call(abi.encodeWithSignature("leave(uint256)",bal));
// Now have Sushi 0x6B3595068778DD592e39A122f4f5a5cF09C90fE2
// yvCurve-Link: 0xf2db9a7c0ACd427A680D640F02d90f6186E71725
bal = IERC20(0xf2db9a7c0ACd427A680D640F02d90f6186E71725).balanceOf(address(this));
(0xf2db9a7c0ACd427A680D640F02d90f6186E71725).call(abi.encodeWithSignature("withdraw(uint256)",bal));
// Now we have linkCRV: 0xcee60cfa923170e4f8204ae08b4fa6a3f5656f3a
bal = IERC20(0xcee60cFa923170e4f8204AE08B4fA6A3F5656F3a).balanceOf(address(this));
// Moved this to constructor:
//require(IERC20(0xcee60cFa923170e4f8204AE08B4fA6A3F5656F3a).approve(address(0xF178C0b5Bb7e7aBF4e12A4838C7b7c5bA2C623c0),79228162514264337593543950335), 'approve failed');
(0xF178C0b5Bb7e7aBF4e12A4838C7b7c5bA2C623c0).call(abi.encodeWithSignature("remove_liquidity_one_coin(uint256,int128,uint256)",bal,0,0)); // second 0 means we want to withdraw link
// Now we have LINK: 0x514910771AF9Ca656af840dff83E8264EcF986CA
// yvBOOST 0x9d409a0A012CFbA9B15F6D4B36Ac57A46966Ab9a
bal = IERC20(0x9d409a0A012CFbA9B15F6D4B36Ac57A46966Ab9a).balanceOf(address(this));
(0x9d409a0A012CFbA9B15F6D4B36Ac57A46966Ab9a).call(abi.encodeWithSignature("withdraw(uint256)",bal));
// Now we have yveCRVDAO: 0xc5bDdf9843308380375a611c18B50Fb9341f502A, can be swapped on Sushiswap"
// Convert all the underlyings according to the specified router (Uniswap or Sushiswap):
for (uint256 i = 0; i < underlyings.length; i++) {
bal = IERC20(underlyings[i]).balanceOf(address(this));
path[0] = underlyings[i];
path[1] = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH
IUniswapV2Router02(routers[i]).swapExactTokensForTokens(bal, 0, path, address(this), block.timestamp + 15000);
}
// Pay back flash loan
bal = weth.balanceOf(address(this));
require( bal >= _amountOfProfitToReturn + _amountBorrowed, 'not profitable');
// Notice that assets are transferred back to the liquidity pool, not to
// the borrow proxy.
bool sent = weth.transfer(liquidityPool, _amountBorrowed + _amountOfProfitToReturn);
require(sent, "token transfer failed");
}
}
{
"compilationTarget": {
"BasketDAOBurn.sol": "BasketDAOBuyAndBurn"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"payable","type":"constructor"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"borrowProxy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountBorrowed","type":"uint256"},{"internalType":"uint256","name":"_amountOfProfitToReturn","type":"uint256"},{"internalType":"address[]","name":"underlyings","type":"address[]"},{"internalType":"address[]","name":"routers","type":"address[]"}],"name":"buyAndRedeemCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"delegateCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"liquidityPool","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountToBorrow","type":"uint256"},{"internalType":"uint256","name":"_minimalProfitability","type":"uint256"},{"internalType":"uint256","name":"_amountOfProfitToReturn","type":"uint256"},{"internalType":"address[]","name":"underlyings","type":"address[]"},{"internalType":"address[]","name":"routers","type":"address[]"}],"name":"safeFlashLoanBuyAndRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newBorrowProxy","type":"address"}],"name":"setBorrowProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_newLiquidityPool","type":"address"}],"name":"setLiquidityPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]