Skip to content

Instantly share code, notes, and snippets.

@skyzer
Created January 29, 2022 01:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skyzer/d0706ec102b0eb4e1d35691a18984478 to your computer and use it in GitHub Desktop.
Save skyzer/d0706ec102b0eb4e1d35691a18984478 to your computer and use it in GitHub Desktop.
// 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