diff --git a/src/EVault/DToken.sol b/src/EVault/DToken.sol index 7391a071..d5e05adb 100644 --- a/src/EVault/DToken.sol +++ b/src/EVault/DToken.sol @@ -6,6 +6,9 @@ import {Errors} from "./shared/Errors.sol"; import {Events} from "./shared/Events.sol"; import {IERC20, IEVault} from "./IEVault.sol"; +/// @title DToken +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Contract implements read only ERC20 interface, and `Transfer` events, for EVault's debt contract DToken is IERC20, Errors, Events { address public immutable eVault; diff --git a/src/EVault/Dispatch.sol b/src/EVault/Dispatch.sol index 65d2756c..db778c9d 100644 --- a/src/EVault/Dispatch.sol +++ b/src/EVault/Dispatch.sol @@ -15,6 +15,9 @@ import {RiskManagerModule} from "./modules/RiskManager.sol"; import "./shared/Constants.sol"; +/// @title Dispatch +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Contract which ties in the EVault modules and provides utilities for routing calls to modules and the EVC abstract contract Dispatch is Base, InitializeModule, @@ -78,6 +81,9 @@ abstract contract Dispatch is } } + // External function which is only callable by the EVault itself. Its purpose is to be static called by `delegateToModuleView` + // which allows view functions to be implemented in modules, even though delegatecall cannot be directly used within + // view functions. function viewDelegate() external { if (msg.sender != address(this)) revert E_Unauthorized(); diff --git a/src/EVault/EVault.sol b/src/EVault/EVault.sol index c1756513..809440a6 100644 --- a/src/EVault/EVault.sol +++ b/src/EVault/EVault.sol @@ -4,17 +4,25 @@ pragma solidity ^0.8.0; import {Dispatch} from "./Dispatch.sol"; +/// @title EVault +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice This contract implements an EVC enabled lending vault +/// @dev The responsibility of this contract is call routing. Select functions are embedded, while most are delegated to the modules. contract EVault is Dispatch { constructor(Integrations memory integrations, DeployedModules memory modules) Dispatch(integrations, modules) {} - // ------------ Initialization ------------- + /////////////////////////////////////////////////////////////////////////////////////////////// + // INITIALIZATION // + /////////////////////////////////////////////////////////////////////////////////////////////// function initialize(address proxyCreator) public virtual override use(MODULE_INITIALIZE) {} - // ----------------- Token ----------------- + /////////////////////////////////////////////////////////////////////////////////////////////// + // TOKEN // + /////////////////////////////////////////////////////////////////////////////////////////////// function name() public view virtual override useView(MODULE_TOKEN) returns (string memory) {} @@ -39,7 +47,9 @@ contract EVault is Dispatch { - // ----------------- Vault ----------------- + /////////////////////////////////////////////////////////////////////////////////////////////// + // VAULT // + /////////////////////////////////////////////////////////////////////////////////////////////// function asset() public view virtual override returns (address) { return super.asset(); } @@ -84,7 +94,9 @@ contract EVault is Dispatch { - // ----------------- Borrowing ----------------- + /////////////////////////////////////////////////////////////////////////////////////////////// + // BORROWING // + /////////////////////////////////////////////////////////////////////////////////////////////// function totalBorrows() public view virtual override useView(MODULE_BORROWING) returns (uint256) {} @@ -121,7 +133,9 @@ contract EVault is Dispatch { - // ----------------- Liquidation ----------------- + /////////////////////////////////////////////////////////////////////////////////////////////// + // LIQUIDATION // + /////////////////////////////////////////////////////////////////////////////////////////////// function checkLiquidation(address liquidator, address violator, address collateral) public view virtual override useView(MODULE_LIQUIDATION) returns (uint256 maxRepay, uint256 maxYield) {} @@ -129,7 +143,9 @@ contract EVault is Dispatch { - // ----------------- RiskManager ----------------- + /////////////////////////////////////////////////////////////////////////////////////////////// + // RISK MANAGEMENT // + /////////////////////////////////////////////////////////////////////////////////////////////// function accountLiquidity(address account, bool liquidation) public view virtual override useView(MODULE_RISKMANAGER) returns (uint256 collateralValue, uint256 liabilityValue) {} @@ -144,7 +160,9 @@ contract EVault is Dispatch { - // ----------------- Balance Forwarder ----------------- + /////////////////////////////////////////////////////////////////////////////////////////////// + // BALANCE TRACKING // + /////////////////////////////////////////////////////////////////////////////////////////////// function balanceTrackerAddress() public view virtual override useView(MODULE_BALANCE_FORWARDER) returns (address) {} @@ -157,7 +175,9 @@ contract EVault is Dispatch { - // ----------------- Governance ----------------- + /////////////////////////////////////////////////////////////////////////////////////////////// + // GOVERNANCE // + /////////////////////////////////////////////////////////////////////////////////////////////// function governorAdmin() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {} diff --git a/src/EVault/IEVault.sol b/src/EVault/IEVault.sol index 537bd822..6cc7f22d 100644 --- a/src/EVault/IEVault.sol +++ b/src/EVault/IEVault.sol @@ -6,12 +6,16 @@ import {IVault as IEVCVault} from "ethereum-vault-connector/interfaces/IVault.so // Full interface of EVault and all it's modules +/// @title IInitialize +/// @notice Interface of the initialization module of EVault interface IInitialize { /// @notice Initialization of the newly deployed proxy contract /// @param proxyCreator Account which created the proxy or should be the initial governor function initialize(address proxyCreator) external; } +/// @title IERC20 +/// @notice Interface of the EVault's Initialize module interface IERC20 { /// @notice Vault share token (eToken) name, ie "Euler Vault: DAI" function name() external view returns (string memory); @@ -50,6 +54,8 @@ interface IERC20 { function approve(address spender, uint256 amount) external returns (bool); } +/// @title IToken +/// @notice Interface of the EVault's Token module interface IToken is IERC20 { /// @notice Transfer the full eToken balance of an address to another /// @param from This address must've approved the to address @@ -57,6 +63,8 @@ interface IToken is IERC20 { function transferFromMax(address from, address to) external returns (bool); } +/// @title IERC4626 +/// @notice Interface of an ERC4626 vault interface IERC4626 { /// @notice Vault underlying asset function asset() external view returns (address); @@ -134,6 +142,8 @@ interface IERC4626 { function redeem(uint256 amount, address receiver, address owner) external returns (uint256); } +/// @title IVault +/// @notice Interface of the EVault's Vault module interface IVault is IERC4626 { /// @notice Balance of the fees accumulator, in eTokens function accumulatedFees() external view returns (uint256); @@ -152,6 +162,8 @@ interface IVault is IERC4626 { function skim(uint256 amount, address receiver) external returns (uint256); } +/// @title IBorrowing +/// @notice Interface of the EVault's Borrowing module interface IBorrowing { /// @notice Sum of all outstanding debts, in underlying units (increases as interest is accrued) function totalBorrows() external view returns (uint256); @@ -223,6 +235,8 @@ interface IBorrowing { function touch() external; } +/// @title ILiquidation +/// @notice Interface of the EVault's Liquidation module interface ILiquidation { /// @notice Checks to see if a liquidation would be profitable, without actually doing anything /// @param liquidator Address that will initiate the liquidation @@ -243,6 +257,8 @@ interface ILiquidation { function liquidate(address violator, address collateral, uint256 repayAssets, uint256 minYieldBalance) external; } +/// @title IRiskManager +/// @notice Interface of the EVault's RiskManager module interface IRiskManager is IEVCVault { /// @notice Retrieve account's total liquidity /// @param account Account holding debt in this vault @@ -280,6 +296,8 @@ interface IRiskManager is IEVCVault { function checkVaultStatus() external returns (bytes4); } +/// @title IBalanceForwarder +/// @notice Interface of the EVault's BalanceForwarder module interface IBalanceForwarder { /// @notice Retrieve the address of rewards contract, tracking changes in account's balances function balanceTrackerAddress() external view returns (address); @@ -298,6 +316,8 @@ interface IBalanceForwarder { function disableBalanceForwarder() external; } +/// @title IGovernance +/// @notice Interface of the EVault's Governance module interface IGovernance { /// @notice Retrieves the address of the governor function governorAdmin() external view returns (address); @@ -408,6 +428,8 @@ interface IGovernance { function setInterestFee(uint16 newFee) external; } +/// @title IEVault +/// @notice Interface of the EVault, an EVC enabled lending vault interface IEVault is IInitialize, IToken, diff --git a/src/EVault/modules/BalanceForwarder.sol b/src/EVault/modules/BalanceForwarder.sol index 92460e00..36850548 100644 --- a/src/EVault/modules/BalanceForwarder.sol +++ b/src/EVault/modules/BalanceForwarder.sol @@ -5,6 +5,9 @@ pragma solidity ^0.8.0; import {IBalanceForwarder} from "../IEVault.sol"; import {Base} from "../shared/Base.sol"; +/// @title BalanceForwarderModule +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling communication a with balance tracker contract. abstract contract BalanceForwarderModule is IBalanceForwarder, Base { /// @inheritdoc IBalanceForwarder function balanceTrackerAddress() public view virtual reentrantOK returns (address) { @@ -13,7 +16,7 @@ abstract contract BalanceForwarderModule is IBalanceForwarder, Base { /// @inheritdoc IBalanceForwarder function balanceForwarderEnabled(address account) public view virtual reentrantOK returns (bool) { - return vaultStorage.users[account].getBalanceForwarderEnabled(); + return vaultStorage.users[account].isBalanceForwarderEnabled(); } /// @inheritdoc IBalanceForwarder @@ -21,7 +24,7 @@ abstract contract BalanceForwarderModule is IBalanceForwarder, Base { if (address(balanceTracker) == address(0)) revert E_BalanceForwarderUnsupported(); address account = EVCAuthenticate(); - bool wasBalanceForwarderEnabled = vaultStorage.users[account].getBalanceForwarderEnabled(); + bool wasBalanceForwarderEnabled = vaultStorage.users[account].isBalanceForwarderEnabled(); vaultStorage.users[account].setBalanceForwarder(true); balanceTracker.balanceTrackerHook(account, vaultStorage.users[account].getBalance().toUint(), false); @@ -34,7 +37,7 @@ abstract contract BalanceForwarderModule is IBalanceForwarder, Base { if (address(balanceTracker) == address(0)) revert E_BalanceForwarderUnsupported(); address account = EVCAuthenticate(); - bool wasBalanceForwarderEnabled = vaultStorage.users[account].getBalanceForwarderEnabled(); + bool wasBalanceForwarderEnabled = vaultStorage.users[account].isBalanceForwarderEnabled(); vaultStorage.users[account].setBalanceForwarder(false); balanceTracker.balanceTrackerHook(account, 0, false); @@ -43,6 +46,7 @@ abstract contract BalanceForwarderModule is IBalanceForwarder, Base { } } +/// @dev Deployable module contract contract BalanceForwarder is BalanceForwarderModule { constructor(Integrations memory integrations) Base(integrations) {} } diff --git a/src/EVault/modules/Borrowing.sol b/src/EVault/modules/Borrowing.sol index 61848424..ba6aeff9 100644 --- a/src/EVault/modules/Borrowing.sol +++ b/src/EVault/modules/Borrowing.sol @@ -17,6 +17,9 @@ interface IFlashLoan { function onFlashLoan(bytes memory data) external; } +/// @title BorrowingModule +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling borrowing and repaying of vault assets abstract contract BorrowingModule is IBorrowing, Base, AssetTransfers, BalanceUtils, LiquidityUtils { using TypesLib for uint256; using SafeERC20Lib for IERC20; @@ -74,9 +77,6 @@ abstract contract BorrowingModule is IBorrowing, Base, AssetTransfers, BalanceUt (uint256 totalCollateralValueRiskAdjusted, uint256 liabilityValue) = calculateLiquidity(vaultCache, account, collaterals, LTVType.BORROWING); - // if there is no liability or it has no value, collateral will not be locked - if (liabilityValue == 0) return 0; - uint256 collateralBalance = IERC20(collateral).balanceOf(account); // if account is not healthy, all of the collateral will be locked @@ -91,6 +91,13 @@ abstract contract BorrowingModule is IBorrowing, Base, AssetTransfers, BalanceUt // calculate extra collateral value in terms of requested collateral balance, by dividing by LTV uint256 extraCollateralValue = ltv.mulInv(totalCollateralValueRiskAdjusted - liabilityValue); + if (extraCollateralValue != 0) { + // adjust due to health score definition which is collateral value > liability value + unchecked { + extraCollateralValue -= 1; + } + } + // convert back to collateral balance (bid) (uint256 collateralPrice,) = vaultCache.oracle.getQuotes(1e18, collateral, vaultCache.unitOfAccount); if (collateralPrice == 0) return 0; // worthless / unpriced collateral is not locked @@ -226,6 +233,7 @@ abstract contract BorrowingModule is IBorrowing, Base, AssetTransfers, BalanceUt } } +/// @dev Deployable module contract contract Borrowing is BorrowingModule { constructor(Integrations memory integrations) Base(integrations) {} } diff --git a/src/EVault/modules/Governance.sol b/src/EVault/modules/Governance.sol index a00ee471..c521dbb8 100644 --- a/src/EVault/modules/Governance.sol +++ b/src/EVault/modules/Governance.sol @@ -12,6 +12,9 @@ import {ProxyUtils} from "../shared/lib/ProxyUtils.sol"; import "../shared/types/Types.sol"; +/// @title GovernanceModule +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling governance, including configuration and fees abstract contract GovernanceModule is IGovernance, Base, BalanceUtils, BorrowUtils, LTVUtils { using TypesLib for uint16; @@ -285,6 +288,7 @@ abstract contract GovernanceModule is IGovernance, Base, BalanceUtils, BorrowUti } } +/// @dev Deployable module contract contract Governance is GovernanceModule { constructor(Integrations memory integrations) Base(integrations) {} } diff --git a/src/EVault/modules/Initialize.sol b/src/EVault/modules/Initialize.sol index 4a5986b8..cddf15ef 100644 --- a/src/EVault/modules/Initialize.sol +++ b/src/EVault/modules/Initialize.sol @@ -12,6 +12,9 @@ import {VaultCache} from "../shared/types/VaultCache.sol"; import "../shared/Constants.sol"; import "../shared/types/Types.sol"; +/// @title InitializeModule +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module implementing the initialization of the new vault contract abstract contract InitializeModule is IInitialize, Base, BorrowUtils { using TypesLib for uint16; @@ -57,6 +60,7 @@ abstract contract InitializeModule is IInitialize, Base, BorrowUtils { } } +/// @dev Deployable module contract contract Initialize is InitializeModule { constructor(Integrations memory integrations) Base(integrations) {} } diff --git a/src/EVault/modules/Liquidation.sol b/src/EVault/modules/Liquidation.sol index ced1f870..5378173d 100644 --- a/src/EVault/modules/Liquidation.sol +++ b/src/EVault/modules/Liquidation.sol @@ -9,6 +9,9 @@ import {LiquidityUtils} from "../shared/LiquidityUtils.sol"; import "../shared/types/Types.sol"; +/// @title LiquidationModule +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling liquidations of unhealthy accounts abstract contract LiquidationModule is ILiquidation, Base, BalanceUtils, LiquidityUtils { using TypesLib for uint256; @@ -117,7 +120,7 @@ abstract contract LiquidationModule is ILiquidation, Base, BalanceUtils, Liquidi calculateLiquidity(vaultCache, liqCache.violator, liqCache.collaterals, LTVType.LIQUIDATION); // no violation - if (liquidityCollateralValue >= liquidityLiabilityValue) return liqCache; + if (liquidityCollateralValue > liquidityLiabilityValue) return liqCache; // Compute discount @@ -216,6 +219,7 @@ abstract contract LiquidationModule is ILiquidation, Base, BalanceUtils, Liquidi } } +/// @dev Deployable module contract contract Liquidation is LiquidationModule { constructor(Integrations memory integrations) Base(integrations) {} } diff --git a/src/EVault/modules/RiskManager.sol b/src/EVault/modules/RiskManager.sol index 6b0adcd2..919b2dfd 100644 --- a/src/EVault/modules/RiskManager.sol +++ b/src/EVault/modules/RiskManager.sol @@ -8,6 +8,9 @@ import {LiquidityUtils} from "../shared/LiquidityUtils.sol"; import "../shared/types/Types.sol"; +/// @title RiskManagerModule +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling risk management, including vault and account health checks abstract contract RiskManagerModule is IRiskManager, Base, LiquidityUtils { using TypesLib for uint256; @@ -113,6 +116,7 @@ abstract contract RiskManagerModule is IRiskManager, Base, LiquidityUtils { } } +/// @dev Deployable module contract contract RiskManager is RiskManagerModule { constructor(Integrations memory integrations) Base(integrations) {} } diff --git a/src/EVault/modules/Token.sol b/src/EVault/modules/Token.sol index 3cce545d..bdbed7a1 100644 --- a/src/EVault/modules/Token.sol +++ b/src/EVault/modules/Token.sol @@ -9,6 +9,9 @@ import {ProxyUtils} from "../shared/lib/ProxyUtils.sol"; import "../shared/types/Types.sol"; +/// @title TokenModule +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling ERC20 behaviour of vault shares abstract contract TokenModule is IToken, Base, BalanceUtils { using TypesLib for uint256; @@ -79,6 +82,7 @@ abstract contract TokenModule is IToken, Base, BalanceUtils { } } +/// @dev Deployable module contract contract Token is TokenModule { constructor(Integrations memory integrations) Base(integrations) {} } diff --git a/src/EVault/modules/Vault.sol b/src/EVault/modules/Vault.sol index f010b48d..9adf420f 100644 --- a/src/EVault/modules/Vault.sol +++ b/src/EVault/modules/Vault.sol @@ -11,6 +11,9 @@ import {ProxyUtils} from "../shared/lib/ProxyUtils.sol"; import "../shared/types/Types.sol"; +/// @title VaultModule +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling ERC4626 standard behaviour abstract contract VaultModule is IVault, Base, AssetTransfers, BalanceUtils { using TypesLib for uint256; using SafeERC20Lib for IERC20; @@ -40,6 +43,7 @@ abstract contract VaultModule is IVault, Base, AssetTransfers, BalanceUtils { } /// @inheritdoc IERC4626 + /// @dev Because `nonReentrantView` can revert, the function might be considered not fully compliant with ERC4626 function maxDeposit(address account) public view virtual nonReentrantView returns (uint256) { VaultCache memory vaultCache = loadVault(); @@ -52,12 +56,17 @@ abstract contract VaultModule is IVault, Base, AssetTransfers, BalanceUtils { } /// @inheritdoc IERC4626 + /// @dev If the hook on `mint` allows only certain amounts, maxMint function might not be fully compliant with ERC4626 + /// @dev Because `nonReentrantView` can revert, the function might be considered not fully compliant with ERC4626 function maxMint(address account) public view virtual nonReentrantView returns (uint256) { VaultCache memory vaultCache = loadVault(); - return validateAndCallHookView(vaultCache.hookedOps, OP_MINT) - ? maxDepositInternal(vaultCache, account).toAssets().toSharesDown(vaultCache).toUint() - : 0; + if (!validateAndCallHookView(vaultCache.hookedOps, OP_MINT)) return 0; + + // make sure to not revert on conversion + uint256 shares = maxDepositInternal(vaultCache, account).toAssets().toUint256SharesDown(vaultCache); + + return shares < MAX_SANE_AMOUNT ? shares : MAX_SANE_AMOUNT; } /// @inheritdoc IERC4626 @@ -67,6 +76,7 @@ abstract contract VaultModule is IVault, Base, AssetTransfers, BalanceUtils { } /// @inheritdoc IERC4626 + /// @dev Because `nonReentrantView` can revert, the function might be considered not fully compliant with ERC4626 function maxWithdraw(address owner) public view virtual nonReentrantView returns (uint256) { VaultCache memory vaultCache = loadVault(); @@ -82,6 +92,7 @@ abstract contract VaultModule is IVault, Base, AssetTransfers, BalanceUtils { } /// @inheritdoc IERC4626 + /// @dev Because `nonReentrantView` can revert, the function might be considered not fully compliant with ERC4626 function maxRedeem(address owner) public view virtual nonReentrantView returns (uint256) { return validateAndCallHookView(vaultStorage.hookedOps, OP_REDEEM) ? maxRedeemInternal(owner).toUint() : 0; } @@ -267,6 +278,7 @@ abstract contract VaultModule is IVault, Base, AssetTransfers, BalanceUtils { } } +/// @dev Deployable module contract contract Vault is VaultModule { constructor(Integrations memory integrations) Base(integrations) {} } diff --git a/src/EVault/shared/AssetTransfers.sol b/src/EVault/shared/AssetTransfers.sol index e87daa79..a8d443a1 100644 --- a/src/EVault/shared/AssetTransfers.sol +++ b/src/EVault/shared/AssetTransfers.sol @@ -7,6 +7,9 @@ import {Base} from "./Base.sol"; import "./types/Types.sol"; +/// @title AssetTransfers +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Transfer assets into and out of the vault abstract contract AssetTransfers is Base { using TypesLib for uint256; using SafeERC20Lib for IERC20; diff --git a/src/EVault/shared/BalanceUtils.sol b/src/EVault/shared/BalanceUtils.sol index 4f25ae0a..97242383 100644 --- a/src/EVault/shared/BalanceUtils.sol +++ b/src/EVault/shared/BalanceUtils.sol @@ -7,6 +7,9 @@ import {IBalanceTracker} from "../../interfaces/IBalanceTracker.sol"; import "./types/Types.sol"; +/// @title BalanceUtils +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Utilities for tracking share balances and allowances abstract contract BalanceUtils is Base { using TypesLib for uint256; diff --git a/src/EVault/shared/Base.sol b/src/EVault/shared/Base.sol index c20bf517..2856b34b 100644 --- a/src/EVault/shared/Base.sol +++ b/src/EVault/shared/Base.sol @@ -11,6 +11,9 @@ import {IBalanceTracker} from "../../interfaces/IBalanceTracker.sol"; import "./types/Types.sol"; +/// @title Base +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Base contract for EVault modules with top level modifiers and utilities abstract contract Base is EVCClient, Cache { IProtocolConfig immutable protocolConfig; IBalanceTracker immutable balanceTracker; diff --git a/src/EVault/shared/BorrowUtils.sol b/src/EVault/shared/BorrowUtils.sol index 299ec673..94eea646 100644 --- a/src/EVault/shared/BorrowUtils.sol +++ b/src/EVault/shared/BorrowUtils.sol @@ -8,6 +8,9 @@ import {IIRM} from "../../InterestRateModels/IIRM.sol"; import "./types/Types.sol"; +/// @title BorrowUtils +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Utilities for tracking debt and interest rates abstract contract BorrowUtils is Base { using TypesLib for uint256; diff --git a/src/EVault/shared/Cache.sol b/src/EVault/shared/Cache.sol index 35ae7333..ab1db971 100644 --- a/src/EVault/shared/Cache.sol +++ b/src/EVault/shared/Cache.sol @@ -10,6 +10,9 @@ import {ProxyUtils} from "./lib/ProxyUtils.sol"; import "./types/Types.sol"; +/// @title Cache +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Utilities for loading vault storage and updating it with interest accrued contract Cache is Storage, Errors { using TypesLib for uint256; using SafeERC20Lib for IERC20; @@ -61,12 +64,12 @@ contract Cache is Storage, Errors { vaultCache.interestAccumulator = vaultStorage.interestAccumulator; // Update interest accumulator and fees balance - uint256 deltaT = block.timestamp - vaultCache.lastInterestAccumulatorUpdate; + uint256 deltaT = block.timestamp - vaultCache.lastInterestAccumulatorUpdate; if (deltaT > 0) { dirty = true; - // Compute new values. Use full precision for intermediate results. + // Compute new cache values. Use full precision for intermediate results. ConfigAmount interestFee = vaultStorage.interestFee; uint256 interestRate = vaultStorage.interestRate; @@ -76,6 +79,7 @@ contract Cache is Storage, Errors { unchecked { (uint256 multiplier, bool overflow) = RPow.rpow(interestRate + 1e27, deltaT, 1e27); + // if exponentiation or accumulator update overflows, keep the old accumulator if (!overflow) { uint256 intermediate = newInterestAccumulator * multiplier; if (newInterestAccumulator == intermediate / multiplier) { diff --git a/src/EVault/shared/Constants.sol b/src/EVault/shared/Constants.sol index 08bcb61e..53e01929 100644 --- a/src/EVault/shared/Constants.sol +++ b/src/EVault/shared/Constants.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -// TODO merge with IRM updates -uint256 constant SECONDS_PER_YEAR = 365.2425 * 86400; // Gregorian calendar -uint256 constant MAX_ALLOWED_INTEREST_RATE = 291867236321699131285; // 1,000,000% APY: ln(1 + (1000000 / 100)) * 1e27 / (365.2425 * 86400) - // Implementation internals // asset amounts are shifted left by this number of bits for increased precision of debt tracking. @@ -15,6 +11,10 @@ uint256 constant MAX_SANE_AMOUNT = type(uint112).max; uint256 constant MAX_SANE_DEBT_AMOUNT = uint256(MAX_SANE_AMOUNT) << INTERNAL_DEBT_PRECISION; // proxy trailing calldata length in bytes. Three addresses, 20 bytes each: vault underlying asset, oracle and unit of account. uint256 constant PROXY_METADATA_LENGTH = 60; +// gregorian calendar +uint256 constant SECONDS_PER_YEAR = 365.2425 * 86400; +// max interest rate accepted from interest rate model contract. 1,000,000% APY: ln(1 + (1000000 / 100)) * 1e27 / (365.2425 * 86400) +uint256 constant MAX_ALLOWED_INTEREST_RATE = 291867236321699131285; // Account status checks special values diff --git a/src/EVault/shared/EVCClient.sol b/src/EVault/shared/EVCClient.sol index 6115b4d9..fc85f173 100644 --- a/src/EVault/shared/EVCClient.sol +++ b/src/EVault/shared/EVCClient.sol @@ -11,6 +11,9 @@ import "./Constants.sol"; import {IERC20} from "../IEVault.sol"; import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; +/// @title EVCClient +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Utilities for interacting with the EVC (Ethereum Vault Connector) abstract contract EVCClient is Storage, Events, Errors { IEVC immutable evc; diff --git a/src/EVault/shared/Errors.sol b/src/EVault/shared/Errors.sol index 9b621d65..7d2894dc 100644 --- a/src/EVault/shared/Errors.sol +++ b/src/EVault/shared/Errors.sol @@ -2,6 +2,9 @@ pragma solidity ^0.8.0; +/// @title Errors +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Contract implementing EVault's custom errors contract Errors { error E_Initialized(); error E_ProxyMetadata(); diff --git a/src/EVault/shared/Events.sol b/src/EVault/shared/Events.sol index bda9c35c..b47153a0 100644 --- a/src/EVault/shared/Events.sol +++ b/src/EVault/shared/Events.sol @@ -2,32 +2,59 @@ pragma solidity ^0.8.0; -import {LTVConfig} from "./types/LTVConfig.sol"; - +/// @title Events +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Contract implementing EVault's events abstract contract Events { - event EVaultCreated(address indexed creator, address indexed asset, address dToken); + // ERC20 + /// @notice Transfer an ERC20 token balance + /// @param from Sender address + /// @param to Receiver address + /// @param value Tokens sent event Transfer(address indexed from, address indexed to, uint256 value); + + /// @notice Set an ERC20 approval + /// @param owner Address granting approval to spend tokens + /// @param spender Address receiving approval to spend tokens + /// @param value Amount of tokens approved to spend event Approval(address indexed owner, address indexed spender, uint256 value); + // ERC4626 + + /// @notice Deposit assets into an ERC4626 vault + /// @param sender Address initiaiting the deposit + /// @param owner Address holding the assets + /// @param assets Amount of assets deposited + /// @param shares Amount of shares minted as recipt for the deposit event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + /// @notice Withdraw from an ERC4626 vault + /// @param sender Address initiating the withdrawal + /// @param receiver Address receiving the assets + /// @param owner Address holding the shares + /// @param assets Amount of assets sent to receiver + /// @param shares Amount of shares burned event Withdraw( address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); - event Borrow(address indexed account, uint256 assets); - event Repay(address indexed account, uint256 assets); - - event DebtSocialized(address indexed account, uint256 assets); + // EVault - event ConvertFees( - address indexed sender, - address indexed protocolReceiver, - address indexed governorReceiver, - uint256 protocolShares, - uint256 governorShares - ); + /// @notice New EVault is initialized + /// @param creator Address designated as the vault's creator + /// @param asset The underlying asset of the vault + /// @param dToken Address of the sidecar debt token + event EVaultCreated(address indexed creator, address indexed asset, address dToken); + /// @notice Log the current vault status + /// @param totalShares Sum of all shares + /// @param totalBorrows Sum of all borrows in assets + /// @param accumulatedFees Interest fees accrued in the accumulator + /// @param cash The amount of assets held by the vault directly + /// @param interestAccumulator Current interest accumulator in ray + /// @param interestRate Current interest rate, which will be applied during the next fee accrual + /// @param timestamp Current block's timestamp event VaultStatus( uint256 totalShares, uint256 totalBorrows, @@ -37,6 +64,23 @@ abstract contract Events { uint256 interestRate, uint256 timestamp ); + + /// @notice Increase account's debt + /// @param account Address adding liability + /// @param assets Amount of debt added in assets + event Borrow(address indexed account, uint256 assets); + + /// @notice Decrease account's debt + /// @param account Address repaying the debt + /// @param assets Amount of debt removed in assets + event Repay(address indexed account, uint256 assets); + + /// @notice Liquidate unhealthy account + /// @param liquidator Address executing the liquidation + /// @param violator Address holding an unhealthy borrow + /// @param collateral Address of the asset seized + /// @param repayAssets Amount of debt in assets transfered from violator to liquidator + /// @param yieldBalance Amount of collateral asset's balance transfered from violator to liquidator event Liquidate( address indexed liquidator, address indexed violator, @@ -45,5 +89,27 @@ abstract contract Events { uint256 yieldBalance ); + /// @notice Socialize debt after liquidating all of the unhealthy account's collateral + /// @param account Address holding an unhealthy borrow + /// @param assets Amount of debt socialized among all of the share holders + event DebtSocialized(address indexed account, uint256 assets); + + /// @notice Split the accumulated fees between the governor and the protocol + /// @param sender Address initializing the conversion + /// @param protocolReceiver Address receiving the protocol's share of the fees + /// @param governorReceiver Address receiving the governor's share of the fees + /// @param protocolShares Amount of shares transferred to the protocol receiver + /// @param governorShares Amount of shares transferred to the governor receiver + event ConvertFees( + address indexed sender, + address indexed protocolReceiver, + address indexed governorReceiver, + uint256 protocolShares, + uint256 governorShares + ); + + /// @notice Enable or disable balance tracking for the account + /// @param account Address which enabled or disabled balance tracking + /// @param status True if balance tracking was enabled, false otherwise event BalanceForwarderStatus(address indexed account, bool status); } diff --git a/src/EVault/shared/LTVUtils.sol b/src/EVault/shared/LTVUtils.sol index ad0c9172..2b096a52 100644 --- a/src/EVault/shared/LTVUtils.sol +++ b/src/EVault/shared/LTVUtils.sol @@ -5,6 +5,9 @@ pragma solidity ^0.8.0; import {Storage} from "./Storage.sol"; import "./types/Types.sol"; +/// @title LTVUtils +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Overridable getters for LTV configuration abstract contract LTVUtils is Storage { function getLTV(address collateral, LTVType ltvType) internal view virtual returns (ConfigAmount) { return vaultStorage.ltvLookup[collateral].getLTV(ltvType); diff --git a/src/EVault/shared/LiquidityUtils.sol b/src/EVault/shared/LiquidityUtils.sol index af322e92..d41eef7b 100644 --- a/src/EVault/shared/LiquidityUtils.sol +++ b/src/EVault/shared/LiquidityUtils.sol @@ -7,6 +7,9 @@ import {LTVUtils} from "./LTVUtils.sol"; import "./types/Types.sol"; +/// @title LiquidityUtils +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Utilities for calculating account liquidity and health status abstract contract LiquidityUtils is BorrowUtils, LTVUtils { using TypesLib for uint256; @@ -37,12 +40,11 @@ abstract contract LiquidityUtils is BorrowUtils, LTVUtils { if (owed.isZero()) return; uint256 liabilityValue = getLiabilityValue(vaultCache, account, owed); - if (liabilityValue == 0) return; uint256 collateralValue; for (uint256 i; i < collaterals.length; ++i) { collateralValue += getCollateralValue(vaultCache, account, collaterals[i], LTVType.BORROWING); - if (collateralValue >= liabilityValue) return; + if (collateralValue > liabilityValue) return; } revert E_AccountLiquidity(); diff --git a/src/EVault/shared/Storage.sol b/src/EVault/shared/Storage.sol index 6406c5ff..fcad5470 100644 --- a/src/EVault/shared/Storage.sol +++ b/src/EVault/shared/Storage.sol @@ -4,10 +4,17 @@ pragma solidity ^0.8.0; import {VaultStorage, Snapshot} from "./types/Types.sol"; +/// @title Storage +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Contract that defines the EVault's data storage abstract contract Storage { + /// @notice Flag indicating if the vault has been initialized bool initialized; + /// @notice Snapshot of vault's cash and borrows created at the beginning of an operation or a batch of operations + /// @dev The snapshot is separate from VaultStorage, because it could be implemented as transient storage Snapshot snapshot; + /// @notice A singleton VaultStorage VaultStorage vaultStorage; } diff --git a/src/EVault/shared/lib/ConversionHelpers.sol b/src/EVault/shared/lib/ConversionHelpers.sol index d78ada49..514957e4 100644 --- a/src/EVault/shared/lib/ConversionHelpers.sol +++ b/src/EVault/shared/lib/ConversionHelpers.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.0; import {VaultCache} from "../types/VaultCache.sol"; +/// @title ConversionHelpers Library +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice The library provides a helper function for conversions between shares and assets library ConversionHelpers { // virtual deposit used in conversions between shares and assets, serving as exchange rate manipulation mitigation uint256 constant VIRTUAL_DEPOSIT_AMOUNT = 1e6; diff --git a/src/EVault/shared/lib/ProxyUtils.sol b/src/EVault/shared/lib/ProxyUtils.sol index 65ee2efe..61ad56b6 100644 --- a/src/EVault/shared/lib/ProxyUtils.sol +++ b/src/EVault/shared/lib/ProxyUtils.sol @@ -5,6 +5,9 @@ pragma solidity ^0.8.0; import {IERC20} from "../../IEVault.sol"; import {IPriceOracle} from "../../../interfaces/IPriceOracle.sol"; +/// @title ProxyUtils Library +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice The library provides a helper function for working with proxy meta data library ProxyUtils { function metadata() internal pure returns (IERC20 asset, IPriceOracle oracle, address unitOfAccount) { assembly { diff --git a/src/EVault/shared/lib/RevertBytes.sol b/src/EVault/shared/lib/RevertBytes.sol index c841d8da..0fdfc24a 100644 --- a/src/EVault/shared/lib/RevertBytes.sol +++ b/src/EVault/shared/lib/RevertBytes.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.0; import "../Errors.sol"; +/// @title RevertBytes Library +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice The library provides a helper function for bubbling up errors library RevertBytes { function revertBytes(bytes memory errMsg) internal pure { if (errMsg.length > 0) { diff --git a/src/EVault/shared/lib/SafeERC20Lib.sol b/src/EVault/shared/lib/SafeERC20Lib.sol index c81ced9d..dfdd2f1f 100644 --- a/src/EVault/shared/lib/SafeERC20Lib.sol +++ b/src/EVault/shared/lib/SafeERC20Lib.sol @@ -6,6 +6,9 @@ import {IERC20} from "../../IEVault.sol"; import {RevertBytes} from "./RevertBytes.sol"; import {IPermit2} from "../../../interfaces/IPermit2.sol"; +/// @title SafeERC20Lib Library +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice The library provides helpers for ERC20 transfers, including Permit2 support library SafeERC20Lib { error E_TransferFromFailed(bytes errorTransferFrom, bytes errorPermit2); error E_Permit2AmountOverflow(); diff --git a/src/EVault/shared/types/AmountCap.sol b/src/EVault/shared/types/AmountCap.sol index 8a8e8f4f..fd4d3f02 100644 --- a/src/EVault/shared/types/AmountCap.sol +++ b/src/EVault/shared/types/AmountCap.sol @@ -4,13 +4,15 @@ pragma solidity ^0.8.0; import {AmountCap} from "./Types.sol"; -// AmountCaps are 16-bit decimal floating point values: -// * The least significant 6 bits are the exponent -// * The most significant 10 bits are the mantissa, scaled by 100 -// * The special value of 0 means limit is not set -// * This is so that uninitialized storage implies no limit -// * For an actual cap value of 0, use a zero mantissa and non-zero exponent - +/// @title AmountCapLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for `AmountCap` custom type +/// @dev AmountCaps are 16-bit decimal floating point values: +/// * The least significant 6 bits are the exponent +/// * The most significant 10 bits are the mantissa, scaled by 100 +/// * The special value of 0 means limit is not set +/// * This is so that uninitialized storage implies no limit +/// * For an actual cap value of 0, use a zero mantissa and non-zero exponent library AmountCapLib { function toUint(AmountCap self) internal pure returns (uint256) { uint256 amountCap = AmountCap.unwrap(self); diff --git a/src/EVault/shared/types/Assets.sol b/src/EVault/shared/types/Assets.sol index 636623ed..d20400c5 100644 --- a/src/EVault/shared/types/Assets.sol +++ b/src/EVault/shared/types/Assets.sol @@ -7,6 +7,9 @@ import {VaultCache} from "./VaultCache.sol"; import {ConversionHelpers} from "../lib/ConversionHelpers.sol"; import "../Constants.sol"; +/// @title AssetsLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Custom type `Assets` represents amounts of the vault's underlying asset library AssetsLib { function toUint(Assets self) internal pure returns (uint256) { return Assets.unwrap(self); @@ -17,9 +20,13 @@ library AssetsLib { } function toSharesDown(Assets amount, VaultCache memory vaultCache) internal pure returns (Shares) { + return TypesLib.toShares(toUint256SharesDown(amount, vaultCache)); + } + + function toUint256SharesDown(Assets amount, VaultCache memory vaultCache) internal pure returns (uint256) { (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); unchecked { - return TypesLib.toShares(amount.toUint() * totalShares / totalAssets); + return amount.toUint() * totalShares / totalAssets; } } diff --git a/src/EVault/shared/types/ConfigAmount.sol b/src/EVault/shared/types/ConfigAmount.sol index 27010bfa..5e702169 100644 --- a/src/EVault/shared/types/ConfigAmount.sol +++ b/src/EVault/shared/types/ConfigAmount.sol @@ -6,9 +6,11 @@ import {ConfigAmount} from "./Types.sol"; import {Errors} from "../Errors.sol"; import "../Constants.sol"; -// ConfigAmounts are floating point values encoded in 16 bits with a 1e4 precision. -// The type is used to store protocol configuration values. - +/// @title ConfigAmountLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for `ConfigAmount` custom type +/// @dev ConfigAmounts are floating point values encoded in 16 bits with a 1e4 precision. +/// @dev The type is used to store protocol configuration values. library ConfigAmountLib { // note assuming arithmetic checks are already performed function mulDiv(ConfigAmount self, uint256 multiplier, uint256 divisor) internal pure returns (uint256) { diff --git a/src/EVault/shared/types/Flags.sol b/src/EVault/shared/types/Flags.sol index 9959b7a4..9066c840 100644 --- a/src/EVault/shared/types/Flags.sol +++ b/src/EVault/shared/types/Flags.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.0; import {Flags} from "./Types.sol"; +/// @title FlagsLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for `Flags` custom type library FlagsLib { /// @dev Are *all* of the flags in bitMask set? function isSet(Flags self, uint32 bitMask) internal pure returns (bool) { diff --git a/src/EVault/shared/types/LTVConfig.sol b/src/EVault/shared/types/LTVConfig.sol index 8caa17c8..0481902e 100644 --- a/src/EVault/shared/types/LTVConfig.sol +++ b/src/EVault/shared/types/LTVConfig.sol @@ -2,25 +2,41 @@ pragma solidity ^0.8.0; -import {Errors} from "../Errors.sol"; import {ConfigAmount} from "./Types.sol"; -import {LTVType} from "./LTVType.sol"; -import "../Constants.sol"; +/// @title LTVType +/// @notice Enum of LTV types +enum LTVType { + BORROWING, + LIQUIDATION +} + +/// @title LTVConfig +/// @notice This packed struct is used to store LTV configuration of a collateral struct LTVConfig { // Packed slot: 6 + 2 + 4 + 2 + 1 = 15 + // The timestamp when the new liquidation LTV ramping is finished uint48 targetTimestamp; + // The value of fully converged LTV value ConfigAmount targetLTV; + // The time it takes the liquidation LTV to converge with borrowing LTV uint32 rampDuration; + // The previous liquidation LTV value, from which the ramping begun ConfigAmount originalLTV; + // A flag indicating the configuration was initialized for the collateral bool initialized; } +/// @title LTVConfigLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for getting and setting the LTV configurations library LTVConfigLib { + // Is the collateral considered safe to liquidate function isRecognizedCollateral(LTVConfig memory self) internal pure returns (bool) { return self.targetTimestamp != 0; } + // Get current LTV of a collateral. When liquidation LTV is lowered, it is ramped down to target value over a period of time. function getLTV(LTVConfig memory self, LTVType ltvType) internal view returns (ConfigAmount) { if ( ltvType == LTVType.BORROWING || block.timestamp >= self.targetTimestamp || self.targetLTV > self.originalLTV @@ -52,6 +68,7 @@ library LTVConfigLib { newLTV.initialized = true; } + // When LTV is cleared, the collateral can't be liquidated, as it's deemed unsafe function clear(LTVConfig storage self) internal { self.targetTimestamp = 0; self.targetLTV = ConfigAmount.wrap(0); diff --git a/src/EVault/shared/types/Owed.sol b/src/EVault/shared/types/Owed.sol index 92c260e5..08c6d5a7 100644 --- a/src/EVault/shared/types/Owed.sol +++ b/src/EVault/shared/types/Owed.sol @@ -5,7 +5,11 @@ pragma solidity ^0.8.0; import {Owed, Assets, TypesLib} from "./Types.sol"; import "../Constants.sol"; -/// @dev The owed type tracks borrowed assets in the assets units scaled up by shifting left INTERNAL_DEBT_PRECISION bits. Increased precision allows for accurate interest accounting. +/// @title OwedLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for `Owed` custom type +/// @dev The owed type tracks borrowed funds in asset units scaled up by shifting left INTERNAL_DEBT_PRECISION bits. +/// @dev Increased precision allows for accurate interest accounting. library OwedLib { function toUint(Owed self) internal pure returns (uint256) { return Owed.unwrap(self); diff --git a/src/EVault/shared/types/Shares.sol b/src/EVault/shared/types/Shares.sol index c9ea71b6..a7683e5c 100644 --- a/src/EVault/shared/types/Shares.sol +++ b/src/EVault/shared/types/Shares.sol @@ -6,6 +6,9 @@ import {Shares, Assets, TypesLib} from "./Types.sol"; import {VaultCache} from "./VaultCache.sol"; import {ConversionHelpers} from "../lib/ConversionHelpers.sol"; +/// @title SharesLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for `Shares` custom type, which is used to store vault's shares balances library SharesLib { function toUint(Shares self) internal pure returns (uint256) { return Shares.unwrap(self); diff --git a/src/EVault/shared/types/Snapshot.sol b/src/EVault/shared/types/Snapshot.sol index 07b07b62..81107ba4 100644 --- a/src/EVault/shared/types/Snapshot.sol +++ b/src/EVault/shared/types/Snapshot.sol @@ -4,20 +4,28 @@ pragma solidity ^0.8.0; import {Assets} from "./Types.sol"; +/// @title Snapshot +/// @notice This struct is used to store a snapshot of the vault's cash and total borrows at the beginning of an operation (or a batch thereof) struct Snapshot { // Packed slot: 14 + 14 + 4 = 32 + // vault's cash holdings Assets cash; + // vault's total borrows in assets, in regular precision Assets borrows; - uint32 _stamp; + // stamp occupies the rest of the storage slot and makes sure the slot is non-zero for gas savings + uint32 stamp; } +/// @title SnapshotLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for working with the `Snapshot` struct library SnapshotLib { uint32 constant STAMP = 1 << 31; // non zero initial value of the snapshot slot to save gas on SSTORE function set(Snapshot storage self, Assets cash, Assets borrows) internal { self.cash = cash; self.borrows = borrows; - self._stamp = STAMP; + self.stamp = STAMP; } function reset(Snapshot storage self) internal { diff --git a/src/EVault/shared/types/Types.sol b/src/EVault/shared/types/Types.sol index b50e39f5..9e966cb0 100644 --- a/src/EVault/shared/types/Types.sol +++ b/src/EVault/shared/types/Types.sol @@ -15,7 +15,8 @@ import "./Owed.sol"; import "./ConfigAmount.sol"; import "./Flags.sol"; import "./AmountCap.sol"; -import "./LTVType.sol"; + +/// @notice In this file, custom types are defined and linked globally with their libraries and operators type Shares is uint112; @@ -54,6 +55,8 @@ using {gtConfigAmount as >, ltConfigAmount as <} for ConfigAmount global; using AmountCapLib for AmountCap global; using FlagsLib for Flags global; +/// @title TypesLib +/// @notice Library for casting basic types' amounts into custom types library TypesLib { function toShares(uint256 amount) internal pure returns (Shares) { if (amount > MAX_SANE_AMOUNT) revert Errors.E_AmountTooLargeToEncode(); diff --git a/src/EVault/shared/types/UserStorage.sol b/src/EVault/shared/types/UserStorage.sol index 455975c3..4c42e88d 100644 --- a/src/EVault/shared/types/UserStorage.sol +++ b/src/EVault/shared/types/UserStorage.sol @@ -4,21 +4,30 @@ pragma solidity ^0.8.0; import {Shares, Owed} from "./Types.sol"; +/// @dev Custom type for holding shares and debt balances of an account, packed with balance forwarder opt-in flag type PackedUserSlot is uint256; +/// @title UserStorage +/// @notice This struct is used to store user account data struct UserStorage { - PackedUserSlot data; // Shares and debt balance, balance forwarder opt-in flag + // Shares and debt balances, balance forwarder opt-in + PackedUserSlot data; + // Snapshot of the interest accumulator from the last change to account's liability uint256 interestAccumulator; + // A mapping with allowances for the vault shares token (eToken) mapping(address spender => uint256 allowance) eTokenAllowance; } +/// @title UserStorageLib +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for working with the UserStorage struct library UserStorageLib { uint256 constant BALANCE_FORWARDER_MASK = 0x8000000000000000000000000000000000000000000000000000000000000000; uint256 constant OWED_MASK = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000; uint256 constant SHARES_MASK = 0x000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFF; uint256 constant OWED_OFFSET = 112; - function getBalanceForwarderEnabled(UserStorage storage self) internal view returns (bool) { + function isBalanceForwarderEnabled(UserStorage storage self) internal view returns (bool) { return unpackBalanceForwarder(self.data); } diff --git a/src/EVault/shared/types/VaultCache.sol b/src/EVault/shared/types/VaultCache.sol index 4053f272..8597d159 100644 --- a/src/EVault/shared/types/VaultCache.sol +++ b/src/EVault/shared/types/VaultCache.sol @@ -7,22 +7,46 @@ import {IPriceOracle} from "../../../interfaces/IPriceOracle.sol"; import {Assets, Owed, Shares, Flags} from "./Types.sol"; +/// @title VaultCache +/// @notice This struct is used to hold all the most often used vault data in memory struct VaultCache { + // Proxy immutables + + // Vault's asset IERC20 asset; + // Vault's pricing oracle IPriceOracle oracle; + // Unit of account is the asset in which collateral and liability values are expressed address unitOfAccount; + // Vault data + + // A timestamp of the last interest accumulator update uint48 lastInterestAccumulatorUpdate; + // The amount of assets held directly by the vault Assets cash; + // Sum of all user debts Owed totalBorrows; + // Sum of all user shares Shares totalShares; + // Interest fees accrued since the last fee conversion Shares accumulatedFees; + // Current interest accumulator uint256 interestAccumulator; + // Vault config + + // Current supply cap in asset units uint256 supplyCap; + // Current borrow cap in asset units uint256 borrowCap; + // A bitfield of operations which trigger a hook call Flags hookedOps; + // A bitfield of vault configuration options Flags configFlags; + // Runtime + + // A flag indicating if the vault snapshot has already been initialized for the currently executing batch bool snapshotInitialized; -} \ No newline at end of file +} diff --git a/src/EVault/shared/types/VaultStorage.sol b/src/EVault/shared/types/VaultStorage.sol index 64a5812e..becb9577 100644 --- a/src/EVault/shared/types/VaultStorage.sol +++ b/src/EVault/shared/types/VaultStorage.sol @@ -6,42 +6,69 @@ import {Assets, Shares, Owed, AmountCap, ConfigAmount, Flags} from "./Types.sol" import {LTVConfig} from "./LTVConfig.sol"; import {UserStorage} from "./UserStorage.sol"; +/// @title VaultStorage +/// @notice This struct is used to hold all of the vault's permanent storage +/// @dev Note that snapshots are not a part of this struct, as they might be reimplemented as transient storage struct VaultStorage { // Packed slot 6 + 14 + 2 + 2 + 4 + 1 + 1 = 30 + // A timestamp of the last interest accumulator update uint48 lastInterestAccumulatorUpdate; + // The amount of assets held directly by the vault Assets cash; + // Current supply cap in asset units AmountCap supplyCap; + // Current borrow cap in asset units AmountCap borrowCap; + // A bitfield of operations which trigger a hook call Flags hookedOps; + // A vault global re-entrancy protection flag bool reentrancyLocked; + // A flag indicating if the vault snapshot has already been initialized for the currently executing batch bool snapshotInitialized; // Packed slot 14 + 18 = 32 + // Sum of all user shares Shares totalShares; + // Sum of all user debts Owed totalBorrows; // Packed slot 14 + 4 = 18 + // Interest fees accrued since the last fee conversion Shares accumulatedFees; + // A bitfield of vault configuration options Flags configFlags; + // Current interest accumulator uint256 interestAccumulator; // Packed slot 20 + 2 + 9 = 31 - address interestRateModel; // 0% interest, if zero address + // Address of the interest rate model contract. If not set, 0% interest is applied + address interestRateModel; + // Percentage of accrued interest that is directed to fees ConfigAmount interestFee; + // Current interest rate applied to outstanding borrows uint72 interestRate; + // Name of the shares token (eToken) string name; + // Symbol of the shares token (eToken) string symbol; + // Address of the vault's creator address creator; + // Address of the vault's governor address governorAdmin; + // Address which receives governor fees address feeReceiver; + // Address which will be called for enabled hooks address hookTarget; + // User accounts mapping(address account => UserStorage) users; + // LTV configuration for collaterals mapping(address collateral => LTVConfig) ltvLookup; + // List of addresses which were at any point configured as collateral address[] ltvList; -} \ No newline at end of file +} diff --git a/test/unit/evault/modules/Governance/pause.t.sol b/test/unit/evault/modules/Governance/pause.t.sol index 7bf0f061..13500344 100644 --- a/test/unit/evault/modules/Governance/pause.t.sol +++ b/test/unit/evault/modules/Governance/pause.t.sol @@ -24,6 +24,10 @@ contract Governance_PauseOps is EVaultTestBase { depositor = makeAddr("depositor"); liquidator1 = makeAddr("liquidator1"); liquidator2 = makeAddr("liquidator2"); + // ----------------- Setup vaults -------------------- + eTST.setLTV(address(eTST2), 0.9e4, 0); + oracle.setPrice(address(assetTST), unitOfAccount, 1e18); + oracle.setPrice(address(eTST2), unitOfAccount, 1e18); // ----------------- Setup depositor ----------------- vm.startPrank(depositor); assetTST.mint(depositor, type(uint256).max); @@ -151,18 +155,19 @@ contract Governance_PauseOps is EVaultTestBase { eTST.skim(type(uint256).max, receiver); } - function testFuzz_borrowingDisabledOpsShouldFailAfterDisabled(uint256 amount, address receiver) public { + function testFuzz_borrowingDisabledOpsShouldFailAfterDisabled(uint256 amount) public { setDisabledOps(eTST, OP_BORROW); evc.enableController(address(this), address(eTST)); vm.expectRevert(Errors.E_OperationDisabled.selector); - eTST.borrow(amount, receiver); + eTST.borrow(amount, address(this)); // re-enable setDisabledOps(eTST, 0); - vm.startPrank(depositor); - evc.enableController(depositor, address(eTST)); - vm.assume(receiver != address(0)); - eTST.borrow(type(uint256).max, receiver); + vm.startPrank(borrower); + evc.enableController(borrower, address(eTST)); + evc.enableCollateral(borrower, address(eTST2)); + amount = bound(amount, 1, MINT_AMOUNT / 2); + eTST.borrow(amount, borrower); vm.stopPrank(); } @@ -185,10 +190,12 @@ contract Governance_PauseOps is EVaultTestBase { // re-enable setDisabledOps(eTST, 0); - vm.startPrank(depositor); - evc.enableController(depositor, address(eTST)); + vm.startPrank(borrower); + evc.enableController(borrower, address(eTST)); + evc.enableCollateral(borrower, address(eTST2)); + amount = bound(amount, 1, MINT_AMOUNT / 2); vm.assume(sharesReceiver != address(0)); - eTST.loop(MINT_AMOUNT, sharesReceiver); + eTST.loop(amount, sharesReceiver); vm.stopPrank(); } @@ -332,14 +339,10 @@ contract Governance_PauseOps is EVaultTestBase { } function liquidateSetup(address liquidator) internal { - eTST.setLTV(address(eTST2), 1e4, 0); - oracle.setPrice(address(assetTST), unitOfAccount, 1e18); - oracle.setPrice(address(eTST2), unitOfAccount, 1e18); - vm.startPrank(borrower); evc.enableController(borrower, address(eTST)); evc.enableCollateral(borrower, address(eTST2)); - eTST.borrow(type(uint256).max, borrower); + eTST.borrow(8 * MINT_AMOUNT / 10, borrower); vm.stopPrank(); oracle.setPrice(address(eTST2), unitOfAccount, 0.5e17);