Skip to content

Commit

Permalink
Clean up / finish Factory (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
haydenshively authored Sep 21, 2023
1 parent 81dbd3c commit 2ff76a6
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 87 deletions.
28 changes: 14 additions & 14 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: 110045)
BorrowerGasTest:test_borrow() (gas: 110015)
BorrowerGasTest:test_getUniswapPositions() (gas: 5219)
BorrowerGasTest:test_modify() (gas: 81642)
BorrowerGasTest:test_modifyWithAnte() (gas: 88097)
BorrowerGasTest:test_modify() (gas: 81612)
BorrowerGasTest:test_modifyWithAnte() (gas: 88067)
BorrowerGasTest:test_repay() (gas: 65245)
BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257254)
BorrowerGasTest:test_uniswapDepositInBorrower() (gas: 257224)
BorrowerGasTest:test_uniswapDepositStandard() (gas: 167558)
BorrowerGasTest:test_uniswapWithdraw() (gas: 147248)
BorrowerGasTest:test_withdraw() (gas: 105162)
FactoryGasTest:test_createBorrower() (gas: 156519)
FactoryGasTest:test_createMarket() (gas: 3915383)
BorrowerGasTest:test_uniswapWithdraw() (gas: 147224)
BorrowerGasTest:test_withdraw() (gas: 105132)
FactoryGasTest:test_createBorrower() (gas: 156474)
FactoryGasTest:test_createMarket() (gas: 3918370)
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_redeemWithCourier() (gas: 83479)
LenderGasTest:test_repay() (gas: 44774)
LiquidatorGasTest:test_noCallbackOneAsset() (gas: 50899)
LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59080)
LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95512)
LiquidatorGasTest:test_warn() (gas: 34382)
LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130064)
LiquidatorGasTest:test_noCallbackOneAsset() (gas: 50869)
LiquidatorGasTest:test_noCallbackTwoAssets() (gas: 59050)
LiquidatorGasTest:test_noCallbackTwoAssetsAndUniswapPosition() (gas: 95488)
LiquidatorGasTest:test_warn() (gas: 34352)
LiquidatorGasTest:test_withCallbackAndSwap() (gas: 130034)
VolatilityGasTest:test_consult() (gas: 43075)
VolatilityGasTest:test_updateNoBinarySearch() (gas: 145980)
10 changes: 5 additions & 5 deletions core/.storage-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +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 |
|---------------|---------------------------------------------------------------|------|--------|-------|-------------------------|
| 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 |
| 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 |
| rewardsToken | contract ERC20 | 4 | 0 | 20 | 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 |

3 changes: 1 addition & 2 deletions core/src/Borrower.sol
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ contract Borrower is IUniswapV3MintCallback {
TOKEN0 = lender0.asset();
TOKEN1 = lender1.asset();

require(pool.token0() == address(TOKEN0));
require(pool.token1() == address(TOKEN1));
assert(pool.token0() == address(TOKEN0) && pool.token1() == address(TOKEN1));
}

receive() external payable {}
Expand Down
151 changes: 95 additions & 56 deletions core/src/Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
CONSTRAINT_RESERVE_FACTOR_MIN,
CONSTRAINT_RESERVE_FACTOR_MAX,
CONSTRAINT_ANTE_MAX,
CONSTRAINT_PAUSE_INTERVAL_MAX,
UNISWAP_AVG_WINDOW
} from "./libraries/constants/Constants.sol";

