Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brave-otter-glides.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/protocol": minor
---

✨ auditor: support non-collateral markets
1,240 changes: 646 additions & 594 deletions .gas-snapshot

Large diffs are not rendered by default.

56 changes: 44 additions & 12 deletions contracts/Auditor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ contract Auditor is Initializable, AccessControlUpgradeable {
function enterMarket(Market market) external {
MarketData storage m = markets[market];
if (!m.isListed) revert MarketNotListed();
if (m.nonCollateral) revert MarketNotCollateral();

uint256 marketMap = accountMarkets[msg.sender];
uint256 marketMask = 1 << m.index;
Expand Down Expand Up @@ -129,7 +130,9 @@ contract Auditor is Initializable, AccessControlUpgradeable {
vars.price = assetPrice(m.priceFeed);

// sum all the collateral prices
sumCollateral += vars.balance.mulDivDown(vars.price, baseUnit).mulWadDown(adjustFactor);
if (!m.nonCollateral) {
sumCollateral += vars.balance.mulDivDown(vars.price, baseUnit).mulWadDown(adjustFactor);
}
Comment thread
patitonar marked this conversation as resolved.

// sum all the debt
sumDebtPlusEffects += vars.borrowBalance.mulDivUp(vars.price, baseUnit).divWadUp(adjustFactor);
Expand All @@ -138,7 +141,7 @@ contract Auditor is Initializable, AccessControlUpgradeable {
if (market == marketToSimulate) {
// calculate the effects of redeeming markets
// (having less collateral is the same as having more debt for this calculation)
if (withdrawAmount != 0) {
if (withdrawAmount != 0 && !m.nonCollateral) {
sumDebtPlusEffects += withdrawAmount.mulDivDown(vars.price, baseUnit).mulWadDown(adjustFactor);
}
}
Expand Down Expand Up @@ -179,8 +182,8 @@ contract Auditor is Initializable, AccessControlUpgradeable {
/// @param account address of the account to check for possible shortfall.
/// @param amount amount that the account wants to withdraw or transfer.
function checkShortfall(Market market, address account, uint256 amount) public view virtual {
// if the account is not 'in' the market, bypass the liquidity check
if ((accountMarkets[account] & (1 << markets[market].index)) == 0) return;
// bypass the liquidity check if the account is not 'in' the market or it is disabled as collateral
if (accountMarkets[account] & (1 << markets[market].index) == 0 || markets[market].nonCollateral) return;

// otherwise, perform a hypothetical liquidity check to guard against shortfall
(uint256 collateral, uint256 debt) = accountLiquidity(account, market, amount);
Expand Down Expand Up @@ -224,10 +227,12 @@ contract Auditor is Initializable, AccessControlUpgradeable {
base.totalDebt += value;
base.adjustedDebt += value.divWadUp(m.adjustFactor);

value = collateral.mulDivDown(m.price, m.baseUnit);
base.totalCollateral += value;
base.adjustedCollateral += value.mulWadDown(m.adjustFactor);
if (market == seizeMarket) base.seizeAvailable = value;
if (!marketData.nonCollateral) {
value = collateral.mulDivDown(m.price, m.baseUnit);
base.totalCollateral += value;
base.adjustedCollateral += value.mulWadDown(m.adjustFactor);
if (market == seizeMarket) base.seizeAvailable = value;
}
}
unchecked {
++i;
Expand Down Expand Up @@ -329,8 +334,10 @@ contract Auditor is Initializable, AccessControlUpgradeable {
if (marketMap & 1 != 0) {
Market market = marketList[i];
MarketData storage m = markets[market];
uint256 assets = market.maxWithdraw(account);
if (assets.mulDivDown(assetPrice(m.priceFeed), 10 ** m.decimals).mulWadDown(m.adjustFactor) > 0) return;
if (!m.nonCollateral) {
uint256 assets = market.maxWithdraw(account);
if (assets.mulDivDown(assetPrice(m.priceFeed), 10 ** m.decimals).mulWadDown(m.adjustFactor) > 0) return;
}
}
unchecked {
++i;
Expand Down Expand Up @@ -368,10 +375,12 @@ contract Auditor is Initializable, AccessControlUpgradeable {
/// @param market market to add to the protocol.
/// @param priceFeed address of Chainlink's Price Feed aggregator used to query the asset price in base.
/// @param adjustFactor market's adjust factor for the underlying asset.
/// @param nonCollateral true to list the market as ineligible as collateral.
function enableMarket(
Market market,
IPriceFeed priceFeed,
uint128 adjustFactor
uint128 adjustFactor,
bool nonCollateral
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (market.auditor() != this) revert AuditorMismatch();
if (markets[market].isListed) revert MarketAlreadyListed();
Expand All @@ -383,14 +392,17 @@ contract Auditor is Initializable, AccessControlUpgradeable {
adjustFactor: adjustFactor,
decimals: decimals,
index: uint8(marketList.length),
priceFeed: priceFeed
priceFeed: priceFeed,
nonCollateral: nonCollateral
});

marketList.push(market);

emit MarketListed(market, decimals);
emit PriceFeedSet(market, priceFeed);
emit AdjustFactorSet(market, adjustFactor);

if (nonCollateral) emit NonCollateralSet(market, true);
}

/// @notice Sets the adjust factor for a certain market.
Expand All @@ -403,6 +415,17 @@ contract Auditor is Initializable, AccessControlUpgradeable {
emit AdjustFactorSet(market, adjustFactor);
}

/// @notice Sets whether a market's floating supply is ineligible as collateral.
/// @dev Making a market that accounts use as collateral non-collateral may leave their positions liquidatable.
/// @param market address of the market to change collateral eligibility for.
/// @param nonCollateral true to make the market ineligible as collateral.
function setNonCollateral(Market market, bool nonCollateral) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (!markets[market].isListed) revert MarketNotListed();

markets[market].nonCollateral = nonCollateral;
emit NonCollateralSet(market, nonCollateral);
}

/// @notice Sets the Chainlink Price Feed Aggregator source for a market.
/// @param market market address of the asset.
/// @param priceFeed address of Chainlink's Price Feed aggregator used to query the asset price in base.
Expand Down Expand Up @@ -437,6 +460,11 @@ contract Auditor is Initializable, AccessControlUpgradeable {
/// @param account address of the account that just left a market.
event MarketExited(Market indexed market, address indexed account);

/// @notice Emitted when a market's collateral eligibility is changed by admin.
/// @param market address of the market whose collateral eligibility changed.
/// @param nonCollateral true if the market is now ineligible as collateral.
event NonCollateralSet(Market indexed market, bool nonCollateral);

/// @notice Emitted when a adjust factor is changed by admin.
/// @param market address of the market that has a new adjust factor.
/// @param adjustFactor adjust factor for the underlying asset.
Expand All @@ -457,12 +485,15 @@ contract Auditor is Initializable, AccessControlUpgradeable {
/// @param index index of the market in the `marketList`.
/// @param isListed true if the market is enabled.
/// @param priceFeed address of the price feed used to query the asset's price.
/// @param nonCollateral true if the market's floating supply is ineligible as collateral.
// solhint-disable-next-line gas-struct-packing
struct MarketData {
uint128 adjustFactor;
uint8 decimals;
uint8 index;
bool isListed;
IPriceFeed priceFeed;
bool nonCollateral;
Comment thread
patitonar marked this conversation as resolved.
}

/// @notice Stores the liquidator and lenders factors used in liquidations to calculate the amount to seize.
Expand Down Expand Up @@ -490,6 +521,7 @@ error InsufficientShortfall();
error InvalidPrice();
error InvalidPriceFeed();
error MarketAlreadyListed();
error MarketNotCollateral();
error MarketNotListed();
error NotMarket();
error RemainingDebt();
Expand Down
2 changes: 1 addition & 1 deletion contracts/periphery/DebtManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ contract DebtManager is Initializable {
}

function checkMarket(Market market) internal view {
(, , , bool listed, ) = auditor.markets(market);
(, , , bool listed, , ) = auditor.markets(market);
Comment thread
patitonar marked this conversation as resolved.
if (!listed) revert MarketNotListed();
}

Expand Down
72 changes: 43 additions & 29 deletions contracts/periphery/DebtPreviewer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ contract DebtPreviewer {
) internal view returns (uint256) {
MinDepositVars memory vars;
Auditor auditor = debtManager.auditor();
(vars.adjustFactorIn, vars.decimalsIn, , , vars.priceFeedIn) = auditor.markets(marketDeposit);
(vars.adjustFactorOut, vars.decimalsOut, , , vars.priceFeedOut) = auditor.markets(marketBorrow);
(vars.adjustFactorIn, vars.decimalsIn, , , vars.priceFeedIn, vars.nonCollateral) = auditor.markets(marketDeposit);
if (vars.nonCollateral) return 0;
(vars.adjustFactorOut, vars.decimalsOut, , , vars.priceFeedOut, ) = auditor.markets(marketBorrow);

return
minHealthFactor
Expand Down Expand Up @@ -182,8 +183,8 @@ contract DebtPreviewer {
uint256 amountIn
) internal view returns (uint256) {
Auditor auditor = debtManager.auditor();
(, uint256 decimalsOut, , , IPriceFeed priceFeedOut) = auditor.markets(marketBorrow);
(, uint256 decimalsIn, , , IPriceFeed priceFeedIn) = auditor.markets(marketDeposit);
(, uint256 decimalsOut, , , IPriceFeed priceFeedOut, ) = auditor.markets(marketBorrow);
(, uint256 decimalsIn, , , IPriceFeed priceFeedIn, ) = auditor.markets(marketDeposit);
return
amountIn.mulDivDown(auditor.assetPrice(priceFeedIn), 10 ** decimalsIn).mulDivDown(
10 ** decimalsOut,
Expand All @@ -206,8 +207,9 @@ contract DebtPreviewer {
) internal view returns (uint256) {
Auditor auditor = debtManager.auditor();
MaxRatioVars memory mr;
(mr.adjustFactorIn, , , , mr.priceFeedIn) = auditor.markets(marketDeposit);
(mr.adjustFactorOut, , , , ) = auditor.markets(marketBorrow);
(mr.adjustFactorIn, , , , mr.priceFeedIn, mr.nonCollateral) = auditor.markets(marketDeposit);
if (mr.nonCollateral) return 1e18;
(mr.adjustFactorOut, , , , , ) = auditor.markets(marketBorrow);
uint256 isolatedMaxRatio = minHealthFactor.divWadDown(
minHealthFactor - mr.adjustFactorIn.mulWadDown(mr.adjustFactorOut)
);
Expand All @@ -220,7 +222,7 @@ contract DebtPreviewer {
Auditor.MarketData memory md;
Auditor.AccountLiquidity memory vars;
mr.market = auditor.marketList(mr.i);
(md.adjustFactor, md.decimals, , , md.priceFeed) = auditor.markets(mr.market);
(md.adjustFactor, md.decimals, , , md.priceFeed, md.nonCollateral) = auditor.markets(mr.market);
(vars.balance, vars.borrowBalance) = mr.market.accountSnapshot(account);
vars.price = auditor.assetPrice(md.priceFeed);
mr.baseUnit = 10 ** md.decimals;
Expand All @@ -232,7 +234,7 @@ contract DebtPreviewer {
} else {
mr.adjustedDebt += vars.borrowBalance.mulDivUp(vars.price, mr.baseUnit).divWadUp(md.adjustFactor);
}
if (mr.market != marketDeposit) {
if (mr.market != marketDeposit && !md.nonCollateral) {
mr.adjustedCollateral += vars.balance.mulDivDown(vars.price, mr.baseUnit).mulWadDown(md.adjustFactor);
}
}
Expand All @@ -241,14 +243,19 @@ contract DebtPreviewer {
}
}

uint256 collateral = mr.adjustedCollateral.mulWadDown(mr.adjustFactorOut) +
minHealthFactor.mulWadDown(mr.principalUSD);
uint256 debt = minHealthFactor.mulWadDown(mr.adjustedDebt.mulWadDown(mr.adjustFactorOut));
if (debt >= collateral) return 1e18;
return
Math.min(
(mr.adjustedCollateral.mulWadDown(mr.adjustFactorOut) +
minHealthFactor.mulWadDown(mr.principalUSD) -
minHealthFactor.mulWadDown(mr.adjustedDebt.mulWadDown(mr.adjustFactorOut))).divWadDown(
Math.max(
Math.min(
(collateral - debt).divWadDown(
mr.principalUSD.mulWadDown(minHealthFactor - mr.adjustFactorIn.mulWadDown(mr.adjustFactorOut))
),
isolatedMaxRatio
isolatedMaxRatio
),
1e18
);
}

Expand All @@ -275,19 +282,23 @@ contract DebtPreviewer {
if (mw.principal <= 0) return 0;

mw.auditor = debtManager.auditor();
(, , , , , mw.nonCollateral) = mw.auditor.markets(marketDeposit);
if (mw.nonCollateral) return uint256(mw.principal);
Auditor.MarketData memory md;
Auditor.AccountLiquidity memory vars;
mw.marketMap = mw.auditor.accountMarkets(account);
mw.borrowAssets = floatingBorrowAssets(marketBorrow, account);
for (mw.i = 0; mw.marketMap != 0; mw.marketMap >>= 1) {
if (mw.marketMap & 1 != 0) {
mw.market = mw.auditor.marketList(mw.i);
(md.adjustFactor, md.decimals, , , md.priceFeed) = mw.auditor.markets(mw.market);
(md.adjustFactor, md.decimals, , , md.priceFeed, md.nonCollateral) = mw.auditor.markets(mw.market);
uint256 baseUnit = 10 ** md.decimals;
(vars.balance, vars.borrowBalance) = mw.market.accountSnapshot(account);
vars.price = mw.auditor.assetPrice(md.priceFeed);
{
mw.memAdjColl = vars.balance.mulDivDown(vars.price, baseUnit).mulWadDown(md.adjustFactor);
mw.memAdjColl = md.nonCollateral
? 0
: vars.balance.mulDivDown(vars.price, baseUnit).mulWadDown(md.adjustFactor);
mw.memAdjDebt = vars.borrowBalance.mulDivDown(vars.price, baseUnit).divWadDown(md.adjustFactor);
mw.adjustedCollateral += mw.memAdjColl;

Expand All @@ -312,13 +323,14 @@ contract DebtPreviewer {
}
}
{
(mw.adjustFactorIn, , , , mw.priceFeedIn) = mw.auditor.markets(marketDeposit);
(mw.adjustFactorOut, , , , ) = mw.auditor.markets(marketBorrow);
(mw.adjustFactorIn, , , , mw.priceFeedIn, ) = mw.auditor.markets(marketDeposit);
(mw.adjustFactorOut, , , , , ) = mw.auditor.markets(marketBorrow);
mw.memOtherDebt = mw.otherDebt.mulWadDown(mw.adjustFactorOut).mulWadDown(minHealthFactor);
mw.memOtherCollateral = (mw.otherCollateral).mulWadDown(mw.adjustFactorOut);
}

if (mw.memOtherDebt <= mw.memOtherCollateral) {
if (mw.adjustedDebt + mw.adjPrincipalForRepay >= mw.adjustedCollateral + mw.adjustedRepay) return 0;
return
Math.min(
Math
Expand All @@ -332,15 +344,14 @@ contract DebtPreviewer {
);
}

return
uint256(mw.principal) -
(mw.memOtherDebt - mw.memOtherCollateral)
.divWadDown(
mw.adjustFactorIn.mulWadDown(ratio).mulWadDown(mw.adjustFactorOut) +
minHealthFactor -
ratio.mulWadDown(minHealthFactor)
)
.mulDivDown(10 ** marketDeposit.decimals(), mw.auditor.assetPrice(mw.priceFeedIn));
uint256 support = mw.adjustFactorIn.mulWadDown(ratio).mulWadDown(mw.adjustFactorOut) + minHealthFactor;
uint256 ratioDebt = ratio.mulWadDown(minHealthFactor);
if (ratioDebt >= support) return 0;
uint256 requiredPrincipal = (mw.memOtherDebt - mw.memOtherCollateral).divWadDown(support - ratioDebt).mulDivDown(
10 ** marketDeposit.decimals(),
mw.auditor.assetPrice(mw.priceFeedIn)
);
return uint256(mw.principal) > requiredPrincipal ? uint256(mw.principal) - requiredPrincipal : 0;
}

/// @notice Calculates the crossed principal amount for a given `account` in the input and output markets.
Expand All @@ -353,8 +364,8 @@ contract DebtPreviewer {
IPriceFeed priceFeedIn;
IPriceFeed priceFeedOut;
Auditor auditor = debtManager.auditor();
(, decimalsIn, , , priceFeedIn) = auditor.markets(marketDeposit);
(, decimalsOut, , , priceFeedOut) = auditor.markets(marketBorrow);
(, decimalsIn, , , priceFeedIn, ) = auditor.markets(marketDeposit);
(, decimalsOut, , , priceFeedOut, ) = auditor.markets(marketBorrow);

return
int256(marketDeposit.maxWithdraw(account)) -
Expand Down Expand Up @@ -469,7 +480,7 @@ contract DebtPreviewer {
r.controller = market.rewardsController();
Auditor auditor = debtManager.auditor();
if (address(r.controller) != address(0)) {
(, r.underlyingDecimals, , , r.underlyingPriceFeed) = auditor.markets(market);
(, r.underlyingDecimals, , , r.underlyingPriceFeed, ) = auditor.markets(market);
unchecked {
r.underlyingBaseUnit = 10 ** r.underlyingDecimals;
}
Expand Down Expand Up @@ -578,6 +589,7 @@ struct Limit {

struct MaxRatioVars {
uint256 i;
bool nonCollateral;
uint256 baseUnit;
uint256 marketMap;
uint256 principalUSD;
Expand All @@ -592,6 +604,7 @@ struct MaxRatioVars {
struct MaxWithdrawVars {
uint256 i;
int256 principal;
bool nonCollateral;
uint256 marketMap;
uint256 otherDebt;
uint256 memAdjDebt;
Expand Down Expand Up @@ -619,6 +632,7 @@ struct MinDepositVars {
IPriceFeed priceFeedIn;
uint256 adjustFactorOut;
IPriceFeed priceFeedOut;
bool nonCollateral;
}

struct Rates {
Expand Down
2 changes: 1 addition & 1 deletion contracts/periphery/DebtRoller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ contract DebtRoller is IFlashLoanRecipient, Initializable, AccessControlUpgradea
}

function _checkMarket(Market market) internal view {
(, , , bool listed, ) = auditor.markets(market);
(, , , bool listed, , ) = auditor.markets(market);
Comment thread
patitonar marked this conversation as resolved.
if (!listed) revert NotMarket();
}
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/periphery/InstallmentsRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ contract InstallmentsRouter {
/// @notice Reverts if the Market is not listed by the Auditor.
/// @param market The Market to check.
function checkMarket(Market market) internal view {
(, , , bool listed, ) = auditor.markets(market);
(, , , bool listed, , ) = auditor.markets(market);
if (!listed) revert MarketNotListed();
}

Expand Down
Loading