FlexStrategy

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "lib/yieldnest-flex-strategy/src/FlexStrategy.sol": {
      "content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.28;

import { BaseStrategy } from "@yieldnest-vault/strategy/BaseStrategy.sol";
import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IAccountingModule } from "./AccountingModule.sol";
import { VaultLib } from "lib/yieldnest-vault/src/library/VaultLib.sol";

interface IFlexStrategy {
    error NoAccountingModule();
    error InvariantViolation();
    error AccountingTokenMismatch();

    event AccountingModuleUpdated(address newValue, address oldValue);
}

/**
 * @notice Storage struct for FlexStrategy
 */
struct FlexStrategyStorage {
    IAccountingModule accountingModule;
}

/**
 * Flex strategy that proxies the deposited base asset to an associated safe,
 * minting IOU accounting tokens in the process to represent transferred assets.
 */
contract FlexStrategy is IFlexStrategy, BaseStrategy {
    using SafeERC20 for IERC20;

    /// @notice The version of the flex strategy contract.
    string public constant FLEX_STRATEGY_VERSION = "0.1.0";

    /// @notice Storage slot for FlexStrategy data
    bytes32 private constant FLEX_STRATEGY_STORAGE_SLOT = keccak256("yieldnest.storage.flexStrategy");

    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Get the storage struct
     */
    function _getFlexStrategyStorage() internal pure returns (FlexStrategyStorage storage s) {
        bytes32 slot = FLEX_STRATEGY_STORAGE_SLOT;
        assembly {
            s.slot := slot
        }
    }

    /**
     * @notice Initializes the vault.
     * @param admin The address of the admin.
     * @param name The name of the vault.
     * @param symbol The symbol of the vault.
     * @param decimals_ The number of decimals for the vault token.
     * @param baseAsset The base asset of the vault.
     * @param paused_ Whether the vault should start in a paused state.
     */
    function initialize(
        address admin,
        string memory name,
        string memory symbol,
        uint8 decimals_,
        address baseAsset,
        address accountingToken,
        bool paused_,
        address provider,
        bool alwaysComputeTotalAssets
    )
        external
        virtual
        initializer
    {
        if (admin == address(0)) revert ZeroAddress();

        _initialize(
            admin,
            name,
            symbol,
            decimals_,
            paused_,
            false, // countNativeAsset. MUST be false. strategy is assumed to hold no native assets
            alwaysComputeTotalAssets, // alwaysComputeTotalAssets
            0 // defaultAssetIndex. MUST be 0. baseAsset is default
        );

        _addAsset(baseAsset, IERC20Metadata(baseAsset).decimals(), true);
        _addAsset(accountingToken, IERC20Metadata(accountingToken).decimals(), false);
        _setAssetWithdrawable(baseAsset, true);

        VaultLib.setProvider(provider);
    }

    modifier hasAccountingModule() {
        if (address(_getFlexStrategyStorage().accountingModule) == address(0)) revert NoAccountingModule();
        _;
    }

    /**
     * @notice Internal function to handle deposits.
     * @param asset_ The address of the asset.
     * @param caller The address of the caller.
     * @param receiver The address of the receiver.
     * @param assets The amount of assets to deposit.
     * @param shares The amount of shares to mint.
     * @param baseAssets The base asset conversion of shares.
     */
    function _deposit(
        address asset_,
        address caller,
        address receiver,
        uint256 assets,
        uint256 shares,
        uint256 baseAssets
    )
        internal
        virtual
        override
        hasAccountingModule
    {
        // call the base strategy deposit function for accounting
        super._deposit(asset_, caller, receiver, assets, shares, baseAssets);

        // virtual accounting
        _getFlexStrategyStorage().accountingModule.deposit(assets);
    }

    /**
     * @notice Internal function to handle withdrawals for base asset
     * @param asset_ The address of the asset.
     * @param caller The address of the caller.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @param assets The amount of assets to withdraw.
     * @param shares The equivalent amount of shares.
     */
    function _withdrawAsset(
        address asset_,
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    )
        internal
        virtual
        override
        hasAccountingModule
        onlyAllocator
    {
        if (asset_ != asset()) {
            revert InvalidAsset(asset_);
        }

        // call the base strategy withdraw function for accounting
        _subTotalAssets(_convertAssetToBase(asset_, assets));

        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

        // NOTE: burn shares before withdrawing the assets
        _burn(owner, shares);

        // burn virtual tokens
        _getFlexStrategyStorage().accountingModule.withdraw(assets, receiver);
        emit WithdrawAsset(caller, receiver, owner, asset_, assets, shares);
    }

    /**
     * @notice Sets the accounting module.
     * @param accountingModule_ address to check.
     * @dev Will revoke approvals for outgoing accounting module, and approve max for incoming accounting module.
     */
    function setAccountingModule(address accountingModule_) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        if (accountingModule_ == address(0)) revert ZeroAddress();

        FlexStrategyStorage storage flexStorage = _getFlexStrategyStorage();
        emit AccountingModuleUpdated(accountingModule_, address(flexStorage.accountingModule));

        IAccountingModule oldAccounting = flexStorage.accountingModule;

        if (address(oldAccounting) != address(0)) {
            IERC20(asset()).approve(address(oldAccounting), 0);

            if (IAccountingModule(accountingModule_).accountingToken() != oldAccounting.accountingToken()) {
                revert AccountingTokenMismatch();
            }
        }

        flexStorage.accountingModule = IAccountingModule(accountingModule_);
        IERC20(asset()).approve(accountingModule_, type(uint256).max);
    }

    /**
     * @notice Internal function to get the available amount of assets.
     * @param asset_ The address of the asset.
     * @return availableAssets The available amount of assets.
     * @dev Overriden. This function is used to calculate the available assets for a given asset,
     *      It returns the balance of the asset in the associated SAFE.
     */
    function _availableAssets(address asset_) internal view virtual override returns (uint256 availableAssets) {
        address baseAsset = asset();
        if (asset_ == baseAsset) {
            return IERC20(baseAsset).balanceOf(_getFlexStrategyStorage().accountingModule.safe());
        }

        return super._availableAssets(asset_);
    }

    /**
     * @notice Returns the fee on total amount.
     * @return 0 as this strategy does not charge any fee on total amount.
     */
    function _feeOnTotal(uint256) public view virtual override returns (uint256) {
        return 0;
    }

    /**
     * @notice Returns the fee on total amount.
     * @return 0 as this strategy does not charge any fee on total amount.
     */
    function _feeOnRaw(uint256) public view virtual override returns (uint256) {
        return 0;
    }

    /// VIEWS ///

    function accountingModule() public view returns (IAccountingModule) {
        return _getFlexStrategyStorage().accountingModule;
    }
}
"
    },
    "lib/yieldnest-flex-strategy/lib/yieldnest-vault/src/strategy/BaseStrategy.sol": {
      "content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;

import {IERC20Metadata as IERC20, Math, SafeERC20} from "src/Common.sol";
import {BaseVault} from "src/BaseVault.sol";
import {IBaseStrategy} from "src/interface/IBaseStrategy.sol";

/**
 * @title BaseStrategy
 * @author Yieldnest
 * @notice This contract is a base strategy for any underlying protocol.
 * vault.
 */
abstract contract BaseStrategy is BaseVault, IBaseStrategy {
    /// @notice The version of the strategy contract.
    string public constant STRATEGY_VERSION = "0.2.0";
    /// @notice Role for allocator permissions
    bytes32 public constant ALLOCATOR_ROLE = keccak256("ALLOCATOR_ROLE");
    /// @notice Role for allocator manager permissions
    bytes32 public constant ALLOCATOR_MANAGER_ROLE = keccak256("ALLOCATOR_MANAGER_ROLE");

    /**
     * @notice Returns whether the strategy has allocators.
     * @return hasAllocators True if the strategy has allocators, otherwise false.
     */
    function getHasAllocator() public view returns (bool hasAllocators) {
        return _getBaseStrategyStorage().hasAllocators;
    }

    /**
     * @notice Sets whether the strategy has allocators.
     * @param hasAllocators_ The new value for the hasAllocator flag.
     */
    function setHasAllocator(bool hasAllocators_) external onlyRole(ALLOCATOR_MANAGER_ROLE) {
        BaseStrategyStorage storage strategyStorage = _getBaseStrategyStorage();
        strategyStorage.hasAllocators = hasAllocators_;

        emit SetHasAllocator(hasAllocators_);
    }

    /**
     * @notice Returns whether the asset is withdrawable.
     * @param asset_ The address of the asset.
     * @return True if the asset is withdrawable, otherwise false.
     */
    function getAssetWithdrawable(address asset_) external view returns (bool) {
        return _getBaseStrategyStorage().isAssetWithdrawable[asset_];
    }

    /**
     * @notice Sets whether the asset is withdrawable.
     * @param asset_ The address of the asset.
     * @param withdrawable_ The new value for the withdrawable flag.
     */
    function setAssetWithdrawable(address asset_, bool withdrawable_) external onlyRole(ASSET_MANAGER_ROLE) {
        _setAssetWithdrawable(asset_, withdrawable_);
    }

    /**
     * @notice Internal function to set whether the asset is withdrawable.
     * @param asset_ The address of the asset.
     * @param withdrawable_ The new value for the withdrawable flag.
     */
    function _setAssetWithdrawable(address asset_, bool withdrawable_) internal {
        BaseStrategyStorage storage strategyStorage = _getBaseStrategyStorage();
        strategyStorage.isAssetWithdrawable[asset_] = withdrawable_;

        emit SetAssetWithdrawable(asset_, withdrawable_);
    }

    /**
     * @notice Modifier to restrict access to allocator roles.
     */
    modifier onlyAllocator() {
        if (_getBaseStrategyStorage().hasAllocators && !hasRole(ALLOCATOR_ROLE, msg.sender)) {
            revert AccessControlUnauthorizedAccount(msg.sender, ALLOCATOR_ROLE);
        }
        _;
    }

    /**
     * @notice Returns the maximum amount of assets that can be withdrawn by a given owner.
     * @param owner The address of the owner.
     * @return maxAssets The maximum amount of assets.
     */
    function maxWithdraw(address owner) public view override returns (uint256 maxAssets) {
        maxAssets = _maxWithdrawAsset(asset(), owner);
    }

    /**
     * @notice Returns the maximum amount of assets that can be withdrawn for a specific asset by a given owner.
     * @param asset_ The address of the asset.
     * @param owner The address of the owner.
     * @return maxAssets The maximum amount of assets.
     */
    function maxWithdrawAsset(address asset_, address owner) public view returns (uint256 maxAssets) {
        maxAssets = _maxWithdrawAsset(asset_, owner);
    }

    /**
     * @notice Internal function to get the maximum amount of assets that can be withdrawn by a given owner.
     * @param asset_ The address of the asset.
     * @param owner The address of the owner.
     * @return maxAssets The maximum amount of assets.
     */
    function _maxWithdrawAsset(address asset_, address owner) internal view virtual returns (uint256 maxAssets) {
        if (paused() || !_getBaseStrategyStorage().isAssetWithdrawable[asset_]) {
            return 0;
        }

        uint256 availableAssets = _availableAssets(asset_);

        maxAssets = previewRedeemAsset(asset_, balanceOf(owner));

        maxAssets = availableAssets < maxAssets ? availableAssets : maxAssets;
    }

    /**
     * @notice Returns the maximum amount of shares that can be redeemed by a given owner.
     * @param owner The address of the owner.
     * @return maxShares The maximum amount of shares.
     */
    function maxRedeem(address owner) public view override returns (uint256 maxShares) {
        maxShares = _maxRedeemAsset(asset(), owner);
    }

    /**
     * @notice Returns the maximum amount of shares that can be redeemed by a given owner.
     * @param asset_ The address of the asset.
     * @param owner The address of the owner.
     * @return maxShares The maximum amount of shares.
     */
    function maxRedeemAsset(address asset_, address owner) public view returns (uint256 maxShares) {
        maxShares = _maxRedeemAsset(asset_, owner);
    }

    /**
     * @notice Internal function to get the maximum amount of shares that can be redeemed by a given owner.
     * @param asset_ The address of the asset.
     * @param owner The address of the owner.
     * @return maxShares The maximum amount of shares.
     */
    function _maxRedeemAsset(address asset_, address owner) internal view virtual returns (uint256 maxShares) {
        if (paused() || !_getBaseStrategyStorage().isAssetWithdrawable[asset_]) {
            return 0;
        }

        uint256 availableAssets = _availableAssets(asset_);

        maxShares = balanceOf(owner);

        maxShares = availableAssets < previewRedeemAsset(asset_, maxShares)
            ? previewWithdrawAsset(asset_, availableAssets)
            : maxShares;
    }

    /**
     * @notice Previews the amount of assets that would be required to mint a given amount of shares.
     * @param asset_ The address of the asset.
     * @param shares The amount of shares to mint.
     * @return assets The equivalent amount of assets.
     */
    function previewMintAsset(address asset_, uint256 shares) public view virtual returns (uint256 assets) {
        (assets,) = _convertToAssets(asset_, shares, Math.Rounding.Ceil);
    }

    /**
     * @notice Previews the amount of assets that would be received for a given amount of shares.
     * @param asset_ The address of the asset.
     * @param shares The amount of shares to redeem.
     * @return assets The equivalent amount of assets.
     */
    function previewRedeemAsset(address asset_, uint256 shares) public view virtual returns (uint256 assets) {
        (assets,) = _convertToAssets(asset_, shares, Math.Rounding.Floor);
        assets = assets - _feeOnTotal(assets);
    }

    /**
     * @notice Previews the amount of shares that would be received for a given amount of assets.
     * @param asset_ The address of the asset.
     * @param assets The amount of assets to deposit.
     * @return shares The equivalent amount of shares.
     */
    function previewWithdrawAsset(address asset_, uint256 assets) public view virtual returns (uint256 shares) {
        uint256 fee = _feeOnRaw(assets);
        (shares,) = _convertToShares(asset_, assets + fee, Math.Rounding.Ceil);
    }

    /**
     * @notice Withdraws a given amount of assets and burns the equivalent amount of shares from the owner.
     * @param assets The amount of assets to withdraw.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @return shares The equivalent amount of shares.
     */
    function withdraw(uint256 assets, address receiver, address owner)
        public
        virtual
        override
        nonReentrant
        returns (uint256 shares)
    {
        shares = _withdrawAsset(asset(), assets, receiver, owner);
    }

    /**
     * @notice Withdraws assets and burns equivalent shares from the owner.
     * @param asset_ The address of the asset.
     * @param assets The amount of assets to withdraw.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @return shares The equivalent amount of shares burned.
     */
    function withdrawAsset(address asset_, uint256 assets, address receiver, address owner)
        public
        virtual
        nonReentrant
        returns (uint256 shares)
    {
        shares = _withdrawAsset(asset_, assets, receiver, owner);
    }

    /**
     * @notice Internal function for withdraws assets and burns equivalent shares from the owner.
     * @param asset_ The address of the asset.
     * @param assets The amount of assets to withdraw.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @return shares The equivalent amount of shares burned.
     */
    function _withdrawAsset(address asset_, uint256 assets, address receiver, address owner)
        internal
        returns (uint256 shares)
    {
        if (paused()) {
            revert Paused();
        }
        uint256 maxAssets = maxWithdrawAsset(asset_, owner);
        if (assets > maxAssets) {
            revert ExceededMaxWithdraw(owner, assets, maxAssets);
        }
        shares = previewWithdrawAsset(asset_, assets);
        _withdrawAsset(asset_, _msgSender(), receiver, owner, assets, shares);
    }

    /**
     * @notice Internal function to handle withdrawals.
     * @param caller The address of the caller.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @param assets The amount of assets to withdraw.
     * @param shares The equivalent amount of shares.
     */
    function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
        internal
        virtual
        override
    {
        _withdrawAsset(asset(), caller, receiver, owner, assets, shares);
    }

    /**
     * @notice Internal function to handle withdrawals for specific assets.
     * @param asset_ The address of the asset.
     * @param caller The address of the caller.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @param assets The amount of assets to withdraw.
     * @param shares The equivalent amount of shares.
     */
    function _withdrawAsset(
        address asset_,
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    ) internal virtual onlyAllocator {
        if (!_getBaseStrategyStorage().isAssetWithdrawable[asset_]) {
            revert AssetNotWithdrawable();
        }

        _subTotalAssets(_convertAssetToBase(asset_, assets));

        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

        // NOTE: burn shares before withdrawing the assets
        _burn(owner, shares);

        SafeERC20.safeTransfer(IERC20(asset_), receiver, assets);

        emit WithdrawAsset(caller, receiver, owner, asset_, assets, shares);
    }

    /**
     * @notice Redeems a given amount of shares and transfers the equivalent amount of assets to the receiver.
     * @param shares The amount of shares to redeem.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @return assets The equivalent amount of assets.
     */
    function redeem(uint256 shares, address receiver, address owner)
        public
        virtual
        override
        nonReentrant
        returns (uint256 assets)
    {
        assets = _redeemAsset(asset(), shares, receiver, owner);
    }

    /**
     * @notice Redeems shares and transfers equivalent assets to the receiver.
     * @param asset_ The address of the asset.
     * @param shares The amount of shares to redeem.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @return assets The equivalent amount of assets.
     */
    function redeemAsset(address asset_, uint256 shares, address receiver, address owner)
        public
        virtual
        nonReentrant
        returns (uint256 assets)
    {
        assets = _redeemAsset(asset_, shares, receiver, owner);
    }

    /**
     * @notice Internal function for redeems shares and transfers equivalent assets to the receiver.
     * @param asset_ The address of the asset.
     * @param shares The amount of shares to redeem.
     * @param receiver The address of the receiver.
     * @param owner The address of the owner.
     * @return assets The equivalent amount of assets.
     */
    function _redeemAsset(address asset_, uint256 shares, address receiver, address owner)
        internal
        returns (uint256 assets)
    {
        if (paused()) {
            revert Paused();
        }
        uint256 maxShares = maxRedeemAsset(asset_, owner);
        if (shares > maxShares) {
            revert ExceededMaxRedeem(owner, shares, maxShares);
        }
        assets = previewRedeemAsset(asset_, shares);
        _withdrawAsset(asset_, _msgSender(), receiver, owner, assets, shares);
    }

    /**
     * @notice Internal function to handle deposits.
     * @param asset_ The address of the asset.
     * @param caller The address of the caller.
     * @param receiver The address of the receiver.
     * @param assets The amount of assets to deposit.
     * @param shares The amount of shares to mint.
     * @param baseAssets The base asset conversion of shares.
     */
    function _deposit(
        address asset_,
        address caller,
        address receiver,
        uint256 assets,
        uint256 shares,
        uint256 baseAssets
    ) internal virtual override onlyAllocator {
        super._deposit(asset_, caller, receiver, assets, shares, baseAssets);
    }

    /**
     * @notice Retrieves the strategy storage structure.
     * @return $ The strategy storage structure.
     */
    function _getBaseStrategyStorage() internal pure virtual returns (BaseStrategyStorage storage $) {
        assembly {
            // keccak256("yieldnest.storage.strategy.base")
            $.slot := 0x5cfdf694cb3bdee9e4b3d9c4b43849916bf3f018805254a1c0e500548c668500
        }
    }

    /**
     * @notice Adds a new asset to the vault.
     * @param asset_ The address of the asset.
     * @param decimals_ The decimals of the asset.
     * @param depositable_ Whether the asset is depositable.
     * @param withdrawable_ Whether the asset is withdrawable.
     */
    function addAsset(address asset_, uint8 decimals_, bool depositable_, bool withdrawable_)
        public
        virtual
        onlyRole(ASSET_MANAGER_ROLE)
    {
        _addAsset(asset_, decimals_, depositable_);
        _setAssetWithdrawable(asset_, withdrawable_);
    }

    /**
     * @notice Adds a new asset to the vault.
     * @param asset_ The address of the asset.
     * @param depositable_ Whether the asset is depositable.
     * @param withdrawable_ Whether the asset is withdrawable.
     */
    function addAsset(address asset_, bool depositable_, bool withdrawable_)
        external
        virtual
        onlyRole(ASSET_MANAGER_ROLE)
    {
        _addAsset(asset_, IERC20(asset_).decimals(), depositable_);
        _setAssetWithdrawable(asset_, withdrawable_);
    }

    /**
     * @notice Internal function to get the available amount of assets.
     * @param asset_ The address of the asset.
     * @return availableAssets The available amount of assets.
     * @dev This function is used to calculate the available assets for a given asset,
     *      It returns the balance of the asset in the vault and also adds any assets
     *      already staked in an underlying staking protocol that is available for withdrawal.
     */
    function _availableAssets(address asset_) internal view virtual returns (uint256 availableAssets) {
        availableAssets = IERC20(asset_).balanceOf(address(this));
    }
}
"
    },
    "lib/yieldnest-flex-strategy/lib/yieldnest-vault/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` 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.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` 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.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` 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.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     * ```
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
"
    },
    "lib/yieldnest-flex-strategy/lib/yieldnest-vault/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}
