Skip to content
This repository was archived by the owner on May 24, 2024. It is now read-only.

Asset policies: Supply/borrow caps and market pausing #191

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
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
82 changes: 78 additions & 4 deletions contracts/BaseLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ abstract contract BaseLogic is BaseModule {

output[0] = firstMarketEntered;

for (uint i = 1; i < numMarketsEntered; ++i) {
for (uint i = 1; i < numMarketsEntered;) {
output[i] = markets[i];
unchecked { ++i; }
}

return output;
Expand All @@ -56,8 +57,9 @@ abstract contract BaseLogic is BaseModule {

address[MAX_POSSIBLE_ENTERED_MARKETS] storage markets = marketsEntered[account];

for (uint i = 1; i < numMarketsEntered; ++i) {
for (uint i = 1; i < numMarketsEntered;) {
if (markets[i] == underlying) return true;
unchecked { ++i; }
}

return false;
Expand All @@ -71,8 +73,9 @@ abstract contract BaseLogic is BaseModule {

if (numMarketsEntered != 0) {
if (accountStorage.firstMarketEntered == underlying) return; // already entered
for (uint i = 1; i < numMarketsEntered; i++) {
for (uint i = 1; i < numMarketsEntered;) {
if (markets[i] == underlying) return; // already entered
unchecked { ++i; }
}
}

Expand Down Expand Up @@ -100,11 +103,12 @@ abstract contract BaseLogic is BaseModule {
if (accountStorage.firstMarketEntered == underlying) {
searchIndex = 0;
} else {
for (uint i = 1; i < numMarketsEntered; i++) {
for (uint i = 1; i < numMarketsEntered;) {
if (markets[i] == underlying) {
searchIndex = i;
break;
}
unchecked { ++i; }
}

if (searchIndex == type(uint).max) return; // already exited
Expand Down Expand Up @@ -639,4 +643,74 @@ abstract contract BaseLogic is BaseModule {
accountLookup[account].lastAverageLiquidityUpdate = uint40(block.timestamp);
accountLookup[account].averageLiquidity = computeNewAverageLiquidity(account, deltaT);
}


// Asset Policies

function assetPolicyCheck(address underlying, uint16 pauseType) internal view {
require((pauseType & assetPolicies[underlying].pauseBitmask) == 0, "e/market-operation-paused");
}

function assetPolicyDirty(AssetCache memory assetCache, uint16 pauseType) internal {
AssetPolicy memory policy = assetPolicies[assetCache.underlying];

require((pauseType & policy.pauseBitmask) == 0, "e/market-operation-paused");

if (policy.supplyCap == 0 && policy.borrowCap == 0) return;
if (assetSnapshots[assetCache.underlying].dirty) return;

uint112 origTotalBalances = encodeAmount(balanceToUnderlyingAmount(assetCache, assetCache.totalBalances) / assetCache.underlyingDecimalsScaler);
uint112 origTotalBorrows = encodeAmount(assetCache.totalBorrows / INTERNAL_DEBT_PRECISION / assetCache.underlyingDecimalsScaler);

assetSnapshots[assetCache.underlying] = AssetSnapshot({
dirty: true,
origTotalBalances: origTotalBalances,
origTotalBorrows: origTotalBorrows
});
}

function assetPolicyClean(AssetCache memory assetCache, address account, bool allowDefer) internal {
AssetPolicy memory policy = assetPolicies[assetCache.underlying];

if (policy.supplyCap == 0 && policy.borrowCap == 0) return;
if (!assetSnapshots[assetCache.underlying].dirty) return;

if (allowDefer && accountLookup[account].deferLiquidityStatus != DEFERLIQUIDITY__NONE) {
doEnterMarket(account, assetCache.underlying);
return;
}

uint112 newTotalBalances = encodeAmount(balanceToUnderlyingAmount(assetCache, assetCache.totalBalances) / assetCache.underlyingDecimalsScaler);
uint112 newTotalBorrows = encodeAmount(assetCache.totalBorrows / INTERNAL_DEBT_PRECISION / assetCache.underlyingDecimalsScaler);

require(policy.supplyCap == 0
|| newTotalBalances < uint(policy.supplyCap) * 1e18 / assetCache.underlyingDecimalsScaler
|| newTotalBalances <= assetSnapshots[assetCache.underlying].origTotalBalances, "e/supply-cap-exceeded");

require(policy.borrowCap == 0
|| newTotalBorrows < uint(policy.borrowCap) * 1e18 / assetCache.underlyingDecimalsScaler
|| newTotalBorrows <= assetSnapshots[assetCache.underlying].origTotalBorrows, "e/borrow-cap-exceeded");

delete assetSnapshots[assetCache.underlying];
}

function assetPolicyCleanAllEntered(address account) internal {
AssetStorage storage assetStorage;
AssetCache memory assetCache;

address[] memory underlyings = getEnteredMarketsArray(account);

for (uint i = 0; i < underlyings.length;) {
address underlying = underlyings[i];

if (assetSnapshots[underlying].dirty) {
assetStorage = eTokenLookup[underlyingLookup[underlying].eTokenAddress];
initAssetCache(underlying, assetStorage, assetCache);

assetPolicyClean(assetCache, account, false);
}

unchecked { ++i; }
}
}
}
10 changes: 10 additions & 0 deletions contracts/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ abstract contract Constants {
uint16 internal constant PRICINGTYPE__OUT_OF_BOUNDS = 5;


// Pause bitmask

uint16 internal constant PAUSETYPE__DEPOSIT = 1 << 0;
uint16 internal constant PAUSETYPE__WITHDRAW = 1 << 1;
uint16 internal constant PAUSETYPE__BORROW = 1 << 2;
uint16 internal constant PAUSETYPE__REPAY = 1 << 3;
uint16 internal constant PAUSETYPE__MINT = 1 << 4;
uint16 internal constant PAUSETYPE__BURN = 1 << 5;


// Modules

// Public single-proxy modules
Expand Down
1 change: 1 addition & 0 deletions contracts/Events.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ abstract contract Events {
event GovSetReserveFee(address indexed underlying, uint32 newReserveFee);
event GovConvertReserves(address indexed underlying, address indexed recipient, uint amount);
event GovSetChainlinkPriceFeed(address indexed underlying, address chainlinkAggregator);
event GovSetAssetPolicy(address indexed underlying, Storage.AssetPolicy newPolicy);

event RequestSwap(address indexed accountIn, address indexed accountOut, address indexed underlyingIn, address underlyingOut, uint amount, uint swapType);
event RequestSwapHub(address indexed accountIn, address indexed accountOut, address indexed underlyingIn, address underlyingOut, uint amountIn, uint amountOut, uint mode, address swapHandler);
Expand Down
15 changes: 15 additions & 0 deletions contracts/Storage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,19 @@ abstract contract Storage is Constants {
mapping(address => address) internal pTokenLookup; // PToken => underlying
mapping(address => address) internal reversePTokenLookup; // underlying => PToken
mapping(address => address) internal chainlinkPriceFeedLookup; // underlying => chainlinkAggregator

struct AssetPolicy {
uint64 supplyCap; // underlying units without decimals, 0 means no cap
uint64 borrowCap; // underlying units without decimals, 0 means no cap
uint16 pauseBitmask;
}

struct AssetSnapshot {
bool dirty;
uint112 origTotalBalances; // underlying units and decimals
uint112 origTotalBorrows; // underlying units and decimals
}

mapping(address => AssetPolicy) internal assetPolicies; // underlying => AssetPolicy
mapping(address => AssetSnapshot) internal assetSnapshots; // underlying => AssetSnapshot
}
7 changes: 6 additions & 1 deletion contracts/modules/DToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ contract DToken is BaseLogic {
emit RequestBorrow(account, amount);

AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
assetPolicyDirty(assetCache, PAUSETYPE__BORROW);

if (amount == type(uint).max) {
amount = assetCache.poolSize;
Expand All @@ -116,6 +117,7 @@ contract DToken is BaseLogic {

increaseBorrow(assetStorage, assetCache, proxyAddr, account, amount);

assetPolicyClean(assetCache, account, true);
checkLiquidity(account);
logAssetStatus(assetCache);
}
Expand All @@ -130,6 +132,7 @@ contract DToken is BaseLogic {
updateAverageLiquidity(account);
emit RequestRepay(account, amount);

assetPolicyCheck(underlying, PAUSETYPE__REPAY);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);

if (amount != type(uint).max) {
Expand Down Expand Up @@ -210,7 +213,6 @@ contract DToken is BaseLogic {
/// @param amount In underlying units. Use max uint256 for full balance.
function transferFrom(address from, address to, uint amount) public nonReentrant returns (bool) {
(address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);

if (from == address(0)) from = msgSender;
require(from != to, "e/self-transfer");
Expand All @@ -219,6 +221,9 @@ contract DToken is BaseLogic {
updateAverageLiquidity(to);
emit RequestTransferDToken(from, to, amount);

assetPolicyCheck(underlying, PAUSETYPE__BORROW | PAUSETYPE__REPAY);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);

if (amount == type(uint).max) amount = getCurrentOwed(assetStorage, assetCache, from);
else amount = decodeExternalAmount(assetCache, amount);

Expand Down
12 changes: 10 additions & 2 deletions contracts/modules/EToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ contract EToken is BaseLogic {
emit RequestDeposit(account, amount);

AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
assetPolicyDirty(assetCache, PAUSETYPE__DEPOSIT);

if (amount == type(uint).max) {
amount = callBalanceOf(assetCache, msgSender);
Expand All @@ -167,6 +168,8 @@ contract EToken is BaseLogic {

increaseBalance(assetStorage, assetCache, proxyAddr, account, amountInternal);

assetPolicyClean(assetCache, account, true);

// Depositing a token to an account with pre-existing debt in that token creates a self-collateralized loan
// which may result in borrow isolation violation if other tokens are also borrowed on the account
if (assetStorage.users[account].owed != 0) checkLiquidity(account);
Expand All @@ -184,6 +187,7 @@ contract EToken is BaseLogic {
updateAverageLiquidity(account);
emit RequestWithdraw(account, amount);

assetPolicyCheck(underlying, PAUSETYPE__WITHDRAW);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);

uint amountInternal;
Expand Down Expand Up @@ -211,6 +215,7 @@ contract EToken is BaseLogic {
emit RequestMint(account, amount);

AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
assetPolicyDirty(assetCache, PAUSETYPE__MINT);

amount = decodeExternalAmount(assetCache, amount);
uint amountInternal = underlyingAmountToBalanceRoundUp(assetCache, amount);
Expand All @@ -224,6 +229,7 @@ contract EToken is BaseLogic {

increaseBorrow(assetStorage, assetCache, assetStorage.dTokenAddress, account, amount);

assetPolicyClean(assetCache, account, true);
checkLiquidity(account);
logAssetStatus(assetCache);
}
Expand All @@ -238,6 +244,7 @@ contract EToken is BaseLogic {
updateAverageLiquidity(account);
emit RequestBurn(account, amount);

assetPolicyCheck(underlying, PAUSETYPE__BURN);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);

uint owed = getCurrentOwed(assetStorage, assetCache, account);
Expand Down Expand Up @@ -323,15 +330,16 @@ contract EToken is BaseLogic {
function transferFrom(address from, address to, uint amount) public nonReentrant returns (bool) {
(address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();

AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);

if (from == address(0)) from = msgSender;
require(from != to, "e/self-transfer");

updateAverageLiquidity(from);
updateAverageLiquidity(to);
emit RequestTransferEToken(from, to, amount);

assetPolicyCheck(underlying, PAUSETYPE__WITHDRAW | PAUSETYPE__DEPOSIT);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);

if (amount == 0) return true;

if (!isSubAccountOf(msgSender, from) && assetStorage.eTokenAllowance[from][msgSender] != type(uint).max) {
Expand Down
2 changes: 2 additions & 0 deletions contracts/modules/Exec.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ contract Exec is BaseLogic {
uint8 status = accountLookup[account].deferLiquidityStatus;
accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__NONE;

assetPolicyCleanAllEntered(account);
if (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account);
}

Expand Down Expand Up @@ -318,6 +319,7 @@ contract Exec is BaseLogic {
uint8 status = accountLookup[account].deferLiquidityStatus;
accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__NONE;

assetPolicyCleanAllEntered(account);
if (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account);
}
}
Expand Down
6 changes: 6 additions & 0 deletions contracts/modules/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ contract Governance is BaseLogic {
emit GovSetChainlinkPriceFeed(underlying, chainlinkAggregator);
}

function setAssetPolicy(address underlying, AssetPolicy memory newPolicy) external nonReentrant governorOnly {
assetPolicies[underlying] = newPolicy;

emit GovSetAssetPolicy(underlying, newPolicy);
}


// getters

Expand Down
11 changes: 11 additions & 0 deletions contracts/modules/Markets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ contract Markets is BaseLogic {
chainlinkAggregator = chainlinkPriceFeedLookup[underlying];
}

/// @notice Retrieves the Asset Policy config for an asset
/// @param underlying Token address
/// @return assetPolicy Asset Policy config
function getAssetPolicy(address underlying) external view returns (Storage.AssetPolicy memory assetPolicy) {
assetPolicy = assetPolicies[underlying];
}

// Enter/exit markets

Expand Down Expand Up @@ -273,6 +279,11 @@ contract Markets is BaseLogic {

require(owed == 0, "e/outstanding-borrow");

if (assetSnapshots[oldMarket].dirty) {
AssetCache memory assetCache = loadAssetCache(oldMarket, assetStorage);
assetPolicyClean(assetCache, account, false);
}

doExitMarket(account, oldMarket);

if (config.collateralFactor != 0 && balance != 0) {
Expand Down
19 changes: 14 additions & 5 deletions contracts/modules/Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -395,14 +395,27 @@ contract Swap is BaseLogic {
function finalizeSwap(SwapCache memory swap) private {
uint balanceIn = checkBalances(swap);

assetPolicyCheck(swap.assetCacheIn.underlying, PAUSETYPE__WITHDRAW);
processWithdraw(eTokenLookup[swap.eTokenIn], swap.assetCacheIn, swap.eTokenIn, swap.accountIn, swap.amountInternalIn, balanceIn);

processDeposit(eTokenLookup[swap.eTokenOut], swap.assetCacheOut, swap.eTokenOut, swap.accountOut, swap.amountOut);
assetPolicyDirty(swap.assetCacheOut, PAUSETYPE__DEPOSIT);
AssetStorage storage assetStorageOut = eTokenLookup[swap.eTokenOut];
processDeposit(assetStorageOut, swap.assetCacheOut, swap.eTokenOut, swap.accountOut, swap.amountOut);

assetPolicyClean(swap.assetCacheOut, swap.accountOut, true);

checkLiquidity(swap.accountIn);

// Depositing a token to the account with a pre-existing debt in that token creates a self-collateralized loan
// which may result in borrow isolation violation if other tokens are also borrowed on the account
if (swap.accountIn != swap.accountOut && assetStorageOut.users[swap.accountOut].owed != 0)
checkLiquidity(swap.accountOut);
}

function finalizeSwapAndRepay(SwapCache memory swap) private {
assetPolicyCheck(swap.assetCacheIn.underlying, PAUSETYPE__WITHDRAW);
assetPolicyCheck(swap.assetCacheOut.underlying, PAUSETYPE__REPAY);

uint balanceIn = checkBalances(swap);

processWithdraw(eTokenLookup[swap.eTokenIn], swap.assetCacheIn, swap.eTokenIn, swap.accountIn, swap.amountInternalIn, balanceIn);
Expand All @@ -429,10 +442,6 @@ contract Swap is BaseLogic {

increaseBalance(assetStorage, assetCache, eTokenAddress, account, amountInternal);

// Depositing a token to an account with pre-existing debt in that token creates a self-collateralized loan
// which may result in borrow isolation violation if other tokens are also borrowed on the account
if (assetStorage.users[account].owed != 0) checkLiquidity(account);

logAssetStatus(assetCache);
}

Expand Down
Loading