Expand All @@ -40,60 +39,95 @@ contract Factory {

event EnrollCourier(uint32 indexed id, address indexed wallet, uint16 cut);

event SetMarketConfig(IUniswapV3Pool indexed pool, MarketConfig config);

// This `Factory` can create a `Market` for any Uniswap V3 pool
struct Market {
// The `Lender` of `token0` in the Uniswap pool
Lender lender0;
// The `Lender` of `token1` in the Uniswap pool
Lender lender1;
// The implementation to which all `Borrower` clones will point
Borrower borrowerImplementation;
}

// Each `Market` has a set of borrowing `Parameters` to help manage risk
struct Parameters {
// The amount of Ether a `Borrower` must hold in order to borrow assets
uint208 ante;
// To avoid liquidation, a `Borrower` must be solvent at TWAP * e^{± nSigma * IV}
uint8 nSigma;
// Borrowing is paused when the manipulation metric > threshold; this scales the threshold up/down
uint8 manipulationThresholdDivisor;
// The time at which borrowing can resume
uint32 pausedUntilTime;
}

struct Courier {
address wallet;
uint16 cut;
}

// The set of all governable `Market` properties
struct MarketConfig {
Parameters parameters;
IRateModel rateModel0;
IRateModel rateModel1;
// Described above
uint208 ante;
// Described above
uint8 nSigma;
// Described above
uint8 manipulationThresholdDivisor;
// The reserve factor for `market.lender0`, expressed as a reciprocal
uint8 reserveFactor0;
// The reserve factor for `market.lender1`, expressed as a reciprocal
uint8 reserveFactor1;
// The rate model for `market.lender0`
IRateModel rateModel0;
// The rate model for `market.lender1`
IRateModel rateModel1;
}

// By enrolling as a `Courier`, frontends can earn a portion of their users' interest
struct Courier {
// The address that receives earnings whenever users withdraw
address wallet;
// The portion of users' interest to take, expressed in basis points
uint16 cut;
}

/// @notice The only address that can propose new `MarketConfig`s and rewards programs
address public immutable GOVERNOR;

/// @notice The oracle to use for prices and implied volatility
VolatilityOracle public immutable ORACLE;

/// @notice The implementation to which all `Lender` clones will point
address public immutable LENDER_IMPLEMENTATION;

/// @notice The rate model that `Lender`s will use when first created
IRateModel public immutable DEFAULT_RATE_MODEL;

ERC20 public rewardsToken;

/*//////////////////////////////////////////////////////////////
WORLD STORAGE
//////////////////////////////////////////////////////////////*/

/// @notice Returns the `Market` addresses associated with a Uniswap V3 pool
mapping(IUniswapV3Pool => Market) public getMarket;

/// @notice Returns the borrowing `Parameters` associated with a Uniswap V3 pool
mapping(IUniswapV3Pool => Parameters) public getParameters;

/// @notice Returns whether the given address is a `Lender` deployed by this `Factory`
mapping(address => bool) public isLender;

/// @notice Returns whether the given address is a `Borrower` deployed by this `Factory`
mapping(address => bool) public isBorrower;

/*//////////////////////////////////////////////////////////////
INCENTIVE STORAGE
//////////////////////////////////////////////////////////////*/

/// @notice The token in which rewards are paid out
ERC20 public rewardsToken;

/// @notice Returns the `Courier` for any given ID
mapping(uint32 => Courier) public couriers;

/// @notice Returns whether the given address has enrolled as a courier
mapping(address => bool) public isCourier;

/*//////////////////////////////////////////////////////////////
Expand All @@ -107,6 +141,10 @@ contract Factory {
DEFAULT_RATE_MODEL = defaultRateModel;
}

/*//////////////////////////////////////////////////////////////
EMERGENCY
//////////////////////////////////////////////////////////////*/

function pause(IUniswapV3Pool pool, uint40 oracleSeed) external {
(, bool seemsLegit) = getMarket[pool].borrowerImplementation.getPrices(oracleSeed);
if (seemsLegit) return;
Expand Down Expand Up @@ -143,12 +181,15 @@ contract Factory {
_setMarketConfig(
pool,
MarketConfig(
Parameters(DEFAULT_ANTE, DEFAULT_N_SIGMA, DEFAULT_MANIPULATION_THRESHOLD_DIVISOR, 0),
DEFAULT_RATE_MODEL,
DEFAULT_RATE_MODEL,
DEFAULT_ANTE,
DEFAULT_N_SIGMA,
DEFAULT_MANIPULATION_THRESHOLD_DIVISOR,
DEFAULT_RESERVE_FACTOR,
DEFAULT_RESERVE_FACTOR
)
DEFAULT_RESERVE_FACTOR,
DEFAULT_RATE_MODEL,
DEFAULT_RATE_MODEL
),
0
);

emit CreateMarket(pool, lender0, lender1);
Expand All @@ -168,9 +209,25 @@ contract Factory {
}

/*//////////////////////////////////////////////////////////////
REFERRALS
INCENTIVES
//////////////////////////////////////////////////////////////*/

function claimRewards(Lender[] calldata lenders, address beneficiary) external returns (uint256 earned) {
// Couriers cannot claim rewards because the accounting isn't quite correct for them. Specifically, we
// save gas by omitting a `Rewards.updateUserState` call for the courier in `Lender._burn`
require(!isCourier[msg.sender]);

unchecked {
uint256 count = lenders.length;
for (uint256 i = 0; i < count; i++) {
assert(isLender[address(lenders[i])]);
earned += lenders[i].claimRewards(msg.sender);
}
}

rewardsToken.safeTransfer(beneficiary, earned);
}

/**
* @notice Enrolls `msg.sender` in the referral program. This allows frontends/wallets/apps to
* credit themselves for a given user's deposit, and receive a portion of their interest. Note
Expand All @@ -194,26 +251,6 @@ contract Factory {
emit EnrollCourier(id, msg.sender, cut);
}

/*//////////////////////////////////////////////////////////////
REWARDS
//////////////////////////////////////////////////////////////*/

function claimRewards(Lender[] calldata lenders, address beneficiary) external returns (uint256 earned) {
// Couriers cannot claim rewards because the accounting isn't quite correct for them. Specifically, we
// save gas by omitting a `Rewards.updateUserState` call for the courier in `Lender._burn`
require(!isCourier[msg.sender]);

unchecked {
uint256 count = lenders.length;
for (uint256 i = 0; i < count; i++) {
assert(isLender[address(lenders[i])]);
earned += lenders[i].claimRewards(msg.sender);
}
}

rewardsToken.safeTransfer(beneficiary, earned);
}

/*//////////////////////////////////////////////////////////////
GOVERNANCE
//////////////////////////////////////////////////////////////*/
Expand All @@ -228,39 +265,41 @@ contract Factory {
lender.setRewardsRate(rate);
}

function governMarketConfig(IUniswapV3Pool pool, MarketConfig memory marketConfig) external {
function governMarketConfig(IUniswapV3Pool pool, MarketConfig memory config) external {
require(msg.sender == GOVERNOR);

require(
// ante: max
(marketConfig.parameters.ante <= CONSTRAINT_ANTE_MAX) &&
(config.ante <= CONSTRAINT_ANTE_MAX) &&
// nSigma: min, max
(CONSTRAINT_N_SIGMA_MIN <= marketConfig.parameters.nSigma &&
marketConfig.parameters.nSigma <= CONSTRAINT_N_SIGMA_MAX) &&
(CONSTRAINT_N_SIGMA_MIN <= config.nSigma && config.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) &&
(CONSTRAINT_MANIPULATION_THRESHOLD_DIVISOR_MIN <= config.manipulationThresholdDivisor &&
config.manipulationThresholdDivisor <= CONSTRAINT_MANIPULATION_THRESHOLD_DIVISOR_MAX) &&
// reserveFactor0: min, max
(CONSTRAINT_RESERVE_FACTOR_MIN <= marketConfig.reserveFactor0 &&
marketConfig.reserveFactor0 <= CONSTRAINT_RESERVE_FACTOR_MAX) &&
(CONSTRAINT_RESERVE_FACTOR_MIN <= config.reserveFactor0 &&
config.reserveFactor0 <= CONSTRAINT_RESERVE_FACTOR_MAX) &&
// reserveFactor1: min, max
(CONSTRAINT_RESERVE_FACTOR_MIN <= marketConfig.reserveFactor1 &&
marketConfig.reserveFactor1 <= CONSTRAINT_RESERVE_FACTOR_MAX),
(CONSTRAINT_RESERVE_FACTOR_MIN <= config.reserveFactor1 &&
config.reserveFactor1 <= CONSTRAINT_RESERVE_FACTOR_MAX),
"Aloe: constraints"
);

_setMarketConfig(pool, marketConfig);
_setMarketConfig(pool, config, getParameters[pool].pausedUntilTime);
}

function _setMarketConfig(IUniswapV3Pool pool, MarketConfig memory marketConfig) private {
getParameters[pool] = marketConfig.parameters;
function _setMarketConfig(IUniswapV3Pool pool, MarketConfig memory config, uint32 pausedUntilTime) private {
getParameters[pool] = Parameters({
ante: config.ante,
nSigma: config.nSigma,
manipulationThresholdDivisor: config.manipulationThresholdDivisor,
pausedUntilTime: pausedUntilTime
});

Market memory market = getMarket[pool];
market.lender0.setRateModelAndReserveFactor(marketConfig.rateModel0, marketConfig.reserveFactor0);
market.lender1.setRateModelAndReserveFactor(marketConfig.rateModel1, marketConfig.reserveFactor1);
market.lender0.setRateModelAndReserveFactor(config.rateModel0, config.reserveFactor0);
market.lender1.setRateModelAndReserveFactor(config.rateModel1, config.reserveFactor1);

emit SetMarketConfig(pool, config);
}
}
12 changes: 6 additions & 6 deletions core/src/libraries/LiquidityAmounts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ library LiquidityAmounts {
assert(sqrtRatioAX96 <= sqrtRatioBX96);

if (sqrtRatioX96 <= sqrtRatioAX96) {
amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
amount0 = _getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
} else if (sqrtRatioX96 < sqrtRatioBX96) {
amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
amount0 = _getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
amount1 = _getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
} else {
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
amount1 = _getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
}
}

Expand Down Expand Up @@ -97,7 +97,7 @@ library LiquidityAmounts {
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount0 The amount of token0. Will fit in a uint224 if you need it to
function getAmount0ForLiquidity(
function _getAmount0ForLiquidity(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
Expand All @@ -110,7 +110,7 @@ library LiquidityAmounts {
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount1 The amount of token1. Will fit in a uint192 if you need it to
function getAmount1ForLiquidity(
function _getAmount1ForLiquidity(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
Expand Down
3 changes: 0 additions & 3 deletions core/src/libraries/constants/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ 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
//////////////////////////////////////////////////////////////*/
Expand Down
1 change: 0 additions & 1 deletion core/test/Factory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
CONSTRAINT_RESERVE_FACTOR_MIN,
CONSTRAINT_RESERVE_FACTOR_MAX,
CONSTRAINT_ANTE_MAX,
CONSTRAINT_PAUSE_INTERVAL_MAX,
UNISWAP_AVG_WINDOW
} from "src/libraries/constants/Constants.sol";

Expand Down

0 comments on commit 2ff76a6

Please sign in to comment.