"
    },
    "lib/yieldnest-flex-strategy/src/AccountingModule.sol": {
      "content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.28;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IAccountingToken } from "./AccountingToken.sol";
import { IVault } from "@yieldnest-vault/interface/IVault.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";

interface IAccountingModule {
    struct StrategySnapshot {
        uint256 timestamp;
        uint256 pricePerShare;
        uint256 totalSupply;
        uint256 totalAssets;
    }

    event LowerBoundUpdated(uint256 newValue, uint256 oldValue);
    event TargetApyUpdated(uint256 newValue, uint256 oldValue);
    event CooldownSecondsUpdated(uint16 newValue, uint16 oldValue);
    event SafeUpdated(address newValue, address oldValue);

    error ZeroAddress();
    error TooEarly();
    error NotStrategy();
    error AccountingLimitsExceeded(uint256 aprSinceLastSnapshot, uint256 targetApr);
    error LossLimitsExceeded(uint256 amount, uint256 lowerBoundAmount);
    error InvariantViolation();
    error TvlTooLow();
    error CurrentTimestampBeforePreviousTimestamp();
    error SnapshotIndexOutOfBounds(uint256 index);

    function deposit(uint256 amount) external;
    function withdraw(uint256 amount, address recipient) external;
    function processRewards(uint256 amount) external;
    function processRewards(uint256 amount, uint256 snapshotIndex) external;
    function processLosses(uint256 amount) external;
    function setCooldownSeconds(uint16 cooldownSeconds) external;

    function baseAsset() external view returns (address);
    function strategy() external view returns (address);
    function DIVISOR() external view returns (uint256);
    function YEAR() external view returns (uint256);
    function accountingToken() external view returns (IAccountingToken);
    function safe() external view returns (address);
    function nextUpdateWindow() external view returns (uint64);
    function targetApy() external view returns (uint256);
    function lowerBound() external view returns (uint256);
    function cooldownSeconds() external view returns (uint16);
    function SAFE_MANAGER_ROLE() external view returns (bytes32);
    function REWARDS_PROCESSOR_ROLE() external view returns (bytes32);
    function LOSS_PROCESSOR_ROLE() external view returns (bytes32);

    function calculateApr(
        uint256 previousPricePerShare,
        uint256 previousTimestamp,
        uint256 currentPricePerShare,
        uint256 currentTimestamp
    )
        external
        view
        returns (uint256 apr);

    function snapshotsLength() external view returns (uint256);
    function snapshots(uint256 index) external view returns (StrategySnapshot memory);
    function lastSnapshot() external view returns (StrategySnapshot memory);
}

/**
 * @notice Storage struct for AccountingModule
 */
struct AccountingModuleStorage {
    IAccountingToken accountingToken;
    address safe;
    address baseAsset;
    address strategy;
    uint64 nextUpdateWindow;
    uint16 cooldownSeconds;
    uint256 targetApy; // in bips;
    uint256 lowerBound; // in bips; % of tvl
    uint256 minRewardableAssets;
    IAccountingModule.StrategySnapshot[] _snapshots;
}

/**
 * Module to configure strategy params,
 *  and mint/burn IOU tokens to represent value accrual/loss.
 */
contract AccountingModule is IAccountingModule, Initializable, AccessControlUpgradeable {
    using SafeERC20 for IERC20;

    /// @notice Role for safe manager permissions
    bytes32 public constant SAFE_MANAGER_ROLE = keccak256("SAFE_MANAGER_ROLE");

    /// @notice Role for processing rewards/losses
    bytes32 public constant REWARDS_PROCESSOR_ROLE = keccak256("REWARDS_PROCESSOR_ROLE");
    bytes32 public constant LOSS_PROCESSOR_ROLE = keccak256("LOSS_PROCESSOR_ROLE");

    uint256 public constant YEAR = 365.25 days;
    uint256 public constant DIVISOR = 1e18;
    uint256 public constant MAX_LOWER_BOUND = DIVISOR / 2;

    /// @notice Storage slot for AccountingModule data
    bytes32 private constant ACCOUNTING_MODULE_STORAGE_SLOT = keccak256("yieldnest.storage.accountingModule");

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Get the storage struct
     */
    function _getAccountingModuleStorage() internal pure returns (AccountingModuleStorage storage s) {
        bytes32 slot = ACCOUNTING_MODULE_STORAGE_SLOT;
        assembly {
            s.slot := slot
        }
    }

    /**
     * /**
     * @notice Initializes the vault.
     * @param strategy_ The strategy address.
     * @param admin The address of the admin.
     * @param safe_ The safe associated with the module.
     * @param accountingToken_ The accountingToken associated with the module.
     * @param targetApy_ The target APY of the strategy.
     * @param lowerBound_ The lower bound of losses of the strategy(as % of TVL).
     * @param minRewardableAssets_ The minimum rewardable assets.
     * @param cooldownSeconds_ The cooldown period in seconds.
     */
    function initialize(
        address strategy_,
        address admin,
        address safe_,
        IAccountingToken accountingToken_,
        uint256 targetApy_,
        uint256 lowerBound_,
        uint256 minRewardableAssets_,
        uint16 cooldownSeconds_
    )
        external
        virtual
        initializer
    {
        __AccessControl_init();

        if (admin == address(0)) revert ZeroAddress();

        _grantRole(DEFAULT_ADMIN_ROLE, admin);

        AccountingModuleStorage storage s = _getAccountingModuleStorage();

        if (address(accountingToken_) == address(0)) revert ZeroAddress();
        s.accountingToken = accountingToken_;

        s.minRewardableAssets = minRewardableAssets_;

        if (strategy_ == address(0)) revert ZeroAddress();
        s.strategy = strategy_;
        s.baseAsset = IERC4626(strategy_).asset();

        _setSafeAddress(safe_);
        _setTargetApy(targetApy_);
        _setLowerBound(lowerBound_);
        _setCooldownSeconds(cooldownSeconds_);

        createStrategySnapshot();
    }

    modifier checkAndResetCooldown() {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (block.timestamp < s.nextUpdateWindow) revert TooEarly();
        s.nextUpdateWindow = (uint64(block.timestamp) + s.cooldownSeconds);
        _;
    }

    modifier onlyStrategy() {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (msg.sender != s.strategy) revert NotStrategy();
        _;
    }

    /// DEPOSIT/WITHDRAW ///

    /**
     * @notice Proxies deposit of base assets from caller to associated SAFE,
     * and mints an equiv amount of accounting tokens
     * @param amount amount to deposit
     */
    function deposit(uint256 amount) external onlyStrategy {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        IERC20(s.baseAsset).safeTransferFrom(s.strategy, s.safe, amount);
        s.accountingToken.mintTo(s.strategy, amount);
    }

    /**
     * @notice Proxies withdraw of base assets from associated SAFE to caller,
     * and burns an equiv amount of accounting tokens
     * @param amount amount to deposit
     * @param recipient address to receive the base assets
     */
    function withdraw(uint256 amount, address recipient) external onlyStrategy {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        s.accountingToken.burnFrom(s.strategy, amount);
        IERC20(s.baseAsset).safeTransferFrom(s.safe, recipient, amount);
    }

    /// REWARDS ///

    /**
     * @notice Process rewards by minting accounting tokens
     * @param amount profits to mint
     */
    function processRewards(uint256 amount) external onlyRole(REWARDS_PROCESSOR_ROLE) checkAndResetCooldown {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        _processRewards(amount, s._snapshots.length - 1);
    }

    /**
     * @notice Process rewards by minting accounting tokens with specific snapshot index
     * @param amount profits to mint
     * @param snapshotIndex index of the snapshot to compare against
     */
    function processRewards(
        uint256 amount,
        uint256 snapshotIndex
    )
        external
        onlyRole(REWARDS_PROCESSOR_ROLE)
        checkAndResetCooldown
    {
        _processRewards(amount, snapshotIndex);
    }

    /**
     * @notice Internal function to process rewards with snapshot validation
     * @param amount profits to mint
     * @param snapshotIndex index of the snapshot to compare against
     *
     * @dev This function validates rewards by comparing current PPS against a historical snapshot.
     * Using a past snapshot (rather than the most recent) helps prevent APR manipulation
     * by smoothing out reward distribution over time.
     *
     *
     * Example with daily processRewards calls:
     *
     * Day 0: PPS = 100  [snapshot 0]
     * Day 1: PPS = 101  [snapshot 1]
     * Day 2: PPS = 102  [snapshot 2]
     * Day 3: PPS = 107  [snapshot 3] ← Big jump due to delayed rewards
     *
     * If we only compared Day 2→3 (102→107):
     *   Daily return: 4.9% → ~720% APR (exceeds cap)
     *
     * Instead, compare Day 0→3 (100→107):
     *   Daily return: ~2.3% → ~240% APR (within sustainable range)
     *
     * This approach provides flexibility by allowing irregular reward distributions
     * while still enforcing APR limits. By comparing against historical snapshots,
     * the system can accommodate delayed or lump-sum rewards without triggering
     * false positives, while maintaining protection against actual APR manipulation.
     */
    function _processRewards(uint256 amount, uint256 snapshotIndex) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        // check if snapshot index is valid
        if (snapshotIndex >= s._snapshots.length) revert SnapshotIndexOutOfBounds(snapshotIndex);

        uint256 totalSupply = s.accountingToken.totalSupply();
        if (totalSupply < s.minRewardableAssets) revert TvlTooLow();

        IVault strategyVault = IVault(s.strategy);

        s.accountingToken.mintTo(s.strategy, amount);
        strategyVault.processAccounting();

        // check if apr is within acceptable bounds

        StrategySnapshot memory previousSnapshot = s._snapshots[snapshotIndex];

        uint256 currentPricePerShare = createStrategySnapshot().pricePerShare;

        // Check if APR is within acceptable bounds
        uint256 aprSinceLastSnapshot = calculateApr(
            previousSnapshot.pricePerShare, previousSnapshot.timestamp, currentPricePerShare, block.timestamp
        );

        if (aprSinceLastSnapshot > s.targetApy) revert AccountingLimitsExceeded(aprSinceLastSnapshot, s.targetApy);
    }

    function createStrategySnapshot() internal returns (StrategySnapshot memory) {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        IVault strategyVault = IVault(s.strategy);

        // Take snapshot of current state
        uint256 currentPricePerShare = strategyVault.convertToAssets(10 ** strategyVault.decimals());

        StrategySnapshot memory snapshot = StrategySnapshot({
            timestamp: block.timestamp,
            pricePerShare: currentPricePerShare,
            totalSupply: strategyVault.totalSupply(),
            totalAssets: strategyVault.totalAssets()
        });

        s._snapshots.push(snapshot);

        return snapshot;
    }

    /**
     * @notice Calculate APR based on price per share changes over time
     * @param previousPricePerShare The price per share at the start of the period
     * @param previousTimestamp The timestamp at the start of the period
     * @param currentPricePerShare The price per share at the end of the period
     * @param currentTimestamp The timestamp at the end of the period
     * @return apr The calculated APR in basis points
     */
    function calculateApr(
        uint256 previousPricePerShare,
        uint256 previousTimestamp,
        uint256 currentPricePerShare,
        uint256 currentTimestamp
    )
        public
        pure
        returns (uint256 apr)
    {
        /*
        ppsStart - Price per share at the start of the period
        ppsEnd - Price per share at the end of the period
        t - Time period in years*
        Formula: (ppsEnd - ppsStart) / (ppsStart * t)
        */

        // Ensure timestamps are ordered (current should be after previous)
        if (currentTimestamp <= previousTimestamp) revert CurrentTimestampBeforePreviousTimestamp();

        // Prevent division by zero
        if (previousPricePerShare == 0) revert InvariantViolation();

        return (currentPricePerShare - previousPricePerShare) * YEAR * DIVISOR / previousPricePerShare
            / (currentTimestamp - previousTimestamp);
    }

    /// LOSS ///

    /**
     * @notice Process losses by burning accounting tokens
     * @param amount losses to burn
     */
    function processLosses(uint256 amount) external onlyRole(LOSS_PROCESSOR_ROLE) checkAndResetCooldown {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        uint256 totalSupply = s.accountingToken.totalSupply();
        if (totalSupply < 10 ** s.accountingToken.decimals()) revert TvlTooLow();

        // check bound on losses
        if (amount > totalSupply * s.lowerBound / DIVISOR) {
            revert LossLimitsExceeded(amount, totalSupply * s.lowerBound / DIVISOR);
        }

        s.accountingToken.burnFrom(s.strategy, amount);
        IVault(s.strategy).processAccounting();

        createStrategySnapshot();
    }

    /// ADMIN ///

    /**
     * @notice Set target APY to determine upper bound. e.g. 1000 = 10% APY
     * @param targetApyInBips in bips
     * @dev hard max of 100% targetApy
     */
    function setTargetApy(uint256 targetApyInBips) external onlyRole(SAFE_MANAGER_ROLE) {
        _setTargetApy(targetApyInBips);
    }

    /**
     * @notice Set lower bound as a function of tvl for losses. e.g. 1000 = 10% of tvl
     * @param _lowerBound in bips, as a function of % of tvl
     * @dev hard max of 50% of tvl
     */
    function setLowerBound(uint256 _lowerBound) external onlyRole(SAFE_MANAGER_ROLE) {
        _setLowerBound(_lowerBound);
    }

    /**
     * @notice Set cooldown in seconds between every processing of rewards/losses
     * @param cooldownSeconds_ new cooldown seconds
     */
    function setCooldownSeconds(uint16 cooldownSeconds_) external onlyRole(SAFE_MANAGER_ROLE) {
        _setCooldownSeconds(cooldownSeconds_);
    }

    /**
     * @notice Set a new safe address
     * @param newSafe new safe address
     */
    function setSafeAddress(address newSafe) external virtual onlyRole(SAFE_MANAGER_ROLE) {
        _setSafeAddress(newSafe);
    }

    /// ADMIN INTERNAL SETTERS ///

    function _setTargetApy(uint256 targetApyInBips) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (targetApyInBips > 10 * DIVISOR) revert InvariantViolation();

        emit TargetApyUpdated(targetApyInBips, s.targetApy);
        s.targetApy = targetApyInBips;
    }

    function _setLowerBound(uint256 _lowerBound) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (_lowerBound > MAX_LOWER_BOUND) revert InvariantViolation();

        emit LowerBoundUpdated(_lowerBound, s.lowerBound);
        s.lowerBound = _lowerBound;
    }

    function _setCooldownSeconds(uint16 cooldownSeconds_) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        emit CooldownSecondsUpdated(cooldownSeconds_, s.cooldownSeconds);
        s.cooldownSeconds = cooldownSeconds_;
    }

    function _setSafeAddress(address newSafe) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (newSafe == address(0)) revert ZeroAddress();
        emit SafeUpdated(newSafe, s.safe);
        s.safe = newSafe;
    }

    /// VIEWS ///

    function baseAsset() external view returns (address) {
        return _getAccountingModuleStorage().baseAsset;
    }

    function strategy() external view returns (address) {
        return _getAccountingModuleStorage().strategy;
    }

    function accountingToken() external view returns (IAccountingToken) {
        return _getAccountingModuleStorage().accountingToken;
    }

    function cooldownSeconds() external view returns (uint16) {
        return _getAccountingModuleStorage().cooldownSeconds;
    }

    function lowerBound() external view returns (uint256) {
        return _getAccountingModuleStorage().lowerBound;
    }

    function nextUpdateWindow() external view returns (uint64) {
        return _getAccountingModuleStorage().nextUpdateWindow;
    }

    function safe() external view returns (address) {
        return _getAccountingModuleStorage().safe;
    }

    function targetApy() external view returns (uint256) {
        return _getAccountingModuleStorage().targetApy;
    }

    function snapshotsLength() external view returns (uint256) {
        return _getAccountingModuleStorage()._snapshots.length;
    }

    function snapshots(uint256 index) external view returns (StrategySnapshot memory) {
        return _getAccountingModuleStorage()._snapshots[index];
    }

    function lastSnapshot() external view returns (StrategySnapshot memory) {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        return s._snapshots[s._snapshots.length - 1];
    }
}
"
    },
    "lib/yieldnest-flex-strategy/lib/yieldnest-vault/src/library/VaultLib.sol": {
      "content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;

import {IVault} from "src/interface/IVault.sol";
import {IProvider} from "src/interface/IProvider.sol";
import {Math, IERC20} from "src/Common.sol";
import {Guard} from "src/module/Guard.sol";

library VaultLib {
    using Math for uint256;

    /// @custom:storage-location erc7201:openzeppelin.storage.ERC20
    struct ERC20Storage {
        mapping(address account => uint256) balances;
        mapping(address account => mapping(address spender => uint256)) allowances;
        uint256 totalSupply;
        string name;
        string symbol;
    }

    /**
     * @notice Get the ERC20 storage.
     * @return $ The ERC20 storage.
     */
    function getERC20Storage() public pure returns (ERC20Storage storage $) {
        assembly {
            // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
            $.slot := 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00
        }
    }

    /**
     * @notice Get the vault storage.
     * @return $ The vault storage.
     */
    function getVaultStorage() public pure returns (IVault.VaultStorage storage $) {
        assembly {
            // keccak256("yieldnest.storage.vault")
            $.slot := 0x22cdba5640455d74cb7564fb236bbbbaf66b93a0cc1bd221f1ee2a6b2d0a2427
        }
    }

    /**
     * @notice Get the asset storage.
     * @return $ The asset storage.
     */
    function getAssetStorage() public pure returns (IVault.AssetStorage storage $) {
        assembly {
            // keccak256("yieldnest.storage.asset")
            $.slot := 0x2dd192a2474c87efcf5ffda906a4b4f8a678b0e41f9245666251cfed8041e680
        }
    }

    /**
     * @notice Get the processor storage.
     * @return $ The processor storage.
     */
    function getProcessorStorage() public pure returns (IVault.ProcessorStorage storage $) {
        assembly {
            // keccak256("yieldnest.storage.vault")
            $.slot := 0x52bb806a772c899365572e319d3d6f49ed2259348d19ab0da8abccd4bd46abb5
        }
    }

    /**
     * @notice Get the fee storage.
     * @return $ The fee storage.
     */
    function getFeeStorage() public pure returns (IVault.FeeStorage storage $) {
        assembly {
            // keccak256("yieldnest.storage.fees")
            $.slot := 0xde924653ae91bd33356774e603163bd5862c93462f31acccae5f965be6e6599b
        }
    }

    /**
     * @notice Adds a new asset to the vault.
     * @param asset_ The address of the asset.
     * @param active_ Whether the asset is active or not.
     */
    function addAsset(address asset_, uint8 decimals_, bool active_) public {
        if (asset_ == address(0)) {
            revert IVault.ZeroAddress();
        }

        IVault.AssetStorage storage assetStorage = getAssetStorage();
        uint256 index = assetStorage.list.length;

        IVault.VaultStorage storage vaultStorage = getVaultStorage();

        // if native asset is counted the Base Asset should match the decimals count.
        if (index == 0 && vaultStorage.countNativeAsset && decimals_ != 18) {
            revert IVault.InvalidNativeAssetDecimals(decimals_);
        }

        // If this is the first asset, check that its decimals are the same as the vault's decimals
        if (index == 0 && decimals_ != vaultStorage.decimals) {
            revert IVault.InvalidAssetDecimals(decimals_);
        }

        // If this is not the first asset, check that its decimals are not higher than the base asset
        if (index > 0) {
            uint8 baseAssetDecimals = assetStorage.assets[assetStorage.list[0]].decimals;
            if (decimals_ > baseAssetDecimals) {
                revert IVault.InvalidAssetDecimals(decimals_);
            }
        }

        // Check if trying to add the Base Asset again
        if (index > 0 && asset_ == assetStorage.list[0]) {
            revert IVault.DuplicateAsset(asset_);
        }

        if (index > 0 && assetStorage.assets[as

Tags:
ERC20, ERC165, Multisig, Mintable, Pausable, Staking, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory|addr:0x1b1eddf9d1cd632fa411fbce3d7e36aa4bb7d766|verified:true|block:23387129|tx:0x4a6af295d248b6991ae9af7d6ebb0892d6da8f1e65584e4bc75466f56994d5eb|first_check:1758189634

Submitted on: 2025-09-18 12:00:36

Comments

Log in to comment.

No comments yet.