diff --git a/src/Blue.sol b/src/Blue.sol index dda3d94e2..b523a6023 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -1,25 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; -import { - IBlueLiquidateCallback, - IBlueRepayCallback, - IBlueSupplyCallback, - IBlueSupplyCollateralCallback, - IBlueFlashLoanCallback -} from "./interfaces/IBlueCallbacks.sol"; +import "./interfaces/IBlue.sol"; +import "./interfaces/IBlueCallbacks.sol"; import {IIrm} from "./interfaces/IIrm.sol"; import {IERC20} from "./interfaces/IERC20.sol"; import {IOracle} from "./interfaces/IOracle.sol"; -import {Id, Market, Signature, IBlue} from "./interfaces/IBlue.sol"; -import {Errors} from "./libraries/Errors.sol"; -import {EventsLib} from "./libraries/EventsLib.sol"; import {UtilsLib} from "./libraries/UtilsLib.sol"; -import {SharesMath} from "./libraries/SharesMath.sol"; -import {FixedPointMathLib} from "./libraries/FixedPointMathLib.sol"; +import {EventsLib} from "./libraries/EventsLib.sol"; +import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {MarketLib} from "./libraries/MarketLib.sol"; +import {SharesMathLib} from "./libraries/SharesMathLib.sol"; import {SafeTransferLib} from "./libraries/SafeTransferLib.sol"; +import {FixedPointMathLib, WAD} from "./libraries/FixedPointMathLib.sol"; uint256 constant MAX_FEE = 0.25e18; uint256 constant ALPHA = 0.5e18; @@ -32,10 +26,10 @@ bytes32 constant AUTHORIZATION_TYPEHASH = keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)"); contract Blue is IBlue { - using SharesMath for uint256; - using FixedPointMathLib for uint256; - using SafeTransferLib for IERC20; using MarketLib for Market; + using SharesMathLib for uint256; + using SafeTransferLib for IERC20; + using FixedPointMathLib for uint256; // Immutables. @@ -85,7 +79,7 @@ contract Blue is IBlue { // Modifiers. modifier onlyOwner() { - require(msg.sender == owner, Errors.NOT_OWNER); + require(msg.sender == owner, ErrorsLib.NOT_OWNER); _; } @@ -104,7 +98,7 @@ contract Blue is IBlue { } function enableLltv(uint256 lltv) external onlyOwner { - require(lltv < FixedPointMathLib.WAD, Errors.LLTV_TOO_HIGH); + require(lltv < WAD, ErrorsLib.LLTV_TOO_HIGH); isLltvEnabled[lltv] = true; emit EventsLib.EnableLltv(lltv); @@ -113,8 +107,8 @@ contract Blue is IBlue { /// @notice It is the owner's responsibility to ensure a fee recipient is set before setting a non-zero fee. function setFee(Market memory market, uint256 newFee) external onlyOwner { Id id = market.id(); - require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); - require(newFee <= MAX_FEE, Errors.MAX_FEE_EXCEEDED); + require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); + require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED); // Accrue interests using the previous fee set before changing it. _accrueInterests(market, id); @@ -134,9 +128,9 @@ contract Blue is IBlue { function createMarket(Market memory market) external { Id id = market.id(); - require(isIrmEnabled[market.irm], Errors.IRM_NOT_ENABLED); - require(isLltvEnabled[market.lltv], Errors.LLTV_NOT_ENABLED); - require(lastUpdate[id] == 0, Errors.MARKET_CREATED); + require(isIrmEnabled[market.irm], ErrorsLib.IRM_NOT_ENABLED); + require(isLltvEnabled[market.lltv], ErrorsLib.LLTV_NOT_ENABLED); + require(lastUpdate[id] == 0, ErrorsLib.MARKET_CREATED); lastUpdate[id] = block.timestamp; @@ -149,9 +143,9 @@ contract Blue is IBlue { external { Id id = market.id(); - require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); - require(UtilsLib.exactlyOneZero(amount, shares), Errors.INCONSISTENT_INPUT); - require(onBehalf != address(0), Errors.ZERO_ADDRESS); + require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); + require(UtilsLib.exactlyOneZero(amount, shares), ErrorsLib.INCONSISTENT_INPUT); + require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS); _accrueInterests(market, id); @@ -173,11 +167,11 @@ contract Blue is IBlue { external { Id id = market.id(); - require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); - require(UtilsLib.exactlyOneZero(amount, shares), Errors.INCONSISTENT_INPUT); + require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); + require(UtilsLib.exactlyOneZero(amount, shares), ErrorsLib.INCONSISTENT_INPUT); // No need to verify that onBehalf != address(0) thanks to the authorization check. - require(receiver != address(0), Errors.ZERO_ADDRESS); - require(_isSenderAuthorized(onBehalf), Errors.UNAUTHORIZED); + require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterests(market, id); @@ -190,7 +184,7 @@ contract Blue is IBlue { emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, amount, shares); - require(totalBorrow[id] <= totalSupply[id], Errors.INSUFFICIENT_LIQUIDITY); + require(totalBorrow[id] <= totalSupply[id], ErrorsLib.INSUFFICIENT_LIQUIDITY); IERC20(market.borrowableAsset).safeTransfer(receiver, amount); } @@ -201,11 +195,11 @@ contract Blue is IBlue { external { Id id = market.id(); - require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); - require(UtilsLib.exactlyOneZero(amount, shares), Errors.INCONSISTENT_INPUT); + require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); + require(UtilsLib.exactlyOneZero(amount, shares), ErrorsLib.INCONSISTENT_INPUT); // No need to verify that onBehalf != address(0) thanks to the authorization check. - require(receiver != address(0), Errors.ZERO_ADDRESS); - require(_isSenderAuthorized(onBehalf), Errors.UNAUTHORIZED); + require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterests(market, id); @@ -218,8 +212,8 @@ contract Blue is IBlue { emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, amount, shares); - require(_isHealthy(market, id, onBehalf), Errors.INSUFFICIENT_COLLATERAL); - require(totalBorrow[id] <= totalSupply[id], Errors.INSUFFICIENT_LIQUIDITY); + require(_isHealthy(market, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL); + require(totalBorrow[id] <= totalSupply[id], ErrorsLib.INSUFFICIENT_LIQUIDITY); IERC20(market.borrowableAsset).safeTransfer(receiver, amount); } @@ -228,9 +222,9 @@ contract Blue is IBlue { external { Id id = market.id(); - require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); - require(UtilsLib.exactlyOneZero(amount, shares), Errors.INCONSISTENT_INPUT); - require(onBehalf != address(0), Errors.ZERO_ADDRESS); + require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); + require(UtilsLib.exactlyOneZero(amount, shares), ErrorsLib.INCONSISTENT_INPUT); + require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS); _accrueInterests(market, id); @@ -253,9 +247,9 @@ contract Blue is IBlue { /// @dev Don't accrue interests because it's not required and it saves gas. function supplyCollateral(Market memory market, uint256 amount, address onBehalf, bytes calldata data) external { Id id = market.id(); - require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); - require(amount != 0, Errors.ZERO_AMOUNT); - require(onBehalf != address(0), Errors.ZERO_ADDRESS); + require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); + require(amount != 0, ErrorsLib.ZERO_AMOUNT); + require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS); // Don't accrue interests because it's not required and it saves gas. @@ -270,11 +264,11 @@ contract Blue is IBlue { function withdrawCollateral(Market memory market, uint256 amount, address onBehalf, address receiver) external { Id id = market.id(); - require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); - require(amount != 0, Errors.ZERO_AMOUNT); + require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); + require(amount != 0, ErrorsLib.ZERO_AMOUNT); // No need to verify that onBehalf != address(0) thanks to the authorization check. - require(receiver != address(0), Errors.ZERO_ADDRESS); - require(_isSenderAuthorized(onBehalf), Errors.UNAUTHORIZED); + require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterests(market, id); @@ -282,7 +276,7 @@ contract Blue is IBlue { emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, amount); - require(_isHealthy(market, id, onBehalf), Errors.INSUFFICIENT_COLLATERAL); + require(_isHealthy(market, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL); IERC20(market.collateralAsset).safeTransfer(receiver, amount); } @@ -291,19 +285,18 @@ contract Blue is IBlue { function liquidate(Market memory market, address borrower, uint256 seized, bytes calldata data) external { Id id = market.id(); - require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); - require(seized != 0, Errors.ZERO_AMOUNT); + require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); + require(seized != 0, ErrorsLib.ZERO_AMOUNT); _accrueInterests(market, id); (uint256 collateralPrice, uint256 priceScale) = IOracle(market.oracle).price(); - require(!_isHealthy(market, id, borrower, collateralPrice, priceScale), Errors.HEALTHY_POSITION); + require(!_isHealthy(market, id, borrower, collateralPrice, priceScale), ErrorsLib.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.mulDivUp(collateralPrice, priceScale).divWadUp(incentive); + uint256 incentive = WAD + ALPHA.wMulDown(WAD.wDivDown(market.lltv) - WAD); + uint256 repaid = seized.mulDivUp(collateralPrice, priceScale).wDivUp(incentive); uint256 repaidShares = repaid.toSharesDown(totalBorrow[id], totalBorrowShares[id]); borrowShares[id][borrower] -= repaidShares; @@ -360,7 +353,7 @@ contract Blue is IBlue { uint256 deadline, Signature calldata signature ) external { - require(block.timestamp < deadline, Errors.SIGNATURE_EXPIRED); + require(block.timestamp < deadline, ErrorsLib.SIGNATURE_EXPIRED); uint256 usedNonce = nonce[authorizer]++; bytes32 hashStruct = @@ -368,7 +361,7 @@ contract Blue is IBlue { bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct)); address signatory = ecrecover(digest, signature.v, signature.r, signature.s); - require(signatory != address(0) && authorizer == signatory, Errors.INVALID_SIGNATURE); + require(signatory != address(0) && authorizer == signatory, ErrorsLib.INVALID_SIGNATURE); emit EventsLib.IncrementNonce(msg.sender, authorizer, usedNonce); @@ -392,13 +385,13 @@ contract Blue is IBlue { if (marketTotalBorrow != 0) { uint256 borrowRate = IIrm(market.irm).borrowRate(market); - uint256 accruedInterests = marketTotalBorrow.mulWadDown(borrowRate.wTaylorCompounded(elapsed)); + uint256 accruedInterests = marketTotalBorrow.wMulDown(borrowRate.wTaylorCompounded(elapsed)); totalBorrow[id] = marketTotalBorrow + accruedInterests; totalSupply[id] += accruedInterests; uint256 feeShares; if (fee[id] != 0) { - uint256 feeAmount = accruedInterests.mulWadDown(fee[id]); + uint256 feeAmount = accruedInterests.wMulDown(fee[id]); // The fee amount is subtracted from the total supply in this calculation to compensate for the fact that total supply is already updated. feeShares = feeAmount.mulDivDown(totalSupplyShares[id], totalSupply[id] - feeAmount); supplyShares[id][feeRecipient] += feeShares; @@ -427,7 +420,7 @@ contract Blue is IBlue { returns (bool) { uint256 borrowed = borrowShares[id][user].toAssetsUp(totalBorrow[id], totalBorrowShares[id]); - uint256 maxBorrow = collateral[id][user].mulDivDown(collateralPrice, priceScale).mulWadDown(market.lltv); + uint256 maxBorrow = collateral[id][user].mulDivDown(collateralPrice, priceScale).wMulDown(market.lltv); return maxBorrow >= borrowed; } diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol deleted file mode 100644 index b7ac25c7b..000000000 --- a/src/libraries/Errors.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -library Errors { - string internal constant NOT_OWNER = "not owner"; - - string internal constant LLTV_TOO_HIGH = "LLTV too high"; - - string internal constant MAX_FEE_EXCEEDED = "MAX_FEE exceeded"; - - string internal constant IRM_NOT_ENABLED = "IRM not enabled"; - - string internal constant LLTV_NOT_ENABLED = "LLTV not enabled"; - - string internal constant MARKET_CREATED = "market created"; - - string internal constant MARKET_NOT_CREATED = "market not created"; - - string internal constant ZERO_AMOUNT = "zero amount"; - - string internal constant INCONSISTENT_INPUT = "inconsistent input"; - - string internal constant ZERO_ADDRESS = "zero address"; - - string internal constant UNAUTHORIZED = "unauthorized"; - - string internal constant INSUFFICIENT_COLLATERAL = "insufficient collateral"; - - string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity"; - - string internal constant HEALTHY_POSITION = "position is healthy"; - - string internal constant INVALID_SIGNATURE = "invalid signature"; - - string internal constant SIGNATURE_EXPIRED = "signature expired"; -} diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol new file mode 100644 index 000000000..885a5d3e9 --- /dev/null +++ b/src/libraries/ErrorsLib.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +library ErrorsLib { + string constant NOT_OWNER = "not owner"; + string constant LLTV_TOO_HIGH = "LLTV too high"; + string constant MAX_FEE_EXCEEDED = "MAX_FEE exceeded"; + string constant IRM_NOT_ENABLED = "IRM not enabled"; + string constant LLTV_NOT_ENABLED = "LLTV not enabled"; + string constant MARKET_CREATED = "market created"; + string constant MARKET_NOT_CREATED = "market not created"; + string constant INCONSISTENT_INPUT = "inconsistent input"; + string constant ZERO_AMOUNT = "zero amount"; + string constant ZERO_ADDRESS = "zero address"; + string constant UNAUTHORIZED = "unauthorized"; + string constant INSUFFICIENT_COLLATERAL = "insufficient collateral"; + string constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity"; + string constant HEALTHY_POSITION = "position is healthy"; + string constant INVALID_SIGNATURE = "invalid signature"; + string constant SIGNATURE_EXPIRED = "signature expired"; +} diff --git a/src/libraries/FixedPointMathLib.sol b/src/libraries/FixedPointMathLib.sol index 7c582aa1e..992bb109d 100644 --- a/src/libraries/FixedPointMathLib.sol +++ b/src/libraries/FixedPointMathLib.sol @@ -3,48 +3,45 @@ pragma solidity ^0.8.0; import {UtilsLib} from "./UtilsLib.sol"; -/// @notice Arithmetic library with operations for fixed-point numbers. -/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) -/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) -library FixedPointMathLib { - /*////////////////////////////////////////////////////////////// - SIMPLIFIED FIXED POINT OPERATIONS - //////////////////////////////////////////////////////////////*/ +uint256 constant WAD = 1e18; +/// @notice Fixed-point arithmetic library. +/// @dev Greatly inspired by Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) +/// and by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) +library FixedPointMathLib { uint256 internal constant MAX_UINT256 = 2 ** 256 - 1; - uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. - - function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { - return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. + /// @dev (x * y) / WAD rounded down. + function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, y, WAD); } - function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { - return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. + /// @dev (x * y) / WAD rounded up. + function wMulUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, y, WAD); } - function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { - return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. + /// @dev (x * WAD) / y rounded down. + function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, WAD, y); } - function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { - return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. + /// @dev (x * WAD) / y rounded up. + function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, WAD, y); } /// @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 * UtilsLib.zeroFloorSub(n, 1)) / 2; - uint256 thirdTerm = mulWadDown(secondTerm, x * UtilsLib.zeroFloorSub(n, 2)) / 3; + uint256 secondTerm = wMulDown(firstTerm, x * UtilsLib.zeroFloorSub(n, 1)) / 2; + uint256 thirdTerm = wMulDown(secondTerm, x * UtilsLib.zeroFloorSub(n, 2)) / 3; return firstTerm + secondTerm + thirdTerm; } - /*////////////////////////////////////////////////////////////// - LOW LEVEL FIXED POINT OPERATIONS - //////////////////////////////////////////////////////////////*/ - + /// @dev (x * y) / denominator rounded down. function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { @@ -56,6 +53,7 @@ library FixedPointMathLib { } } + /// @dev (x * y) / denominator rounded up. function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { diff --git a/src/libraries/SharesMath.sol b/src/libraries/SharesMathLib.sol similarity index 98% rename from src/libraries/SharesMath.sol rename to src/libraries/SharesMathLib.sol index 3d9728c3d..a19741766 100644 --- a/src/libraries/SharesMath.sol +++ b/src/libraries/SharesMathLib.sol @@ -5,7 +5,7 @@ import {FixedPointMathLib} from "./FixedPointMathLib.sol"; /// @notice Shares management library. /// @dev This implementation mitigates share price manipulations, using OpenZeppelin's method of virtual shares: https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack. -library SharesMath { +library SharesMathLib { using FixedPointMathLib for uint256; uint256 internal constant VIRTUAL_SHARES = 1e18; diff --git a/src/mocks/IrmMock.sol b/src/mocks/IrmMock.sol index f0558d2ad..9ab5a07f3 100644 --- a/src/mocks/IrmMock.sol +++ b/src/mocks/IrmMock.sol @@ -19,7 +19,7 @@ contract IrmMock is IIrm { function borrowRate(Market memory market) external view returns (uint256) { Id id = market.id(); - uint256 utilization = BLUE.totalBorrow(id).divWadDown(BLUE.totalSupply(id)); + uint256 utilization = BLUE.totalBorrow(id).wDivDown(BLUE.totalSupply(id)); // Divide by the number of seconds in a year. // This is a very simple model (to refine later) where x% utilization corresponds to x% APR. diff --git a/src/mocks/OracleMock.sol b/src/mocks/OracleMock.sol index 8792ffa2e..86fe3d2af 100644 --- a/src/mocks/OracleMock.sol +++ b/src/mocks/OracleMock.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.0; import {IOracle} from "../interfaces/IOracle.sol"; -import {FixedPointMathLib} from "src/libraries/FixedPointMathLib.sol"; +import {FixedPointMathLib, WAD} from "src/libraries/FixedPointMathLib.sol"; contract OracleMock is IOracle { uint256 internal _price; function price() external view returns (uint256, uint256) { - return (_price, FixedPointMathLib.WAD); + return (_price, WAD); } function setPrice(uint256 newPrice) external { diff --git a/test/forge/Blue.t.sol b/test/forge/Blue.t.sol index 8745ed857..6d0cd6694 100644 --- a/test/forge/Blue.t.sol +++ b/test/forge/Blue.t.sol @@ -7,7 +7,7 @@ import "forge-std/console.sol"; import {SigUtils} from "./helpers/SigUtils.sol"; import "src/Blue.sol"; -import {SharesMath} from "src/libraries/SharesMath.sol"; +import {SharesMathLib} from "src/libraries/SharesMathLib.sol"; import { IBlueLiquidateCallback, IBlueRepayCallback, @@ -26,7 +26,7 @@ contract BlueTest is IBlueLiquidateCallback { using MarketLib for Market; - using SharesMath for uint256; + using SharesMathLib for uint256; using stdStorage for StdStorage; using FixedPointMathLib for uint256; @@ -40,8 +40,8 @@ contract BlueTest is ERC20 private collateralAsset; Oracle private oracle; Irm private irm; - Market public market; - Id public id; + Market private market; + Id private id; function setUp() public { // Create Blue. @@ -63,7 +63,7 @@ contract BlueTest is blue.createMarket(market); vm.stopPrank(); - oracle.setPrice(FixedPointMathLib.WAD); + oracle.setPrice(WAD); borrowableAsset.approve(address(blue), type(uint256).max); collateralAsset.approve(address(blue), type(uint256).max); @@ -97,7 +97,7 @@ contract BlueTest is uint256 totalShares = blue.totalSupplyShares(id); uint256 totalSupply = blue.totalSupply(id); - return supplyShares.divWadDown(totalShares).mulWadDown(totalSupply); + return supplyShares.wDivDown(totalShares).wMulDown(totalSupply); } function borrowBalance(address user) internal view returns (uint256) { @@ -106,7 +106,7 @@ contract BlueTest is uint256 totalShares = blue.totalBorrowShares(id); uint256 totalBorrow = blue.totalBorrow(id); - return borrowerShares.divWadUp(totalShares).mulWadUp(totalBorrow); + return borrowerShares.wDivUp(totalShares).wMulUp(totalBorrow); } // Invariants @@ -149,7 +149,7 @@ contract BlueTest is Blue blue2 = new Blue(OWNER); vm.prank(attacker); - vm.expectRevert(bytes(Errors.NOT_OWNER)); + vm.expectRevert(bytes(ErrorsLib.NOT_OWNER)); blue2.setOwner(newOwner); } @@ -157,7 +157,7 @@ contract BlueTest is vm.assume(attacker != blue.owner()); vm.prank(attacker); - vm.expectRevert(bytes(Errors.NOT_OWNER)); + vm.expectRevert(bytes(ErrorsLib.NOT_OWNER)); blue.enableIrm(newIrm); } @@ -181,7 +181,7 @@ contract BlueTest is vm.assume(marketFuzz.irm != address(irm)); vm.prank(OWNER); - vm.expectRevert(bytes(Errors.IRM_NOT_ENABLED)); + vm.expectRevert(bytes(ErrorsLib.IRM_NOT_ENABLED)); blue.createMarket(marketFuzz); } @@ -189,12 +189,12 @@ contract BlueTest is vm.assume(attacker != OWNER); vm.prank(attacker); - vm.expectRevert(bytes(Errors.NOT_OWNER)); + vm.expectRevert(bytes(ErrorsLib.NOT_OWNER)); blue.enableLltv(newLltv); } function testEnableLltv(uint256 newLltv) public { - newLltv = bound(newLltv, 0, FixedPointMathLib.WAD - 1); + newLltv = bound(newLltv, 0, WAD - 1); vm.prank(OWNER); blue.enableLltv(newLltv); @@ -203,10 +203,10 @@ contract BlueTest is } function testEnableLltvShouldFailWhenLltvTooHigh(uint256 newLltv) public { - newLltv = bound(newLltv, FixedPointMathLib.WAD, type(uint256).max); + newLltv = bound(newLltv, WAD, type(uint256).max); vm.prank(OWNER); - vm.expectRevert(bytes(Errors.LLTV_TOO_HIGH)); + vm.expectRevert(bytes(ErrorsLib.LLTV_TOO_HIGH)); blue.enableLltv(newLltv); } @@ -223,24 +223,24 @@ contract BlueTest is fee = bound(fee, MAX_FEE + 1, type(uint256).max); vm.prank(OWNER); - vm.expectRevert(bytes(Errors.MAX_FEE_EXCEEDED)); + vm.expectRevert(bytes(ErrorsLib.MAX_FEE_EXCEEDED)); blue.setFee(market, fee); } function testSetFeeShouldRevertIfMarketNotCreated(Market memory marketFuzz, uint256 fee) public { vm.assume(neq(marketFuzz, market)); - fee = bound(fee, 0, FixedPointMathLib.WAD); + fee = bound(fee, 0, WAD); vm.prank(OWNER); - vm.expectRevert(bytes(Errors.MARKET_NOT_CREATED)); + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); blue.setFee(marketFuzz, fee); } function testSetFeeShouldRevertIfNotOwner(uint256 fee, address caller) public { vm.assume(caller != OWNER); - fee = bound(fee, 0, FixedPointMathLib.WAD); + fee = bound(fee, 0, WAD); - vm.expectRevert(bytes(Errors.NOT_OWNER)); + vm.expectRevert(bytes(ErrorsLib.NOT_OWNER)); blue.setFee(market, fee); } @@ -254,7 +254,7 @@ contract BlueTest is function testSetFeeRecipientShouldRevertIfNotOwner(address caller, address recipient) public { vm.assume(caller != OWNER); - vm.expectRevert(bytes(Errors.NOT_OWNER)); + vm.expectRevert(bytes(ErrorsLib.NOT_OWNER)); vm.prank(caller); blue.setFeeRecipient(recipient); } @@ -274,7 +274,7 @@ contract BlueTest is borrowableAsset.setBalance(address(this), amountLent); blue.supply(market, amountLent, 0, address(this), hex""); - uint256 collateralAmount = amountBorrowed.divWadUp(LLTV); + uint256 collateralAmount = amountBorrowed.wDivUp(LLTV); collateralAsset.setBalance(address(this), collateralAmount); blue.supplyCollateral(market, collateralAmount, BORROWER, hex""); @@ -295,7 +295,7 @@ contract BlueTest is vm.assume(totalSupplyAfter > totalSupplyBefore); uint256 accrued = totalSupplyAfter - totalSupplyBefore; - uint256 expectedFee = accrued.mulWadDown(fee); + uint256 expectedFee = accrued.wMulDown(fee); uint256 expectedFeeShares = expectedFee.mulDivDown(totalSupplySharesBefore, totalSupplyAfter - expectedFee); assertEq(blue.supplyShares(id, recipient), expectedFeeShares); @@ -306,7 +306,7 @@ contract BlueTest is marketFuzz.irm = address(irm); vm.prank(OWNER); - vm.expectRevert(bytes(Errors.LLTV_NOT_ENABLED)); + vm.expectRevert(bytes(ErrorsLib.LLTV_NOT_ENABLED)); blue.createMarket(marketFuzz); } @@ -348,13 +348,13 @@ contract BlueTest is borrowableAsset.setBalance(address(this), amountLent); blue.supply(market, amountLent, 0, address(this), hex""); - uint256 collateralAmount = shares.toAssetsUp(blue.totalBorrow(id), blue.totalBorrowShares(id)).divWadUp(LLTV); + uint256 collateralAmount = shares.toAssetsUp(blue.totalBorrow(id), blue.totalBorrowShares(id)).wDivUp(LLTV); collateralAsset.setBalance(address(this), collateralAmount); blue.supplyCollateral(market, collateralAmount, BORROWER, hex""); if (amountBorrowed > amountLent) { vm.prank(BORROWER); - vm.expectRevert(bytes(Errors.INSUFFICIENT_LIQUIDITY)); + vm.expectRevert(bytes(ErrorsLib.INSUFFICIENT_LIQUIDITY)); blue.borrow(market, amountBorrowed, 0, BORROWER, receiver); return; } @@ -362,7 +362,7 @@ contract BlueTest is vm.prank(BORROWER); blue.borrow(market, amountBorrowed, 0, BORROWER, receiver); - assertEq(blue.borrowShares(id, BORROWER), amountBorrowed * SharesMath.VIRTUAL_SHARES, "borrow share"); + assertEq(blue.borrowShares(id, BORROWER), amountBorrowed * SharesMathLib.VIRTUAL_SHARES, "borrow share"); assertEq(borrowableAsset.balanceOf(receiver), amountBorrowed, "receiver balance"); assertEq(borrowableAsset.balanceOf(address(blue)), amountLent - amountBorrowed, "blue balance"); } @@ -374,7 +374,7 @@ contract BlueTest is borrowableAsset.setBalance(address(this), amount); if (amount > 0) blue.supply(market, amount, 0, address(this), hex""); - uint256 collateralAmount = shares.toAssetsUp(blue.totalBorrow(id), blue.totalBorrowShares(id)).divWadUp(LLTV); + uint256 collateralAmount = shares.toAssetsUp(blue.totalBorrow(id), blue.totalBorrowShares(id)).wDivUp(LLTV); collateralAsset.setBalance(address(this), collateralAmount); if (collateralAmount > 0) blue.supplyCollateral(market, collateralAmount, BORROWER, hex""); @@ -411,7 +411,7 @@ contract BlueTest is _testWithdrawCommon(amountLent); amountBorrowed = bound(amountBorrowed, 1, blue.totalSupply(id)); - uint256 collateralAmount = amountBorrowed.divWadUp(LLTV); + uint256 collateralAmount = amountBorrowed.wDivUp(LLTV); collateralAsset.setBalance(address(this), collateralAmount); blue.supplyCollateral(market, collateralAmount, BORROWER, hex""); @@ -426,7 +426,7 @@ contract BlueTest is blue.withdraw(market, 0, sharesWithdrawn, address(this), receiver); return; } else if (amountWithdrawn > totalSupplyBefore - amountBorrowed) { - vm.expectRevert(bytes(Errors.INSUFFICIENT_LIQUIDITY)); + vm.expectRevert(bytes(ErrorsLib.INSUFFICIENT_LIQUIDITY)); blue.withdraw(market, 0, sharesWithdrawn, address(this), receiver); return; } @@ -476,7 +476,7 @@ contract BlueTest is borrowableAsset.setBalance(address(this), 2 ** 66); blue.supply(market, amountBorrowed, 0, address(this), hex""); - uint256 collateralAmount = amountBorrowed.divWadUp(LLTV); + uint256 collateralAmount = amountBorrowed.wDivUp(LLTV); collateralAsset.setBalance(address(this), collateralAmount); blue.supplyCollateral(market, collateralAmount, borrower, hex""); @@ -601,10 +601,10 @@ contract BlueTest is vm.prank(BORROWER); blue.supplyCollateral(market, amountCollateral, BORROWER, hex""); - uint256 maxBorrow = amountCollateral.mulWadDown(collateralPrice).mulWadDown(LLTV); + uint256 maxBorrow = amountCollateral.wMulDown(collateralPrice).wMulDown(LLTV); vm.prank(BORROWER); - if (maxBorrow < amountBorrowed) vm.expectRevert(bytes(Errors.INSUFFICIENT_COLLATERAL)); + if (maxBorrow < amountBorrowed) vm.expectRevert(bytes(ErrorsLib.INSUFFICIENT_COLLATERAL)); blue.borrow(market, amountBorrowed, 0, BORROWER, BORROWER); } @@ -613,11 +613,10 @@ contract BlueTest is amountLent = bound(amountLent, 1000, 2 ** 64); uint256 amountCollateral = amountLent; - uint256 borrowingPower = amountCollateral.mulWadDown(LLTV); - uint256 amountBorrowed = borrowingPower.mulWadDown(0.8e18); - uint256 toSeize = amountCollateral.mulWadDown(LLTV); - uint256 incentive = - FixedPointMathLib.WAD + ALPHA.mulWadDown(FixedPointMathLib.WAD.divWadDown(LLTV) - FixedPointMathLib.WAD); + uint256 borrowingPower = amountCollateral.wMulDown(LLTV); + uint256 amountBorrowed = borrowingPower.wMulDown(0.8e18); + uint256 toSeize = amountCollateral.wMulDown(LLTV); + uint256 incentive = WAD + ALPHA.wMulDown(WAD.wDivDown(LLTV) - WAD); borrowableAsset.setBalance(address(this), amountLent); collateralAsset.setBalance(BORROWER, amountCollateral); @@ -644,7 +643,7 @@ contract BlueTest is uint256 liquidatorNetWorthAfter = netWorth(LIQUIDATOR); (uint256 collateralPrice, uint256 priceScale) = IOracle(market.oracle).price(); - uint256 expectedRepaid = toSeize.mulDivUp(collateralPrice, priceScale).divWadUp(incentive); + uint256 expectedRepaid = toSeize.mulDivUp(collateralPrice, priceScale).wDivUp(incentive); uint256 expectedNetWorthAfter = liquidatorNetWorthBefore + toSeize.mulDivDown(collateralPrice, priceScale) - expectedRepaid; assertEq(liquidatorNetWorthAfter, expectedNetWorthAfter, "LIQUIDATOR net worth"); @@ -657,11 +656,10 @@ contract BlueTest is amountLent = bound(amountLent, 1000, 2 ** 64); uint256 amountCollateral = amountLent; - uint256 borrowingPower = amountCollateral.mulWadDown(LLTV); - uint256 amountBorrowed = borrowingPower.mulWadDown(0.8e18); + uint256 borrowingPower = amountCollateral.wMulDown(LLTV); + uint256 amountBorrowed = borrowingPower.wMulDown(0.8e18); uint256 toSeize = amountCollateral; - uint256 incentive = FixedPointMathLib.WAD - + ALPHA.mulWadDown(FixedPointMathLib.WAD.divWadDown(market.lltv) - FixedPointMathLib.WAD); + uint256 incentive = WAD + ALPHA.wMulDown(WAD.wDivDown(market.lltv) - WAD); borrowableAsset.setBalance(address(this), amountLent); collateralAsset.setBalance(BORROWER, amountCollateral); @@ -688,7 +686,7 @@ contract BlueTest is uint256 liquidatorNetWorthAfter = netWorth(LIQUIDATOR); (uint256 collateralPrice, uint256 priceScale) = IOracle(market.oracle).price(); - uint256 expectedRepaid = toSeize.mulDivUp(collateralPrice, priceScale).divWadUp(incentive); + uint256 expectedRepaid = toSeize.mulDivUp(collateralPrice, priceScale).wDivUp(incentive); uint256 expectedNetWorthAfter = liquidatorNetWorthBefore + toSeize.mulDivDown(collateralPrice, priceScale) - expectedRepaid; assertEq(liquidatorNetWorthAfter, expectedNetWorthAfter, "LIQUIDATOR net worth"); @@ -713,12 +711,14 @@ contract BlueTest is assertApproxEqAbs(supplyBalance(address(this)), firstAmount, 100, "same balance first user"); assertEq( - blue.supplyShares(id, address(this)), firstAmount * SharesMath.VIRTUAL_SHARES, "expected shares first user" + blue.supplyShares(id, address(this)), + firstAmount * SharesMathLib.VIRTUAL_SHARES, + "expected shares first user" ); assertApproxEqAbs(supplyBalance(BORROWER), secondAmount, 100, "same balance second user"); assertApproxEqAbs( blue.supplyShares(id, BORROWER), - secondAmount * SharesMath.VIRTUAL_SHARES, + secondAmount * SharesMathLib.VIRTUAL_SHARES, 100, "expected shares second user" ); @@ -727,81 +727,81 @@ contract BlueTest is function testUnknownMarket(Market memory marketFuzz) public { vm.assume(neq(marketFuzz, market)); - vm.expectRevert(bytes(Errors.MARKET_NOT_CREATED)); + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); blue.supply(marketFuzz, 1, 0, address(this), hex""); - vm.expectRevert(bytes(Errors.MARKET_NOT_CREATED)); + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); blue.withdraw(marketFuzz, 1, 0, address(this), address(this)); - vm.expectRevert(bytes(Errors.MARKET_NOT_CREATED)); + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); blue.borrow(marketFuzz, 1, 0, address(this), address(this)); - vm.expectRevert(bytes(Errors.MARKET_NOT_CREATED)); + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); blue.repay(marketFuzz, 1, 0, address(this), hex""); - vm.expectRevert(bytes(Errors.MARKET_NOT_CREATED)); + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); blue.supplyCollateral(marketFuzz, 1, address(this), hex""); - vm.expectRevert(bytes(Errors.MARKET_NOT_CREATED)); + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); blue.withdrawCollateral(marketFuzz, 1, address(this), address(this)); - vm.expectRevert(bytes(Errors.MARKET_NOT_CREATED)); + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); blue.liquidate(marketFuzz, address(0), 1, hex""); } function testInputZero() public { - vm.expectRevert(bytes(Errors.INCONSISTENT_INPUT)); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); blue.supply(market, 0, 0, address(this), hex""); - vm.expectRevert(bytes(Errors.INCONSISTENT_INPUT)); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); blue.supply(market, 1, 1, address(this), hex""); - vm.expectRevert(bytes(Errors.INCONSISTENT_INPUT)); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); blue.withdraw(market, 0, 0, address(this), address(this)); - vm.expectRevert(bytes(Errors.INCONSISTENT_INPUT)); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); blue.withdraw(market, 1, 1, address(this), address(this)); - vm.expectRevert(bytes(Errors.INCONSISTENT_INPUT)); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); blue.borrow(market, 0, 0, address(this), address(this)); - vm.expectRevert(bytes(Errors.INCONSISTENT_INPUT)); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); blue.borrow(market, 1, 1, address(this), address(this)); - vm.expectRevert(bytes(Errors.INCONSISTENT_INPUT)); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); blue.repay(market, 0, 0, address(this), hex""); - vm.expectRevert(bytes(Errors.INCONSISTENT_INPUT)); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); blue.repay(market, 1, 1, address(this), hex""); - vm.expectRevert(bytes(Errors.ZERO_AMOUNT)); + vm.expectRevert(bytes(ErrorsLib.ZERO_AMOUNT)); blue.supplyCollateral(market, 0, address(this), hex""); - vm.expectRevert(bytes(Errors.ZERO_AMOUNT)); + vm.expectRevert(bytes(ErrorsLib.ZERO_AMOUNT)); blue.withdrawCollateral(market, 0, address(this), address(this)); - vm.expectRevert(bytes(Errors.ZERO_AMOUNT)); + vm.expectRevert(bytes(ErrorsLib.ZERO_AMOUNT)); blue.liquidate(market, address(0), 0, hex""); } function testZeroAddress() public { - vm.expectRevert(bytes(Errors.ZERO_ADDRESS)); + vm.expectRevert(bytes(ErrorsLib.ZERO_ADDRESS)); blue.supply(market, 0, 1, address(0), hex""); - vm.expectRevert(bytes(Errors.ZERO_ADDRESS)); + vm.expectRevert(bytes(ErrorsLib.ZERO_ADDRESS)); blue.withdraw(market, 0, 1, address(this), address(0)); - vm.expectRevert(bytes(Errors.ZERO_ADDRESS)); + vm.expectRevert(bytes(ErrorsLib.ZERO_ADDRESS)); blue.borrow(market, 0, 1, address(this), address(0)); - vm.expectRevert(bytes(Errors.ZERO_ADDRESS)); + vm.expectRevert(bytes(ErrorsLib.ZERO_ADDRESS)); blue.repay(market, 0, 1, address(0), hex""); - vm.expectRevert(bytes(Errors.ZERO_ADDRESS)); + vm.expectRevert(bytes(ErrorsLib.ZERO_ADDRESS)); blue.supplyCollateral(market, 1, address(0), hex""); - vm.expectRevert(bytes(Errors.ZERO_ADDRESS)); + vm.expectRevert(bytes(ErrorsLib.ZERO_ADDRESS)); blue.withdrawCollateral(market, 1, address(this), address(0)); } function testEmptyMarket(uint256 amount) public { - amount = bound(amount, 1, type(uint256).max / SharesMath.VIRTUAL_SHARES); + amount = bound(amount, 1, type(uint256).max / SharesMathLib.VIRTUAL_SHARES); vm.expectRevert(stdError.arithmeticError); blue.withdraw(market, amount, 0, address(this), address(this)); @@ -823,11 +823,11 @@ contract BlueTest is vm.startPrank(attacker); - vm.expectRevert(bytes(Errors.UNAUTHORIZED)); + vm.expectRevert(bytes(ErrorsLib.UNAUTHORIZED)); blue.withdraw(market, 0, 1, address(this), address(this)); - vm.expectRevert(bytes(Errors.UNAUTHORIZED)); + vm.expectRevert(bytes(ErrorsLib.UNAUTHORIZED)); blue.withdrawCollateral(market, 1, address(this), address(this)); - vm.expectRevert(bytes(Errors.UNAUTHORIZED)); + vm.expectRevert(bytes(ErrorsLib.UNAUTHORIZED)); blue.borrow(market, 0, 1, address(this), address(this)); vm.stopPrank(); @@ -934,7 +934,7 @@ contract BlueTest is borrowableAsset.setBalance(address(this), amount); blue.supply(market, amount, 0, address(this), hex""); - uint256 collateralAmount = amount.divWadUp(LLTV); + uint256 collateralAmount = amount.wDivUp(LLTV); collateralAsset.setBalance(address(this), collateralAmount); blue.supplyCollateral(market, collateralAmount, address(this), hex""); blue.borrow(market, amount, 0, address(this), address(this)); @@ -952,10 +952,10 @@ contract BlueTest is borrowableAsset.setBalance(address(this), amount); blue.supply(market, amount, 0, address(this), hex""); - uint256 collateralAmount = amount.divWadUp(LLTV); + uint256 collateralAmount = amount.wDivUp(LLTV); collateralAsset.setBalance(address(this), collateralAmount); blue.supplyCollateral(market, collateralAmount, address(this), hex""); - blue.borrow(market, amount.mulWadDown(LLTV), 0, address(this), address(this)); + blue.borrow(market, amount.wMulDown(LLTV), 0, address(this), address(this)); oracle.setPrice(0.5e18); @@ -969,7 +969,7 @@ contract BlueTest is function testFlashActions(uint256 amount) public { amount = bound(amount, 10, 2 ** 64); oracle.setPrice(1e18); - uint256 toBorrow = amount.mulWadDown(LLTV); + uint256 toBorrow = amount.wMulDown(LLTV); borrowableAsset.setBalance(address(this), 2 * toBorrow); blue.supply(market, toBorrow, 0, address(this), hex""); diff --git a/test/forge/Math.t.sol b/test/forge/Math.t.sol index 29422dc63..76e6c4746 100644 --- a/test/forge/Math.t.sol +++ b/test/forge/Math.t.sol @@ -10,22 +10,20 @@ contract MathTest is Test { function testWTaylorCompounded(uint256 rate, uint256 timeElapsed) public { // Assume rate is less than a ~500% APY. (~180% APR) - vm.assume(rate < (FixedPointMathLib.WAD / 20_000_000) && timeElapsed < 365 days); - uint256 result = rate.wTaylorCompounded(timeElapsed) + FixedPointMathLib.WAD; - uint256 toCompare = wPow(FixedPointMathLib.WAD + rate, timeElapsed); + vm.assume(rate < (WAD / 20_000_000) && timeElapsed < 365 days); + uint256 result = rate.wTaylorCompounded(timeElapsed) + WAD; + uint256 toCompare = wPow(WAD + rate, timeElapsed); assertLe(result, toCompare, "rate should be less than the compounded rate"); - assertGe( - result, FixedPointMathLib.WAD + timeElapsed * rate, "rate should be greater than the simple interest rate" - ); + assertGe(result, WAD + timeElapsed * rate, "rate should be greater than the simple interest rate"); assertLe((toCompare - result) * 100_00 / toCompare, 8_00, "The error should be less than or equal to 8%"); } // Exponentiation by squaring with rounding up. function wPow(uint256 x, uint256 n) private pure returns (uint256 z) { - z = FixedPointMathLib.WAD; + z = WAD; for (; n != 0; n /= 2) { - z = n % 2 != 0 ? z.mulWadUp(x) : z; - x = x.mulWadUp(x); + z = n % 2 != 0 ? z.wMulUp(x) : z; + x = x.wMulUp(x); } } }