Skip to content

Commit

Permalink
Merge branch 'main' into feat/amount-and-shares-as-input
Browse files Browse the repository at this point in the history
  • Loading branch information
MathisGD committed Aug 9, 2023
2 parents ba0586b + 3de9d77 commit 2b4e3f3
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 118 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/formatting.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Formatting

on:
push:
branches:
- main
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive

- name: Install node
uses: actions/setup-node@v3
with:
node-version: 18
cache: yarn

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Run Linter
run: yarn lint
27 changes: 22 additions & 5 deletions .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,27 @@ on:
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true

jobs:
forge-test:
strategy:
fail-fast: true
matrix:
type: ["slow", "fast"]
include:
- type: "slow"
fuzz-runs: 100000
max-test-rejects: 500000
invariant-runs: 1000
invariant-depth: 100
- type: "fast"
fuzz-runs: 256
max-test-rejects: 65536
invariant-runs: 256
invariant-depth: 15

runs-on: ubuntu-latest
steps:
Expand All @@ -21,10 +38,10 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Run Forge tests
- name: Run Forge tests in ${{ matrix.type }} mode
run: forge test -vvv
env:
FOUNDRY_FUZZ_RUNS: 100000
FOUNDRY_FUZZ_MAX_TEST_REJECTS: 500000
FOUNDRY_INVARIANT_RUNS: 1000
FOUNDRY_INVARIANT_DEPTH: 100
FOUNDRY_FUZZ_RUNS: ${{ matrix.fuzz-runs }}
FOUNDRY_FUZZ_MAX_TEST_REJECTS: ${{ matrix.max-test-rejects }}
FOUNDRY_INVARIANT_RUNS: ${{ matrix.invariant-runs }}
FOUNDRY_INVARIANT_DEPTH: ${{ matrix.invariant-depth }}
7 changes: 4 additions & 3 deletions .github/workflows/hardhat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ on:
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true

