Skip to content

Commit

Permalink
Make manipulationThresholdDivisor governable (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
haydenshively authored Sep 20, 2023
1 parent 9568095 commit 44fd720
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 95 deletions.
26 changes: 13 additions & 13 deletions core/.gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
BorrowerGasTest:test_addMargin() (gas: 16203)
BorrowerGasTest:test_borrow() (gas: 110553)
BorrowerGasTest:test_borrow() (gas: 110705)
BorrowerGasTest:test_getUniswapPositions() (gas: 5219)
BorrowerGasTest:test_modify() (gas: 82150)
BorrowerGasTest:test_modifyWithAnte() (gas: 88605)
BorrowerGasTest:test_repay() (gas: 65205)
BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257762)
BorrowerGasTest:test_modify() (gas: 82302)
BorrowerGasTest:test_modifyWithAnte() (gas: 88757)
BorrowerGasTest:test_repay() (gas: 65245)
BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257914)
BorrowerGasTest:test_uniswapDepositStandard() (gas: 167558)
BorrowerGasTest:test_uniswapWithdraw() (gas: 147654)
BorrowerGasTest:test_withdraw() (gas: 105670)
BorrowerGasTest:test_uniswapWithdraw() (gas: 147776)
BorrowerGasTest:test_withdraw() (gas: 105822)
FactoryGasTest:test_createBorrower() (gas: 156519)
FactoryGasTest:test_createMarket() (gas: 3879742)
FactoryGasTest:test_createMarket() (gas: 3878781)
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: 51108)
LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59289)
LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95669)
LiquidatorGasTest:test_warn() (gas: 35092)
LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130273)
LiquidatorGasTest:test_noCallbackOneAsset() (gas: 51225)
LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59406)
LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95763)
LiquidatorGasTest:test_warn() (gas: 35209)
LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130390)
VolatilityGasTest:test_consult() (gas: 43075)
VolatilityGasTest:test_updateNoBinarySearch() (gas: 145902)
19 changes: 13 additions & 6 deletions core/src/Borrower.sol
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,8 @@ contract Borrower is IUniswapV3MintCallback {

(uint256 liabilities0, uint256 liabilities1) = _getLiabilities();
if (liabilities0 > 0 || liabilities1 > 0) {
(uint256 ante, uint256 nSigma, uint256 pausedUntilTime) = FACTORY.getParameters(UNISWAP_POOL);
(Prices memory prices, bool seemsLegit) = _getPrices(oracleSeed, nSigma);
(uint208 ante, uint8 nSigma, uint8 mtd, uint32 pausedUntilTime) = FACTORY.getParameters(UNISWAP_POOL);
(Prices memory prices, bool seemsLegit) = _getPrices(oracleSeed, nSigma, mtd);

require(
seemsLegit && (block.timestamp > pausedUntilTime) && (address(this).balance >= ante),
Expand Down Expand Up @@ -438,20 +438,27 @@ contract Borrower is IUniswapV3MintCallback {
}

function getPrices(uint40 oracleSeed) public view returns (Prices memory prices, bool seemsLegit) {
(, uint256 nSigma, ) = FACTORY.getParameters(UNISWAP_POOL);
(prices, seemsLegit) = _getPrices(oracleSeed, nSigma);
(, uint8 nSigma, uint8 manipulationThresholdDivisor, ) = FACTORY.getParameters(UNISWAP_POOL);
(prices, seemsLegit) = _getPrices(oracleSeed, nSigma, manipulationThresholdDivisor);
}

function _getPrices(
uint40 oracleSeed,
uint256 nSigma
uint8 nSigma,
uint8 manipulationThresholdDivisor
) private view returns (Prices memory prices, bool seemsLegit) {
uint56 metric;
uint256 iv;
// compute current price and volatility
(metric, prices.c, iv) = ORACLE.consult(UNISWAP_POOL, oracleSeed);
// compute prices at which solvency will be checked
(prices.a, prices.b, seemsLegit) = BalanceSheet.computeProbePrices(prices.c, iv, nSigma, metric);
(prices.a, prices.b, seemsLegit) = BalanceSheet.computeProbePrices(
metric,
prices.c,
iv,
nSigma,
manipulationThresholdDivisor
);
}

function _getAssets(
Expand Down
23 changes: 16 additions & 7 deletions core/src/Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import {IUniswapV3Pool} from "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {
DEFAULT_ANTE,
DEFAULT_N_SIGMA,
DEFAULT_MANIPULATION_THRESHOLD_DIVISOR,
DEFAULT_RESERVE_FACTOR,
CONSTRAINT_N_SIGMA_MIN,
CONSTRAINT_N_SIGMA_MAX,
CONSTRAINT_MANIPULATION_THRESHOLD_DIVISOR_MIN,
CONSTRAINT_MANIPULATION_THRESHOLD_DIVISOR_MAX,
CONSTRAINT_RESERVE_FACTOR_MIN,
CONSTRAINT_RESERVE_FACTOR_MAX,
CONSTRAINT_ANTE_MAX,
Expand Down Expand Up @@ -44,8 +47,9 @@ contract Factory {
}

struct Parameters {
uint216 ante;
uint208 ante;
uint8 nSigma;
uint8 manipulationThresholdDivisor;
uint32 pausedUntilTime;
}

Expand Down Expand Up @@ -139,7 +143,7 @@ contract Factory {
_setMarketConfig(
pool,
MarketConfig(
Parameters(DEFAULT_ANTE, DEFAULT_N_SIGMA, 0),
Parameters(DEFAULT_ANTE, DEFAULT_N_SIGMA, DEFAULT_MANIPULATION_THRESHOLD_DIVISOR, 0),
DEFAULT_RATE_MODEL,
DEFAULT_RATE_MODEL,
DEFAULT_RESERVE_FACTOR,
Expand Down Expand Up @@ -228,11 +232,16 @@ contract Factory {
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) &&
// ante: max
(marketConfig.parameters.ante <= CONSTRAINT_ANTE_MAX) &&
// nSigma: min, max
(CONSTRAINT_N_SIGMA_MIN <= marketConfig.parameters.nSigma &&
marketConfig.parameters.nSigma <= CONSTRAINT_N_SIGMA_MAX) &&
// manipulationThresholdDivisor: min, max
(CONSTRAINT_MANIPULATION_THRESHOLD_DIVISOR_MIN <=
marketConfig.parameters.manipulationThresholdDivisor &&
marketConfig.parameters.manipulationThresholdDivisor <=
CONSTRAINT_MANIPULATION_THRESHOLD_DIVISOR_MAX) &&
// pauseUntilTime: max
(marketConfig.parameters.pausedUntilTime <= block.timestamp + CONSTRAINT_PAUSE_INTERVAL_MAX) &&
// reserveFactor0: min, max
Expand Down
21 changes: 9 additions & 12 deletions core/src/libraries/BalanceSheet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ pragma solidity 0.8.17;

import {FixedPointMathLib as SoladyMath} from "solady/utils/FixedPointMathLib.sol";

import {
MAX_LEVERAGE,
LIQUIDATION_INCENTIVE,
PROBE_PERCENT_MIN,
PROBE_PERCENT_MAX,
MANIPULATION_THRESHOLD_DIVISOR
} from "./constants/Constants.sol";
import {MAX_LEVERAGE, LIQUIDATION_INCENTIVE, PROBE_PERCENT_MIN, PROBE_PERCENT_MAX} from "./constants/Constants.sol";
import {square, mulDiv128} from "./MulDiv.sol";
import {TickMath} from "./TickMath.sol";

Expand Down Expand Up @@ -81,14 +75,15 @@ library BalanceSheet {
}

function computeProbePrices(
uint56 metric,
uint160 sqrtMeanPriceX96,
uint256 iv,
uint256 nSigma,
uint56 metric
uint8 nSigma,
uint8 manipulationThresholdDivisor
) internal pure returns (uint160 a, uint160 b, bool seemsLegit) {
unchecked {
iv = SoladyMath.clamp((nSigma * iv) / 10, PROBE_PERCENT_MIN, PROBE_PERCENT_MAX);
seemsLegit = metric < _manipulationThreshold(_effectiveCollateralFactor(iv));
seemsLegit = metric < _manipulationThreshold(_effectiveCollateralFactor(iv), manipulationThresholdDivisor);

a = uint160((sqrtMeanPriceX96 * SoladyMath.sqrt(1e12 - iv)) / 1e6);
b = uint160(SoladyMath.min((sqrtMeanPriceX96 * SoladyMath.sqrt(1e12 + iv)) / 1e6, type(uint160).max));
Expand Down Expand Up @@ -124,8 +119,10 @@ library BalanceSheet {
}

/// @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)) - 778261) / (2 * MANIPULATION_THRESHOLD_DIVISOR);
function _manipulationThreshold(uint256 cf, uint8 manipulationThresholdDivisor) private pure returns (uint24) {
unchecked {
return uint24(-TickMath.getTickAtSqrtRatio(uint160(cf)) - 778261) / (2 * manipulationThresholdDivisor);
}
}

/// @dev Equivalent to \\( \frac{1 - σ}{1 + \frac{1}{liquidationIncentive} + \frac{1}{maxLeverage}} \\) where
Expand Down
28 changes: 17 additions & 11 deletions core/src/libraries/constants/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,22 @@ 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.01 ether;
uint208 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. Expressed x10, e.g. 50 → 5σ
uint8 constant DEFAULT_N_SIGMA = 50;

/// @dev Assume someone is manipulating the Uniswap TWAP oracle. To steal money from the protocol and create bad debt,
/// they would need to change the TWAP by a factor of (1 / collateralFactor), where the collateralFactor is a function
/// of volatility. We have a manipulation metric that increases as an attacker tries to change the TWAP. If this metric
/// rises above a certain threshold, certain functionality will be paused, e.g. no new debt can be created. The
/// threshold is calculated as follows:
///
/// \\( \text{manipulationThreshold} =
/// \frac{log_{1.0001}\left( \frac{1}{\text{collateralFactor}} \right)}{\text{MANIPULATION_THRESHOLD_DIVISOR}} \\)
uint8 constant DEFAULT_MANIPULATION_THRESHOLD_DIVISOR = 12;

/// @dev The default portion of interest that will accrue to a `Lender`'s `RESERVE` address.
/// Expressed as a reciprocal, e.g. 16 → 6.25%
uint8 constant DEFAULT_RESERVE_FACTOR = 16;
Expand All @@ -51,6 +61,12 @@ uint8 constant CONSTRAINT_N_SIGMA_MIN = 40;
/// Expressed x10, e.g. 80 → 8σ
uint8 constant CONSTRAINT_N_SIGMA_MAX = 80;

/// @dev The minimum value of the `manipulationThresholdDivisor`, described above
uint8 constant CONSTRAINT_MANIPULATION_THRESHOLD_DIVISOR_MIN = 12;

/// @dev The maximum value of the `manipulationThresholdDivisor`, described above
uint8 constant CONSTRAINT_MANIPULATION_THRESHOLD_DIVISOR_MAX = 20;

/// @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;

Expand Down Expand Up @@ -141,13 +157,3 @@ uint256 constant FEE_GROWTH_SAMPLE_PERIOD = 1 hours;
/// from `UNISWAP_AVG_WINDOW` seconds ago. Larger values make the resulting price/liquidity values harder to
/// manipulate, but also make the oracle slower to respond to changes.
uint32 constant UNISWAP_AVG_WINDOW = 30 minutes;

/// @dev Assume someone is manipulating the Uniswap TWAP oracle. To steal money from the protocol and create bad debt,
/// they would need to change the TWAP by a factor of (1 / collateralFactor), where the collateralFactor is a function
/// of volatility. We have a manipulation metric that increases as an attacker tries to change the TWAP. If this metric
/// rises above a certain threshold, certain functionality will be paused, e.g. no new debt can be created. The
/// threshold is calculated as follows:
///
/// \\( \text{manipulationThreshold} =
/// \frac{log_{1.0001}\left( \frac{1}{\text{collateralFactor}} \right)}{\text{MANIPULATION_THRESHOLD_DIVISOR}} \\)
uint24 constant MANIPULATION_THRESHOLD_DIVISOR = 12;
Loading

0 comments on commit 44fd720

Please sign in to comment.