Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(flash-loan): add flash loan #168

Merged
merged 14 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/Blue.sol
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity 0.8.20;

import {IIrm} from "src/interfaces/IIrm.sol";
import {IERC20} from "src/interfaces/IERC20.sol";
import {IFlashLender} from "src/interfaces/IFlashLender.sol";
import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol";

import {Errors} from "./libraries/Errors.sol";
import {SharesMath} from "src/libraries/SharesMath.sol";
Expand All @@ -14,7 +16,7 @@ uint256 constant WAD = 1e18;
uint256 constant MAX_FEE = 0.2e18;
uint256 constant ALPHA = 0.5e18;

contract Blue {
contract Blue is IFlashLender {
using SharesMath for uint256;
using FixedPointMathLib for uint256;
using SafeTransferLib for IERC20;
Expand Down Expand Up @@ -245,6 +247,17 @@ contract Blue {
market.borrowableAsset.safeTransferFrom(msg.sender, address(this), repaid);
}

// Flash Loans.

/// @inheritdoc IFlashLender
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
function flashLoan(IFlashBorrower receiver, address token, uint256 amount, bytes calldata data) external {
IERC20(token).safeTransfer(address(receiver), amount);

receiver.onFlashLoan(msg.sender, token, amount, data);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

IERC20(token).safeTransferFrom(address(receiver), address(this), amount);
}

// Position management.

function setApproval(address manager, bool isAllowed) external {
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/IERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ pragma solidity >=0.5.0;

/// @dev Empty because we only call functions in assembly. It prevents calling
/// transfer (transferFrom) instead of safeTransfer (safeTransferFrom).
interface IERC20 {}
interface IERC20 {
function balanceOf(address owner) external view returns (uint256);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
}
13 changes: 13 additions & 0 deletions src/interfaces/IFlashBorrower.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.5.0;

interface IFlashBorrower {
/**
* @dev Receives a flash loan.
* @param initiator The initiator of the loan.
* @param token The token lent.
* @param amount The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function onFlashLoan(address initiator, address token, uint256 amount, bytes calldata data) external;
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
}
15 changes: 15 additions & 0 deletions src/interfaces/IFlashLender.sol
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.5.0;

import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol";

interface IFlashLender {
/**
* @dev Initiate a flash loan.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param token The token lent.
* @param amount The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan(IFlashBorrower receiver, address token, uint256 amount, bytes calldata data) external;
}
2 changes: 2 additions & 0 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ library Errors {
string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity";

string internal constant HEALTHY_POSITION = "position is healthy";

string internal constant INVALID_SUCCESS_HASH = "invalid success hash";
}
26 changes: 26 additions & 0 deletions src/mocks/FlashBorrowerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {IFlashLender} from "src/interfaces/IFlashLender.sol";
import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol";

import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";

contract FlashBorrowerMock is IFlashBorrower {
using SafeTransferLib for ERC20;

IFlashLender private immutable _LENDER;

constructor(IFlashLender lender) {
_LENDER = lender;
}

/* EXTERNAL */

/// @inheritdoc IFlashBorrower
function onFlashLoan(address, address token, uint256 amount, bytes calldata) external {
require(msg.sender == address(_LENDER), "invalid lender");

ERC20(token).safeApprove(address(_LENDER), amount);
}
}
14 changes: 14 additions & 0 deletions test/forge/Blue.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "src/Blue.sol";
import {ERC20Mock as ERC20} from "src/mocks/ERC20Mock.sol";
import {OracleMock as Oracle} from "src/mocks/OracleMock.sol";
import {IrmMock as Irm} from "src/mocks/IrmMock.sol";
import {FlashBorrowerMock} from "src/mocks/FlashBorrowerMock.sol";

contract BlueTest is Test {
using FixedPointMathLib for uint256;
Expand All @@ -25,6 +26,7 @@ contract BlueTest is Test {
Irm private irm;
Market public market;
Id public id;
FlashBorrowerMock internal flashBorrower;

function setUp() public {
// Create Blue.
Expand All @@ -35,6 +37,7 @@ contract BlueTest is Test {
collateralAsset = new ERC20("collateral", "C", 18);
borrowableOracle = new Oracle();
collateralOracle = new Oracle();
flashBorrower = new FlashBorrowerMock(blue);

irm = new Irm(blue);
market = Market(
Expand Down Expand Up @@ -685,6 +688,17 @@ contract BlueTest is Test {

vm.stopPrank();
}

function testFlashLoan(uint256 amount) public {
amount = bound(amount, 1, 2 ** 64);

borrowableAsset.setBalance(address(this), amount);
blue.supply(market, amount, address(this));

blue.flashLoan(flashBorrower, address(borrowableAsset), amount, bytes(""));
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved

assertEq(borrowableAsset.balanceOf(address(blue)), amount, "balanceOf");
}
}

function neq(Market memory a, Market memory b) pure returns (bool) {
Expand Down
15 changes: 15 additions & 0 deletions test/hardhat/Blue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { expect } from "chai";
import { BigNumber, constants, utils } from "ethers";
import hre from "hardhat";
import { Blue, OracleMock, ERC20Mock, IrmMock } from "types";
import { FlashBorrowerMock } from "types/src/mocks/FlashBorrowerMock";

const closePositions = false;
const initBalance = constants.MaxUint256.div(2);
Expand Down Expand Up @@ -45,6 +46,7 @@ describe("Blue", () => {
let borrowableOracle: OracleMock;
let collateralOracle: OracleMock;
let irm: IrmMock;
let flashBorrower: FlashBorrowerMock;

let market: Market;
let id: Buffer;
Expand Down Expand Up @@ -110,6 +112,10 @@ describe("Blue", () => {

await borrowable.setBalance(liquidator.address, initBalance);
await borrowable.connect(liquidator).approve(blue.address, constants.MaxUint256);

const FlashBorrowerFactory = await hre.ethers.getContractFactory("FlashBorrowerMock", admin);

flashBorrower = await FlashBorrowerFactory.deploy(blue.address);
});

it("should simulate gas cost [main]", async () => {
Expand Down Expand Up @@ -185,4 +191,13 @@ describe("Blue", () => {
await borrowableOracle.setPrice(BigNumber.WAD);
}
});

it("should simuate gas cost [flashloan]", async () => {
const user = signers[0];
const amount = BigNumber.WAD;

await blue.connect(user).supply(market, amount, user.address);

await blue.flashLoan(flashBorrower.address, borrowable.address, amount.div(2), []);
});
});