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

view - asset policies #190

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ This repo contains the smart contracts and tests for the [Euler Protocol](https:
## Docs

* [General Euler Docs](https://docs.euler.finance/)
* [Contract Architecture](https://docs.euler.finance/developers/architecture)
* [Contract Reference](https://docs.euler.finance/developers/contract-reference)
* [IEuler.sol Solidity Interface](https://github.com/euler-xyz/euler-interfaces/blob/master/IEuler.sol)
* [Contract Architecturel](https://docs.euler.finance/developers/getting-started/architecture)
* [Contract Reference](https://docs.euler.finance/developers/getting-started/contract-reference)
* [IEuler.sol Solidity Interface](https://github.com/euler-xyz/euler-interfaces/blob/master/contracts/IEuler.sol)

## License

Expand Down
63 changes: 63 additions & 0 deletions contracts/BaseLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -639,4 +639,67 @@ 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(true, origTotalBalances, 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");

assetSnapshots[assetCache.underlying] = AssetSnapshot(false, 0, 0);
}

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

address[] memory underlyings = getEnteredMarketsArray(account);

for (uint i = 0; i < underlyings.length; ++i) {
address underlying = underlyings[i];
if (!assetSnapshots[underlying].dirty) continue;

assetStorage = eTokenLookup[underlyingLookup[underlying].eTokenAddress];
initAssetCache(underlying, assetStorage, assetCache);

assetPolicyClean(assetCache, account, false);
}
}
}
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
8 changes: 8 additions & 0 deletions contracts/modules/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ contract Governance is BaseLogic {
emit GovSetChainlinkPriceFeed(underlying, chainlinkAggregator);
}

function setAssetPolicy(address underlying, AssetPolicy memory newPolicy) external nonReentrant governorOnly {
require(underlyingLookup[underlying].eTokenAddress != address(0), "e/gov/underlying-not-activated");

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
9 changes: 9 additions & 0 deletions contracts/modules/SwapHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ contract SwapHub is BaseLogic {
/// @param swapHandler address of a swap handler to use
/// @param params struct defining the requested trade
function swap(uint subAccountIdIn, uint subAccountIdOut, address swapHandler, ISwapHandler.SwapParams memory params) external nonReentrant {
assetPolicyCheck(params.underlyingIn, PAUSETYPE__WITHDRAW);

SwapCache memory cache = initSwap(subAccountIdIn, subAccountIdOut, params);

assetPolicyDirty(cache.assetCacheOut, PAUSETYPE__DEPOSIT);

emit RequestSwapHub(
cache.accountIn,
cache.accountOut,
Expand All @@ -62,6 +66,8 @@ contract SwapHub is BaseLogic {
increaseBalance(assetStorageOut, cache.assetCacheOut, cache.eTokenOut, cache.accountOut, amountOutInternal);
logAssetStatus(cache.assetCacheOut);

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

// Check liquidity
checkLiquidity(cache.accountIn);

Expand All @@ -78,6 +84,9 @@ contract SwapHub is BaseLogic {
/// @param params struct defining the requested trade
/// @param targetDebt how much debt should remain after calling the function
function swapAndRepay(uint subAccountIdIn, uint subAccountIdOut, address swapHandler, ISwapHandler.SwapParams memory params, uint targetDebt) external nonReentrant {
assetPolicyCheck(params.underlyingIn, PAUSETYPE__WITHDRAW);
assetPolicyCheck(params.underlyingOut, PAUSETYPE__REPAY);

SwapCache memory cache = initSwap(subAccountIdIn, subAccountIdOut, params);

emit RequestSwapHubRepay(
Expand Down
13 changes: 13 additions & 0 deletions contracts/test/MockSwapHandler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.8.0;

import "../swapHandlers/ISwapHandler.sol";
import "../Utils.sol";

/// @notice Base contract for swap handlers
contract MockSwapHandler is ISwapHandler {
function executeSwap(SwapParams calldata params) override external {
Utils.safeTransfer(params.underlyingOut, msg.sender, params.amountOut);
}
}
7 changes: 7 additions & 0 deletions contracts/views/EulerGeneralView.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ contract EulerGeneralView is Constants {
uint borrowAPY;
uint supplyAPY;

Storage.AssetPolicy assetPolicy;

// Pricing

uint twap;
Expand Down Expand Up @@ -165,6 +167,11 @@ contract EulerGeneralView is Constants {
(m.borrowAPY, m.supplyAPY) = computeAPYs(borrowSPY, m.totalBorrows, m.totalBalances, m.reserveFee);
}

{
Storage.AssetPolicy memory p = marketsProxy.getAssetPolicy(m.underlying);
m.assetPolicy = p;
}

(m.twap, m.twapPeriod, m.currPrice) = execProxy.getPriceFull(m.underlying);
(m.pricingType, m.pricingParameters, m.pricingForwarded) = marketsProxy.getPricingConfig(m.underlying);

Expand Down
Loading