Skip to content

Commit

Permalink
Merge branch 'main' of github.com:morpho-labs/blue into test/tree
Browse files Browse the repository at this point in the history
merge
  • Loading branch information
Jean-Grimal committed Jul 28, 2023
2 parents 0e2abd4 + 752f383 commit 51475f5
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 18 deletions.
2 changes: 1 addition & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const config: HardhatUserConfig = {
solidity: {
compilers: [
{
version: "0.8.20",
version: "0.8.21",
settings: {
optimizer: {
enabled: true,
Expand Down
42 changes: 36 additions & 6 deletions src/Blue.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
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";
import {FixedPointMathLib} from "src/libraries/FixedPointMathLib.sol";
import {Id, Market, MarketLib} from "src/libraries/MarketLib.sol";
import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol";

uint256 constant WAD = 1e18;
uint256 constant MAX_FEE = 0.2e18;
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 @@ -75,7 +76,7 @@ contract Blue {
}

function enableLltv(uint256 lltv) external onlyOwner {
require(lltv < WAD, Errors.LLTV_TOO_HIGH);
require(lltv < FixedPointMathLib.WAD, Errors.LLTV_TOO_HIGH);
isLltvEnabled[lltv] = true;
}

Expand Down Expand Up @@ -222,7 +223,8 @@ contract Blue {
require(!_isHealthy(market, id, borrower, collateralPrice, borrowablePrice), Errors.HEALTHY_POSITION);

// The liquidation incentive is 1 + ALPHA * (1 / LLTV - 1).
uint256 incentive = WAD + ALPHA.mulWadDown(WAD.divWadDown(market.lltv) - WAD);
uint256 incentive = FixedPointMathLib.WAD
+ ALPHA.mulWadDown(FixedPointMathLib.WAD.divWadDown(market.lltv) - FixedPointMathLib.WAD);
uint256 repaid = seized.mulWadUp(collateralPrice).divWadUp(incentive).divWadUp(borrowablePrice);
uint256 repaidShares = repaid.toSharesDown(totalBorrow[id], totalBorrowShares[id]);

Expand All @@ -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 Expand Up @@ -300,4 +313,21 @@ contract Blue {

return collateralValue.mulWadDown(market.lltv) >= borrowValue;
}

// Storage view.

function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
uint256 nSlots = slots.length;

res = new bytes32[](nSlots);

for (uint256 i; i < nSlots;) {
bytes32 slot = slots[i++];

/// @solidity memory-safe-assembly
assembly {
mstore(add(res, mul(i, 32)), sload(slot))
}
}
}
}
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";
}
2 changes: 1 addition & 1 deletion src/mocks/ERC20Mock.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
pragma solidity 0.8.21;

import {ERC20} from "solmate/tokens/ERC20.sol";

Expand Down
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);
}
}
2 changes: 1 addition & 1 deletion src/mocks/IrmMock.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
pragma solidity 0.8.21;

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

Expand Down
2 changes: 1 addition & 1 deletion src/mocks/OracleMock.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
pragma solidity 0.8.21;

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

Expand Down
50 changes: 42 additions & 8 deletions test/forge/Blue.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
pragma solidity 0.8.21;

import "forge-std/Test.sol";
import "forge-std/console.sol";
Expand All @@ -8,8 +8,10 @@ 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;
using FixedPointMathLib for uint256;

address private constant BORROWER = address(1234);
Expand All @@ -25,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 @@ -35,8 +38,10 @@ 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(
IERC20(address(borrowableAsset)),
IERC20(address(collateralAsset)),
Expand All @@ -45,7 +50,7 @@ contract BlueTest is Test {
irm,
LLTV
);
id = Id.wrap(keccak256(abi.encode(market)));
id = market.id();

vm.startPrank(OWNER);
blue.enableIrm(irm);
Expand Down Expand Up @@ -173,7 +178,7 @@ contract BlueTest is Test {
}

function testEnableLltv(uint256 newLltv) public {
newLltv = bound(newLltv, 0, WAD - 1);
newLltv = bound(newLltv, 0, FixedPointMathLib.WAD - 1);

vm.prank(OWNER);
blue.enableLltv(newLltv);
Expand All @@ -182,7 +187,7 @@ contract BlueTest is Test {
}

function testEnableLltvShouldFailWhenLltvTooHigh(uint256 newLltv) public {
newLltv = bound(newLltv, WAD, type(uint256).max);
newLltv = bound(newLltv, FixedPointMathLib.WAD, type(uint256).max);

vm.prank(OWNER);
vm.expectRevert(bytes(Errors.LLTV_TOO_HIGH));
Expand All @@ -208,7 +213,7 @@ contract BlueTest is Test {

function testSetFeeShouldRevertIfMarketNotCreated(Market memory marketFuzz, uint256 fee) public {
vm.assume(neq(marketFuzz, market));
fee = bound(fee, 0, WAD);
fee = bound(fee, 0, FixedPointMathLib.WAD);

vm.prank(OWNER);
vm.expectRevert("unknown market");
Expand All @@ -217,7 +222,7 @@ contract BlueTest is Test {

function testSetFeeShouldRevertIfNotOwner(uint256 fee, address caller) public {
vm.assume(caller != OWNER);
fee = bound(fee, 0, WAD);
fee = bound(fee, 0, FixedPointMathLib.WAD);

vm.expectRevert("not owner");
blue.setFee(market, fee);
Expand Down Expand Up @@ -484,7 +489,8 @@ contract BlueTest is Test {
uint256 borrowingPower = amountCollateral.mulWadDown(LLTV);
uint256 amountBorrowed = borrowingPower.mulWadDown(0.8e18);
uint256 toSeize = amountCollateral.mulWadDown(LLTV);
uint256 incentive = WAD + ALPHA.mulWadDown(WAD.divWadDown(LLTV) - WAD);
uint256 incentive =
FixedPointMathLib.WAD + ALPHA.mulWadDown(FixedPointMathLib.WAD.divWadDown(LLTV) - FixedPointMathLib.WAD);

borrowableAsset.setBalance(address(this), amountLent);
collateralAsset.setBalance(BORROWER, amountCollateral);
Expand Down Expand Up @@ -527,7 +533,8 @@ contract BlueTest is Test {
uint256 borrowingPower = amountCollateral.mulWadDown(LLTV);
uint256 amountBorrowed = borrowingPower.mulWadDown(0.8e18);
uint256 toSeize = amountCollateral;
uint256 incentive = WAD + ALPHA.mulWadDown(WAD.divWadDown(market.lltv) - WAD);
uint256 incentive = FixedPointMathLib.WAD
+ ALPHA.mulWadDown(FixedPointMathLib.WAD.divWadDown(market.lltv) - FixedPointMathLib.WAD);

borrowableAsset.setBalance(address(this), amountLent);
collateralAsset.setBalance(BORROWER, amountCollateral);
Expand Down Expand Up @@ -685,6 +692,33 @@ 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);
slots[1] = bytes32(slot / 2);

bytes32 value1 = keccak256(abi.encode(value0));
vm.store(address(blue), slots[0], value0);
vm.store(address(blue), slots[1], value1);

bytes32[] memory values = blue.extsload(slots);

assertEq(values.length, 2, "values.length");
assertEq(values[0], slot > 0 ? value0 : value1, "value0");
assertEq(values[1], value1, "value1");
}
}

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), []);
});
});

0 comments on commit 51475f5

Please sign in to comment.