jobs:
yarn-test:
strategy:
fail-fast: true

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"printWidth": 120
"printWidth": 120,
"plugins": ["@trivago/prettier-plugin-sort-imports"]
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"scripts": {
"postinstall": "husky install",
"compile": "npx hardhat compile --force",
"test": "npx hardhat test"
"test": "npx hardhat test",
"lint": "prettier --check test/hardhat && forge fmt --check",
"lint:fix": "prettier --write test/hardhat && forge fmt"
},
"dependencies": {
"@ethersproject/abi": "^5.7.0",
Expand Down
28 changes: 15 additions & 13 deletions src/Blue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ contract Blue is IBlue {
Id id = market.id();
require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED);
require(newFee <= MAX_FEE, Errors.MAX_FEE_EXCEEDED);

// Accrue interests using the previous fee set before changing it.
_accrueInterests(market, id);

fee[id] = newFee;

emit SetFee(id, newFee);
Expand All @@ -131,6 +135,7 @@ contract Blue is IBlue {
require(isIrmEnabled[market.irm], Errors.IRM_NOT_ENABLED);
require(isLltvEnabled[market.lltv], Errors.LLTV_NOT_ENABLED);
require(lastUpdate[id] == 0, Errors.MARKET_CREATED);

lastUpdate[id] = block.timestamp;

emit CreateMarket(id, market);
Expand Down Expand Up @@ -301,15 +306,14 @@ contract Blue is IBlue {

_accrueInterests(market, id);

uint256 collateralPrice = IOracle(market.collateralOracle).price();
uint256 borrowablePrice = IOracle(market.borrowableOracle).price();
(uint256 collateralPrice, uint256 priceScale) = IOracle(market.oracle).price();

require(!_isHealthy(market, id, borrower, collateralPrice, borrowablePrice), Errors.HEALTHY_POSITION);
require(!_isHealthy(market, id, borrower, collateralPrice, priceScale), Errors.HEALTHY_POSITION);

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

borrowShares[id][borrower] -= repaidShares;
Expand Down Expand Up @@ -398,7 +402,7 @@ contract Blue is IBlue {

if (marketTotalBorrow != 0) {
uint256 borrowRate = IIrm(market.irm).borrowRate(market);
uint256 accruedInterests = marketTotalBorrow.mulWadDown(borrowRate * elapsed);
uint256 accruedInterests = marketTotalBorrow.mulWadDown(borrowRate.wTaylorCompounded(elapsed));
totalBorrow[id] = marketTotalBorrow + accruedInterests;
totalSupply[id] += accruedInterests;

Expand All @@ -422,22 +426,20 @@ contract Blue is IBlue {
function _isHealthy(Market memory market, Id id, address user) internal view returns (bool) {
if (borrowShares[id][user] == 0) return true;

uint256 collateralPrice = IOracle(market.collateralOracle).price();
uint256 borrowablePrice = IOracle(market.borrowableOracle).price();
(uint256 collateralPrice, uint256 priceScale) = IOracle(market.oracle).price();

return _isHealthy(market, id, user, collateralPrice, borrowablePrice);
return _isHealthy(market, id, user, collateralPrice, priceScale);
}

function _isHealthy(Market memory market, Id id, address user, uint256 collateralPrice, uint256 borrowablePrice)
function _isHealthy(Market memory market, Id id, address user, uint256 collateralPrice, uint256 priceScale)
internal
view
returns (bool)
{
uint256 borrowValue =
borrowShares[id][user].toAssetsUp(totalBorrow[id], totalBorrowShares[id]).mulWadUp(borrowablePrice);
uint256 collateralValue = collateral[id][user].mulWadDown(collateralPrice);
uint256 borrowed = borrowShares[id][user].toAssetsUp(totalBorrow[id], totalBorrowShares[id]);
uint256 maxBorrow = collateral[id][user].mulDivDown(collateralPrice, priceScale).mulWadDown(market.lltv);

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

// Storage view.
Expand Down
3 changes: 1 addition & 2 deletions src/interfaces/IBlue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ type Id is bytes32;
struct Market {
address borrowableAsset;
address collateralAsset;
address borrowableOracle;
address collateralOracle;
address oracle;
address irm;
uint256 lltv;
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/IOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
pragma solidity >=0.5.0;

interface IOracle {
function price() external view returns (uint256);
/// @notice Returns the price of the collateral asset quoted in the borrowable asset and the price's unit scale.
function price() external view returns (uint256 collateralPrice, uint256 scale);
}
21 changes: 21 additions & 0 deletions src/libraries/FixedPointMathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ library FixedPointMathLib {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}

/// @dev The sum of the last three terms in a four term taylor series expansion
/// to approximate a compound interest rate: (1 + x)^n - 1.
function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
uint256 firstTerm = x * n;
uint256 secondTerm = mulWadDown(firstTerm, x * zeroFloorSub(n, 1)) / 2;
uint256 thirdTerm = mulWadDown(secondTerm, x * zeroFloorSub(n, 2)) / 3;

return firstTerm + secondTerm + thirdTerm;
}

/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
Expand All @@ -55,4 +65,15 @@ library FixedPointMathLib {
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}

/*//////////////////////////////////////////////////////////////
INTEGER OPERATIONS
//////////////////////////////////////////////////////////////*/

/// @dev Returns max(x - y, 0).
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
}
10 changes: 8 additions & 2 deletions src/mocks/OracleMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ pragma solidity ^0.8.0;

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

import {FixedPointMathLib} from "src/libraries/FixedPointMathLib.sol";

contract OracleMock is IOracle {
uint256 public price;
uint256 internal _price;

function price() external view returns (uint256, uint256) {
return (_price, FixedPointMathLib.WAD);
}

function setPrice(uint256 newPrice) external {
price = newPrice;
_price = newPrice;
}
}
Loading

0 comments on commit 2b4e3f3

Please sign in to comment.