From 60ab139a6ae6b6262159971d08f8d447f1423eb3 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:04:53 -0500 Subject: [PATCH 01/11] Make rewardToken mutable to prepare for governance --- core/.gas-snapshot | 30 +++++++++++----------- core/src/Factory.sol | 12 ++++----- core/src/Ledger.sol | 2 +- core/src/libraries/constants/Constants.sol | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/core/.gas-snapshot b/core/.gas-snapshot index 060b0c47..13f24b28 100644 --- a/core/.gas-snapshot +++ b/core/.gas-snapshot @@ -1,26 +1,26 @@ BorrowerGasTest:test_addMargin() (gas: 16203) -BorrowerGasTest:test_borrow() (gas: 110492) +BorrowerGasTest:test_borrow() (gas: 110489) BorrowerGasTest:test_getUniswapPositions() (gas: 5219) -BorrowerGasTest:test_modify() (gas: 82111) -BorrowerGasTest:test_modifyWithAnte() (gas: 88566) -BorrowerGasTest:test_repay() (gas: 111851) -BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257726) +BorrowerGasTest:test_modify() (gas: 82108) +BorrowerGasTest:test_modifyWithAnte() (gas: 88563) +BorrowerGasTest:test_repay() (gas: 111848) +BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257723) BorrowerGasTest:test_uniswapDepositStandard() (gas: 167558) -BorrowerGasTest:test_uniswapWithdraw() (gas: 147623) -BorrowerGasTest:test_withdraw() (gas: 105631) -FactoryGasTest:test_createBorrower() (gas: 156437) -FactoryGasTest:test_createMarket() (gas: 3900597) +BorrowerGasTest:test_uniswapWithdraw() (gas: 147620) +BorrowerGasTest:test_withdraw() (gas: 105628) +FactoryGasTest:test_createBorrower() (gas: 156431) +FactoryGasTest:test_createMarket() (gas: 3900591) LenderGasTest:test_accrueInterest() (gas: 46287) LenderGasTest:test_borrow() (gas: 40812) LenderGasTest:test_deposit() (gas: 53639) LenderGasTest:test_depositWithCourier() (gas: 53785) LenderGasTest:test_redeem() (gas: 53334) -LenderGasTest:test_redeemWithCourier() (gas: 83730) +LenderGasTest:test_redeemWithCourier() (gas: 83726) LenderGasTest:test_repay() (gas: 44752) -LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51020) -LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59179) -LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95598) -LiquidatorGasTest:test_warn() (gas: 35026) -LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130185) +LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51017) +LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59176) +LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95596) +LiquidatorGasTest:test_warn() (gas: 35023) +LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130182) VolatilityGasTest:test_consult() (gas: 43075) VolatilityGasTest:test_updateNoBinarySearch() (gas: 145880) \ No newline at end of file diff --git a/core/src/Factory.sol b/core/src/Factory.sol index ac00a399..14669fe8 100644 --- a/core/src/Factory.sol +++ b/core/src/Factory.sol @@ -47,10 +47,10 @@ contract Factory { IRateModel public immutable RATE_MODEL; - ERC20 public immutable REWARDS_TOKEN; - address public immutable LENDER_IMPLEMENTATION; + ERC20 public rewardsToken; + /*////////////////////////////////////////////////////////////// WORLD STORAGE //////////////////////////////////////////////////////////////*/ @@ -80,12 +80,12 @@ contract Factory { CONSTRUCTOR //////////////////////////////////////////////////////////////*/ - constructor(VolatilityOracle oracle, IRateModel rateModel, ERC20 rewardsToken) { + constructor(VolatilityOracle oracle, IRateModel rateModel, ERC20 rewardsToken_) { ORACLE = oracle; RATE_MODEL = rateModel; - REWARDS_TOKEN = rewardsToken; - LENDER_IMPLEMENTATION = address(new Lender(address(this))); + + rewardsToken = rewardsToken_; } function pause(IUniswapV3Pool pool) external { @@ -178,6 +178,6 @@ contract Factory { } } - REWARDS_TOKEN.safeTransfer(beneficiary, earned); + rewardsToken.safeTransfer(beneficiary, earned); } } diff --git a/core/src/Ledger.sol b/core/src/Ledger.sol index da702a31..be6cd333 100644 --- a/core/src/Ledger.sol +++ b/core/src/Ledger.sol @@ -300,7 +300,7 @@ contract Ledger { * @return The maximum amount of `asset()` that can be withdrawn */ function maxWithdraw(address owner) external view returns (uint256) { - return convertToAssets(this.maxRedeem(owner)); + return convertToAssets(maxRedeem(owner)); } /*////////////////////////////////////////////////////////////// diff --git a/core/src/libraries/constants/Constants.sol b/core/src/libraries/constants/Constants.sol index 3d2938eb..80e45f5a 100644 --- a/core/src/libraries/constants/Constants.sol +++ b/core/src/libraries/constants/Constants.sol @@ -29,7 +29,7 @@ uint256 constant MAX_RATE = 706354; /// @dev The default amount of Ether required to take on debt in a `Borrower`. The `Factory` can override this value /// on a per-market basis. -uint216 constant DEFAULT_ANTE = 0.1 ether; +uint216 constant DEFAULT_ANTE = 0.01 ether; /// @dev The default number of standard deviations of price movement used to determine probe prices for `Borrower` /// solvency. The `Factory` can override this value on a per-market basis. From e87383b2a9e08dbcafb65f578a6127e13ba6a949 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Fri, 8 Sep 2023 16:21:33 -0500 Subject: [PATCH 02/11] layout --- core/.storage-layout.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/.storage-layout.md b/core/.storage-layout.md index 0ebcc522..e4bcd4b0 100644 --- a/core/.storage-layout.md +++ b/core/.storage-layout.md @@ -24,10 +24,11 @@ forge inspect --pretty src/Borrower.sol:Borrower storage-layout forge inspect --pretty src/Factory.sol:Factory storage-layout | Name | Type | Slot | Offset | Bytes | Contract | |---------------|---------------------------------------------------------------|------|--------|-------|-------------------------| -| getMarket | mapping(contract IUniswapV3Pool => struct Factory.Market) | 0 | 0 | 32 | src/Factory.sol:Factory | -| getParameters | mapping(contract IUniswapV3Pool => struct Factory.Parameters) | 1 | 0 | 32 | src/Factory.sol:Factory | -| isLender | mapping(address => bool) | 2 | 0 | 32 | src/Factory.sol:Factory | -| isBorrower | mapping(address => bool) | 3 | 0 | 32 | src/Factory.sol:Factory | -| couriers | mapping(uint32 => struct Factory.Courier) | 4 | 0 | 32 | src/Factory.sol:Factory | -| isCourier | mapping(address => bool) | 5 | 0 | 32 | src/Factory.sol:Factory | +| rewardsToken | contract ERC20 | 0 | 0 | 20 | src/Factory.sol:Factory | +| getMarket | mapping(contract IUniswapV3Pool => struct Factory.Market) | 1 | 0 | 32 | src/Factory.sol:Factory | +| getParameters | mapping(contract IUniswapV3Pool => struct Factory.Parameters) | 2 | 0 | 32 | src/Factory.sol:Factory | +| isLender | mapping(address => bool) | 3 | 0 | 32 | src/Factory.sol:Factory | +| isBorrower | mapping(address => bool) | 4 | 0 | 32 | src/Factory.sol:Factory | +| couriers | mapping(uint32 => struct Factory.Courier) | 5 | 0 | 32 | src/Factory.sol:Factory | +| isCourier | mapping(address => bool) | 6 | 0 | 32 | src/Factory.sol:Factory | From c568b4e2ead3d104a7a499e3cb79c6c430cc18df Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Fri, 8 Sep 2023 21:11:18 -0500 Subject: [PATCH 03/11] Add governance methods in Factory --- core/.gas-snapshot | 42 ++++----- core/script/Deploy.s.sol | 18 +++- core/src/Factory.sol | 98 +++++++++++++++++--- core/src/Ledger.sol | 5 + core/src/Lender.sol | 23 +++-- core/src/libraries/Rewards.sol | 2 +- core/src/libraries/constants/Constants.sol | 22 ++++- core/test/Borrower.t.sol | 5 +- core/test/Liquidator.t.sol | 5 +- core/test/Utils.sol | 9 +- core/test/gas/BorrowerGas.t.sol | 5 +- core/test/gas/FactoryGas.t.sol | 7 +- core/test/gas/LiquidatorGas.t.sol | 5 +- core/test/invariants/ERC4626Invariants.t.sol | 3 +- 14 files changed, 184 insertions(+), 65 deletions(-) diff --git a/core/.gas-snapshot b/core/.gas-snapshot index 13f24b28..68f8f045 100644 --- a/core/.gas-snapshot +++ b/core/.gas-snapshot @@ -1,26 +1,26 @@ BorrowerGasTest:test_addMargin() (gas: 16203) -BorrowerGasTest:test_borrow() (gas: 110489) +BorrowerGasTest:test_borrow() (gas: 110555) BorrowerGasTest:test_getUniswapPositions() (gas: 5219) -BorrowerGasTest:test_modify() (gas: 82108) -BorrowerGasTest:test_modifyWithAnte() (gas: 88563) -BorrowerGasTest:test_repay() (gas: 111848) -BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257723) +BorrowerGasTest:test_modify() (gas: 82152) +BorrowerGasTest:test_modifyWithAnte() (gas: 88607) +BorrowerGasTest:test_repay() (gas: 111914) +BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257767) BorrowerGasTest:test_uniswapDepositStandard() (gas: 167558) -BorrowerGasTest:test_uniswapWithdraw() (gas: 147620) -BorrowerGasTest:test_withdraw() (gas: 105628) -FactoryGasTest:test_createBorrower() (gas: 156431) -FactoryGasTest:test_createMarket() (gas: 3900591) -LenderGasTest:test_accrueInterest() (gas: 46287) -LenderGasTest:test_borrow() (gas: 40812) -LenderGasTest:test_deposit() (gas: 53639) -LenderGasTest:test_depositWithCourier() (gas: 53785) -LenderGasTest:test_redeem() (gas: 53334) -LenderGasTest:test_redeemWithCourier() (gas: 83726) -LenderGasTest:test_repay() (gas: 44752) -LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51017) -LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59176) -LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95596) -LiquidatorGasTest:test_warn() (gas: 35023) -LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130182) +BorrowerGasTest:test_uniswapWithdraw() (gas: 147656) +BorrowerGasTest:test_withdraw() (gas: 105672) +FactoryGasTest:test_createBorrower() (gas: 156519) +FactoryGasTest:test_createMarket() (gas: 3905006) +LenderGasTest:test_accrueInterest() (gas: 46070) +LenderGasTest:test_borrow() (gas: 40834) +LenderGasTest:test_deposit() (gas: 53422) +LenderGasTest:test_depositWithCourier() (gas: 53568) +LenderGasTest:test_redeem() (gas: 53114) +LenderGasTest:test_redeemWithCourier() (gas: 83506) +LenderGasTest:test_repay() (gas: 44774) +LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51083) +LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59264) +LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95666) +LiquidatorGasTest:test_warn() (gas: 35067) +LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130248) VolatilityGasTest:test_consult() (gas: 43075) VolatilityGasTest:test_updateNoBinarySearch() (gas: 145880) \ No newline at end of file diff --git a/core/script/Deploy.s.sol b/core/script/Deploy.s.sol index 44354a62..6f4afe22 100644 --- a/core/script/Deploy.s.sol +++ b/core/script/Deploy.s.sol @@ -3,19 +3,27 @@ pragma solidity 0.8.17; import "forge-std/Script.sol"; -import {Factory, ERC20} from "../src/Factory.sol"; -import {RateModel} from "../src/RateModel.sol"; -import {VolatilityOracle} from "../src/VolatilityOracle.sol"; +import {Factory, ERC20} from "src/Factory.sol"; +import {Lender} from "src/Lender.sol"; +import {RateModel} from "src/RateModel.sol"; +import {VolatilityOracle} from "src/VolatilityOracle.sol"; bytes32 constant TAG = bytes32(uint256(0xA10EBE1A)); +address constant GOVERNOR = address(0); +address constant RESERVE = address(0); contract DeployScript is Script { function run() external { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); VolatilityOracle oracle = new VolatilityOracle{salt: TAG}(); - RateModel rateModel = new RateModel{salt: TAG}(); - /*Factory factory =*/ new Factory{salt: TAG}(oracle, rateModel, ERC20(address(0))); + RateModel rateModel = new RateModel{salt: TAG}(); + /*Factory factory =*/ new Factory{salt: TAG}({ + governor: GOVERNOR, + reserve: RESERVE, + oracle: oracle, + defaultRateModel: rateModel + }); vm.stopBroadcast(); } diff --git a/core/src/Factory.sol b/core/src/Factory.sol index 14669fe8..f06de0dc 100644 --- a/core/src/Factory.sol +++ b/core/src/Factory.sol @@ -10,6 +10,12 @@ import { DEFAULT_ANTE, DEFAULT_N_SIGMA, DEFAULT_RESERVE_FACTOR, + CONSTRAINT_N_SIGMA_MIN, + CONSTRAINT_N_SIGMA_MAX, + CONSTRAINT_RESERVE_FACTOR_MIN, + CONSTRAINT_RESERVE_FACTOR_MAX, + CONSTRAINT_ANTE_MAX, + CONSTRAINT_PAUSE_INTERVAL_MAX, UNISWAP_AVG_WINDOW } from "./libraries/constants/Constants.sol"; @@ -43,12 +49,22 @@ contract Factory { uint32 pausedUntilTime; } - VolatilityOracle public immutable ORACLE; + struct MarketConfig { + Parameters parameters; + IRateModel rateModel0; + IRateModel rateModel1; + uint8 reserveFactor0; + uint8 reserveFactor1; + } - IRateModel public immutable RATE_MODEL; + address public immutable GOVERNOR; + + VolatilityOracle public immutable ORACLE; address public immutable LENDER_IMPLEMENTATION; + IRateModel public immutable DEFAULT_RATE_MODEL; + ERC20 public rewardsToken; /*////////////////////////////////////////////////////////////// @@ -80,12 +96,11 @@ contract Factory { CONSTRUCTOR //////////////////////////////////////////////////////////////*/ - constructor(VolatilityOracle oracle, IRateModel rateModel, ERC20 rewardsToken_) { + constructor(address governor, address reserve, VolatilityOracle oracle, IRateModel defaultRateModel) { + GOVERNOR = governor; ORACLE = oracle; - RATE_MODEL = rateModel; - LENDER_IMPLEMENTATION = address(new Lender(address(this))); - - rewardsToken = rewardsToken_; + LENDER_IMPLEMENTATION = address(new Lender(reserve)); + DEFAULT_RATE_MODEL = defaultRateModel; } function pause(IUniswapV3Pool pool) external { @@ -96,7 +111,7 @@ contract Factory { } /*////////////////////////////////////////////////////////////// - WORLD CREATION + WORLD MANAGEMENT //////////////////////////////////////////////////////////////*/ function createMarket(IUniswapV3Pool pool) external { @@ -105,19 +120,31 @@ contract Factory { address asset0 = pool.token0(); address asset1 = pool.token1(); + // Deploy market-specific components bytes32 salt = keccak256(abi.encodePacked(pool)); Lender lender0 = Lender(LENDER_IMPLEMENTATION.cloneDeterministic({salt: salt, data: abi.encodePacked(asset0)})); Lender lender1 = Lender(LENDER_IMPLEMENTATION.cloneDeterministic({salt: salt, data: abi.encodePacked(asset1)})); + Borrower borrowerImplementation = new Borrower(ORACLE, pool, lender0, lender1); - lender0.initialize(RATE_MODEL, DEFAULT_RESERVE_FACTOR); - lender1.initialize(RATE_MODEL, DEFAULT_RESERVE_FACTOR); + // Store deployment addresses + getMarket[pool] = Market(lender0, lender1, borrowerImplementation); isLender[address(lender0)] = true; isLender[address(lender1)] = true; - Borrower borrowerImplementation = new Borrower(ORACLE, pool, lender0, lender1); + // Initialize lenders and set default market config + lender0.initialize(); + lender1.initialize(); + _setMarketConfig( + pool, + MarketConfig( + Parameters(DEFAULT_ANTE, DEFAULT_N_SIGMA, 0), + DEFAULT_RATE_MODEL, + DEFAULT_RATE_MODEL, + DEFAULT_RESERVE_FACTOR, + DEFAULT_RESERVE_FACTOR + ) + ); - getMarket[pool] = Market(lender0, lender1, borrowerImplementation); - getParameters[pool] = Parameters(DEFAULT_ANTE, DEFAULT_N_SIGMA, 0); emit CreateMarket(pool, lender0, lender1); } @@ -180,4 +207,49 @@ contract Factory { rewardsToken.safeTransfer(beneficiary, earned); } + + /*////////////////////////////////////////////////////////////// + GOVERNANCE + //////////////////////////////////////////////////////////////*/ + + function governRewardsToken(ERC20 rewardsToken_) external { + require(msg.sender == GOVERNOR && address(rewardsToken) == address(0)); + rewardsToken = rewardsToken_; + } + + function governRewardsRate(Lender lender, uint56 rate) external { + require(msg.sender == GOVERNOR); + lender.setRewardsRate(rate); + } + + function governMarketConfig(IUniswapV3Pool pool, MarketConfig memory marketConfig) external { + require(msg.sender == GOVERNOR); + + require( + // nSigma: min, max + (CONSTRAINT_N_SIGMA_MIN <= marketConfig.parameters.nSigma && + marketConfig.parameters.nSigma <= CONSTRAINT_N_SIGMA_MAX) && + // ante: max + (marketConfig.parameters.ante <= CONSTRAINT_ANTE_MAX) && + // pauseUntilTime: max + (marketConfig.parameters.pausedUntilTime <= block.timestamp + CONSTRAINT_PAUSE_INTERVAL_MAX) && + // reserveFactor0: min, max + (CONSTRAINT_RESERVE_FACTOR_MIN <= marketConfig.reserveFactor0 && + marketConfig.reserveFactor0 <= CONSTRAINT_RESERVE_FACTOR_MAX) && + // reserveFactor1: min, max + (CONSTRAINT_RESERVE_FACTOR_MIN <= marketConfig.reserveFactor1 && + marketConfig.reserveFactor1 <= CONSTRAINT_RESERVE_FACTOR_MAX), + "Aloe: constraints" + ); + + _setMarketConfig(pool, marketConfig); + } + + function _setMarketConfig(IUniswapV3Pool pool, MarketConfig memory marketConfig) private { + getParameters[pool] = marketConfig.parameters; + + Market memory market = getMarket[pool]; + market.lender0.setRateModelAndReserveFactor(marketConfig.rateModel0, marketConfig.reserveFactor0); + market.lender1.setRateModelAndReserveFactor(marketConfig.rateModel1, marketConfig.reserveFactor1); + } } diff --git a/core/src/Ledger.sol b/core/src/Ledger.sol index be6cd333..3b6d7a6c 100644 --- a/core/src/Ledger.sol +++ b/core/src/Ledger.sol @@ -76,8 +76,13 @@ contract Ledger { GOVERNABLE PARAMETERS //////////////////////////////////////////////////////////////*/ + /** + * @dev `rateModel.getYieldPerSecond` is given 100000 gas, and the output is clamped to `MAX_RATE`. If + * the call reverts, it's treated the same as if it returned 0. + */ IRateModel public rateModel; + /// @dev The portion of interest that accrues to the `RESERVE`. Expressed as a reciprocal, e.g. 16 → 6.25% uint8 public reserveFactor; /*////////////////////////////////////////////////////////////// diff --git a/core/src/Lender.sol b/core/src/Lender.sol index 5501ba33..effdaf47 100644 --- a/core/src/Lender.sol +++ b/core/src/Lender.sol @@ -5,7 +5,7 @@ import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {SafeCastLib} from "solmate/utils/SafeCastLib.sol"; -import {BORROWS_SCALER, ONE, MIN_RESERVE_FACTOR, MAX_RESERVE_FACTOR} from "./libraries/constants/Constants.sol"; +import {BORROWS_SCALER, ONE} from "./libraries/constants/Constants.sol"; import {Q112} from "./libraries/constants/Q.sol"; import {Rewards} from "./libraries/Rewards.sol"; @@ -50,23 +50,32 @@ contract Lender is Ledger { constructor(address reserve) Ledger(reserve) {} - function initialize(IRateModel rateModel_, uint8 reserveFactor_) external { + function initialize() external { require(borrowIndex == 0); borrowIndex = uint72(ONE); lastAccrualTime = uint32(block.timestamp); initialDomainSeparator = _computeDomainSeparator(); initialChainId = block.chainid; + } + /// @notice Sets the `rateModel` and `reserveFactor`. Only the `FACTORY` can call this. + function setRateModelAndReserveFactor(IRateModel rateModel_, uint8 reserveFactor_) external { + require(msg.sender == address(FACTORY)); rateModel = rateModel_; - require(MIN_RESERVE_FACTOR <= reserveFactor_ && reserveFactor_ <= MAX_RESERVE_FACTOR); reserveFactor = reserveFactor_; } - // TODO: governance-only functions for: - // - depositing/withdrawing the rewards token - // - setting the rewards rate - // - setting reserve factor and rate model + /** + * @notice Sets the rewards rate. May be 0. Only the `FACTORY` can call this. + * @param rate The rewards rate, specified in [token units per second]. If non-zero, keep between 10^19 and + * 10^24 token units per year for smooth operation. Assuming `FACTORY.rewardsToken()` has 18 decimals, this is + * between 10 and 1 million tokens per year. + */ + function setRewardsRate(uint56 rate) external { + require(msg.sender == address(FACTORY)); + Rewards.setRate(rate); + } function whitelist(address borrower) external { // Requirements: diff --git a/core/src/libraries/Rewards.sol b/core/src/libraries/Rewards.sol index c12da8fb..d33dfcb0 100644 --- a/core/src/libraries/Rewards.sol +++ b/core/src/libraries/Rewards.sol @@ -38,7 +38,7 @@ library Rewards { } /** - * @notice Sets the pool's reward rate. May be 0. + * @notice Sets the pool's rewards rate. May be 0. * @param rate The rewards rate, specified as [token units per second]. Keep between 10^19 and 10^24 * token units per year for smooth operation -- between 10 and 1 million tokens, assuming 18 decimals. */ diff --git a/core/src/libraries/constants/Constants.sol b/core/src/libraries/constants/Constants.sol index 80e45f5a..193c7977 100644 --- a/core/src/libraries/constants/Constants.sol +++ b/core/src/libraries/constants/Constants.sol @@ -36,18 +36,30 @@ uint216 constant DEFAULT_ANTE = 0.01 ether; uint8 constant DEFAULT_N_SIGMA = 5; /// @dev The default portion of interest that will accrue to a `Lender`'s `RESERVE` address. -/// Expressed as a reciprocal, e.g. 8 → 12.5% -uint8 constant DEFAULT_RESERVE_FACTOR = 8; +/// Expressed as a reciprocal, e.g. 16 → 6.25% +uint8 constant DEFAULT_RESERVE_FACTOR = 16; /*////////////////////////////////////////////////////////////// - GOVERNANCE BOUNDS + GOVERNANCE CONSTRAINTS //////////////////////////////////////////////////////////////*/ +/// @dev The lowest number of standard deviations of price movement allowed for determining `Borrower` probe prices +uint8 constant CONSTRAINT_N_SIGMA_MIN = 4; + +/// @dev The highest number of standard deviations of price movement allowed for determining `Borrower` probe prices +uint8 constant CONSTRAINT_N_SIGMA_MAX = 5; + /// @dev The lower bound on what any `Lender`'s reserve factor can be. Expressed as reciprocal, e.g. 4 → 25% -uint256 constant MIN_RESERVE_FACTOR = 4; +uint8 constant CONSTRAINT_RESERVE_FACTOR_MIN = 4; /// @dev The upper bound on what any `Lender`'s reserve factor can be. Expressed as reciprocal, e.g. 20 → 5% -uint256 constant MAX_RESERVE_FACTOR = 20; +uint8 constant CONSTRAINT_RESERVE_FACTOR_MAX = 20; + +/// @dev The maximum amount of Ether that `Borrower`s can be required to post before taking on debt +uint216 constant CONSTRAINT_ANTE_MAX = 0.1 ether; + +/// @dev The maximum uninterrupted amount of time for which borrowing can be paused by governance +uint32 constant CONSTRAINT_PAUSE_INTERVAL_MAX = 2 days; /*////////////////////////////////////////////////////////////// LIQUIDATION diff --git a/core/test/Borrower.t.sol b/core/test/Borrower.t.sol index 2a9ad6c2..baa5d856 100644 --- a/core/test/Borrower.t.sol +++ b/core/test/Borrower.t.sol @@ -50,9 +50,10 @@ contract BorrowerTest is Test, IManager { vm.rollFork(15_348_451); Factory factory = new Factory( + address(0), + address(0), VolatilityOracle(address(new VolatilityOracleMock())), - new RateModel(), - ERC20(address(0)) + new RateModel() ); factory.createMarket(pool); diff --git a/core/test/Liquidator.t.sol b/core/test/Liquidator.t.sol index d8186026..3847454f 100644 --- a/core/test/Liquidator.t.sol +++ b/core/test/Liquidator.t.sol @@ -28,9 +28,10 @@ contract LiquidatorTest is Test, IManager, ILiquidator { vm.rollFork(15_348_451); Factory factory = new Factory( + address(0), + address(0), VolatilityOracle(address(new VolatilityOracleMock())), - new RateModel(), - ERC20(address(0)) + new RateModel() ); factory.createMarket(pool); diff --git a/core/test/Utils.sol b/core/test/Utils.sol index a7a66cbf..9caf5669 100644 --- a/core/test/Utils.sol +++ b/core/test/Utils.sol @@ -22,13 +22,16 @@ function deploySingleBorrower(IUniswapV3Pool pool, Lender lender0, Lender lender contract FactoryForLenderTests is Factory { constructor( RateModel rateModel, - ERC20 rewardsToken - ) Factory(VolatilityOracle(address(0)), rateModel, rewardsToken) {} + ERC20 rewardsToken_ + ) Factory(address(0), address(this), VolatilityOracle(address(0)), rateModel) { + rewardsToken = rewardsToken_; + } function deploySingleLender(ERC20 asset) external returns (Lender) { address proxy = ClonesWithImmutableArgs.clone(LENDER_IMPLEMENTATION, abi.encodePacked(address(asset))); - Lender(proxy).initialize(RATE_MODEL, 8); + Lender(proxy).initialize(); + Lender(proxy).setRateModelAndReserveFactor(DEFAULT_RATE_MODEL, 8); return Lender(proxy); } } diff --git a/core/test/gas/BorrowerGas.t.sol b/core/test/gas/BorrowerGas.t.sol index 88b2a8f0..c2f7c26d 100644 --- a/core/test/gas/BorrowerGas.t.sol +++ b/core/test/gas/BorrowerGas.t.sol @@ -28,9 +28,10 @@ contract BorrowerGasTest is Test, IManager { vm.rollFork(15_348_451); Factory factory = new Factory( + address(0), + address(0), VolatilityOracle(address(new VolatilityOracleMock())), - new RateModel(), - ERC20(address(0)) + new RateModel() ); factory.createMarket(pool); diff --git a/core/test/gas/FactoryGas.t.sol b/core/test/gas/FactoryGas.t.sol index fa58e7db..62298634 100644 --- a/core/test/gas/FactoryGas.t.sol +++ b/core/test/gas/FactoryGas.t.sol @@ -17,7 +17,12 @@ contract FactoryGasTest is Test { vm.createSelectFork(vm.rpcUrl("mainnet")); vm.rollFork(15_348_451); - factory = new Factory(new VolatilityOracle(), new RateModel(), ERC20(address(0))); + factory = new Factory( + address(0), + address(0), + new VolatilityOracle(), + new RateModel() + ); } function setUp() public { diff --git a/core/test/gas/LiquidatorGas.t.sol b/core/test/gas/LiquidatorGas.t.sol index 2cb95b25..b9abf5e3 100644 --- a/core/test/gas/LiquidatorGas.t.sol +++ b/core/test/gas/LiquidatorGas.t.sol @@ -28,9 +28,10 @@ contract LiquidatorGasTest is Test, IManager, ILiquidator { vm.rollFork(15_348_451); Factory factory = new Factory( + address(0), + address(0), VolatilityOracle(address(new VolatilityOracleMock())), - new RateModel(), - ERC20(address(0)) + new RateModel() ); factory.createMarket(pool); diff --git a/core/test/invariants/ERC4626Invariants.t.sol b/core/test/invariants/ERC4626Invariants.t.sol index d705247b..fee0e66d 100644 --- a/core/test/invariants/ERC4626Invariants.t.sol +++ b/core/test/invariants/ERC4626Invariants.t.sol @@ -39,7 +39,8 @@ contract ERC4626InvariantsTest is Test { abi.encodePacked(address(asset)) )); RateModel rateModel = new RateModel(); - lender.initialize(rateModel, 8); // TODO: replace 8 with an env var + lender.initialize(); + lender.setRateModelAndReserveFactor(rateModel, 8); // TODO: replace 8 with an env var vault = ERC4626(address(lender)); vaultHarness = new ERC4626Harness(lender); From e7cacbf38d961b524add6e38b367aa06512b472e Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Fri, 8 Sep 2023 21:13:49 -0500 Subject: [PATCH 04/11] Tweak --- core/src/Factory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/Factory.sol b/core/src/Factory.sol index f06de0dc..a74c266a 100644 --- a/core/src/Factory.sol +++ b/core/src/Factory.sol @@ -111,7 +111,7 @@ contract Factory { } /*////////////////////////////////////////////////////////////// - WORLD MANAGEMENT + WORLD CREATION //////////////////////////////////////////////////////////////*/ function createMarket(IUniswapV3Pool pool) external { From deb6700dd7bff1cee23201a23a286339581e7b68 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sat, 9 Sep 2023 23:08:25 -0500 Subject: [PATCH 05/11] gs --- core/.gas-snapshot | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/core/.gas-snapshot b/core/.gas-snapshot index 68f8f045..04bb3a5d 100644 --- a/core/.gas-snapshot +++ b/core/.gas-snapshot @@ -1,26 +1,26 @@ BorrowerGasTest:test_addMargin() (gas: 16203) -BorrowerGasTest:test_borrow() (gas: 110555) +BorrowerGasTest:test_borrow() (gas: 110489) BorrowerGasTest:test_getUniswapPositions() (gas: 5219) -BorrowerGasTest:test_modify() (gas: 82152) -BorrowerGasTest:test_modifyWithAnte() (gas: 88607) -BorrowerGasTest:test_repay() (gas: 111914) -BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257767) +BorrowerGasTest:test_modify() (gas: 82108) +BorrowerGasTest:test_modifyWithAnte() (gas: 88563) +BorrowerGasTest:test_repay() (gas: 111848) +BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257723) BorrowerGasTest:test_uniswapDepositStandard() (gas: 167558) -BorrowerGasTest:test_uniswapWithdraw() (gas: 147656) -BorrowerGasTest:test_withdraw() (gas: 105672) -FactoryGasTest:test_createBorrower() (gas: 156519) -FactoryGasTest:test_createMarket() (gas: 3905006) -LenderGasTest:test_accrueInterest() (gas: 46070) -LenderGasTest:test_borrow() (gas: 40834) -LenderGasTest:test_deposit() (gas: 53422) -LenderGasTest:test_depositWithCourier() (gas: 53568) -LenderGasTest:test_redeem() (gas: 53114) -LenderGasTest:test_redeemWithCourier() (gas: 83506) -LenderGasTest:test_repay() (gas: 44774) -LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51083) -LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59264) -LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95666) -LiquidatorGasTest:test_warn() (gas: 35067) -LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130248) +BorrowerGasTest:test_uniswapWithdraw() (gas: 147620) +BorrowerGasTest:test_withdraw() (gas: 105628) +FactoryGasTest:test_createBorrower() (gas: 156431) +FactoryGasTest:test_createMarket() (gas: 3900591) +LenderGasTest:test_accrueInterest() (gas: 46287) +LenderGasTest:test_borrow() (gas: 40812) +LenderGasTest:test_deposit() (gas: 53639) +LenderGasTest:test_depositWithCourier() (gas: 53785) +LenderGasTest:test_redeem() (gas: 53334) +LenderGasTest:test_redeemWithCourier() (gas: 83726) +LenderGasTest:test_repay() (gas: 44752) +LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51017) +LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59176) +LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95596) +LiquidatorGasTest:test_warn() (gas: 35023) +LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130182) VolatilityGasTest:test_consult() (gas: 43075) -VolatilityGasTest:test_updateNoBinarySearch() (gas: 145880) \ No newline at end of file +VolatilityGasTest:test_updateNoBinarySearch() (gas: 145910) \ No newline at end of file From 661e986dd69f02d34b18963f8747d51a7fcf811b Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sun, 10 Sep 2023 00:45:39 -0500 Subject: [PATCH 06/11] gs --- core/.gas-snapshot | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/core/.gas-snapshot b/core/.gas-snapshot index 04bb3a5d..23aab4bf 100644 --- a/core/.gas-snapshot +++ b/core/.gas-snapshot @@ -1,26 +1,26 @@ BorrowerGasTest:test_addMargin() (gas: 16203) -BorrowerGasTest:test_borrow() (gas: 110489) +BorrowerGasTest:test_borrow() (gas: 110555) BorrowerGasTest:test_getUniswapPositions() (gas: 5219) -BorrowerGasTest:test_modify() (gas: 82108) -BorrowerGasTest:test_modifyWithAnte() (gas: 88563) -BorrowerGasTest:test_repay() (gas: 111848) -BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257723) +BorrowerGasTest:test_modify() (gas: 82152) +BorrowerGasTest:test_modifyWithAnte() (gas: 88607) +BorrowerGasTest:test_repay() (gas: 111914) +BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257767) BorrowerGasTest:test_uniswapDepositStandard() (gas: 167558) -BorrowerGasTest:test_uniswapWithdraw() (gas: 147620) -BorrowerGasTest:test_withdraw() (gas: 105628) -FactoryGasTest:test_createBorrower() (gas: 156431) -FactoryGasTest:test_createMarket() (gas: 3900591) -LenderGasTest:test_accrueInterest() (gas: 46287) -LenderGasTest:test_borrow() (gas: 40812) -LenderGasTest:test_deposit() (gas: 53639) -LenderGasTest:test_depositWithCourier() (gas: 53785) -LenderGasTest:test_redeem() (gas: 53334) -LenderGasTest:test_redeemWithCourier() (gas: 83726) -LenderGasTest:test_repay() (gas: 44752) -LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51017) -LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59176) -LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95596) -LiquidatorGasTest:test_warn() (gas: 35023) -LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130182) +BorrowerGasTest:test_uniswapWithdraw() (gas: 147656) +BorrowerGasTest:test_withdraw() (gas: 105672) +FactoryGasTest:test_createBorrower() (gas: 156519) +FactoryGasTest:test_createMarket() (gas: 3905006) +LenderGasTest:test_accrueInterest() (gas: 46070) +LenderGasTest:test_borrow() (gas: 40834) +LenderGasTest:test_deposit() (gas: 53422) +LenderGasTest:test_depositWithCourier() (gas: 53568) +LenderGasTest:test_redeem() (gas: 53114) +LenderGasTest:test_redeemWithCourier() (gas: 83506) +LenderGasTest:test_repay() (gas: 44774) +LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51083) +LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59264) +LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95666) +LiquidatorGasTest:test_warn() (gas: 35067) +LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130248) VolatilityGasTest:test_consult() (gas: 43075) VolatilityGasTest:test_updateNoBinarySearch() (gas: 145910) \ No newline at end of file From 9f6b61bcd3bef0b788707a9e8266fa159e342680 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sun, 10 Sep 2023 20:54:26 -0500 Subject: [PATCH 07/11] Remove extra sig digs from IV and improve constants --- core/.gas-snapshot | 28 ++--- core/src/VolatilityOracle.sol | 4 +- core/src/libraries/BalanceSheet.sol | 31 +++-- core/src/libraries/Volatility.sol | 9 +- core/src/libraries/constants/Constants.sol | 32 ++--- core/test/Utils.sol | 2 +- core/test/VolatilityOracle.t.sol | 14 +-- core/test/libraries/BalanceSheet.t.sol | 139 ++++++++++++++------- core/test/libraries/Volatility.t.sol | 10 +- 9 files changed, 163 insertions(+), 106 deletions(-) diff --git a/core/.gas-snapshot b/core/.gas-snapshot index 23aab4bf..d77ccd12 100644 --- a/core/.gas-snapshot +++ b/core/.gas-snapshot @@ -1,15 +1,15 @@ BorrowerGasTest:test_addMargin() (gas: 16203) -BorrowerGasTest:test_borrow() (gas: 110555) +BorrowerGasTest:test_borrow() (gas: 110565) BorrowerGasTest:test_getUniswapPositions() (gas: 5219) -BorrowerGasTest:test_modify() (gas: 82152) -BorrowerGasTest:test_modifyWithAnte() (gas: 88607) -BorrowerGasTest:test_repay() (gas: 111914) -BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257767) +BorrowerGasTest:test_modify() (gas: 82162) +BorrowerGasTest:test_modifyWithAnte() (gas: 88617) +BorrowerGasTest:test_repay() (gas: 111924) +BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257777) BorrowerGasTest:test_uniswapDepositStandard() (gas: 167558) -BorrowerGasTest:test_uniswapWithdraw() (gas: 147656) -BorrowerGasTest:test_withdraw() (gas: 105672) +BorrowerGasTest:test_uniswapWithdraw() (gas: 147664) +BorrowerGasTest:test_withdraw() (gas: 105682) FactoryGasTest:test_createBorrower() (gas: 156519) -FactoryGasTest:test_createMarket() (gas: 3905006) +FactoryGasTest:test_createMarket() (gas: 3903606) LenderGasTest:test_accrueInterest() (gas: 46070) LenderGasTest:test_borrow() (gas: 40834) LenderGasTest:test_deposit() (gas: 53422) @@ -17,10 +17,10 @@ LenderGasTest:test_depositWithCourier() (gas: 53568) LenderGasTest:test_redeem() (gas: 53114) LenderGasTest:test_redeemWithCourier() (gas: 83506) LenderGasTest:test_repay() (gas: 44774) -LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51083) -LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59264) -LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95666) -LiquidatorGasTest:test_warn() (gas: 35067) -LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130248) +LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51093) +LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59274) +LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95674) +LiquidatorGasTest:test_warn() (gas: 35077) +LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130258) VolatilityGasTest:test_consult() (gas: 43075) -VolatilityGasTest:test_updateNoBinarySearch() (gas: 145910) \ No newline at end of file +VolatilityGasTest:test_updateNoBinarySearch() (gas: 145902) \ No newline at end of file diff --git a/core/src/VolatilityOracle.sol b/core/src/VolatilityOracle.sol index 1dcb2890..96356eb5 100644 --- a/core/src/VolatilityOracle.sol +++ b/core/src/VolatilityOracle.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.17; import {IUniswapV3Pool} from "v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import { - IV_MAX, IV_SCALE, + IV_COLD_START, IV_CHANGE_PER_SECOND, UNISWAP_AVG_WINDOW, FEE_GROWTH_AVG_WINDOW, @@ -38,7 +38,7 @@ contract VolatilityOracle { if (lastWrites[pool].time == 0) { feeGrowthGlobals[pool][0] = _getFeeGrowthGlobalsNow(pool); - lastWrites[pool] = LastWrite({index: 0, time: uint32(block.timestamp), iv: uint216(IV_MAX)}); + lastWrites[pool] = LastWrite({index: 0, time: uint32(block.timestamp), iv: IV_COLD_START}); } } diff --git a/core/src/libraries/BalanceSheet.sol b/core/src/libraries/BalanceSheet.sol index f710d7f1..1ed79acb 100644 --- a/core/src/libraries/BalanceSheet.sol +++ b/core/src/libraries/BalanceSheet.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.17; import {FixedPointMathLib as SoladyMath} from "solady/utils/FixedPointMathLib.sol"; import { - IV_MIN, - IV_MAX, + LTV_MIN, + LTV_MAX, MAX_LEVERAGE, LIQUIDATION_INCENTIVE, MANIPULATION_THRESHOLD_DIVISOR @@ -32,6 +32,18 @@ struct Prices { /// @notice Provides functions for computing a `Borrower`'s health /// @author Aloe Labs, Inc. library BalanceSheet { + /// @dev The minimum percentage that can be added/subtracted to the TWAP to get probe prices + uint256 internal constant PROBE_PERCENT_MIN = + 1e12 - + (LTV_MAX * (LIQUIDATION_INCENTIVE * MAX_LEVERAGE + LIQUIDATION_INCENTIVE + MAX_LEVERAGE)) / + (LIQUIDATION_INCENTIVE * MAX_LEVERAGE); + + /// @dev The maximum percentage that can be added/subtracted to the TWAP to get probe prices + uint256 internal constant PROBE_PERCENT_MAX = + 1e12 - + (LTV_MIN * (LIQUIDATION_INCENTIVE * MAX_LEVERAGE + LIQUIDATION_INCENTIVE + MAX_LEVERAGE)) / + (LIQUIDATION_INCENTIVE * MAX_LEVERAGE); + function isHealthy( Prices memory prices, Assets memory mem, @@ -87,11 +99,11 @@ library BalanceSheet { uint56 metric ) internal pure returns (uint160 a, uint160 b, bool isSus) { unchecked { - iv = nSigma * SoladyMath.clamp(iv, IV_MIN, IV_MAX); + iv = SoladyMath.clamp(nSigma * iv, PROBE_PERCENT_MIN, PROBE_PERCENT_MAX); isSus = metric > _manipulationThreshold(_effectiveCollateralFactor(iv)); - a = uint160((sqrtMeanPriceX96 * SoladyMath.sqrt(1e18 - iv)) / 1e9); - b = uint160(SoladyMath.min((sqrtMeanPriceX96 * SoladyMath.sqrt(1e18 + iv)) / 1e9, type(uint160).max)); + a = uint160((sqrtMeanPriceX96 * SoladyMath.sqrt(1e12 - iv)) / 1e6); + b = uint160(SoladyMath.min((sqrtMeanPriceX96 * SoladyMath.sqrt(1e12 + iv)) / 1e6, type(uint160).max)); } } @@ -123,18 +135,17 @@ library BalanceSheet { } } - /// @dev Equivalent to \\( \frac{log_{1.0001} \left( \frac{10^{18}}{cf} \right)}{12} \\) - /// assuming `MANIPULATION_THRESHOLD_DIVISOR` is 24 + /// @dev Equivalent to \\( \frac{log_{1.0001} \left( \frac{10^{12}}{cf} \right)}{\text{MANIPULATION_THRESHOLD_DIVISOR}} \\) function _manipulationThreshold(uint256 cf) private pure returns (uint24) { - return uint24(-TickMath.getTickAtSqrtRatio(uint160(cf)) - 501937) / MANIPULATION_THRESHOLD_DIVISOR; + return uint24(-TickMath.getTickAtSqrtRatio(uint160(cf)) - 778261) / (2 * MANIPULATION_THRESHOLD_DIVISOR); } /// @dev Equivalent to \\( \frac{1 - σ}{1 + \frac{1}{liquidationIncentive} + \frac{1}{maxLeverage}} \\) where - /// \\( σ = \frac{clampedAndScaledIV}{10^{18}} \\) in floating point + /// \\( σ = \frac{clampedAndScaledIV}{10^{12}} \\) in floating point function _effectiveCollateralFactor(uint256 clampedAndScaledIV) private pure returns (uint256 cf) { unchecked { cf = - ((1e18 - clampedAndScaledIV) * (LIQUIDATION_INCENTIVE * MAX_LEVERAGE)) / + ((1e12 - clampedAndScaledIV) * (LIQUIDATION_INCENTIVE * MAX_LEVERAGE)) / (LIQUIDATION_INCENTIVE * MAX_LEVERAGE + LIQUIDATION_INCENTIVE + MAX_LEVERAGE); } } diff --git a/core/src/libraries/Volatility.sol b/core/src/libraries/Volatility.sol index 6b133e34..19dad4a2 100644 --- a/core/src/libraries/Volatility.sol +++ b/core/src/libraries/Volatility.sol @@ -37,14 +37,14 @@ library Volatility { * @param a The pool's cumulative feeGrowthGlobals some time in the past * @param b The pool's cumulative feeGrowthGlobals as of the current block * @param scale The timescale (in seconds) in which IV should be reported, e.g. hourly, daily, annualized - * @return An estimate of the implied volatility scaled by 1e18 + * @return An estimate of the implied volatility scaled by 1e12 */ function estimate( PoolMetadata memory metadata, Oracle.PoolData memory data, FeeGrowthGlobals memory a, FeeGrowthGlobals memory b, - uint256 scale + uint32 scale ) internal pure returns (uint256) { uint256 tickTvl = computeTickTvl(metadata.tickSpacing, data.currentTick, data.sqrtPriceX96, data.tickLiquidity); @@ -73,9 +73,8 @@ library Volatility { if (volumeGamma0Gamma1 > (1 << 128)) volumeGamma0Gamma1 = (1 << 128); unchecked { - // Scale volume to the target time frame - volumeGamma0Gamma1 = (volumeGamma0Gamma1 * scale) / (b.timestamp - a.timestamp); - return SoladyMath.sqrt((4e36 * volumeGamma0Gamma1) / tickTvl); + // Scale volume to the target time frame, divide by `tickTvl`, and sqrt for final result + return SoladyMath.sqrt((4e24 * volumeGamma0Gamma1 * scale) / (b.timestamp - a.timestamp) / tickTvl); } } diff --git a/core/src/libraries/constants/Constants.sol b/core/src/libraries/constants/Constants.sol index 7f6fd23c..5b7a5cda 100644 --- a/core/src/libraries/constants/Constants.sol +++ b/core/src/libraries/constants/Constants.sol @@ -65,6 +65,14 @@ uint32 constant CONSTRAINT_PAUSE_INTERVAL_MAX = 2 days; LIQUIDATION //////////////////////////////////////////////////////////////*/ +/// @dev The minimum loan-to-value ratio. Actual ratio is based on implied volatility; this is just a lower bound. +/// Expressed as a 1e12 percentage, e.g. 0.10e12 → 10% +uint256 constant LTV_MIN = 0.10e12; + +/// @dev The maximum loan-to-value ratio. Actual ratio is based on implied volatility; this is just a upper bound. +/// Expressed as a 1e12 percentage, e.g. 0.90e12 → 90% +uint256 constant LTV_MAX = 0.90e12; + /// @dev \\( 1 + \frac{1}{\text{MAX_LEVERAGE}} \\) should be greater than the maximum feasible single-block /// `accrualFactor` so that liquidators have time to respond to interest updates uint256 constant MAX_LEVERAGE = 200; @@ -77,26 +85,22 @@ uint256 constant LIQUIDATION_GRACE_PERIOD = 2 minutes; IV AND TWAP //////////////////////////////////////////////////////////////*/ -/// @dev The minimum implied volatility. Clamped to this **before** multiplying by `nSigma`. -/// Expressed as a wad percentage at `IV_SCALE`, e.g. {0.01e18, 24 hours} → 1% daily → 19% annual -uint256 constant IV_MIN = 0.01e18; - -/// @dev The maximum implied volatility. Clamped to this **before** multiplying by `nSigma`. -/// Expressed as a wad percentage at `IV_SCALE`, e.g. {0.18e18, 24 hours} → 18% daily → 344% annual -/// To avoid underflow in `BalanceSheet.computeProbePrices`, ensure that `IV_MAX * nSigma <= 1e18` -uint256 constant IV_MAX = 0.18e18; - /// @dev The timescale of implied volatility, applied to measurements and calculations. When `BalanceSheet` detects /// that an `nSigma` event would cause insolvency in this time period, it enables liquidations. So if you squint your /// eyes and wave your hands enough, this is (in expectation) the time liquidators have to act before the protocol /// accrues bad debt. -uint256 constant IV_SCALE = 24 hours; +uint32 constant IV_SCALE = 24 hours; + +/// @dev The initial value of implied volatility, used when `VolatilityOracle.prepare` is called for a new pool. +/// Expressed as a 1e12 percentage at `IV_SCALE`, e.g. {0.20e12, 24 hours} → 20% daily → 382% annual. Error on the +/// side of making this too large (resulting in low LTV). +uint128 constant IV_COLD_START = 0.20e12; /// @dev The maximum rate at which (reported) implied volatility can change. Raw samples in `VolatilityOracle.update` /// are clamped (before being stored) so as not to exceed this rate. -/// Expressed in wad percentage points at `IV_SCALE` **per second**, e.g. {462962962962, 24 hours} means daily IV can +/// Expressed in 1e12 percentage points at `IV_SCALE` **per second**, e.g. {462962, 24 hours} means daily IV can /// change by 0.0000463 percentage points per second → 4 percentage points per day. -uint256 constant IV_CHANGE_PER_SECOND = 462962962962; +uint256 constant IV_CHANGE_PER_SECOND = 462962; /// @dev To estimate volume, we need 2 samples. One is always at the current block, the other is from /// `FEE_GROWTH_AVG_WINDOW` seconds ago, +/- `FEE_GROWTH_SAMPLE_PERIOD / 2`. Larger values make the resulting volume @@ -124,5 +128,5 @@ uint32 constant UNISWAP_AVG_WINDOW = 30 minutes; /// threshold is calculated as follows: /// /// \\( \text{manipulationThreshold} = -/// 2 \cdot \frac{log_{1.0001}\left( \frac{1}{\text{collateralFactor}} \right)}{\text{MANIPULATION_THRESHOLD_DIVISOR}} \\) -uint24 constant MANIPULATION_THRESHOLD_DIVISOR = 24; +/// \frac{log_{1.0001}\left( \frac{1}{\text{collateralFactor}} \right)}{\text{MANIPULATION_THRESHOLD_DIVISOR}} \\) +uint24 constant MANIPULATION_THRESHOLD_DIVISOR = 12; diff --git a/core/test/Utils.sol b/core/test/Utils.sol index 9caf5669..7342f6dc 100644 --- a/core/test/Utils.sol +++ b/core/test/Utils.sol @@ -48,7 +48,7 @@ contract VolatilityOracleMock { function consult(IUniswapV3Pool pool, uint40 seed) external view returns (uint56, uint160, uint256) { (Oracle.PoolData memory data, uint56 metric) = Oracle.consult(pool, seed); - return (metric, data.sqrtMeanPriceX96, 0.025e18); + return (metric, data.sqrtMeanPriceX96, 0.025e12); } } diff --git a/core/test/VolatilityOracle.t.sol b/core/test/VolatilityOracle.t.sol index d22f4308..c98f1be1 100644 --- a/core/test/VolatilityOracle.t.sol +++ b/core/test/VolatilityOracle.t.sol @@ -40,7 +40,7 @@ contract VolatilityOracleTest is Test { assertGt(metric, 0); assertGt(price, 0); - assertEqDecimal(iv, 0, 18); + assertEqDecimal(iv, 0, 12); } } @@ -72,7 +72,7 @@ contract VolatilityOracleTest is Test { assertEq(fggTime, block.timestamp); assertEq(index, 0); assertEq(time, block.timestamp); - assertEqDecimal(iv, IV_MAX, 18); + assertEqDecimal(iv, IV_COLD_START, 12); } vm.expectRevert(bytes("Aloe: cardinality")); @@ -96,12 +96,12 @@ contract VolatilityOracleTest is Test { (, , uint256 ivOld) = oracle.consult(pool, (1 << 32)); (, , uint256 ivNew) = oracle.update(pool, (1 << 32)); - assertEqDecimal(ivOld, ivOldExpected, 18); - assertEqDecimal(ivNew, ivOld, 18); + assertEqDecimal(ivOld, ivOldExpected, 12); + assertEqDecimal(ivNew, ivOld, 12); (index, time, ivNew) = oracle.lastWrites(pool); - assertEqDecimal(ivNew, ivOld, 18); + assertEqDecimal(ivNew, ivOld, 12); assertEq(index, 1); assertEq(time, block.timestamp); } @@ -125,7 +125,7 @@ contract VolatilityOracleTest is Test { (, , uint256 ivNew) = oracle.update(pool, (1 << 32)); (bytes32[] memory reads, bytes32[] memory writes) = vm.accesses(address(oracle)); - assertEqDecimal(ivNew, ivOld, 18); + assertEqDecimal(ivNew, ivOld, 12); assertEq(reads.length, 1); assertEq(writes.length, 0); } @@ -180,7 +180,7 @@ contract VolatilityOracleTest is Test { (, , uint256 ivWritten) = oracle.update(pool, (1 << 32)); (uint256 newIndex, uint256 newTime, uint256 ivStored) = oracle.lastWrites(pool); - assertEqDecimal(ivStored, ivWritten, 18); + assertEqDecimal(ivStored, ivWritten, 12); assertEq(newIndex, (currentIndex + 1) % FEE_GROWTH_ARRAY_LENGTH); uint256 maxChange = (newTime - currentTime) * IV_CHANGE_PER_SECOND; diff --git a/core/test/libraries/BalanceSheet.t.sol b/core/test/libraries/BalanceSheet.t.sol index 12c539ef..60c0ae22 100644 --- a/core/test/libraries/BalanceSheet.t.sol +++ b/core/test/libraries/BalanceSheet.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.17; import "forge-std/Test.sol"; +import {CONSTRAINT_N_SIGMA_MIN, CONSTRAINT_N_SIGMA_MAX, LTV_MIN, IV_COLD_START} from "src/libraries/constants/Constants.sol"; import {BalanceSheet, TickMath, square} from "src/libraries/BalanceSheet.sol"; import {FixedPointMathLib as SoladyMath} from "solady/utils/FixedPointMathLib.sol"; @@ -14,124 +15,166 @@ contract BalanceSheetTest is Test { function test_spec_computeProbePrices() public { bool isSus; - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.00e18, 5, 87); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.00e12, 5, 87); assertFalse(isSus, "0.00 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.00e18, 5, 88); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.00e12, 5, 88); assertTrue(isSus, "0.00 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.01e18, 5, 87); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.01e12, 5, 87); assertFalse(isSus, "0.01 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.01e18, 5, 88); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.01e12, 5, 88); assertTrue(isSus, "0.01 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.02e18, 5, 132); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.02e12, 5, 132); assertFalse(isSus, "0.02 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.02e18, 5, 133); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.02e12, 5, 133); assertTrue(isSus, "0.02 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.03e18, 5, 180); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.03e12, 5, 180); assertFalse(isSus, "0.03 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.03e18, 5, 181); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.03e12, 5, 181); assertTrue(isSus, "0.03 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.04e18, 5, 230); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.04e12, 5, 230); assertFalse(isSus, "0.04 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.04e18, 5, 231); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.04e12, 5, 231); assertTrue(isSus, "0.04 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.05e18, 5, 284); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.05e12, 5, 284); assertFalse(isSus, "0.05 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.05e18, 5, 285); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.05e12, 5, 285); assertTrue(isSus, "0.05 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.06e18, 5, 341); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.06e12, 5, 341); assertFalse(isSus, "0.06 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.06e18, 5, 342); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.06e12, 5, 342); assertTrue(isSus, "0.06 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.07e18, 5, 403); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.07e12, 5, 403); assertFalse(isSus, "0.07 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.07e18, 5, 404); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.07e12, 5, 404); assertTrue(isSus, "0.07 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.08e18, 5, 470); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.08e12, 5, 470); assertFalse(isSus, "0.08 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.08e18, 5, 471); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.08e12, 5, 471); assertTrue(isSus, "0.08 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.09e18, 5, 542); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.09e12, 5, 542); assertFalse(isSus, "0.09 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.09e18, 5, 543); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.09e12, 5, 543); assertTrue(isSus, "0.09 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.10e18, 5, 622); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.10e12, 5, 622); assertFalse(isSus, "0.10 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.10e18, 5, 623); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.10e12, 5, 623); assertTrue(isSus, "0.10 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.11e18, 5, 710); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.11e12, 5, 710); assertFalse(isSus, "0.11 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.11e18, 5, 711); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.11e12, 5, 711); assertTrue(isSus, "0.11 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.12e18, 5, 808); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.12e12, 5, 808); assertFalse(isSus, "0.12 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.12e18, 5, 809); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.12e12, 5, 809); assertTrue(isSus, "0.12 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.13e18, 5, 919); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.13e12, 5, 919); assertFalse(isSus, "0.13 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.13e18, 5, 920); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.13e12, 5, 920); assertTrue(isSus, "0.13 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.14e18, 5, 1048); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.14e12, 5, 1048); assertFalse(isSus, "0.14 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.14e18, 5, 1049); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.14e12, 5, 1049); assertTrue(isSus, "0.14 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.15e18, 5, 1199); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.15e12, 5, 1199); assertFalse(isSus, "0.15 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.15e18, 5, 1200); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.15e12, 5, 1200); assertTrue(isSus, "0.15 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.16e18, 5, 1385); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.16e12, 5, 1385); assertFalse(isSus, "0.16 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.16e18, 5, 1386); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.16e12, 5, 1386); assertTrue(isSus, "0.16 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.17e18, 5, 1625); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.17e12, 5, 1625); assertFalse(isSus, "0.17 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.17e18, 5, 1626); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.17e12, 5, 1626); assertTrue(isSus, "0.17 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.18e18, 5, 1963); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.18e12, 5, 1918); assertFalse(isSus, "0.18 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.18e18, 5, 1964); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.18e12, 5, 1919); assertTrue(isSus, "0.18 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.19e18, 5, 1963); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.19e12, 5, 1918); assertFalse(isSus, "0.19 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.19e18, 5, 1964); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.19e12, 5, 1919); assertTrue(isSus, "0.19 true"); + + (, , isSus) = BalanceSheet.computeProbePrices(0, IV_COLD_START, 5, 1918); + assertFalse(isSus, "cold start false"); + (, , isSus) = BalanceSheet.computeProbePrices(0, IV_COLD_START, 5, 1919); + assertTrue(isSus, "cold start true"); } - function test_computeProbePrices(uint160 sqrtMeanPriceX96, uint256 sigma) public { + function test_computeProbePrices(uint160 sqrtMeanPriceX96, uint256 iv, uint256 nSigma) public { // The lower bound is related to how precise our assertion is. For prices to be correct within 0.01%, // the sqrtPrice must be >= 2^40 (approximately). Calculations for that are here: // https://www.desmos.com/calculator/gfbkcnt0vs // The upper bound is due to the fact that the result (specifically `b`) must fit in uint160. The maximum - // volatility factor is 1 + 5*0.18, so we divide `TickMath.MAX_SQRT_RATIO` by sqrt(1e18 + 5*0.18e18) - sqrtMeanPriceX96 = uint160(bound(sqrtMeanPriceX96, (1 << 40), TickMath.MAX_SQRT_RATIO / 1.0863e9)); - (uint256 a, uint256 b, ) = BalanceSheet.computeProbePrices(sqrtMeanPriceX96, sigma, 5, 0); + // volatility factor is 1 + IV_AWARE_PROBE_PERCENT_MAX, so we divide `TickMath.MAX_SQRT_RATIO` by + // sqrt(1e12 + IV_AWARE_PROBE_PERCENT_MAX) + sqrtMeanPriceX96 = uint160(bound(sqrtMeanPriceX96, (1 << 40), uint256(TickMath.MAX_SQRT_RATIO) * 1e6 / 1376408)); + nSigma = uint160(bound(nSigma, CONSTRAINT_N_SIGMA_MIN, CONSTRAINT_N_SIGMA_MAX)); + + (uint256 a, uint256 b, ) = BalanceSheet.computeProbePrices(sqrtMeanPriceX96, iv, nSigma, 0); uint256 price = square(sqrtMeanPriceX96); a = square(uint160(a)); b = square(uint160(b)); - if (sigma < 0.01e18) sigma = 0.01e18; - else if (sigma > 0.18e18) sigma = 0.18e18; + if (iv < BalanceSheet.PROBE_PERCENT_MIN / nSigma) iv = BalanceSheet.PROBE_PERCENT_MIN / nSigma; + else if (iv > BalanceSheet.PROBE_PERCENT_MAX / nSigma) iv = BalanceSheet.PROBE_PERCENT_MAX / nSigma; + + assertApproxEqRel(a, SoladyMath.fullMulDiv(price, 1e12 - nSigma * iv, 1e12), 0.0001e18); + assertApproxEqRel(b, SoladyMath.fullMulDiv(price, 1e12 + nSigma * iv, 1e12), 0.0001e18); + } + + function test_constants() public { + assertEq(BalanceSheet.PROBE_PERCENT_MIN, 50500000000); + assertEq(BalanceSheet.PROBE_PERCENT_MAX, 894500000000); + + // Necessary for collateral factor computation to work + assertGt(LTV_MIN, TickMath.MIN_SQRT_RATIO); + } + + /// @dev We have to override this because we need 512 bit multiplication in the assertion + function assertApproxEqRel( + uint256 a, + uint256 b, + uint256 maxPercentDelta // An 18 decimal fixed point number, where 1e18 == 100% + ) internal override { + if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. + + uint256 percentDelta = _percentDelta(a, b); + + if (percentDelta > maxPercentDelta) { + emit log("Error: a ~= b not satisfied [uint]"); + emit log_named_uint(" Expected", b); + emit log_named_uint(" Actual", a); + emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18); + emit log_named_decimal_uint(" % Delta", percentDelta, 18); + fail(); + } + } + + function _percentDelta(uint256 a, uint256 b) private pure returns (uint256) { + uint256 absDelta = stdMath.delta(a, b); - assertApproxEqRel(a, SoladyMath.fullMulDiv(price, 1e18 - 5 * sigma, 1e18), 0.0001e18); - assertApproxEqRel(b, SoladyMath.fullMulDiv(price, 1e18 + 5 * sigma, 1e18), 0.0001e18); + return SoladyMath.fullMulDivUp(absDelta, 1e18, b); } } diff --git a/core/test/libraries/Volatility.t.sol b/core/test/libraries/Volatility.t.sol index 0e9b6ca4..28731571 100644 --- a/core/test/libraries/Volatility.t.sol +++ b/core/test/libraries/Volatility.t.sol @@ -35,7 +35,7 @@ contract VolatilityTest is Test { ), 1 days ); - assertEq(dailyIV, 20405953715139097); // 2.041% + assertEq(dailyIV, 20405953715); // 2.041% dailyIV = Volatility.estimate( metadata, @@ -52,7 +52,7 @@ contract VolatilityTest is Test { ), 1 hours ); - assertEq(dailyIV, 4165347859745125); // 0.417% + assertEq(dailyIV, 4165347859); // 0.417% dailyIV = Volatility.estimate( metadata, @@ -65,7 +65,7 @@ contract VolatilityTest is Test { ), 1 days ); - assertEq(dailyIV, 6970260375825778); // 0.697% + assertEq(dailyIV, 6970260375); // 0.697% dailyIV = Volatility.estimate( metadata, @@ -272,7 +272,7 @@ contract VolatilityTest is Test { uint32 oracleLookback, uint128 tickLiquidity, uint32 scale - ) public view { + ) public { scale = uint32(bound(scale, 1 minutes, 2 days)); oracleLookback = uint32(bound(oracleLookback, 15 seconds, 1 days)); secondsPerLiquidityX128 = uint160(bound(secondsPerLiquidityX128, feeGrowthSampleAge, type(uint160).max)); @@ -313,7 +313,7 @@ contract VolatilityTest is Test { b.timestamp = feeGrowthSampleAge; } - Volatility.estimate(metadata, data, a, b, scale); + assertLt(Volatility.estimate(metadata, data, a, b, scale), 1 << 128); } function boundTick(int24 tick, int24 tickSpacing) private pure returns (int24) { From e5a1a41fb2d8acff631340e6873cc709d3b3945a Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sun, 10 Sep 2023 21:53:14 -0500 Subject: [PATCH 08/11] Move constants back to constants file --- core/src/libraries/BalanceSheet.sol | 16 ++----------- core/src/libraries/constants/Constants.sol | 27 ++++++++++++++++------ core/test/libraries/BalanceSheet.t.sol | 23 +++++++++++++----- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/core/src/libraries/BalanceSheet.sol b/core/src/libraries/BalanceSheet.sol index 1ed79acb..a11ed7d1 100644 --- a/core/src/libraries/BalanceSheet.sol +++ b/core/src/libraries/BalanceSheet.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.17; import {FixedPointMathLib as SoladyMath} from "solady/utils/FixedPointMathLib.sol"; import { - LTV_MIN, - LTV_MAX, MAX_LEVERAGE, LIQUIDATION_INCENTIVE, + PROBE_PERCENT_MIN, + PROBE_PERCENT_MAX, MANIPULATION_THRESHOLD_DIVISOR } from "./constants/Constants.sol"; import {square, mulDiv128} from "./MulDiv.sol"; @@ -32,18 +32,6 @@ struct Prices { /// @notice Provides functions for computing a `Borrower`'s health /// @author Aloe Labs, Inc. library BalanceSheet { - /// @dev The minimum percentage that can be added/subtracted to the TWAP to get probe prices - uint256 internal constant PROBE_PERCENT_MIN = - 1e12 - - (LTV_MAX * (LIQUIDATION_INCENTIVE * MAX_LEVERAGE + LIQUIDATION_INCENTIVE + MAX_LEVERAGE)) / - (LIQUIDATION_INCENTIVE * MAX_LEVERAGE); - - /// @dev The maximum percentage that can be added/subtracted to the TWAP to get probe prices - uint256 internal constant PROBE_PERCENT_MAX = - 1e12 - - (LTV_MIN * (LIQUIDATION_INCENTIVE * MAX_LEVERAGE + LIQUIDATION_INCENTIVE + MAX_LEVERAGE)) / - (LIQUIDATION_INCENTIVE * MAX_LEVERAGE); - function isHealthy( Prices memory prices, Assets memory mem, diff --git a/core/src/libraries/constants/Constants.sol b/core/src/libraries/constants/Constants.sol index 5b7a5cda..29258c2f 100644 --- a/core/src/libraries/constants/Constants.sol +++ b/core/src/libraries/constants/Constants.sol @@ -65,6 +65,17 @@ uint32 constant CONSTRAINT_PAUSE_INTERVAL_MAX = 2 days; LIQUIDATION //////////////////////////////////////////////////////////////*/ +/// @dev \\( 1 + \frac{1}{\text{MAX_LEVERAGE}} \\) should be greater than the maximum feasible single-block +/// `accrualFactor` so that liquidators have time to respond to interest updates +uint256 constant MAX_LEVERAGE = 200; + +/// @dev The discount that liquidators receive when swapping assets. Expressed as reciprocal, e.g. 20 → 5% +uint256 constant LIQUIDATION_INCENTIVE = 20; + +/// @dev The minimum time that must pass between `Borrower.warn` and `Borrower.liquidate` for any liquidation that +/// involves the swap callbacks (`swap1For0` and `swap0For1`). There is no grace period for in-kind liquidations. +uint256 constant LIQUIDATION_GRACE_PERIOD = 2 minutes; + /// @dev The minimum loan-to-value ratio. Actual ratio is based on implied volatility; this is just a lower bound. /// Expressed as a 1e12 percentage, e.g. 0.10e12 → 10% uint256 constant LTV_MIN = 0.10e12; @@ -73,13 +84,15 @@ uint256 constant LTV_MIN = 0.10e12; /// Expressed as a 1e12 percentage, e.g. 0.90e12 → 90% uint256 constant LTV_MAX = 0.90e12; -/// @dev \\( 1 + \frac{1}{\text{MAX_LEVERAGE}} \\) should be greater than the maximum feasible single-block -/// `accrualFactor` so that liquidators have time to respond to interest updates -uint256 constant MAX_LEVERAGE = 200; - -uint256 constant LIQUIDATION_INCENTIVE = 20; // Expressed as reciprocal, e.g. 20 → 5% +/// @dev The minimum percentage that can be added/subtracted to the TWAP to get probe prices in `BalanceSheet` +uint256 constant PROBE_PERCENT_MIN = 1e12 - + (LTV_MAX * (LIQUIDATION_INCENTIVE * MAX_LEVERAGE + LIQUIDATION_INCENTIVE + MAX_LEVERAGE)) / + (LIQUIDATION_INCENTIVE * MAX_LEVERAGE); -uint256 constant LIQUIDATION_GRACE_PERIOD = 2 minutes; +/// @dev The maximum percentage that can be added/subtracted to the TWAP to get probe prices in `BalanceSheet` +uint256 constant PROBE_PERCENT_MAX = 1e12 - + (LTV_MIN * (LIQUIDATION_INCENTIVE * MAX_LEVERAGE + LIQUIDATION_INCENTIVE + MAX_LEVERAGE)) / + (LIQUIDATION_INCENTIVE * MAX_LEVERAGE); /*////////////////////////////////////////////////////////////// IV AND TWAP @@ -94,7 +107,7 @@ uint32 constant IV_SCALE = 24 hours; /// @dev The initial value of implied volatility, used when `VolatilityOracle.prepare` is called for a new pool. /// Expressed as a 1e12 percentage at `IV_SCALE`, e.g. {0.20e12, 24 hours} → 20% daily → 382% annual. Error on the /// side of making this too large (resulting in low LTV). -uint128 constant IV_COLD_START = 0.20e12; +uint128 constant IV_COLD_START = uint128(PROBE_PERCENT_MAX / DEFAULT_N_SIGMA); /// @dev The maximum rate at which (reported) implied volatility can change. Raw samples in `VolatilityOracle.update` /// are clamped (before being stored) so as not to exceed this rate. diff --git a/core/test/libraries/BalanceSheet.t.sol b/core/test/libraries/BalanceSheet.t.sol index 60c0ae22..2337ad38 100644 --- a/core/test/libraries/BalanceSheet.t.sol +++ b/core/test/libraries/BalanceSheet.t.sol @@ -3,7 +3,15 @@ pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {CONSTRAINT_N_SIGMA_MIN, CONSTRAINT_N_SIGMA_MAX, LTV_MIN, IV_COLD_START} from "src/libraries/constants/Constants.sol"; +import { + DEFAULT_N_SIGMA, + CONSTRAINT_N_SIGMA_MIN, + CONSTRAINT_N_SIGMA_MAX, + LTV_MIN, + PROBE_PERCENT_MIN, + PROBE_PERCENT_MAX, + IV_COLD_START +} from "src/libraries/constants/Constants.sol"; import {BalanceSheet, TickMath, square} from "src/libraries/BalanceSheet.sol"; import {FixedPointMathLib as SoladyMath} from "solady/utils/FixedPointMathLib.sol"; @@ -128,7 +136,9 @@ contract BalanceSheetTest is Test { // The upper bound is due to the fact that the result (specifically `b`) must fit in uint160. The maximum // volatility factor is 1 + IV_AWARE_PROBE_PERCENT_MAX, so we divide `TickMath.MAX_SQRT_RATIO` by // sqrt(1e12 + IV_AWARE_PROBE_PERCENT_MAX) - sqrtMeanPriceX96 = uint160(bound(sqrtMeanPriceX96, (1 << 40), uint256(TickMath.MAX_SQRT_RATIO) * 1e6 / 1376408)); + sqrtMeanPriceX96 = uint160( + bound(sqrtMeanPriceX96, (1 << 40), (uint256(TickMath.MAX_SQRT_RATIO) * 1e6) / 1376408) + ); nSigma = uint160(bound(nSigma, CONSTRAINT_N_SIGMA_MIN, CONSTRAINT_N_SIGMA_MAX)); (uint256 a, uint256 b, ) = BalanceSheet.computeProbePrices(sqrtMeanPriceX96, iv, nSigma, 0); @@ -137,16 +147,17 @@ contract BalanceSheetTest is Test { a = square(uint160(a)); b = square(uint160(b)); - if (iv < BalanceSheet.PROBE_PERCENT_MIN / nSigma) iv = BalanceSheet.PROBE_PERCENT_MIN / nSigma; - else if (iv > BalanceSheet.PROBE_PERCENT_MAX / nSigma) iv = BalanceSheet.PROBE_PERCENT_MAX / nSigma; + if (iv < PROBE_PERCENT_MIN / nSigma) iv = PROBE_PERCENT_MIN / nSigma; + else if (iv > PROBE_PERCENT_MAX / nSigma) iv = PROBE_PERCENT_MAX / nSigma; assertApproxEqRel(a, SoladyMath.fullMulDiv(price, 1e12 - nSigma * iv, 1e12), 0.0001e18); assertApproxEqRel(b, SoladyMath.fullMulDiv(price, 1e12 + nSigma * iv, 1e12), 0.0001e18); } function test_constants() public { - assertEq(BalanceSheet.PROBE_PERCENT_MIN, 50500000000); - assertEq(BalanceSheet.PROBE_PERCENT_MAX, 894500000000); + // Just checking that things are reasonable + assertEqDecimal(PROBE_PERCENT_MIN, 50500000000, 12); + assertEqDecimal(PROBE_PERCENT_MAX, 894500000000, 12); // Necessary for collateral factor computation to work assertGt(LTV_MIN, TickMath.MIN_SQRT_RATIO); From 51671a58c936941645748347050a0e4b4dfc8516 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sun, 10 Sep 2023 21:56:51 -0500 Subject: [PATCH 09/11] Note on TickMath --- core/src/libraries/constants/Constants.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/libraries/constants/Constants.sol b/core/src/libraries/constants/Constants.sol index 29258c2f..4b0e2e7f 100644 --- a/core/src/libraries/constants/Constants.sol +++ b/core/src/libraries/constants/Constants.sol @@ -77,7 +77,8 @@ uint256 constant LIQUIDATION_INCENTIVE = 20; uint256 constant LIQUIDATION_GRACE_PERIOD = 2 minutes; /// @dev The minimum loan-to-value ratio. Actual ratio is based on implied volatility; this is just a lower bound. -/// Expressed as a 1e12 percentage, e.g. 0.10e12 → 10% +/// Expressed as a 1e12 percentage, e.g. 0.10e12 → 10%. Must be greater than `TickMath.MIN_SQRT_RATIO` because +/// we reuse a base 1.0001 logarithm in `BalanceSheet` uint256 constant LTV_MIN = 0.10e12; /// @dev The maximum loan-to-value ratio. Actual ratio is based on implied volatility; this is just a upper bound. From 441e9aada2917aebe75ded7f7a8b10ac6cb75f87 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sun, 10 Sep 2023 23:22:08 -0500 Subject: [PATCH 10/11] Add digit to nSigma so it's more governable --- core/.gas-snapshot | 30 ++++---- core/src/libraries/BalanceSheet.sol | 2 +- core/src/libraries/Volatility.sol | 1 + core/src/libraries/constants/Constants.sol | 16 ++-- core/test/libraries/BalanceSheet.t.sol | 88 +++++++++++----------- 5 files changed, 72 insertions(+), 65 deletions(-) diff --git a/core/.gas-snapshot b/core/.gas-snapshot index d77ccd12..0c9ebb2e 100644 --- a/core/.gas-snapshot +++ b/core/.gas-snapshot @@ -1,15 +1,15 @@ BorrowerGasTest:test_addMargin() (gas: 16203) -BorrowerGasTest:test_borrow() (gas: 110565) +BorrowerGasTest:test_borrow() (gas: 110577) BorrowerGasTest:test_getUniswapPositions() (gas: 5219) -BorrowerGasTest:test_modify() (gas: 82162) -BorrowerGasTest:test_modifyWithAnte() (gas: 88617) -BorrowerGasTest:test_repay() (gas: 111924) -BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257777) +BorrowerGasTest:test_modify() (gas: 82174) +BorrowerGasTest:test_modifyWithAnte() (gas: 88629) +BorrowerGasTest:test_repay() (gas: 111936) +BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257789) BorrowerGasTest:test_uniswapDepositStandard() (gas: 167558) -BorrowerGasTest:test_uniswapWithdraw() (gas: 147664) -BorrowerGasTest:test_withdraw() (gas: 105682) +BorrowerGasTest:test_uniswapWithdraw() (gas: 147673) +BorrowerGasTest:test_withdraw() (gas: 105694) FactoryGasTest:test_createBorrower() (gas: 156519) -FactoryGasTest:test_createMarket() (gas: 3903606) +FactoryGasTest:test_createMarket() (gas: 3904606) LenderGasTest:test_accrueInterest() (gas: 46070) LenderGasTest:test_borrow() (gas: 40834) LenderGasTest:test_deposit() (gas: 53422) @@ -17,10 +17,10 @@ LenderGasTest:test_depositWithCourier() (gas: 53568) LenderGasTest:test_redeem() (gas: 53114) LenderGasTest:test_redeemWithCourier() (gas: 83506) LenderGasTest:test_repay() (gas: 44774) -LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51093) -LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59274) -LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95674) -LiquidatorGasTest:test_warn() (gas: 35077) -LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130258) -VolatilityGasTest:test_consult() (gas: 43075) -VolatilityGasTest:test_updateNoBinarySearch() (gas: 145902) \ No newline at end of file +LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51105) +LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59286) +LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95684) +LiquidatorGasTest:test_warn() (gas: 35089) +LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130270) +VolatilityGasTest:test_consult() (gas: 43024) +VolatilityGasTest:test_updateNoBinarySearch() (gas: 145908) \ No newline at end of file diff --git a/core/src/libraries/BalanceSheet.sol b/core/src/libraries/BalanceSheet.sol index a11ed7d1..2005cd42 100644 --- a/core/src/libraries/BalanceSheet.sol +++ b/core/src/libraries/BalanceSheet.sol @@ -87,7 +87,7 @@ library BalanceSheet { uint56 metric ) internal pure returns (uint160 a, uint160 b, bool isSus) { unchecked { - iv = SoladyMath.clamp(nSigma * iv, PROBE_PERCENT_MIN, PROBE_PERCENT_MAX); + iv = SoladyMath.clamp((nSigma * iv) / 10, PROBE_PERCENT_MIN, PROBE_PERCENT_MAX); isSus = metric > _manipulationThreshold(_effectiveCollateralFactor(iv)); a = uint160((sqrtMeanPriceX96 * SoladyMath.sqrt(1e12 - iv)) / 1e6); diff --git a/core/src/libraries/Volatility.sol b/core/src/libraries/Volatility.sol index 19dad4a2..200d7032 100644 --- a/core/src/libraries/Volatility.sol +++ b/core/src/libraries/Volatility.sol @@ -32,6 +32,7 @@ library Volatility { /** * @notice Estimates implied volatility using [this math](https://lambert-guillaume.medium.com/on-chain-volatility-and-uniswap-v3-d031b98143d1). + * @dev The return value can fit in uint128 if necessary * @param metadata The pool's metadata (may be cached) * @param data A summary of the pool's state from `pool.slot0` `pool.observe` and `pool.liquidity` * @param a The pool's cumulative feeGrowthGlobals some time in the past diff --git a/core/src/libraries/constants/Constants.sol b/core/src/libraries/constants/Constants.sol index 4b0e2e7f..6ed74f72 100644 --- a/core/src/libraries/constants/Constants.sol +++ b/core/src/libraries/constants/Constants.sol @@ -32,8 +32,8 @@ uint256 constant MAX_RATE = 706354; uint216 constant DEFAULT_ANTE = 0.01 ether; /// @dev The default number of standard deviations of price movement used to determine probe prices for `Borrower` -/// solvency. The `Factory` can override this value on a per-market basis. -uint8 constant DEFAULT_N_SIGMA = 5; +/// solvency. The `Factory` can override this value on a per-market basis. Expressed x10, e.g. 50 → 5σ +uint8 constant DEFAULT_N_SIGMA = 50; /// @dev The default portion of interest that will accrue to a `Lender`'s `RESERVE` address. /// Expressed as a reciprocal, e.g. 16 → 6.25% @@ -43,11 +43,13 @@ uint8 constant DEFAULT_RESERVE_FACTOR = 16; GOVERNANCE CONSTRAINTS //////////////////////////////////////////////////////////////*/ -/// @dev The lowest number of standard deviations of price movement allowed for determining `Borrower` probe prices -uint8 constant CONSTRAINT_N_SIGMA_MIN = 4; +/// @dev The lowest number of standard deviations of price movement allowed for determining `Borrower` probe prices. +/// Expressed x10, e.g. 40 → 4σ +uint8 constant CONSTRAINT_N_SIGMA_MIN = 40; -/// @dev The highest number of standard deviations of price movement allowed for determining `Borrower` probe prices -uint8 constant CONSTRAINT_N_SIGMA_MAX = 5; +/// @dev The highest number of standard deviations of price movement allowed for determining `Borrower` probe prices. +/// Expressed x10, e.g. 80 → 8σ +uint8 constant CONSTRAINT_N_SIGMA_MAX = 80; /// @dev The lower bound on what any `Lender`'s reserve factor can be. Expressed as reciprocal, e.g. 4 → 25% uint8 constant CONSTRAINT_RESERVE_FACTOR_MIN = 4; @@ -108,7 +110,7 @@ uint32 constant IV_SCALE = 24 hours; /// @dev The initial value of implied volatility, used when `VolatilityOracle.prepare` is called for a new pool. /// Expressed as a 1e12 percentage at `IV_SCALE`, e.g. {0.20e12, 24 hours} → 20% daily → 382% annual. Error on the /// side of making this too large (resulting in low LTV). -uint128 constant IV_COLD_START = uint128(PROBE_PERCENT_MAX / DEFAULT_N_SIGMA); +uint128 constant IV_COLD_START = uint128((PROBE_PERCENT_MAX * 10) / CONSTRAINT_N_SIGMA_MIN); /// @dev The maximum rate at which (reported) implied volatility can change. Raw samples in `VolatilityOracle.update` /// are clamped (before being stored) so as not to exceed this rate. diff --git a/core/test/libraries/BalanceSheet.t.sol b/core/test/libraries/BalanceSheet.t.sol index 2337ad38..bc462bca 100644 --- a/core/test/libraries/BalanceSheet.t.sol +++ b/core/test/libraries/BalanceSheet.t.sol @@ -23,109 +23,109 @@ contract BalanceSheetTest is Test { function test_spec_computeProbePrices() public { bool isSus; - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.00e12, 5, 87); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.00e12, 50, 87); assertFalse(isSus, "0.00 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.00e12, 5, 88); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.00e12, 50, 88); assertTrue(isSus, "0.00 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.01e12, 5, 87); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.01e12, 50, 87); assertFalse(isSus, "0.01 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.01e12, 5, 88); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.01e12, 50, 88); assertTrue(isSus, "0.01 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.02e12, 5, 132); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.02e12, 50, 132); assertFalse(isSus, "0.02 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.02e12, 5, 133); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.02e12, 50, 133); assertTrue(isSus, "0.02 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.03e12, 5, 180); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.03e12, 50, 180); assertFalse(isSus, "0.03 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.03e12, 5, 181); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.03e12, 50, 181); assertTrue(isSus, "0.03 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.04e12, 5, 230); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.04e12, 50, 230); assertFalse(isSus, "0.04 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.04e12, 5, 231); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.04e12, 50, 231); assertTrue(isSus, "0.04 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.05e12, 5, 284); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.05e12, 50, 284); assertFalse(isSus, "0.05 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.05e12, 5, 285); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.05e12, 50, 285); assertTrue(isSus, "0.05 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.06e12, 5, 341); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.06e12, 50, 341); assertFalse(isSus, "0.06 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.06e12, 5, 342); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.06e12, 50, 342); assertTrue(isSus, "0.06 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.07e12, 5, 403); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.07e12, 50, 403); assertFalse(isSus, "0.07 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.07e12, 5, 404); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.07e12, 50, 404); assertTrue(isSus, "0.07 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.08e12, 5, 470); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.08e12, 50, 470); assertFalse(isSus, "0.08 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.08e12, 5, 471); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.08e12, 50, 471); assertTrue(isSus, "0.08 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.09e12, 5, 542); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.09e12, 50, 542); assertFalse(isSus, "0.09 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.09e12, 5, 543); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.09e12, 50, 543); assertTrue(isSus, "0.09 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.10e12, 5, 622); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.10e12, 50, 622); assertFalse(isSus, "0.10 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.10e12, 5, 623); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.10e12, 50, 623); assertTrue(isSus, "0.10 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.11e12, 5, 710); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.11e12, 50, 710); assertFalse(isSus, "0.11 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.11e12, 5, 711); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.11e12, 50, 711); assertTrue(isSus, "0.11 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.12e12, 5, 808); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.12e12, 50, 808); assertFalse(isSus, "0.12 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.12e12, 5, 809); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.12e12, 50, 809); assertTrue(isSus, "0.12 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.13e12, 5, 919); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.13e12, 50, 919); assertFalse(isSus, "0.13 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.13e12, 5, 920); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.13e12, 50, 920); assertTrue(isSus, "0.13 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.14e12, 5, 1048); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.14e12, 50, 1048); assertFalse(isSus, "0.14 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.14e12, 5, 1049); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.14e12, 50, 1049); assertTrue(isSus, "0.14 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.15e12, 5, 1199); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.15e12, 50, 1199); assertFalse(isSus, "0.15 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.15e12, 5, 1200); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.15e12, 50, 1200); assertTrue(isSus, "0.15 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.16e12, 5, 1385); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.16e12, 50, 1385); assertFalse(isSus, "0.16 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.16e12, 5, 1386); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.16e12, 50, 1386); assertTrue(isSus, "0.16 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.17e12, 5, 1625); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.17e12, 50, 1625); assertFalse(isSus, "0.17 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.17e12, 5, 1626); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.17e12, 50, 1626); assertTrue(isSus, "0.17 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.18e12, 5, 1918); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.18e12, 50, 1918); assertFalse(isSus, "0.18 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.18e12, 5, 1919); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.18e12, 50, 1919); assertTrue(isSus, "0.18 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.19e12, 5, 1918); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.19e12, 50, 1918); assertFalse(isSus, "0.19 false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, 0.19e12, 5, 1919); + (, , isSus) = BalanceSheet.computeProbePrices(0, 0.19e12, 50, 1919); assertTrue(isSus, "0.19 true"); - (, , isSus) = BalanceSheet.computeProbePrices(0, IV_COLD_START, 5, 1918); + (, , isSus) = BalanceSheet.computeProbePrices(0, IV_COLD_START, 50, 1918); assertFalse(isSus, "cold start false"); - (, , isSus) = BalanceSheet.computeProbePrices(0, IV_COLD_START, 5, 1919); + (, , isSus) = BalanceSheet.computeProbePrices(0, IV_COLD_START, 50, 1919); assertTrue(isSus, "cold start true"); } @@ -159,6 +159,10 @@ contract BalanceSheetTest is Test { assertEqDecimal(PROBE_PERCENT_MIN, 50500000000, 12); assertEqDecimal(PROBE_PERCENT_MAX, 894500000000, 12); + // Necessary for iv scaling not to overflow + assertLt(PROBE_PERCENT_MIN, 1 << 128); + assertLt(PROBE_PERCENT_MAX, 1 << 128); + // Necessary for collateral factor computation to work assertGt(LTV_MIN, TickMath.MIN_SQRT_RATIO); } From 453754a656d9320c9b1dd1f0bc1ab888817575f2 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sun, 10 Sep 2023 23:35:25 -0500 Subject: [PATCH 11/11] Fix test --- core/test/libraries/BalanceSheet.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/test/libraries/BalanceSheet.t.sol b/core/test/libraries/BalanceSheet.t.sol index bc462bca..75631244 100644 --- a/core/test/libraries/BalanceSheet.t.sol +++ b/core/test/libraries/BalanceSheet.t.sol @@ -147,11 +147,11 @@ contract BalanceSheetTest is Test { a = square(uint160(a)); b = square(uint160(b)); - if (iv < PROBE_PERCENT_MIN / nSigma) iv = PROBE_PERCENT_MIN / nSigma; - else if (iv > PROBE_PERCENT_MAX / nSigma) iv = PROBE_PERCENT_MAX / nSigma; + if (iv < PROBE_PERCENT_MIN * 10 / nSigma) iv = PROBE_PERCENT_MIN * 10 / nSigma; + else if (iv > PROBE_PERCENT_MAX * 10 / nSigma) iv = PROBE_PERCENT_MAX * 10 / nSigma; - assertApproxEqRel(a, SoladyMath.fullMulDiv(price, 1e12 - nSigma * iv, 1e12), 0.0001e18); - assertApproxEqRel(b, SoladyMath.fullMulDiv(price, 1e12 + nSigma * iv, 1e12), 0.0001e18); + assertApproxEqRel(a, SoladyMath.fullMulDiv(price, 1e12 - (nSigma * iv) / 10, 1e12), 0.0001e18); + assertApproxEqRel(b, SoladyMath.fullMulDiv(price, 1e12 + (nSigma * iv) / 10, 1e12), 0.0001e18); } function test_constants() public {