Created
January 29, 2022 01:36
-
-
Save skyzer/d0706ec102b0eb4e1d35691a18984478 to your computer and use it in GitHub Desktop.
This file contains hidden or 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.6.12; | |
pragma experimental ABIEncoderV2; | |
import {FlashLoanReceiverBase} from "@aave/protocol-v2/contracts/flashloan/base/FlashLoanReceiverBase.sol"; | |
import {SafeERC20} from "@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol"; | |
import "@aave/protocol-v2/contracts/interfaces/ILendingPool.sol"; | |
import "@aave/protocol-v2/contracts/interfaces/ILendingPoolAddressesProvider.sol"; | |
import {IBentoBoxV1} from "@sushiswap/bentobox-sdk/contracts/IBentoBoxV1.sol"; | |
import {IERC20 as IBentoERC20} from "@boringcrypto/boring-solidity/contracts/interfaces/IERC20.sol"; | |
import {IERC20 as ISafeERC20} from "@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/IERC20.sol"; | |
// import { IERC20 } from '@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; | |
import "@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/Ownable.sol"; | |
import "./interfaces.sol"; | |
contract MyV2FlashLoan is FlashLoanReceiverBase, Ownable { | |
using SafeERC20 for ISafeERC20; | |
IERC20 public MIM = IERC20(0x99D8a9C45b2ecA8864373A26D1459e3Dff1e17F3); | |
address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; | |
// yearn vault WETH | |
IyvWETH public yvWETH = IyvWETH(0xa258C4606Ca8206D8aA700cE2143D7db854D168c); | |
// Compound ETH | |
address public cETH = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5; | |
// abracadabra yvWETH cauldron | |
ICauldronV2 public yvWethCauldron = | |
ICauldronV2(0x920D9BD936Da4eAFb5E25c6bDC9f6CB528953F9f); | |
// curve pool to swap USDT to WBTC | |
IPool public poolBtc = IPool(0xD51a44d3FaE010294C616388b506AcdA1bfAAE46); | |
// curve pool to swap mim to DAI, USDT, USDC | |
IPool public pool = IPool(0x5a6A4D54456819380173272A5E8E9B9904BdF41B); | |
address[] public assets = [ | |
0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI | |
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC | |
0xdAC17F958D2ee523a2206206994597C13D831ec7, // USDT | |
0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599 // WBTC | |
]; | |
// Compound tokens to repay | |
CErc20Interface[] public cTokens = [ | |
CErc20Interface(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643), // DAI | |
CErc20Interface(0x39AA39c021dfbaE8faC545936693aC917d5E7563), // USDC | |
CErc20Interface(0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9), // USDT | |
CErc20Interface(0xccF4429DB6322D5C611ee964527D42E5d685DD6a) // WBTC | |
]; | |
constructor() | |
public | |
FlashLoanReceiverBase( | |
ILendingPoolAddressesProvider( | |
0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5 | |
) | |
) | |
{} | |
receive() external payable {} | |
function exchange(uint256[] memory amounts) private { | |
MIM.approve(address(pool), type(uint256).max); | |
for (uint256 i; i < 3; i++) { | |
pool.exchange_underlying( | |
0, // mim | |
int128(i + 1), // dai | |
// +0.5% is slippage | |
pool.get_dy_underlying( | |
int128(i + 1), | |
0, | |
(amounts[i] * 10055) / 10000 | |
), | |
amounts[i] | |
); | |
} | |
// swap wbtc dust, seems we can exchange using usd that we already have | |
// since the amount we need here is just 1.5$ | |
uint256 swapAmount = poolBtc.get_dy(1, 0, (amounts[3] * 10155) / 10000); | |
if ( | |
IERC20(assets[2]).allowance(address(this), address(poolBtc)) < | |
swapAmount | |
) { | |
ISafeERC20(assets[2]).safeApprove(address(poolBtc), 0); | |
ISafeERC20(assets[2]).safeApprove( | |
address(poolBtc), | |
type(uint256).max | |
); | |
} | |
poolBtc.exchange( | |
0, // usdt | |
1, // wbtc | |
swapAmount, | |
amounts[3] | |
); | |
} | |
// calculate total MIM needed to repay flashloan | |
function getBorrowAmountToRepayFlashLoan(uint256[] memory amounts) | |
public | |
view | |
returns (uint256) | |
{ | |
// borrow fee 0.05% and slippage 0.5% | |
return | |
pool.get_dy_underlying(1, 0, (amounts[0] * 10060) / 10000) + | |
pool.get_dy_underlying(2, 0, (amounts[1] * 10060) / 10000) + | |
pool.get_dy_underlying(3, 0, (amounts[2] * 10060) / 10000); | |
} | |
function executeOperation( | |
address[] calldata assets, | |
uint256[] calldata amounts, | |
uint256[] calldata premiums, | |
address initiator, | |
bytes calldata params | |
) external override returns (bool) { | |
require(initiator == address(this), "Not allowed for wallets"); | |
uint256 rebalancePercent = abi.decode(params, (uint256)); | |
uint256[] memory amountsWithPremiums = new uint256[](amounts.length); | |
// repay compound loans from flashloan amounts | |
for (uint256 i = 0; i < cTokens.length; i++) { | |
if ( | |
IERC20(assets[i]).allowance( | |
address(this), | |
address(cTokens[i]) | |
) < amounts[i] | |
) { | |
ISafeERC20(assets[i]).safeApprove(address(cTokens[i]), 0); | |
ISafeERC20(assets[i]).safeApprove( | |
address(cTokens[i]), | |
type(uint256).max | |
); | |
} | |
require( | |
cTokens[i].repayBorrowBehalf(owner(), amounts[i]) == 0, | |
"repay failed" | |
); | |
amountsWithPremiums[i] = amounts[i] + premiums[i]; | |
} | |
// withdraw compound ETH | |
CErc20Interface(cETH).transferFrom( | |
owner(), | |
address(this), | |
(IERC20(cETH).balanceOf(owner()) * rebalancePercent) / 10000 | |
); | |
require( | |
CErc20Interface(cETH).redeem( | |
IERC20(cETH).balanceOf(address(this)) | |
) == 0, | |
"redeem failed" | |
); | |
// convert ETH to WETH | |
IWETH(WETH).deposit{value: payable(this).balance}(); | |
// deposit WETH to yearn and receive yvWETH | |
IERC20(WETH).approve( | |
address(yvWETH), | |
IERC20(WETH).balanceOf(address(this)) | |
); | |
yvWETH.deposit(); | |
// add collateral and borrow at yvWethCauldron | |
// avoid stack-too-deep with this {} | |
{ | |
// amount of MIM needed to repay flashloan | |
uint256 borrowAmount = getBorrowAmountToRepayFlashLoan( | |
amountsWithPremiums | |
); | |
uint256 collateralAmount = IERC20(address(yvWETH)).balanceOf( | |
address(this) | |
); | |
yvWETH.approve(yvWethCauldron.bentoBox(), collateralAmount); | |
(uint256 amountOut, uint256 shareOut) = IBentoBoxV1( | |
yvWethCauldron.bentoBox() | |
).deposit( | |
IBentoERC20(address(yvWETH)), | |
address(this), | |
address(this), | |
collateralAmount, | |
0 | |
); | |
if ( | |
IBentoBoxV1(yvWethCauldron.bentoBox()).masterContractApproved( | |
yvWethCauldron.masterContract(), | |
address(this) | |
) == false | |
) { | |
IBentoBoxV1(yvWethCauldron.bentoBox()) | |
.setMasterContractApproval( | |
address(this), | |
yvWethCauldron.masterContract(), | |
true, | |
0, | |
0, | |
0 | |
); | |
} | |
yvWethCauldron.addCollateral(address(this), false, shareOut); | |
(, shareOut) = yvWethCauldron.borrow(address(this), borrowAmount); | |
(amountOut, shareOut) = IBentoBoxV1(yvWethCauldron.bentoBox()) | |
.withdraw( | |
IBentoERC20(address(MIM)), | |
address(this), | |
address(this), | |
0, | |
shareOut | |
); | |
} | |
// exchange MIM to DAI, USDT, USDC, WBTC to repay flashloan | |
exchange(amountsWithPremiums); | |
// repay flash loan | |
for (uint256 i = 0; i < assets.length; i++) { | |
if ( | |
IERC20(assets[i]).allowance( | |
address(this), | |
address(LENDING_POOL) | |
) < cTokens[i].borrowBalanceStored(owner()) | |
) { | |
IERC20(assets[i]).safeApprove(address(LENDING_POOL), 0); | |
IERC20(assets[i]).safeApprove( | |
address(LENDING_POOL), | |
type(uint256).max | |
); | |
} | |
} | |
return true; | |
} | |
function flashloanAndRebalance(uint256 percentRebalance) public onlyOwner { | |
address receiverAddress = address(this); | |
uint256[] memory modes = new uint256[](assets.length); | |
uint256[] memory amounts = new uint256[](assets.length); | |
for (uint256 i = 0; i < assets.length; i++) { | |
amounts[i] = | |
(CErc20Interface(cTokens[i]).borrowBalanceCurrent(msg.sender) * | |
percentRebalance) / | |
10000; | |
} | |
address onBehalfOf = address(this); | |
bytes memory params = abi.encode(percentRebalance); | |
uint16 referralCode = 0; | |
LENDING_POOL.flashLoan( | |
receiverAddress, | |
assets, | |
amounts, | |
modes, | |
onBehalfOf, | |
params, | |
referralCode | |
); | |
for (uint256 i = 0; i < assets.length; i++) { | |
if (i < 3) { | |
if ( | |
IERC20(assets[i]).allowance(address(this), address(pool)) < | |
IERC20(assets[i]).balanceOf(address(this)) | |
) { | |
IERC20(assets[i]).safeApprove(address(pool), 0); | |
IERC20(assets[i]).safeApprove( | |
address(pool), | |
type(uint256).max | |
); | |
} | |
pool.exchange_underlying( | |
int128(i + 1), | |
0, // mim | |
IERC20(assets[i]).balanceOf(address(this)), | |
0 | |
); | |
} else { | |
// not worth to swap ~4 wei | |
ISafeERC20(assets[i]).safeTransfer( | |
msg.sender, | |
IERC20(assets[i]).balanceOf(address(this)) | |
); | |
} | |
} | |
MIM.transfer(msg.sender, MIM.balanceOf(address(this))); | |
} | |
function LTV() public view returns (uint256) { | |
Rebase memory _totalBorrow = yvWethCauldron.totalBorrow(); | |
IBentoBoxV1 bentoBox = IBentoBoxV1(yvWethCauldron.bentoBox()); | |
uint256 collateral = bentoBox.toAmount( | |
IBentoERC20(address(MIM)), | |
yvWethCauldron.userCollateralShare(address(this)), | |
false | |
); | |
// reference for math https://github.com/Abracadabra-money/magic-einternet-money/blob/455f0f6831cbcb659bbe46b6d8f39d0aafd4e5c3/contracts/CauldronV2.sol#L163 | |
return | |
(((yvWethCauldron.userBorrowPart(address(this)) * | |
_totalBorrow.elastic * | |
yvWethCauldron.exchangeRate()) / _totalBorrow.base) * 10000) / | |
collateral / | |
1e18; | |
} | |
function borrow(uint256 borrowAmount) public onlyOwner { | |
(, uint256 shareOut) = yvWethCauldron.borrow( | |
address(this), | |
borrowAmount | |
); | |
(uint256 amountOut, ) = IBentoBoxV1(yvWethCauldron.bentoBox()).withdraw( | |
IBentoERC20(address(MIM)), | |
address(this), | |
msg.sender, | |
0, | |
shareOut | |
); | |
} | |
function repayAndWithdrawWETH() public onlyOwner { | |
IBentoBoxV1 bentoBox = IBentoBoxV1(yvWethCauldron.bentoBox()); | |
// TODO: probably can do it on permit (even directly in bentoBox) | |
MIM.transferFrom(msg.sender, address(this), MIM.balanceOf(msg.sender)); | |
MIM.approve(address(bentoBox), type(uint256).max); | |
// need to deposit in order to repay | |
bentoBox.deposit( | |
IBentoERC20(address(MIM)), | |
address(this), | |
address(this), | |
MIM.balanceOf(address(this)), | |
0 | |
); | |
// repay borrowed MIM | |
yvWethCauldron.repay( | |
address(this), | |
false, | |
yvWethCauldron.userBorrowPart(address(this)) | |
); | |
// transfer left over amount | |
MIM.transferFrom( | |
address(this), | |
msg.sender, | |
MIM.balanceOf(address(this)) | |
); | |
yvWethCauldron.removeCollateral( | |
address(this), | |
yvWethCauldron.userCollateralShare(address(this)) | |
); | |
(uint256 amountOut2, ) = bentoBox.withdraw( | |
IBentoERC20(address(yvWETH)), | |
address(this), | |
address(this), | |
// msg.sender, | |
bentoBox.balanceOf(IBentoERC20(address(yvWETH)), address(this)), | |
0 | |
); | |
yvWETH.withdraw(); | |
IERC20(WETH).transfer( | |
msg.sender, | |
IERC20(WETH).balanceOf(address(this)) | |
); | |
// console.log(yvWethCauldron.userCollateralShare(address(this))); | |
// console.log(yvWethCauldron.userCollateralShare(msg.sender)); | |
// console.log(yvWethCauldron.userBorrowPart(address(this))); | |
// console.log(yvWethCauldron.userBorrowPart(msg.sender)); | |
// console.log(yvWETH.balanceOf(address(this))); | |
// console.log(yvWETH.balanceOf(msg.sender)); | |
// console.log(MIM.balanceOf(address(this))); | |
// console.log(MIM.balanceOf(msg.sender)); | |
// console.log(IERC20(WETH).balanceOf(address(this))); | |
// console.log(IERC20(WETH).balanceOf(msg.sender)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment