// SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; /**$$$$$$$$$$$$$$$$$$$$$$$$$$$&Mr/|1+~>>iiiiiiiiiii>~+{|tuMW$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$B#j]->iiiiiiiiiiiiiiiiiiiiiiiiiiii>-?f*B$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$@zj}~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~}fv@$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$@z(+iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii+)zB$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$Mf~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~t#@$$$$$$$$$$$$$$$ $$$$$$$$$$$$$@u[iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii?n@$$$$$$$$$$$$$ $$$$$$$$$$$@z]iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii?u@$$$$$$$$$$$ $$$$$$$$$$v]iiiiiiiiiiiiiiii,.';iiiiiiiiiiiiiiiiiiiiiiiiii;'."iiiiiiiiiiiiiiii?u$$$$$$$$$$ $$$$$$$$%)>iiiiiiiiiiiiiii,. ';iiiiiiiiiiiiiiiiiiiiii;' ."iiiiiiiiiiiiiiii1%$$$$$$$$ $$$$$$$c~iiiiiiiiiiiiiii,. ';iiiiiiiiiiiiiiiiii;' ."iiiiiiiiiiiiiii~u$$$$$$$ $$$$$B/>iiiiiiiiiiiiii!' `IiiiiiiiiiiiiiiI` .Iiiiiiiiiiiiiii>|%$$$$$ $$$$@)iiiiiiiiiiiiiiiii;' `Iiiiiiiiiiil` ';iiiiiiiiiiiiiiiii}@$$$$ $$$B|iiiiiiiiiiiiiiiiiiii;' `Iiiiiiil` ';iiiiiiiiiiiiiiiiiiii1B$$$ $$@)iiiiiiiiiiiiiiiiiiiiiii:' `;iiI` ':iiiiiiiiiiiiiiiiiiiiiii{B$$ $$|iiiiiiiiiiiiiiiiiiiiiiiiii;' `` ':iiiiiiiiiiiiiiiiiiiiiiiiii1$$ $v>iiiiiiiiiiiiiiiiiiiiiiiiiiii:' ':iiiiiiiiiiiiiiiiiiiiiiiiiiii>x$ &?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-W ziiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiv -iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii- iiiiiiiiiiiiiiiiii. `!iiiiiiiiiiiiiiiiiiiiiii^ .liiiiiiiiiiiiiiiiiir$ $$(iiiiiiiiiiiiiiiiii;. ."iiiiiiiiiiiiiiiiiiii,. :iiiiiiiiiiiiiiiiii}$$ $$@{iiiiiiiiiiiiiiiiii;. .`:iiiiiiiiiiiiii;^. :iiiiiiiiiiiiiiiiii}B$$ $$$B)iiiiiiiiiiiiiiiiii!' '`",::::,"`'. .Iiiiiiiiiiiiiiiiiii{%$$$ $$$$@1iiiiiiiiiiiiiiiiiii,. ^iiiiiiiiiiiiiiiiiii[@$$$$ $$$$$B|>iiiiiiiiiiiiiiiiii!^. `liiiiiiiiiiiiiiiiii>)%$$$$$ $$$$$$$c~iiiiiiiiiiiiiiiiiiii"' ."!iiiiiiiiiiiiiiiiiii~n$$$$$$$ $$$$$$$$B)iiiiiiiiiiiiiiiiiiiii!,`. .'"liiiiiiiiiiiiiiiiiiiii1%$$$$$$$$ $$$$$$$$$@u]iiiiiiiiiiiiiiiiiiiiiiil,^`'.. ..''^,liiiiiiiiiiiiiiiiiiiiiii-x@$$$$$$$$$ $$$$$$$$$$$@v?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-x$$$$$$$$$$$$ $$$$$$$$$$$$$@n?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-rB$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$/~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii<\*@$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$Bc1~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~{v%$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$Bvf]iiiiiiiiiiiiiiiiiiiiiiiiiiiii+_tc%$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$W#u/|{+~>iiiiiiiiiiii><+{|/n#W$$$$$$$$$$$$$$$$$$$$$$$$$$$$*/ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IFactory} from "./interfaces/IFactory.sol"; import {IBaseStrategy} from "./interfaces/IBaseStrategy.sol"; /** * @title Yearn Tokenized Strategy * @author yearn.finance * @notice * This TokenizedStrategy can be used by anyone wishing to easily build * and deploy their own custom ERC4626 compliant single strategy Vault. * * The TokenizedStrategy contract is meant to be used as the proxy * implementation contract that will handle all logic, storage and * management for a custom strategy that inherits the `BaseStrategy`. * Any function calls to the strategy that are not defined within that * strategy will be forwarded through a delegateCall to this contract. * * A strategist only needs to override a few simple functions that are * focused entirely on the strategy specific needs to easily and cheaply * deploy their own permissionless 4626 compliant vault. */ contract TokenizedStrategy { using Math for uint256; using SafeERC20 for ERC20; /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ /** * @notice Emitted when a strategy is shutdown. */ event StrategyShutdown(); /** * @notice Emitted when a strategies paused status is updated. */ event UpdatePaused(bool paused); /** * @notice Emitted on the initialization of any new `strategy` that uses `asset` * with this specific `apiVersion`. */ event NewTokenizedStrategy( address indexed strategy, address indexed asset, string apiVersion ); /** * @notice Emitted when the strategy reports `profit` or `loss` and * `performanceFees` and `protocolFees` are paid out. */ event Reported( uint256 profit, uint256 loss, uint256 protocolFees, uint256 performanceFees ); /** * @notice Emitted when the strategy accrues `profit` or `loss` outside of * an explicit report and `performanceFees` and `protocolFees` are paid out. */ event Accrued( uint256 profit, uint256 loss, uint256 protocolFees, uint256 performanceFees ); /** * @notice Emitted when the 'performanceFeeRecipient' address is * updated to 'newPerformanceFeeRecipient'. */ event UpdatePerformanceFeeRecipient( address indexed newPerformanceFeeRecipient ); /** * @notice Emitted when the 'keeper' address is updated to 'newKeeper'. */ event UpdateKeeper(address indexed newKeeper); /** * @notice Emitted when the 'performanceFee' is updated to 'newPerformanceFee'. */ event UpdatePerformanceFee(uint16 newPerformanceFee); /** * @notice Emitted when the 'management' address is updated to 'newManagement'. */ event UpdateManagement(address indexed newManagement); /** * @notice Emitted when the 'emergencyAdmin' address is updated to 'newEmergencyAdmin'. */ event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); /** * @notice Emitted when the 'profitMaxUnlockTime' is updated to 'newProfitMaxUnlockTime'. */ event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); /** * @notice Emitted when the 'pendingManagement' address is updated to 'newPendingManagement'. */ event UpdatePendingManagement(address indexed newPendingManagement); /** * @notice Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval( address indexed owner, address indexed spender, uint256 value ); /** * @notice Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @notice Emitted when the `caller` has exchanged `assets` for `shares`, * and transferred those `shares` to `owner`. */ event Deposit( address indexed caller, address indexed owner, uint256 assets, uint256 shares ); /** * @notice Emitted when the `caller` has exchanged `owner`s `shares` for `assets`, * and transferred those `assets` to `receiver`. */ event Withdraw( address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); /*////////////////////////////////////////////////////////////// STORAGE STRUCT //////////////////////////////////////////////////////////////*/ /** * @dev The struct that will hold all the storage data for each strategy * that uses this implementation. * * This replaces all state variables for a traditional contract. This * full struct will be initialized on the creation of the strategy * and continually updated and read from for the life of the contract. * * We combine all the variables into one struct to limit the amount of * times the custom storage slots need to be loaded during complex functions. * * Loading the corresponding storage slot for the struct does not * load any of the contents of the struct into memory. So the size * will not increase memory related gas usage. */ // prettier-ignore struct StrategyData { // The ERC20 compliant underlying asset that will be // used by the Strategy ERC20 asset; // These are the corresponding ERC20 variables needed for the // strategies token that is issued and burned on each deposit or withdraw. uint8 decimals; // The amount of decimals that `asset` and strategy use. string name; // The name of the token for the strategy. uint256 totalSupply; // The total amount of shares currently issued. mapping(address => uint256) nonces; // Mapping of nonces used for permit functions. mapping(address => uint256) balances; // Mapping to track current balances for each account that holds shares. mapping(address => mapping(address => uint256)) allowances; // Mapping to track the allowances for the strategies shares. // Last realized total assets. This is used as the accrual baseline during // write flows and to freeze view math during in-flight external callbacks. uint256 lastTotalAssets; // Variables for profit reporting and locking. // We use uint96 for timestamps to fit in the same slot as an address. That overflows in 2.5e+21 years. // I know Yearn moves slowly but surely V4 will be out by then. // If the timestamps ever overflow tell the cyborgs still using this code I'm sorry for being cheap. uint256 profitUnlockingRate; // The rate at which locked profit is unlocking. uint96 fullProfitUnlockDate; // The timestamp at which all locked shares will unlock. address keeper; // Address given permission to call {report} and {tend}. uint32 profitMaxUnlockTime; uint16 performanceFee; // The percent in basis points of profit that is charged as a fee. address performanceFeeRecipient; // The address to pay the `performanceFee` to. uint96 lastReport; // The last time a report updated the lock schedule. // Access management variables. address management; // Main address that can set all configurable variables. address pendingManagement; // Address that is pending to take over `management`. address emergencyAdmin; // Address to act in emergencies as well as `management`. // Strategy Status uint8 entered; // To prevent reentrancy. Use uint8 for gas savings. bool shutdown; // Bool that can be used to stop deposits into the strategy. bool paused; // Bool that can be used to stop user facing 4626 functions. uint72 lastAccrual; // The last time accounting synced. } /*////////////////////////////////////////////////////////////// MODIFIERS //////////////////////////////////////////////////////////////*/ /** * @dev Require that the call is coming from the strategies management. */ modifier onlyManagement() { requireManagement(msg.sender); _; } /** * @dev Require that the call is coming from either the strategies * management or the keeper. */ modifier onlyKeepers() { requireKeeperOrManagement(msg.sender); _; } /** * @dev Require that the call is coming from either the strategies * management or the emergencyAdmin. */ modifier onlyEmergencyAuthorized() { requireEmergencyAuthorized(msg.sender); _; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Placed over all state changing functions for increased safety. */ modifier nonReentrant() { StrategyData storage S = _strategyStorage(); // On the first call to nonReentrant, `entered` will be false (2) require(S.entered != ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail S.entered = ENTERED; _; // Reset to false (1) once call has finished. S.entered = NOT_ENTERED; } /** * @dev Require that the strategy is not paused. */ modifier whenNotPaused() { require(!_strategyStorage().paused, "paused"); _; } /** * @notice Require a caller is `management`. * @dev Is left public so that it can be used by the Strategy. * * When the Strategy calls this the msg.sender would be the * address of the strategy so we need to specify the sender. * * @param _sender The original msg.sender. */ function requireManagement(address _sender) public view { require(_sender == _strategyStorage().management, "!management"); } /** * @notice Require a caller is the `keeper` or `management`. * @dev Is left public so that it can be used by the Strategy. * * When the Strategy calls this the msg.sender would be the * address of the strategy so we need to specify the sender. * * @param _sender The original msg.sender. */ function requireKeeperOrManagement(address _sender) public view { StrategyData storage S = _strategyStorage(); require(_sender == S.keeper || _sender == S.management, "!keeper"); } /** * @notice Require a caller is the `management` or `emergencyAdmin`. * @dev Is left public so that it can be used by the Strategy. * * When the Strategy calls this the msg.sender would be the * address of the strategy so we need to specify the sender. * * @param _sender The original msg.sender. */ function requireEmergencyAuthorized(address _sender) public view { StrategyData storage S = _strategyStorage(); require( _sender == S.emergencyAdmin || _sender == S.management, "!emergency authorized" ); } /*////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ /// @notice API version this TokenizedStrategy implements. string internal constant API_VERSION = "3.1.0"; /// @notice Value to set the `entered` flag to during a call. uint8 internal constant ENTERED = 2; /// @notice Value to set the `entered` flag to at the end of the call. uint8 internal constant NOT_ENTERED = 1; /// @notice Maximum in Basis Points the Performance Fee can be set to. uint16 public constant MAX_FEE = 5_000; // 50% /// @notice Used for fee calculations. uint256 internal constant MAX_BPS = 10_000; /// @notice Used for profit unlocking rate calculations. uint256 internal constant MAX_BPS_EXTENDED = 1_000_000_000_000; /// @notice Holder for dead shares minted against unsolicited initial assets. address internal constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD; /// @notice Supply floor below which accrued profit is minted to /// {DEAD_ADDRESS} to block first-depositor inflation. uint256 internal constant MINIMUM_SUPPLY = 1e3; /** * @dev Custom storage slot that will be used to store the * `StrategyData` struct that holds each strategies * specific storage variables. * * Any storage updates done by the TokenizedStrategy actually update * the storage of the calling contract. This variable points * to the specific location that will be used to store the * struct that holds all that data. * * We use a custom string in order to get a random * storage slot that will allow for strategists to use any * amount of storage in their strategy without worrying * about collisions. */ bytes32 internal constant BASE_STRATEGY_STORAGE = bytes32(uint256(keccak256("yearn.base.strategy.storage")) - 1); /*////////////////////////////////////////////////////////////// IMMUTABLE //////////////////////////////////////////////////////////////*/ /// @notice Address of the previously deployed Vault factory that the // protocol fee config is retrieved from. address public immutable FACTORY; /*////////////////////////////////////////////////////////////// STORAGE GETTER //////////////////////////////////////////////////////////////*/ /** * @dev will return the actual storage slot where the strategy * specific `StrategyData` struct is stored for both read * and write operations. * * This loads just the slot location, not the full struct * so it can be used in a gas efficient manner. */ function _strategyStorage() internal pure returns (StrategyData storage S) { // Since STORAGE_SLOT is a constant, we have to put a variable // on the stack to access it from an inline assembly block. bytes32 slot = BASE_STRATEGY_STORAGE; assembly { S.slot := slot } } /*////////////////////////////////////////////////////////////// INITIALIZATION //////////////////////////////////////////////////////////////*/ /** * @notice Used to initialize storage for a newly deployed strategy. * @dev This should be called atomically whenever a new strategy is * deployed and can only be called once for each strategy. * * This will set all the default storage that must be set for a * strategy to function. Any changes can be made post deployment * through external calls from `management`. * * The function will also emit an event that off chain indexers can * look for to track any new deployments using this TokenizedStrategy. * * @param _asset Address of the underlying asset. * @param _name Name the strategy will use. * @param _management Address to set as the strategies `management`. * @param _performanceFeeRecipient Address to receive performance fees. * @param _keeper Address to set as strategies `keeper`. */ function initialize( address _asset, string memory _name, address _management, address _performanceFeeRecipient, address _keeper ) external { // Cache storage pointer. StrategyData storage S = _strategyStorage(); // Make sure we aren't initialized. require(address(S.asset) == address(0), "initialized"); // Set the strategy's underlying asset. S.asset = ERC20(_asset); // Set the Strategy Tokens name. S.name = _name; // Set decimals based off the `asset`. S.decimals = ERC20(_asset).decimals(); // Default to a 10 day profit unlock period. S.profitMaxUnlockTime = 10 days; // Set address to receive performance fees. // Can't be address(0) or we will be burning fees. require(_performanceFeeRecipient != address(0), "ZERO ADDRESS"); // Can't mint shares to its self because of profit locking. require(_performanceFeeRecipient != address(this), "self"); S.performanceFeeRecipient = _performanceFeeRecipient; // Default to a 10% performance fee. S.performanceFee = 1_000; // Initialize both timestamps to the deployment block. S.lastReport = uint96(block.timestamp); // -1 to not allow first deposit inflation S.lastAccrual = uint72(block.timestamp - 1); // Set the default management address. Can't be 0. require(_management != address(0), "ZERO ADDRESS"); S.management = _management; // Set the keeper address S.keeper = _keeper; // Emit event to signal a new strategy has been initialized. emit NewTokenizedStrategy(address(this), _asset, API_VERSION); } /*////////////////////////////////////////////////////////////// ERC4626 WRITE METHODS //////////////////////////////////////////////////////////////*/ /** * @notice Mints `shares` of strategy shares to `receiver` by * depositing exactly `assets` of underlying tokens. * @param assets The amount of underlying to deposit in. * @param receiver The address to receive the `shares`. * @return shares The actual amount of shares issued. */ function deposit( uint256 assets, address receiver ) external whenNotPaused nonReentrant returns (uint256 shares) { // Get the storage slot for all following calls. StrategyData storage S = _strategyStorage(); _accrue(S); // Deposit full balance if using max uint. if (assets == type(uint256).max) { assets = S.asset.balanceOf(msg.sender); } // Checking max deposit will also check if shutdown. require( assets <= _maxDeposit(S, receiver), "ERC4626: deposit more than max" ); // Check for rounding error. require( (shares = _convertToShares(S, assets, Math.Rounding.Down)) != 0, "ZERO_SHARES" ); _deposit(S, receiver, assets, shares); } /** * @notice Mints exactly `shares` of strategy shares to * `receiver` by depositing `assets` of underlying tokens. * @param shares The amount of strategy shares mint. * @param receiver The address to receive the `shares`. * @return assets The actual amount of asset deposited. */ function mint( uint256 shares, address receiver ) external whenNotPaused nonReentrant returns (uint256 assets) { // Get the storage slot for all following calls. StrategyData storage S = _strategyStorage(); _accrue(S); // Checking max mint will also check if shutdown. require(shares <= _maxMint(S, receiver), "ERC4626: mint more than max"); // Check for rounding error. require( (assets = _convertToAssets(S, shares, Math.Rounding.Up)) != 0, "ZERO_ASSETS" ); _deposit(S, receiver, assets, shares); } /** * @notice Withdraws exactly `assets` from `owners` shares and sends * the underlying tokens to `receiver`. * @dev This will default to not allowing any loss to be taken. * @param assets The amount of underlying to withdraw. * @param receiver The address to receive `assets`. * @param owner The address whose shares are burnt. * @return shares The actual amount of shares burnt. */ function withdraw( uint256 assets, address receiver, address owner ) external returns (uint256 shares) { return withdraw(assets, receiver, owner, 0); } /** * @notice Withdraws `assets` from `owners` shares and sends * the underlying tokens to `receiver`. * @dev This includes an added parameter to allow for losses. * @param assets The amount of underlying to withdraw. * @param receiver The address to receive `assets`. * @param owner The address whose shares are burnt. * @param maxLoss The amount of acceptable loss in Basis points. * @return shares The actual amount of shares burnt. */ function withdraw( uint256 assets, address receiver, address owner, uint256 maxLoss ) public whenNotPaused nonReentrant returns (uint256 shares) { // Get the storage slot for all following calls. StrategyData storage S = _strategyStorage(); _accrue(S); require( assets <= _maxWithdraw(S, owner), "ERC4626: withdraw more than max" ); // Check for rounding error or 0 value. require( (shares = _convertToShares(S, assets, Math.Rounding.Up)) != 0, "ZERO_SHARES" ); // Withdraw and track the actual amount withdrawn for loss check. _withdraw(S, receiver, owner, assets, shares, maxLoss); } /** * @notice Redeems exactly `shares` from `owner` and * sends `assets` of underlying tokens to `receiver`. * @dev This will default to allowing any loss passed to be realized. * @param shares The amount of shares burnt. * @param receiver The address to receive `assets`. * @param owner The address whose shares are burnt. * @return assets The actual amount of underlying withdrawn. */ function redeem( uint256 shares, address receiver, address owner ) external returns (uint256) { // We default to not limiting a potential loss. return redeem(shares, receiver, owner, MAX_BPS); } /** * @notice Redeems exactly `shares` from `owner` and * sends `assets` of underlying tokens to `receiver`. * @dev This includes an added parameter to allow for losses. * @param shares The amount of shares burnt. * @param receiver The address to receive `assets`. * @param owner The address whose shares are burnt. * @param maxLoss The amount of acceptable loss in Basis points. * @return . The actual amount of underlying withdrawn. */ function redeem( uint256 shares, address receiver, address owner, uint256 maxLoss ) public whenNotPaused nonReentrant returns (uint256) { // Get the storage slot for all following calls. StrategyData storage S = _strategyStorage(); _accrue(S); require( shares <= _maxRedeem(S, owner), "ERC4626: redeem more than max" ); uint256 assets; // Check for rounding error or 0 value. require( (assets = _convertToAssets(S, shares, Math.Rounding.Down)) != 0, "ZERO_ASSETS" ); // We need to return the actual amount withdrawn in case of a loss. return _withdraw(S, receiver, owner, assets, shares, maxLoss); } /*////////////////////////////////////////////////////////////// EXTERNAL 4626 VIEW METHODS //////////////////////////////////////////////////////////////*/ /** * @notice Get the total amount of assets this strategy holds * under the current block-latched accounting estimate. * * Normal write flows freeze this value after the first sync in a block. * A manual {report} can refresh it again within that same block. * * @dev This reflects the *simulated* constant-accrual state, including * any pending profit or loss that has not yet been realized by a * state-changing accrual. {convertToShares}, {convertToAssets} and all * preview functions use the same simulated state, while {totalSupply} * only reflects realized ERC20 supply. The two views can therefore * appear inconsistent until the pending accrual is realized. * * @return . Total assets the strategy holds. */ function totalAssets() external view returns (uint256) { return _totalAssets(_strategyStorage()); } /** * @notice Get the current supply of the strategies shares. * * @dev This is the *realized* ERC20 supply (stored supply minus * unlocked strategy-held shares). It does not include fee shares * that would be minted for pending constant-accrual profit, nor the * locked-share burn simulated for pending losses. {totalAssets}, * conversions and previews do reflect that simulated state, so * comparing this value against those views may show different * accounting assumptions until the pending accrual is realized. * * @return . Total amount of shares outstanding. */ function totalSupply() external view returns (uint256) { return _totalSupply(_strategyStorage()); } /** * @notice The amount of shares that the strategy would * exchange for the amount of assets provided, in an * ideal scenario where all the conditions are met. * * @param assets The amount of underlying. * @return . Expected shares that `assets` represents. */ function convertToShares(uint256 assets) external view returns (uint256) { return _convertToShares(_strategyStorage(), assets, Math.Rounding.Down); } /** * @notice The amount of assets that the strategy would * exchange for the amount of shares provided, in an * ideal scenario where all the conditions are met. * * @param shares The amount of the strategies shares. * @return . Expected amount of `asset` the shares represents. */ function convertToAssets(uint256 shares) external view returns (uint256) { return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Down); } /** * @notice Allows an on-chain or off-chain user to simulate * the effects of their deposit at the current block, given * current on-chain conditions. * @dev This will round down. * * @param assets The amount of `asset` to deposits. * @return . Expected shares that would be issued. */ function previewDeposit(uint256 assets) external view returns (uint256) { return _convertToShares(_strategyStorage(), assets, Math.Rounding.Down); } /** * @notice Allows an on-chain or off-chain user to simulate * the effects of their mint at the current block, given * current on-chain conditions. * @dev This is used instead of convertToAssets so that it can * round up for safer mints. * * @param shares The amount of shares to mint. * @return . The needed amount of `asset` for the mint. */ function previewMint(uint256 shares) external view returns (uint256) { return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Up); } /** * @notice Allows an on-chain or off-chain user to simulate * the effects of their withdrawal at the current block, * given current on-chain conditions. * @dev This is used instead of convertToShares so that it can * round up for safer withdraws. * * @param assets The amount of `asset` that would be withdrawn. * @return . The amount of shares that would be burnt. */ function previewWithdraw(uint256 assets) external view returns (uint256) { return _convertToShares(_strategyStorage(), assets, Math.Rounding.Up); } /** * @notice Allows an on-chain or off-chain user to simulate * the effects of their redemption at the current block, * given current on-chain conditions. * @dev This will round down. * * @param shares The amount of shares that would be redeemed. * @return . The amount of `asset` that would be returned. */ function previewRedeem(uint256 shares) external view returns (uint256) { return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Down); } /** * @notice Total number of underlying assets that can * be deposited into the strategy, where `receiver` * corresponds to the receiver of the shares of a {deposit} call. * * @param receiver The address receiving the shares. * @return . The max that `receiver` can deposit in `asset`. */ function maxDeposit(address receiver) external view returns (uint256) { return _maxDeposit(_strategyStorage(), receiver); } /** * @notice Total number of shares that can be minted to `receiver` * of a {mint} call. * * @param receiver The address receiving the shares. * @return _maxMint The max that `receiver` can mint in shares. */ function maxMint(address receiver) external view returns (uint256) { return _maxMint(_strategyStorage(), receiver); } /** * @notice Total number of underlying assets that can be * withdrawn from the strategy by `owner`, where `owner` * corresponds to the msg.sender of a {redeem} call. * * @param owner The owner of the shares. * @return _maxWithdraw Max amount of `asset` that can be withdrawn. */ function maxWithdraw(address owner) external view returns (uint256) { return _maxWithdraw(_strategyStorage(), owner); } /** * @notice Variable `maxLoss` is ignored. * @dev Accepts a `maxLoss` variable in order to match the multi * strategy vaults ABI. */ function maxWithdraw( address owner, uint256 /*maxLoss*/ ) external view returns (uint256) { return _maxWithdraw(_strategyStorage(), owner); } /** * @notice Total number of strategy shares that can be * redeemed from the strategy by `owner`, where `owner` * corresponds to the msg.sender of a {redeem} call. * * @param owner The owner of the shares. * @return _maxRedeem Max amount of shares that can be redeemed. */ function maxRedeem(address owner) external view returns (uint256) { return _maxRedeem(_strategyStorage(), owner); } /** * @notice Variable `maxLoss` is ignored. * @dev Accepts a `maxLoss` variable in order to match the multi * strategy vaults ABI. */ function maxRedeem( address owner, uint256 /*maxLoss*/ ) external view returns (uint256) { return _maxRedeem(_strategyStorage(), owner); } /*////////////////////////////////////////////////////////////// INTERNAL 4626 VIEW METHODS //////////////////////////////////////////////////////////////*/ /// @dev Internal implementation of {totalAssets}. function _totalAssets( StrategyData storage S ) internal view returns (uint256) { (, uint256 assets) = _simulatedTotals(S); return assets; } /// @dev Internal implementation of {totalSupply}. /// @notice We don't increase totalSupply until actual shares are minted. /// This can cause disconnection between conversions done manually. function _totalSupply( StrategyData storage S ) internal view returns (uint256) { return S.totalSupply - _unlockedShares(S); } /// @dev Internal helper to simulate the supply/assets state under the current block latch. function _simulatedTotals( StrategyData storage S ) internal view returns (uint256 supply, uint256 assets) { supply = _totalSupply(S); if (S.entered == ENTERED || block.timestamp == S.lastAccrual) { return (supply, S.lastTotalAssets); } assets = _strategyTotalAssets(); if ( S.lastTotalAssets == 0 // Mirrors {_accrue}: supply floor dead mint, fee-free recovery. ) { return (supply < MINIMUM_SUPPLY ? supply + assets : supply, assets); } if (assets > S.lastTotalAssets) { uint256 profit; unchecked { profit = assets - S.lastTotalAssets; } if (supply < MINIMUM_SUPPLY) { // Mirrors the {_accrue} supply floor confiscation. supply += _convertToSharesFromTotals( profit, supply, S.lastTotalAssets, Math.Rounding.Up ); } else { uint16 fee = S.performanceFee; if (fee != 0) { uint256 totalFees = (profit * fee) / MAX_BPS; supply += _convertToSharesFromTotals( totalFees, supply, assets - totalFees, Math.Rounding.Down ); } } } else if (assets < S.lastTotalAssets) { uint256 loss; unchecked { loss = S.lastTotalAssets - assets; } (uint256 lockedBurn, ) = _lossBurnState( S, loss, supply, S.lastTotalAssets ); supply -= lockedBurn; } } /// @dev Internal helper to ask the strategy for its current total assets. function _strategyTotalAssets() internal view returns (uint256) { return IBaseStrategy(address(this)).strategyTotalAssets(); } /// @dev Converts using an explicit supply/assets snapshot. function _convertToSharesFromTotals( uint256 assets, uint256 supply, uint256 totalAssets_, Math.Rounding _rounding ) internal pure returns (uint256) { if (supply == 0) return assets; if (totalAssets_ == 0) return 0; return assets.mulDiv(supply, totalAssets_, _rounding); } /// @dev Converts using an explicit supply/assets snapshot. function _convertToAssetsFromTotals( uint256 shares, uint256 supply, uint256 totalAssets_, Math.Rounding _rounding ) internal pure returns (uint256) { return supply == 0 ? shares : shares.mulDiv(totalAssets_, supply, _rounding); } /// @dev Internal implementation of {convertToShares}. function _convertToShares( StrategyData storage S, uint256 assets, Math.Rounding _rounding ) internal view returns (uint256) { (uint256 totalSupply_, uint256 totalAssets_) = _simulatedTotals(S); // If supply is 0, PPS = 1. return _convertToSharesFromTotals( assets, totalSupply_, totalAssets_, _rounding ); } /// @dev Internal implementation of {convertToAssets}. function _convertToAssets( StrategyData storage S, uint256 shares, Math.Rounding _rounding ) internal view returns (uint256) { (uint256 supply, uint256 totalAssets_) = _simulatedTotals(S); return _convertToAssetsFromTotals(shares, supply, totalAssets_, _rounding); } /// @dev Internal implementation of {maxDeposit}. function _maxDeposit( StrategyData storage S, address receiver ) internal view returns (uint256) { // Cannot deposit when shutdown, paused or to the strategy. if (S.shutdown || S.paused || receiver == address(this)) return 0; return IBaseStrategy(address(this)).availableDepositLimit(receiver); } /// @dev Internal implementation of {maxMint}. function _maxMint( StrategyData storage S, address receiver ) internal view returns (uint256 maxMint_) { // Cannot mint when shutdown, paused or to the strategy. if (S.shutdown || S.paused || receiver == address(this)) return 0; maxMint_ = IBaseStrategy(address(this)).availableDepositLimit(receiver); if (maxMint_ != type(uint256).max) { maxMint_ = _convertToShares(S, maxMint_, Math.Rounding.Down); } } /// @dev Internal implementation of {maxWithdraw}. function _maxWithdraw( StrategyData storage S, address owner ) internal view returns (uint256 maxWithdraw_) { // Cannot withdraw when paused. if (S.paused) return 0; // Get the max the owner could withdraw currently. maxWithdraw_ = IBaseStrategy(address(this)).availableWithdrawLimit( owner ); // If there is no limit enforced. if (maxWithdraw_ == type(uint256).max) { // Saves a min check if there is no withdrawal limit. maxWithdraw_ = _convertToAssets( S, _balanceOf(S, owner), Math.Rounding.Down ); } else { maxWithdraw_ = Math.min( _convertToAssets(S, _balanceOf(S, owner), Math.Rounding.Down), maxWithdraw_ ); } } /// @dev Internal implementation of {maxRedeem}. function _maxRedeem( StrategyData storage S, address owner ) internal view returns (uint256 maxRedeem_) { // Cannot redeem when paused. if (S.paused) return 0; // Get the max the owner could withdraw currently. maxRedeem_ = IBaseStrategy(address(this)).availableWithdrawLimit(owner); // Conversion would overflow and saves a min check if there is no withdrawal limit. if (maxRedeem_ == type(uint256).max) { maxRedeem_ = _balanceOf(S, owner); } else { maxRedeem_ = Math.min( // Can't redeem more than the balance. _convertToShares(S, maxRedeem_, Math.Rounding.Down), _balanceOf(S, owner) ); } } /*////////////////////////////////////////////////////////////// INTERNAL 4626 WRITE METHODS //////////////////////////////////////////////////////////////*/ /** * @dev Function to be called during {deposit} and {mint}. * * This function handles all logic including transfers, * minting and accounting. * * We do all external calls before updating any internal * values to prevent view reentrancy issues from the token * transfers or the _deployFunds() calls. */ function _deposit( StrategyData storage S, address receiver, uint256 assets, uint256 shares ) internal { // Cache storage variables used more than once. ERC20 _asset = S.asset; // Need to transfer before minting or ERC777s could reenter. _asset.safeTransferFrom(msg.sender, address(this), assets); // We can deploy the full loose balance currently held. IBaseStrategy(address(this)).deployFunds( _asset.balanceOf(address(this)) ); // Adjust total Assets. S.lastTotalAssets += assets; // mint shares _mint(S, receiver, shares); emit Deposit(msg.sender, receiver, assets, shares); } /** * @dev To be called during {redeem} and {withdraw}. * * This will handle all logic, transfers and accounting * in order to service the withdraw request. * * If we are not able to withdraw the full amount needed, it will * be counted as a loss and passed on to the user. */ function _withdraw( StrategyData storage S, address receiver, address owner, uint256 assets, uint256 shares, uint256 maxLoss ) internal returns (uint256) { require(receiver != address(0), "ZERO ADDRESS"); require(maxLoss <= MAX_BPS, "exceeds MAX_BPS"); // Spend allowance if applicable. if (msg.sender != owner) { _spendAllowance(S, owner, msg.sender, shares); } // Cache `asset` since it is used multiple times.. ERC20 _asset = S.asset; uint256 idle = _asset.balanceOf(address(this)); uint256 loss; // Check if we need to withdraw funds. if (idle < assets) { // Tell Strategy to free what we need. unchecked { IBaseStrategy(address(this)).freeFunds(assets - idle); } // Return the actual amount withdrawn. Adjust for potential under withdraws. idle = _asset.balanceOf(address(this)); // If we didn't get enough out then we have a loss. if (idle < assets) { unchecked { loss = assets - idle; } // If a non-default max loss parameter was set. if (maxLoss < MAX_BPS) { // Make sure we are within the acceptable range. require( loss <= (assets * maxLoss) / MAX_BPS, "too much loss" ); } // Lower the amount to be withdrawn. assets = idle; } } // Update assets based on how much we took. S.lastTotalAssets -= (assets + loss); _burn(S, owner, shares); // Transfer the amount of underlying to the receiver. _asset.safeTransfer(receiver, assets); emit Withdraw(msg.sender, receiver, owner, assets, shares); // Return the actual amount of assets withdrawn. return assets; } /// @dev Synchronize accounting using the strategy's current view estimate unless this block is already latched. function _accrue( StrategyData storage S ) internal returns (uint256 profit, uint256 loss) { if (block.timestamp == S.lastAccrual) { return (0, 0); } uint256 newTotalAssets = _strategyTotalAssets(); uint256 oldTotalAssets = S.lastTotalAssets; uint256 totalFees; uint256 protocolFees; if (newTotalAssets > oldTotalAssets) { unchecked { profit = newTotalAssets - oldTotalAssets; } // Profit accrued below the supply floor is confiscated to dead // shares at a flat PPS, fee free, so a dust supply can never // capture a donation. uint256 supply = _totalSupply(S); if (supply < MINIMUM_SUPPLY) { _mint( S, DEAD_ADDRESS, // If there is no prior PPS to preserve, target 1:1. oldTotalAssets == 0 ? newTotalAssets : _convertToSharesFromTotals( profit, supply, oldTotalAssets, Math.Rounding.Up ) ); } else if (oldTotalAssets != 0) { (totalFees, protocolFees, ) = _chargeFees( S, profit, newTotalAssets, true ); } } else if (oldTotalAssets > newTotalAssets) { unchecked { loss = oldTotalAssets - newTotalAssets; } _realizeLoss(S, loss); _syncUnlockScheduleAfterLoss(S); } S.lastTotalAssets = newTotalAssets; S.lastAccrual = uint72(block.timestamp); emit Accrued(profit, loss, protocolFees, totalFees - protocolFees); } /// @dev Mint fee shares for asset-based live accrual. function _chargeFees( StrategyData storage S, uint256 profit, uint256 newTotalAssets, bool useNewPps ) internal returns ( uint256 totalFees, uint256 protocolFees, uint256 totalFeeShares ) { uint16 fee = S.performanceFee; if (fee == 0 || S.totalSupply == 0) return (0, 0, 0); // Asses performance fees. unchecked { // Get in `asset` for the event. totalFees = (profit * fee) / MAX_BPS; } // Get fee shares based on PPS that the txn will end on. // During live accrual or when profit unlock is 0, // we use the new diluted PPS. During locked-profit // reports we need to use the old PPS since it does not change. totalFeeShares = _convertToSharesFromTotals( totalFees, _totalSupply(S), useNewPps ? newTotalAssets - totalFees : S.lastTotalAssets, Math.Rounding.Down ); (uint16 protocolFeeBps, address protocolFeesRecipient) = IFactory( FACTORY ).protocol_fee_config(); uint256 protocolFeeShares; // Check if there is a protocol fee to charge. if (protocolFeeBps != 0) { unchecked { // Calculate protocol fees based on the performance Fees. protocolFeeShares = (totalFeeShares * protocolFeeBps) / MAX_BPS; // Need amount in underlying for event. protocolFees = (totalFees * protocolFeeBps) / MAX_BPS; } // Mint the protocol fees to the recipient. _mint(S, protocolFeesRecipient, protocolFeeShares); } // Mint the difference to the strategy fee recipient. unchecked { _mint( S, S.performanceFeeRecipient, totalFeeShares - protocolFeeShares ); } } function _realizeLoss(StrategyData storage S, uint256 loss) internal { (, uint256 sharesToBurn) = _lossBurnState( S, loss, _totalSupply(S), S.lastTotalAssets ); // Check if there is anything to burn. if (sharesToBurn != 0) { _burn(S, address(this), sharesToBurn); } } /// @dev Returns the loss burn split between already-unlocked and still-locked shares. function _lossBurnState( StrategyData storage S, uint256 loss, uint256 supply, uint256 totalAssets_ ) internal view returns (uint256 lockedBurn, uint256 totalBurn) { uint256 buffer = S.balances[address(this)]; if (buffer == 0) return (0, 0); uint256 unlocked = _unlockedShares(S); if (unlocked >= buffer) return (0, buffer); uint256 lossShares = _convertToSharesFromTotals( loss, supply, totalAssets_, Math.Rounding.Down ); unchecked { lockedBurn = Math.min(buffer - unlocked, lossShares); totalBurn = unlocked + lockedBurn; } } /// @dev Re-seeds unlock accounting after `_accrue` burns buffer shares for a loss. function _syncUnlockScheduleAfterLoss(StrategyData storage S) internal { uint256 totalLockedShares = S.balances[address(this)]; S.lastReport = uint96(block.timestamp); uint96 _fullProfitUnlockDate = S.fullProfitUnlockDate; if (_fullProfitUnlockDate > block.timestamp && totalLockedShares != 0) { unchecked { S.profitUnlockingRate = (totalLockedShares * MAX_BPS_EXTENDED) / (_fullProfitUnlockDate - block.timestamp); } } else { S.profitUnlockingRate = 0; } } /*////////////////////////////////////////////////////////////// PROFIT REPORTING //////////////////////////////////////////////////////////////*/ /** * @notice Function for keepers to call to harvest and record all * profits accrued. * * @dev This will account for any gains/losses since the last report * and charge fees accordingly. * * Any profit over the fees charged will be immediately locked * so there is no change in PricePerShare. Then slowly unlocked * over the `maxProfitUnlockTime` each second based on the * calculated `profitUnlockingRate`. * * In case of a loss it will first attempt to offset the loss * with any remaining locked shares from the last report in * order to reduce any negative impact to PPS. * * Will then recalculate the new time to unlock profits over and the * rate based on a weighted average of any remaining time from the * last report and the new amount of shares to be locked. * * @return profit The notional amount of gain if any since the last * report in terms of `asset`. * @return loss The notional amount of loss if any since the last * report in terms of `asset`. */ function report() external nonReentrant onlyKeepers returns (uint256 profit, uint256 loss) { // Cache storage pointer since its used repeatedly. StrategyData storage S = _strategyStorage(); // Accrue to update total assets for non harvestable yield _accrue(S); // Tell the strategy to report the real total assets it has. // It should do all reward selling and redepositing now and // account for deployed and loose `asset` so we can accurately // account for all funds including those potentially airdropped // and then have any profits immediately locked. uint256 newTotalAssets = IBaseStrategy(address(this)) .harvestAndReport(); uint256 oldTotalAssets = S.lastTotalAssets; // Initialize variables needed throughout. uint256 totalFees; uint256 protocolFees; uint256 sharesToLock; uint256 _profitMaxUnlockTime = S.profitMaxUnlockTime; // Calculate profit/loss. if (newTotalAssets > oldTotalAssets) { // We have a profit. unchecked { profit = newTotalAssets - oldTotalAssets; } // We need to get the equivalent amount of shares // at the current PPS before any minting or burning. sharesToLock = _convertToShares(S, profit, Math.Rounding.Down); uint256 totalFeeShares; (totalFees, protocolFees, totalFeeShares) = _chargeFees( S, profit, newTotalAssets, _profitMaxUnlockTime == 0 ); // Check if we are locking profit. if (_profitMaxUnlockTime != 0) { // lock (profit - fees) unchecked { sharesToLock -= totalFeeShares; } uint256 sharesToBurn = _unlockedShares(S); // If we are burning more than re-locking. if (sharesToBurn > sharesToLock) { // Burn the difference unchecked { _burn(S, address(this), sharesToBurn - sharesToLock); } } else if (sharesToLock > sharesToBurn) { // Mint the shares to lock the strategy. unchecked { _mint(S, address(this), sharesToLock - sharesToBurn); } } } } else { // Expect we have a loss. unchecked { loss = oldTotalAssets - newTotalAssets; } _realizeLoss(S, loss); } // Update unlocking rate and time to fully unlocked. uint256 totalLockedShares = S.balances[address(this)]; if (totalLockedShares != 0) { uint256 previouslyLockedTime; uint96 _fullProfitUnlockDate = S.fullProfitUnlockDate; // Check if we need to account for shares still unlocking. if (_fullProfitUnlockDate > block.timestamp) { unchecked { // There will only be previously locked shares if time remains. // We calculate this here since it should be rare. previouslyLockedTime = (_fullProfitUnlockDate - block.timestamp) * (totalLockedShares - sharesToLock); } } // newProfitLockingPeriod is a weighted average between the remaining // time of the previously locked shares and the profitMaxUnlockTime. uint256 newProfitLockingPeriod = (previouslyLockedTime + sharesToLock * _profitMaxUnlockTime) / totalLockedShares; // Calculate how many shares unlock per second. S.profitUnlockingRate = (totalLockedShares * MAX_BPS_EXTENDED) / newProfitLockingPeriod; // Calculate how long until the full amount of shares is unlocked. S.fullProfitUnlockDate = uint96( block.timestamp + newProfitLockingPeriod ); } else { // Only setting this to 0 will turn in the desired effect, // no need to update profitUnlockingRate. S.fullProfitUnlockDate = 0; } // Update the new total assets value. S.lastTotalAssets = newTotalAssets; S.lastReport = uint96(block.timestamp); // Emit event with info emit Reported( profit, loss, protocolFees, // Protocol fees totalFees - protocolFees // Performance Fees ); } /** * @notice Get how many report-locked shares have unlocked. * @return . The amount of shares that have unlocked. */ function unlockedShares() external view returns (uint256) { return _unlockedShares(_strategyStorage()); } /// @dev To determine how many report-locked shares have unlocked. function _unlockedShares( StrategyData storage S ) internal view returns (uint256 unlocked) { uint96 _fullProfitUnlockDate = S.fullProfitUnlockDate; if (_fullProfitUnlockDate > block.timestamp) { unchecked { unlocked = (S.profitUnlockingRate * (block.timestamp - S.lastReport)) / MAX_BPS_EXTENDED; } } else if (_fullProfitUnlockDate != 0) { unlocked = S.balances[address(this)]; } } /*////////////////////////////////////////////////////////////// TENDING //////////////////////////////////////////////////////////////*/ /** * @notice For a 'keeper' to 'tend' the strategy if a custom * tendTrigger() is implemented. * * @dev Both 'tendTrigger' and '_tend' will need to be overridden * for this to be used. * * This will callback the internal '_tend' call in the BaseStrategy * with the total current amount available to the strategy to deploy. * * This is a permissioned function so if desired it could * be used for illiquid or manipulatable strategies to compound * rewards, perform maintenance or deposit/withdraw funds. * * This performs no accounting checkpoint itself. Under constant * accrual, any value change made by '_tend' is reflected in * {totalAssets}, conversions and previews through simulation — * immediately within this block if no accrual has latched it yet * (see {totalAssets}), otherwise from the next block — and is * realized (fees charged, losses offset) by the next state-changing * accrual: any deposit, mint, withdraw, redeem, fee configuration * change, or report. * * Any pre-existing pending profit or loss nets against the effects * of the tend before fees are assessed. This is intended: performance * fees are charged on the net result, not the gross. */ function tend() external nonReentrant onlyKeepers { // Tend the strategy with the current loose balance. IBaseStrategy(address(this)).tendThis( _strategyStorage().asset.balanceOf(address(this)) ); } /*////////////////////////////////////////////////////////////// STRATEGY SHUTDOWN //////////////////////////////////////////////////////////////*/ /** * @notice Used to shutdown the strategy preventing any further deposits. * @dev Can only be called by the current `management` or `emergencyAdmin`. * * This will stop any new {deposit} or {mint} calls but will * not prevent {withdraw} or {redeem}. It will also still allow for * {tend} and {report} so that management can report any last losses * in an emergency as well as provide any maintenance to allow for full * withdraw. * * This is a one way switch and can never be set back once shutdown. */ function shutdownStrategy() external onlyEmergencyAuthorized { _strategyStorage().shutdown = true; emit StrategyShutdown(); } /** * @notice Used to set the pause status for user facing 4626 functions. * @dev Pausing can be called by the current `management` or `emergencyAdmin`. * Unpausing can only be called by the current `management`. * * This will stop {deposit}, {mint}, {withdraw} and {redeem}, but will * leave management functions live so the strategy can still be tended, * reported, configured, shutdown or manually withdrawn in an emergency. */ function setPaused(bool paused) external { if (paused) { requireEmergencyAuthorized(msg.sender); } else { requireManagement(msg.sender); } _strategyStorage().paused = paused; emit UpdatePaused(paused); } /** * @notice To manually withdraw funds from the yield source after a * strategy has been shutdown. * @dev This can only be called when the strategy is paused or shutdown. * * This path deliberately performs no accrual so that the rescue flow * has no dependency on `strategyTotalAssets()`, which may revert or * be unreliable mid-emergency. Any profit or loss caused by the * unwind follows the same rules as {tend}: it is reflected in * {totalAssets}, conversions and previews through simulation and is * realized by the next state-changing accrual. Management can call * {report} afterward if controlled realization is desired. * * A strategist will need to override the {_emergencyWithdraw} function * in their strategy for this to work. * * @param amount The amount of asset to attempt to free. */ function emergencyWithdraw( uint256 amount ) external nonReentrant onlyEmergencyAuthorized { StrategyData storage S = _strategyStorage(); // Make sure the strategy has been paused or shutdown. require(S.paused || S.shutdown, "not paused or shutdown"); // Withdraw from the yield source. IBaseStrategy(address(this)).shutdownWithdraw(amount); } /*////////////////////////////////////////////////////////////// GETTER FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Get the underlying asset for the strategy. * @return . The underlying asset. */ function asset() external view returns (address) { return address(_strategyStorage().asset); } /** * @notice Get the API version for this TokenizedStrategy. * @return . The API version for this TokenizedStrategy */ function apiVersion() external pure returns (string memory) { return API_VERSION; } /** * @notice Get the current address that controls the strategy. * @return . Address of management */ function management() external view returns (address) { return _strategyStorage().management; } /** * @notice Get the current pending management address if any. * @return . Address of pendingManagement */ function pendingManagement() external view returns (address) { return _strategyStorage().pendingManagement; } /** * @notice Get the current address that can call tend and report. * @return . Address of the keeper */ function keeper() external view returns (address) { return _strategyStorage().keeper; } /** * @notice Get the current address that can shutdown and emergency withdraw. * @return . Address of the emergencyAdmin */ function emergencyAdmin() external view returns (address) { return _strategyStorage().emergencyAdmin; } /** * @notice Get the current performance fee charged on profits. * denominated in Basis Points where 10_000 == 100% * @return . Current performance fee. */ function performanceFee() external view returns (uint16) { return _strategyStorage().performanceFee; } /** * @notice Get the current address that receives the performance fees. * @return . Address of performanceFeeRecipient */ function performanceFeeRecipient() external view returns (address) { return _strategyStorage().performanceFeeRecipient; } /** * @notice Gets the current time profits are set to unlock over. * @dev Returns `type(uint256).max` when the packed value was clamped. * @return . The current profit max unlock time. */ function profitMaxUnlockTime() external view returns (uint256) { uint256 _profitMaxUnlockTime = _strategyStorage().profitMaxUnlockTime; if (_profitMaxUnlockTime == type(uint32).max) { return type(uint256).max; } return _profitMaxUnlockTime; } /** * @notice The timestamp of the last report call. * @return . The last report. */ function lastReport() external view returns (uint256) { return uint256(_strategyStorage().lastReport); } /** * @notice The timestamp of the last accounting sync. * @return . The last accrual. */ function lastAccrual() external view returns (uint256) { return uint256(_strategyStorage().lastAccrual); } /** * @notice The last realized total assets baseline. * @return . The last stored total assets. */ function lastTotalAssets() external view returns (uint256) { return _strategyStorage().lastTotalAssets; } /** * @notice Gets the timestamp at which all reported profits will be unlocked. * @return . The full profit unlocking timestamp. */ function fullProfitUnlockDate() external view returns (uint256) { return uint256(_strategyStorage().fullProfitUnlockDate); } /** * @notice The per second rate at which reported profits are unlocking. * @return . The current profit unlocking rate. */ function profitUnlockingRate() external view returns (uint256) { return _strategyStorage().profitUnlockingRate; } /** * @notice Get the price per share. * @dev This value offers limited precision. Integrations that require * exact precision should use convertToAssets or convertToShares instead. * * @return . The price per share. */ function pricePerShare() external view returns (uint256) { StrategyData storage S = _strategyStorage(); return _convertToAssets(S, 10 ** S.decimals, Math.Rounding.Down); } /** * @notice To check if the strategy has been shutdown. * @return . Whether or not the strategy is shutdown. */ function isShutdown() external view returns (bool) { return _strategyStorage().shutdown; } /** * @notice To check if the strategy has been paused. * @return . Whether or not the strategy is paused. */ function isPaused() external view returns (bool) { return _strategyStorage().paused; } /*////////////////////////////////////////////////////////////// SETTER FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Step one of two to set a new address to be in charge of the strategy. * @dev Can only be called by the current `management`. The address is * set to pending management and will then have to call {acceptManagement} * in order for the 'management' to officially change. * * Cannot set `management` to address(0). * * @param _management New address to set `pendingManagement` to. */ function setPendingManagement(address _management) external onlyManagement { require(_management != address(0), "ZERO ADDRESS"); _strategyStorage().pendingManagement = _management; emit UpdatePendingManagement(_management); } /** * @notice Step two of two to set a new 'management' of the strategy. * @dev Can only be called by the current `pendingManagement`. */ function acceptManagement() external { StrategyData storage S = _strategyStorage(); require(msg.sender == S.pendingManagement, "!pending"); S.management = msg.sender; S.pendingManagement = address(0); emit UpdateManagement(msg.sender); } /** * @notice Sets a new address to be in charge of tend and reports. * @dev Can only be called by the current `management`. * * @param _keeper New address to set `keeper` to. */ function setKeeper(address _keeper) external onlyManagement { _strategyStorage().keeper = _keeper; emit UpdateKeeper(_keeper); } /** * @notice Sets a new address to be able to shutdown the strategy. * @dev Can only be called by the current `management`. * * @param _emergencyAdmin New address to set `emergencyAdmin` to. */ function setEmergencyAdmin( address _emergencyAdmin ) external onlyManagement { _strategyStorage().emergencyAdmin = _emergencyAdmin; emit UpdateEmergencyAdmin(_emergencyAdmin); } /** * @notice Sets the performance fee to be charged on reported gains. * @dev Can only be called by the current `management`. * * Denominated in Basis Points. So 100% == 10_000. * Cannot set greater than to MAX_FEE. * * @param _performanceFee New performance fee. */ function setPerformanceFee(uint16 _performanceFee) external onlyManagement { _accrue(_strategyStorage()); require(_performanceFee <= MAX_FEE, "MAX FEE"); _strategyStorage().performanceFee = _performanceFee; emit UpdatePerformanceFee(_performanceFee); } /** * @notice Sets a new address to receive performance fees. * @dev Can only be called by the current `management`. * * Cannot set to address(0). * * @param _performanceFeeRecipient New address to set `management` to. */ function setPerformanceFeeRecipient( address _performanceFeeRecipient ) external onlyManagement { _accrue(_strategyStorage()); require(_performanceFeeRecipient != address(0), "ZERO ADDRESS"); require(_performanceFeeRecipient != address(this), "Cannot be self"); _strategyStorage().performanceFeeRecipient = _performanceFeeRecipient; emit UpdatePerformanceFeeRecipient(_performanceFeeRecipient); } /** * @notice Sets the time for profits to be unlocked over. * @dev Can only be called by the current `management`. * * NOTE: Setting to 0 will cause all currently locked profit * to be unlocked instantly and should be done with care. * * `profitMaxUnlockTime` is packed as a `uint32`. Larger inputs are * clamped to `type(uint32).max`, and the getter exposes that sentinel * as `type(uint256).max`. * * @param _profitMaxUnlockTime New `profitMaxUnlockTime`. */ function setProfitMaxUnlockTime( uint256 _profitMaxUnlockTime ) external onlyManagement { StrategyData storage S = _strategyStorage(); _accrue(S); uint32 newProfitMaxUnlockTime = _profitMaxUnlockTime > type(uint32).max ? type(uint32).max : uint32(_profitMaxUnlockTime); if (newProfitMaxUnlockTime == 0) { uint256 shares = S.balances[address(this)]; if (shares != 0) { _burn(S, address(this), shares); } S.profitUnlockingRate = 0; S.fullProfitUnlockDate = 0; } S.profitMaxUnlockTime = newProfitMaxUnlockTime; emit UpdateProfitMaxUnlockTime(_profitMaxUnlockTime); } /** * @notice Updates the name for the strategy. * @param _name The new name for the strategy. */ function setName(string calldata _name) external onlyManagement { _strategyStorage().name = _name; } /*////////////////////////////////////////////////////////////// ERC20 METHODS //////////////////////////////////////////////////////////////*/ /** * @notice Returns the name of the token. * @return . The name the strategy is using for its token. */ function name() external view returns (string memory) { return _strategyStorage().name; } /** * @notice Returns the symbol of the strategies token. * @dev Will be 'ys + asset symbol'. * @return . The symbol the strategy is using for its tokens. */ function symbol() external view returns (string memory) { return string(abi.encodePacked("ys", _strategyStorage().asset.symbol())); } /** * @notice Returns the number of decimals used to get its user representation. * @return . The decimals used for the strategy and `asset`. */ function decimals() external view returns (uint8) { return _strategyStorage().decimals; } /** * @notice Returns the current balance for a given '_account'. * @param account the address to return the balance for. * @return . The current balance in y shares of the '_account'. */ function balanceOf(address account) external view returns (uint256) { return _balanceOf(_strategyStorage(), account); } /// @dev Internal implementation of {balanceOf}. function _balanceOf( StrategyData storage S, address account ) internal view returns (uint256) { if (account == address(this)) { return S.balances[account] - _unlockedShares(S); } return S.balances[account]; } /** * @notice Transfer '_amount` of shares from `msg.sender` to `to`. * @dev * Requirements: * * - `from` cannot be the address of the strategy. * - `to` cannot be the zero address. * - `to` cannot be the address of the strategy. * - the caller must have a balance of at least `_amount`. * * @param to The address shares will be transferred to. * @param amount The amount of shares to be transferred from sender. * @return . a boolean value indicating whether the operation succeeded. */ function transfer(address to, uint256 amount) external returns (bool) { _transfer(_strategyStorage(), msg.sender, to, amount); return true; } /** * @notice Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. * @param owner The address who owns the shares. * @param spender The address who would be moving the owners shares. * @return . The remaining amount of shares of `owner` that could be moved by `spender`. */ function allowance( address owner, address spender ) external view returns (uint256) { return _allowance(_strategyStorage(), owner, spender); } /// @dev Internal implementation of {allowance}. function _allowance( StrategyData storage S, address owner, address spender ) internal view returns (uint256) { return S.allowances[owner][spender]; } /** * @notice Sets `amount` as the allowance of `spender` over the caller's tokens. * @dev * * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. * * @param spender the address to allow the shares to be moved by. * @param amount the amount of shares to allow `spender` to move. * @return . a boolean value indicating whether the operation succeeded. */ function approve(address spender, uint256 amount) external returns (bool) { _approve(_strategyStorage(), msg.sender, spender, amount); return true; } /** * @notice `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * @dev * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` cannot be the address of the strategy. * - `to` cannot be the address of the strategy. * - `from` must have a balance of at least `amount`. * - the caller must have allowance for ``from``'s tokens of at least * `amount`. * * Emits a {Transfer} event. * * @param from the address to be moving shares from. * @param to the address to be moving shares to. * @param amount the quantity of shares to move. * @return . a boolean value indicating whether the operation succeeded. */ function transferFrom( address from, address to, uint256 amount ) external returns (bool) { StrategyData storage S = _strategyStorage(); _spendAllowance(S, from, msg.sender, amount); _transfer(S, from, to, amount); return true; } /** * @dev Moves `amount` of tokens from `from` to `to`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `from` cannot be the strategies address. * - `to` cannot be the strategies address. * - `from` must have a balance of at least `amount`. * */ function _transfer( StrategyData storage S, address from, address to, uint256 amount ) internal { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); require(from != address(this), "ERC20 transfer from strategy"); require(to != address(this), "ERC20 transfer to strategy"); S.balances[from] -= amount; unchecked { S.balances[to] += amount; } emit Transfer(from, to, amount); } /** * @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * */ function _mint( StrategyData storage S, address account, uint256 amount ) internal { require(account != address(0), "ERC20: mint to the zero address"); S.totalSupply += amount; unchecked { S.balances[account] += amount; } emit Transfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn( StrategyData storage S, address account, uint256 amount ) internal { require(account != address(0), "ERC20: burn from the zero address"); S.balances[account] -= amount; unchecked { S.totalSupply -= amount; } emit Transfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve( StrategyData storage S, address owner, address spender, uint256 amount ) internal { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); S.allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Updates `owner` s allowance for `spender` based on spent `amount`. * * Does not update the allowance amount in case of infinite allowance. * Revert if not enough allowance is available. * * Might emit an {Approval} event. */ function _spendAllowance( StrategyData storage S, address owner, address spender, uint256 amount ) internal { uint256 currentAllowance = _allowance(S, owner, spender); if (currentAllowance != type(uint256).max) { require( currentAllowance >= amount, "ERC20: insufficient allowance" ); unchecked { _approve(S, owner, spender, currentAllowance - amount); } } } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ /** * @notice Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * @dev Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. * * @param _owner the address of the account to return the nonce for. * @return . the current nonce for the account. */ function nonces(address _owner) external view returns (uint256) { return _strategyStorage().nonces[_owner]; } /** * @notice Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * @dev IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { require(deadline >= block.timestamp, "ERC20: PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, _strategyStorage().nonces[owner]++, deadline ) ) ) ), v, r, s ); require( recoveredAddress != address(0) && recoveredAddress == owner, "ERC20: INVALID_SIGNER" ); _approve(_strategyStorage(), recoveredAddress, spender, value); } } /** * @notice Returns the domain separator used in the encoding of the signature * for {permit}, as defined by {EIP712}. * * @return . The domain separator that will be used for any {permit} calls. */ function DOMAIN_SEPARATOR() public view returns (bytes32) { return keccak256( abi.encode( keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ), keccak256("Yearn Vault"), keccak256(bytes(API_VERSION)), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// DEPLOYMENT //////////////////////////////////////////////////////////////*/ /** * @dev On contract creation we set `asset` for this contract to address(1). * This prevents it from ever being initialized in the future. * @param _factory Address of the factory of the same version for protocol fees. */ constructor(address _factory) { FACTORY = _factory; _strategyStorage().asset = ERC20(address(1)); } }