Skip to content

Commit 752f383

Browse files
authored
Merge pull request #168 from morpho-labs/feat/flashloan
feat(flash-loan): add flash loan
2 parents 30aeb34 + 5e8be49 commit 752f383

File tree

7 files changed

+95
-1
lines changed

7 files changed

+95
-1
lines changed

src/Blue.sol

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ pragma solidity 0.8.21;
33

44
import {IIrm} from "src/interfaces/IIrm.sol";
55
import {IERC20} from "src/interfaces/IERC20.sol";
6+
import {IFlashLender} from "src/interfaces/IFlashLender.sol";
7+
import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol";
68

79
import {Errors} from "./libraries/Errors.sol";
810
import {SharesMath} from "src/libraries/SharesMath.sol";
@@ -13,7 +15,7 @@ import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol";
1315
uint256 constant MAX_FEE = 0.25e18;
1416
uint256 constant ALPHA = 0.5e18;
1517

16-
contract Blue {
18+
contract Blue is IFlashLender {
1719
using SharesMath for uint256;
1820
using FixedPointMathLib for uint256;
1921
using SafeTransferLib for IERC20;
@@ -245,6 +247,17 @@ contract Blue {
245247
market.borrowableAsset.safeTransferFrom(msg.sender, address(this), repaid);
246248
}
247249

250+
// Flash Loans.
251+
252+
/// @inheritdoc IFlashLender
253+
function flashLoan(IFlashBorrower receiver, address token, uint256 amount, bytes calldata data) external {
254+
IERC20(token).safeTransfer(address(receiver), amount);
255+
256+
receiver.onFlashLoan(msg.sender, token, amount, data);
257+
258+
IERC20(token).safeTransferFrom(address(receiver), address(this), amount);
259+
}
260+
248261
// Position management.
249262

250263
function setApproval(address manager, bool isAllowed) external {

src/interfaces/IFlashBorrower.sol

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.5.0;
3+
4+
interface IFlashBorrower {
5+
/// @dev Receives a flash loan.
6+
/// @param initiator The initiator of the loan.
7+
/// @param token The token lent.
8+
/// @param amount The amount of tokens lent.
9+
/// @param data Arbitrary data structure, intended to contain user-defined parameters.
10+
function onFlashLoan(address initiator, address token, uint256 amount, bytes calldata data) external;
11+
}

src/interfaces/IFlashLender.sol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.5.0;
3+
4+
import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol";
5+
6+
interface IFlashLender {
7+
/// @dev Initiate a flash loan.
8+
/// @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
9+
/// @param token The token lent.
10+
/// @param amount The amount of tokens lent.
11+
/// @param data Arbitrary data structure, intended to contain user-defined parameters.
12+
function flashLoan(IFlashBorrower receiver, address token, uint256 amount, bytes calldata data) external;
13+
}

src/libraries/Errors.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ library Errors {
2525
string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity";
2626

2727
string internal constant HEALTHY_POSITION = "position is healthy";
28+
29+
string internal constant INVALID_SUCCESS_HASH = "invalid success hash";
2830
}

src/mocks/FlashBorrowerMock.sol

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.0;
3+
4+
import {IFlashLender} from "src/interfaces/IFlashLender.sol";
5+
import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol";
6+
7+
import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
8+
9+
contract FlashBorrowerMock is IFlashBorrower {
10+
using SafeTransferLib for ERC20;
11+
12+
IFlashLender private immutable _LENDER;
13+
14+
constructor(IFlashLender lender) {
15+
_LENDER = lender;
16+
}
17+
18+
/* EXTERNAL */
19+
20+
/// @inheritdoc IFlashBorrower
21+
function onFlashLoan(address, address token, uint256 amount, bytes calldata) external {
22+
require(msg.sender == address(_LENDER), "invalid lender");
23+
24+
ERC20(token).safeApprove(address(_LENDER), amount);
25+
}
26+
}

test/forge/Blue.t.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import "src/Blue.sol";
88
import {ERC20Mock as ERC20} from "src/mocks/ERC20Mock.sol";
99
import {OracleMock as Oracle} from "src/mocks/OracleMock.sol";
1010
import {IrmMock as Irm} from "src/mocks/IrmMock.sol";
11+
import {FlashBorrowerMock} from "src/mocks/FlashBorrowerMock.sol";
1112

1213
contract BlueTest is Test {
1314
using MarketLib for Market;
@@ -26,6 +27,7 @@ contract BlueTest is Test {
2627
Irm private irm;
2728
Market public market;
2829
Id public id;
30+
FlashBorrowerMock internal flashBorrower;
2931

3032
function setUp() public {
3133
// Create Blue.
@@ -36,6 +38,7 @@ contract BlueTest is Test {
3638
collateralAsset = new ERC20("collateral", "C", 18);
3739
borrowableOracle = new Oracle();
3840
collateralOracle = new Oracle();
41+
flashBorrower = new FlashBorrowerMock(blue);
3942

4043
irm = new Irm(blue);
4144

@@ -690,6 +693,17 @@ contract BlueTest is Test {
690693
vm.stopPrank();
691694
}
692695

696+
function testFlashLoan(uint256 amount) public {
697+
amount = bound(amount, 1, 2 ** 64);
698+
699+
borrowableAsset.setBalance(address(this), amount);
700+
blue.supply(market, amount, address(this));
701+
702+
blue.flashLoan(flashBorrower, address(borrowableAsset), amount, bytes(""));
703+
704+
assertEq(borrowableAsset.balanceOf(address(blue)), amount, "balanceOf");
705+
}
706+
693707
function testExtsLoad(uint256 slot, bytes32 value0) public {
694708
bytes32[] memory slots = new bytes32[](2);
695709
slots[0] = bytes32(slot);

test/hardhat/Blue.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { expect } from "chai";
55
import { BigNumber, constants, utils } from "ethers";
66
import hre from "hardhat";
77
import { Blue, OracleMock, ERC20Mock, IrmMock } from "types";
8+
import { FlashBorrowerMock } from "types/src/mocks/FlashBorrowerMock";
89

910
const closePositions = false;
1011
const initBalance = constants.MaxUint256.div(2);
@@ -45,6 +46,7 @@ describe("Blue", () => {
4546
let borrowableOracle: OracleMock;
4647
let collateralOracle: OracleMock;
4748
let irm: IrmMock;
49+
let flashBorrower: FlashBorrowerMock;
4850

4951
let market: Market;
5052
let id: Buffer;
@@ -110,6 +112,10 @@ describe("Blue", () => {
110112

111113
await borrowable.setBalance(liquidator.address, initBalance);
112114
await borrowable.connect(liquidator).approve(blue.address, constants.MaxUint256);
115+
116+
const FlashBorrowerFactory = await hre.ethers.getContractFactory("FlashBorrowerMock", admin);
117+
118+
flashBorrower = await FlashBorrowerFactory.deploy(blue.address);
113119
});
114120

115121
it("should simulate gas cost [main]", async () => {
@@ -185,4 +191,13 @@ describe("Blue", () => {
185191
await borrowableOracle.setPrice(BigNumber.WAD);
186192
}
187193
});
194+
195+
it("should simuate gas cost [flashloan]", async () => {
196+
const user = signers[0];
197+
const amount = BigNumber.WAD;
198+
199+
await blue.connect(user).supply(market, amount, user.address);
200+
201+
await blue.flashLoan(flashBorrower.address, borrowable.address, amount.div(2), []);
202+
});
188203
});

0 commit comments

Comments
 (0)