Skip to content

Commit

Permalink
Merge pull request #168 from morpho-labs/feat/flashloan
Browse files Browse the repository at this point in the history
feat(flash-loan): add flash loan
  • Loading branch information
MathisGD authored Jul 28, 2023
2 parents 30aeb34 + 5e8be49 commit 752f383
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 1 deletion.
15 changes: 14 additions & 1 deletion src/Blue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity 0.8.21;

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 @@ -13,7 +15,7 @@ import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol";
uint256 constant MAX_FEE = 0.25e18;
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
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);

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

// Position management.

function setApproval(address manager, bool isAllowed) external {
Expand Down
11 changes: 11 additions & 0 deletions src/interfaces/IFlashBorrower.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 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;
}
13 changes: 13 additions & 0 deletions src/interfaces/IFlashLender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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 MarketLib for Market;
Expand All @@ -26,6 +27,7 @@ contract BlueTest is Test {
Irm private irm;
Market public market;
Id public id;
FlashBorrowerMock internal flashBorrower;

function setUp() public {
// Create Blue.
Expand All @@ -36,6 +38,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);

Expand Down Expand Up @@ -690,6 +693,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(""));

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

function testExtsLoad(uint256 slot, bytes32 value0) public {
bytes32[] memory slots = new bytes32[](2);
slots[0] = bytes32(slot);
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), []);
});
});

0 comments on commit 752f383

Please sign in to comment.