From 1793b0603eda9a736f16a9cf85c1d4ee3fe63e96 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:22:57 -0500 Subject: [PATCH] Add digit to nSigma so it's more governable (#153) --- core/.gas-snapshot | 26 +++--- 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 | 96 +++++++++++----------- 5 files changed, 74 insertions(+), 67 deletions(-) diff --git a/core/.gas-snapshot b/core/.gas-snapshot index d77ccd12..9cf0f826 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) +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: 43075) VolatilityGasTest:test_updateNoBinarySearch() (gas: 145902) \ 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..75631244 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"); } @@ -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 { @@ -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); }