// File: contracts/base/ScalingERC20.sol



//██████╗  █████╗ ██╗      █████╗ ██████╗ ██╗███╗   ██╗
//██╔══██╗██╔══██╗██║     ██╔══██╗██╔══██╗██║████╗  ██║
//██████╔╝███████║██║     ███████║██║  ██║██║██╔██╗ ██║
//██╔═══╝ ██╔══██║██║     ██╔══██║██║  ██║██║██║╚██╗██║
//██║     ██║  ██║███████╗██║  ██║██████╔╝██║██║ ╚████║
//╚═╝     ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚═════╝ ╚═╝╚═╝  ╚═══╝


pragma solidity 0.8.16;
//SPDX-License-Identifier: BUSL-1.1

import "../oz/interfaces/IERC20.sol";
import "../oz/utils/Context.sol";
import {Errors} from  "../utils/Errors.sol";
import {WadRayMath} from  "../utils/WadRayMath.sol";

/** @title ScalingERC20 contract
 *  @author Paladin, inspired by Aave & OpenZeppelin implementations
 *  @notice ERC20 implementation of scaled balance token
*/
abstract contract ScalingERC20 is Context, IERC20 {
    using WadRayMath for uint256;

    // Constants

    /** @notice 1e18 scale */
    uint256 public constant UNIT = 1e18;

    /** @notice 1e27 - RAY - Initial Index for balance to scaled balance */
    uint256 internal constant INITIAL_INDEX = 1e27;

    // Structs

    /** @notice UserState struct 
    *   scaledBalance: scaled balance of the user
    *   index: last index for the user
    */
    struct UserState {
        uint128 scaledBalance;
        uint128 index;
    }

    // Storage

    /** @notice Total scaled supply */
    uint256 internal _totalSupply;

    /** @notice Allowances for users */
    mapping(address => mapping(address => uint256)) internal _allowances;

    /** @notice Token name */
    string private _name;
    /** @notice Token symbol */
    string private _symbol;
    /** @notice Token decimals */
    uint8 private _decimals;

    /** @notice User states */
    mapping(address => UserState) internal _userStates;


    // Events

    /** @notice Event emitted when minting */
    event Mint(address indexed user, uint256 scaledAmount, uint256 index);
    /** @notice Event emitted when burning */
    event Burn(address indexed user, uint256 scaledAmount, uint256 index);


    // Constructor

    constructor(
        string memory __name,
        string memory __symbol
    ) {
        _name = __name;
        _symbol = __symbol;
        _decimals = 18;
    }


    // View methods

    /**
    * @notice Get the name of the ERC20
    * @return string : Name
    */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
    * @notice Get the symbol of the ERC20
    * @return string : Symbol
    */
    function symbol() external view returns (string memory) {
        return _symbol;
    }

    /**
    * @notice Get the decimals of the ERC20
    * @return uint256 : Number of decimals
    */
    function decimals() external view returns (uint8) {
        return _decimals;
    }

    /**
    * @notice Get the current total supply
    * @return uint256 : Current total supply
    */
    function totalSupply() public view override virtual returns (uint256) {
        uint256 _scaledSupply = _totalSupply;
        if(_scaledSupply == 0) return 0;
        return _scaledSupply.rayMul(_getCurrentIndex());
    }

    /**
    * @notice Get the current user balance
    * @param account Address of user
    * @return uint256 : User balance
    */
    function balanceOf(address account) public view override virtual returns (uint256) {
        return uint256(_userStates[account].scaledBalance).rayMul(_getCurrentIndex());
    }

    /**
    * @notice Get the current total scaled supply
    * @return uint256 : Current total scaled supply
    */
    function totalScaledSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
    * @notice Get the current user scaled balance
    * @param account Address of user
    * @return uint256 : User scaled balance
    */
    function scaledBalanceOf(address account) public view virtual returns (uint256) {
        return _userStates[account].scaledBalance;
    }

    /**
    * @notice Get the allowance of a spender for a given owner
    * @param owner Address of the owner
    * @param spender Address of the spender
    * @return uint256 : allowance amount
    */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }




    // Write methods

    /**
    * @notice Approve a spender to spend tokens
    * @param spender Address of the spender
    * @param amount Amount to approve
    * @return bool : success
    */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

    /**
    * @notice Increase the allowance given to a spender
    * @param spender Address of the spender
    * @param addedValue Increase amount
    * @return bool : success
    */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = msg.sender;
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
    * @notice Decrease the allowance given to a spender
    * @param spender Address of the spender
    * @param subtractedValue Decrease amount
    * @return bool : success
    */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = msg.sender;
        uint256 currentAllowance = allowance(owner, spender);
        if(currentAllowance < subtractedValue) revert Errors.ERC20_AllowanceUnderflow();
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
    * @notice Transfer tokens to the given recipient
    * @param recipient Address to receive the tokens
    * @param amount Amount to transfer
    * @return bool : success
    */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }

    /**
    * @notice Transfer tokens from the spender to the given recipient
    * @param sender Address sending the tokens
    * @param recipient Address to receive the tokens
    * @param amount Amount to transfer
    * @return bool : success
    */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        uint256 _allowance = _allowances[sender][msg.sender];
        if(_allowance < amount) revert Errors.ERC20_AmountOverAllowance();
        if(_allowance != type(uint256).max) {
            _approve(
                sender,
                msg.sender,
                _allowances[sender][msg.sender] - amount
            );
        }
        _transfer(sender, recipient, amount);
        emit Transfer(sender, recipient, amount);
        return true;
    }


    // Internal methods

    /**
    * @dev Get the current index to convert between balance and scaled balances
    * @return uint256 : Current index
    */
    // To implement in inheriting contract
    function _getCurrentIndex() internal virtual view returns(uint256) {}

    /**
    * @dev Approve a spender to spend tokens
    * @param owner Address of the woner
    * @param spender Address of the spender
    * @param amount Amount to approve
    */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        if(owner == address(0) || spender == address(0)) revert Errors.ERC20_ApproveAddressZero();

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
    * @dev Transfer tokens from the spender to the given recipient
    * @param sender Address sending the tokens
    * @param recipient Address to receive the tokens
    * @param amount Amount to transfer
    */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        if(sender == address(0) || recipient == address(0)) revert Errors.ERC20_AddressZero();
        if(sender == recipient) revert Errors.ERC20_SelfTransfer();
        if(amount == 0) revert Errors.ERC20_NullAmount();

        // Get the scaled amount to transfer for the given amount
        uint128 _scaledAmount = safe128(amount.rayDiv(_getCurrentIndex()));
        _transferScaled(sender, recipient, _scaledAmount);
    }

    /**
    * @dev Transfer the scaled amount of tokens
    * @param sender Address sending the tokens
    * @param recipient Address to receive the tokens
    * @param scaledAmount Scaled amount to transfer
    */
    function _transferScaled(
        address sender,
        address recipient,
        uint128 scaledAmount
    ) internal virtual {
        if(scaledAmount > _userStates[sender].scaledBalance) revert Errors.ERC20_AmountExceedBalance();

        _beforeTokenTransfer(sender, recipient, scaledAmount);

        unchecked {
            // Should never fail because of previous check
            // & because the scaledBalance of an user should never exceed the _totalSupply
            _userStates[sender].scaledBalance -= scaledAmount;
            _userStates[recipient].scaledBalance += scaledAmount;
        }

        _afterTokenTransfer(sender, recipient, scaledAmount);
    }

    /**
    * @dev Mint the given amount to the given address (by minting the correct scaled amount)
    * @param account Address to mint to
    * @param amount Amount to mint
    * @param _currentIndex Index to use to calculate the scaled amount
    * @return uint256 : Amount minted
    */
    function _mint(address account, uint256 amount, uint256 _currentIndex) internal virtual returns(uint256) {
        uint256 _scaledAmount = amount.rayDiv(_currentIndex);
        if(_scaledAmount == 0) revert Errors.ERC20_NullAmount();

        _beforeTokenTransfer(address(0), account, _scaledAmount);

        _userStates[account].index = safe128(_currentIndex);

        _totalSupply += _scaledAmount;
        _userStates[account].scaledBalance += safe128(_scaledAmount);

        _afterTokenTransfer(address(0), account, _scaledAmount);

        emit Mint(account, _scaledAmount, _currentIndex);
        emit Transfer(address(0), account, amount);

        return amount;
    }

    /**
    * @dev Burn the given amount from the given address (by burning the correct scaled amount)
    * @param account Address to burn from
    * @param amount Amount to burn
    * @param maxWithdraw True if burning the full balance
    * @return uint256 : Amount burned
    */
    function _burn(address account, uint256 amount, bool maxWithdraw) internal virtual returns(uint256) {
        uint256 _currentIndex = _getCurrentIndex();
        uint256 _scaledBalance = _userStates[account].scaledBalance;

        // if given maxWithdraw as true, we want to burn the whole balance for the user
        uint256 _scaledAmount = maxWithdraw ?  _scaledBalance: amount.rayDiv(_currentIndex);
        if(_scaledAmount == 0) revert Errors.ERC20_NullAmount();
        if(_scaledAmount > _scaledBalance) revert Errors.ERC20_AmountExceedBalance();

        _beforeTokenTransfer(account, address(0), _scaledAmount);
        
        _userStates[account].index = safe128(_currentIndex);

        _totalSupply -= _scaledAmount;
        _userStates[account].scaledBalance -= safe128(_scaledAmount);

        _afterTokenTransfer(account, address(0), _scaledAmount);
        
        emit Burn(account, _scaledAmount, _currentIndex);
        emit Transfer(account, address(0), amount);

        return amount;
    }


    // Virtual hooks

    /**
    * @dev Hook executed before each transfer
    * @param from Sender address
    * @param to Receiver address
    * @param amount Amount to transfer
    */
    // To implement in inheriting contract
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
    * @dev Hook executed after each transfer
    * @param from Sender address
    * @param to Receiver address
    * @param amount Amount to transfer
    */
    // To implement in inheriting contract
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}


    // Maths

    function safe128(uint256 n) internal pure returns (uint128) {
        if(n > type(uint128).max) revert Errors.NumberExceed128Bits();
        return uint128(n);
    }


}
// File: contracts/DullahanVault.sol



//██████╗  █████╗ ██╗      █████╗ ██████╗ ██╗███╗   ██╗
//██╔══██╗██╔══██╗██║     ██╔══██╗██╔══██╗██║████╗  ██║
//██████╔╝███████║██║     ███████║██║  ██║██║██╔██╗ ██║
//██╔═══╝ ██╔══██║██║     ██╔══██║██║  ██║██║██║╚██╗██║
//██║     ██║  ██║███████╗██║  ██║██████╔╝██║██║ ╚████║
//╚═╝     ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚═════╝ ╚═╝╚═╝  ╚═══╝


pragma solidity 0.8.16;
//SPDX-License-Identifier: BUSL-1.1

import "./base/ScalingERC20.sol";
import "./interfaces/IERC4626.sol";
import "./oz/interfaces/IERC20.sol";
import "./oz/libraries/SafeERC20.sol";
import "./oz/utils/ReentrancyGuard.sol";
import "./oz/utils/Pausable.sol";
import "./interfaces/IStakedAave.sol";
import "./interfaces/IGovernancePowerDelegationToken.sol";
import {Errors} from "./utils/Errors.sol";
import {WadRayMath} from  "./utils/WadRayMath.sol";

/** @title DullahanVault contract
 *  @author Paladin
 *  @notice Main Dullahan Vault. IERC4626 compatible & ScalingERC20 token
 */
contract DullahanVault is IERC4626, ScalingERC20, ReentrancyGuard, Pausable {
    using SafeERC20 for IERC20;
    using WadRayMath for uint256;

    // Constants

    /** @notice Max value for BPS - 100% */
    uint256 public constant MAX_BPS = 10000;
    /** @notice Max value possible for an uint256 */
    uint256 public constant MAX_UINT256 = 2**256 - 1;

    /** @notice Amount to deposit to seed the Vault during initialization */
    uint256 private constant SEED_DEPOSIT = 0.001 ether;

    /** @notice Address of the stkAAVE token */
    address public immutable STK_AAVE;
    /** @notice Address of the AAVE token */
    address public immutable AAVE;


    // Struct

    /** @notice PodsManager struct 
    *   rentingAllowed: Is the Manager allowed to rent from the Vault
    *   totalRented: Total amount rented to the Manager (based on the AAVE max total supply, should be safe)
    */
    struct PodsManager {
        bool rentingAllowed;
        uint248 totalRented;
    }


    // Storage

    /** @notice Is the Vault initialized */
    bool public initialized;

    /** @notice Address of the Vault admin */
    address public admin;
    /** @notice Address of the Vault pending admin */
    address public pendingAdmin;

    /** @notice Total amount of stkAAVE rented to Pod Managers */
    uint256 public totalRentedAmount;

    /** @notice Pod Manager states */
    mapping(address => PodsManager) public podManagers;

    /** @notice Address receiving the delegated voting power from the Vault */
    address public votingPowerManager;
    /** @notice Address receiving the delegated proposal power from the Vault */
    address public proposalPowerManager;

    /** @notice Percentage of funds to stay in the contract for withdraws */
    uint256 public bufferRatio = 500;

    /** @notice Amount accrued as Reserve */
    uint256 public reserveAmount;
    /** @notice Ratio of claimed rewards to be set as Reserve */
    uint256 public reserveRatio;
    /** @notice Address of the Reserve Manager */
    address public reserveManager;


    // Events

    /** @notice Event emitted when the Vault is initialized */
    event Initialized();

    /** @notice Event emitted when stkAAVE is rented to a Pod */
    event RentToPod(address indexed manager, address indexed pod, uint256 amount);
    /** @notice Event emitted when stkAAVE claim is notified by a Pod */
    event NotifyRentedAmount(address indexed manager, address indexed pod, uint256 addedAmount);
    /** @notice Event emitted when stkAAVE is pulled back from a Pod */
    event PullFromPod(address indexed manager, address indexed pod, uint256 amount);

    /** @notice Event emitted when the adminship is transfered */
    event AdminTransferred(
        address indexed previousAdmin,
        address indexed newAdmin
    );
    /** @notice Event emitted when a new pending admin is set */
    event NewPendingAdmin(
        address indexed previousPendingAdmin,
        address indexed newPendingAdmin
    );

    /** @notice Event emitted when a new Pod Manager is added */
    event NewPodManager(address indexed newManager);
    /** @notice Event emitted when a Pod Manager is blocked */
    event BlockedPodManager(address indexed manager);

    /** @notice Event emitted when depositing in the Reserve */
    event ReserveDeposit(address indexed from, uint256 amount);
    /** @notice Event emitted when withdrawing from the Reserve */
    event ReserveWithdraw(address indexed to, uint256 amount);

    /** @notice Event emitted when the Voting maanger is updated */
    event UpdatedVotingPowerManager(address indexed oldManager, address indexed newManager);
    /** @notice Event emitted when the Proposal maanger is updated */
    event UpdatedProposalPowerManager(address indexed oldManager, address indexed newManager);
    /** @notice Event emitted when the Reserve manager is updated */
    event UpdatedReserveManager(address indexed oldManager, address indexed newManager);
    /** @notice Event emitted when the Buffer ratio is updated */
    event UpdatedBufferRatio(uint256 oldRatio, uint256 newRatio);
    /** @notice Event emitted when the Reserve ratio is updated */
    event UpdatedReserveRatio(uint256 oldRatio, uint256 newRatio);

    /** @notice Event emitted when an ERC20 token is recovered */
    event TokenRecovered(address indexed token, uint256 amount);


    // Modifers

    /** @notice Check that the caller is the admin */
    modifier onlyAdmin() {
        if (msg.sender != admin) revert Errors.CallerNotAdmin();
        _;
    }

    /** @notice Check that the caller is the admin or the Reserve maanger */
    modifier onlyAllowed() {
        if (msg.sender != admin && msg.sender != reserveManager) revert Errors.CallerNotAdmin();
        _;
    }

    /** @notice Check that the contract is initialized */
    modifier isInitialized() {
        if (!initialized) revert Errors.NotInitialized();
        _;
    }


    // Constructor

    constructor(
        address _admin,
        uint256 _reserveRatio,
        address _reserveManager,
        address _aave,
        address _stkAave,
        string memory _name,
        string memory _symbol
    ) ScalingERC20(_name, _symbol) {
        if(_admin == address(0) || _reserveManager == address(0) || _aave == address(0) || _stkAave == address(0)) revert Errors.AddressZero();
        if(_reserveRatio == 0) revert Errors.NullAmount();

        admin = _admin;

        reserveRatio = _reserveRatio;
        reserveManager = _reserveManager;

        AAVE = _aave;
        STK_AAVE = _stkAave;
    }

    /**
    * @notice Initialize the Vault
    * @dev Initialize the Vault by performing a seed deposit & delegating voting power
    * @param _votingPowerManager Address to receive the voting power delegation
    * @param _proposalPowerManager Address to receive the proposal power delegation
    */
    function init(address _votingPowerManager, address _proposalPowerManager) external onlyAdmin {
        if(initialized) revert Errors.AlreadyInitialized();

        initialized = true;

        votingPowerManager = _votingPowerManager;
        proposalPowerManager = _proposalPowerManager;

        // Seed deposit to prevent 1 wei LP token exploit
        _deposit(
            SEED_DEPOSIT,
            msg.sender,
            msg.sender
        );

        // Set the delegates, so any received token updates the delegates power
        IGovernancePowerDelegationToken(STK_AAVE).delegateByType(
            _votingPowerManager,
            IGovernancePowerDelegationToken.DelegationType.VOTING_POWER
        );
        IGovernancePowerDelegationToken(STK_AAVE).delegateByType(
            _proposalPowerManager,
            IGovernancePowerDelegationToken.DelegationType.PROPOSITION_POWER
        );

        emit Initialized();
    }


    // View functions

    /**
    * @notice Get the vault's asset
    * @return address : Address of the asset
    */
    function asset() external view returns (address) {
        return STK_AAVE;
    }

    /**
    * @notice Get the total amount of assets in the Vault
    * @return uint256 : total amount of assets
    */
    function totalAssets() public view returns (uint256) {
        return
            IERC20(STK_AAVE).balanceOf(address(this)) +
            totalRentedAmount -
            reserveAmount;
    }

    /**
    * @notice Get the total supply of shares
    * @return uint256 : Total supply of shares
    */
    function totalSupply()
        public
        view
        override(ScalingERC20, IERC20)
        returns (uint256)
    {
        return totalAssets();
    }

    /**
    * @notice Get the current total amount of asset available in the Vault
    * @return uint256 : Current total amount available
    */
    function totalAvailable() public view returns (uint256) {
        uint256 availableBalance = IERC20(STK_AAVE).balanceOf(address(this));
        availableBalance = reserveAmount >= availableBalance ? 0 : availableBalance - reserveAmount;
        uint256 bufferAmount = (totalAssets() * bufferRatio) / MAX_BPS;
        return availableBalance > bufferAmount ? availableBalance - bufferAmount : 0;
    }

    /**
    * @notice Convert a given amount of assets to shares
    * @param assets amount of assets
    * @return uint256 : amount of shares
    */
    function convertToShares(uint256 assets) public pure returns (uint256) {
        // Because we use a ScalingERC20, shares of the user will grow over time to match the owed assets
        // (assets & shares are always 1:1)
        return assets;
    }

    /**
    * @notice Convert a given amount of shares to assets
    * @param shares amount of shares
    * @return uint256 : amount of assets
    */
    function convertToAssets(uint256 shares) public pure returns (uint256) {
        // Because we use a ScalingERC20, shares of the user will grow over time to match the owed assets
        // (assets & shares are always 1:1)
        return shares;
    }

    /**
    * @notice Return the amount of shares expected for depositing the given assets
    * @param assets Amount of assets to be deposited
    * @return uint256 : amount of shares
    */
    function previewDeposit(uint256 assets) public pure returns (uint256) {
        // Because we use a ScalingERC20, shares of the user will grow over time to match the owed assets
        // (assets & shares are always 1:1)
        return assets;
    }

    /**
    * @notice Return the amount of assets expected for minting the given shares
    * @param shares Amount of shares to be minted
    * @return uint256 : amount of assets
    */
    function previewMint(uint256 shares) public pure returns (uint256) {
        // Because we use a ScalingERC20, shares of the user will grow over time to match the owed assets
        // (assets & shares are always 1:1)
        return shares;
    }

    /**
    * @notice Return the amount of shares expected for withdrawing the given assets
    * @param assets Amount of assets to be withdrawn
    * @return uint256 : amount of shares
    */
    function previewWithdraw(uint256 assets) public pure returns (uint256) {
        // Because we use a ScalingERC20, shares of the user will grow over time to match the owed assets
        // (assets & shares are always 1:1)
        return assets;
    }

    /**
    * @notice Return the amount of assets expected for burning the given shares
    * @param shares Amount of shares to be burned
    * @return uint256 : amount of assets
    */
    function previewRedeem(uint256 shares) public pure returns (uint256) {
        // Because we use a ScalingERC20, shares of the user will grow over time to match the owed assets
        // (assets & shares are always 1:1)
        return shares;
    }

    /**
    * @notice Get the maximum amount that can be deposited by the user
    * @param user User address
    * @return uint256 : Max amount to deposit
    */
    function maxDeposit(address user) public view returns (uint256) {
        return type(uint256).max;
    }

    /**
    * @notice Get the maximum amount that can be minted by the user
    * @param user User address
    * @return uint256 : Max amount to mint
    */
    function maxMint(address user) public view returns (uint256) {
        return type(uint256).max;
    }

    /**
    * @notice Get the maximum amount that can be withdrawn by the user
    * @param owner Owner address
    * @return uint256 : Max amount to withdraw
    */
    function maxWithdraw(address owner) public view returns (uint256) {
        return balanceOf(owner);
    }

    /**
    * @notice Get the maximum amount that can be burned by the user
    * @param owner Owner address
    * @return uint256 : Max amount to burn
    */
    function maxRedeem(address owner) public view returns (uint256) {
        return balanceOf(owner);
    }

    /**
    * @notice Get the current index to convert between balance and scaled balances
    * @return uint256 : Current index
    */
    function getCurrentIndex() public view returns(uint256) {
        return _getCurrentIndex();
    }

    /**
    * @notice Get the current delegates for the Vault voting power & proposal power
    */
    function getDelegates() external view returns(address votingPower, address proposalPower) {
        return (votingPowerManager, proposalPowerManager);
    }


    // State-changing functions

    /**
    * @notice Deposit assets in the Vault & mint shares
    * @param assets Amount to deposit
    * @param receiver Address to receive the shares
    * @return shares - uint256 : Amount of shares minted
    */
    function deposit(
        uint256 assets,
        address receiver
    ) public isInitialized nonReentrant whenNotPaused returns (uint256 shares) {
        (assets, shares) = _deposit(assets, receiver, msg.sender);

        emit Deposit(msg.sender, receiver, assets, shares);
    }

    /**
    * @notice Mint vault shares by depositing assets
    * @param shares Amount of shares to mint
    * @param receiver Address to receive the shares
    * @return assets - uint256 : Amount of assets deposited
    */
    function mint(
        uint256 shares,
        address receiver
        ) public isInitialized nonReentrant whenNotPaused returns (uint256 assets) {
        (assets, shares) = _deposit(shares, receiver, msg.sender);

        emit Deposit(msg.sender, receiver, assets, shares);
    }

    /**
    * @notice Withdraw from the Vault & burn shares
    * @param assets Amount of assets to withdraw
    * @param receiver Address to receive the assets
    * @param owner Address of the owner of the shares
    * @return shares - uint256 : Amount of shares burned
    */
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public isInitialized nonReentrant returns (uint256 shares) {
        (uint256 _withdrawn, uint256 _burntShares) = _withdraw(
            assets,
            owner,
            receiver,
            msg.sender
        );

        emit Withdraw(msg.sender, receiver, owner, _withdrawn, _burntShares);
        return _burntShares;
    }

    /**
    * @notice Burn shares to withdraw from the Vault
    * @param shares Amount of shares to burn
    * @param receiver Address to receive the assets
    * @param owner Address of the owner of the shares
    * @return assets - uint256 : Amount of assets withdrawn
    */
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) public isInitialized nonReentrant returns (uint256 assets) {
        (uint256 _withdrawn, uint256 _burntShares) = _withdraw(
            shares,
            owner,
            receiver,
            msg.sender
        );

        emit Withdraw(msg.sender, receiver, owner, _withdrawn, _burntShares);
        return _withdrawn;
    }

    /**
    * @notice Claim Safety Module rewards & stake them in stkAAVE
    */
    function updateStkAaveRewards() external nonReentrant {
        _getStkAaveRewards();
    }


    // Pods Manager functions

    /**
    * @notice Rent stkAAVE for a Pod
    * @dev Rent stkAAVE to a Pod, sending the amount & tracking the manager that requested 
    * @param pod Address of the Pod
    * @param amount Amount to rent
    */
    function rentStkAave(address pod, uint256 amount) external nonReentrant {
        address manager = msg.sender;
        if(!podManagers[manager].rentingAllowed) revert Errors.CallerNotAllowedManager();
        if(pod == address(0)) revert Errors.AddressZero();
        if(amount == 0) revert Errors.NullAmount();

        // Fetch Aave Safety Module rewards & stake them into stkAAVE
        _getStkAaveRewards();

        IERC20 _stkAave = IERC20(STK_AAVE);

        // Check that the asked amount is available :
        // - Vault has enough asset for the asked amount
        // - Asked amount does not include the buffer allocated to withdraws
        uint256 availableBalance = _stkAave.balanceOf(address(this));
        availableBalance = reserveAmount >= availableBalance ? 0 : availableBalance - reserveAmount;
        uint256 bufferAmount = (totalAssets() * bufferRatio) / MAX_BPS;
        if(availableBalance < bufferAmount) revert Errors.WithdrawBuffer();
        if(amount > (availableBalance - bufferAmount)) revert Errors.NotEnoughAvailableFunds();

        // Track the amount rented for the manager that requested it
        // & send the token to the Pod
        podManagers[manager].totalRented += safe248(amount);
        totalRentedAmount += amount;
        _stkAave.safeTransfer(pod, amount);
    
        emit RentToPod(manager, pod, amount);
    }

    /**
    * @notice Notify a claim on rented stkAAVE
    * @dev Notify the newly claimed rewards from rented stkAAVE to a Pod & add it as rented to the Pod
    * @param pod Address of the Pod
    * @param addedAmount Amount added
    */
    // To track pods stkAave claims & re-stake into the main balance for ScalingeRC20 logic
    function notifyRentedAmount(address pod, uint256 addedAmount) external nonReentrant {
        address manager = msg.sender;
        if(podManagers[manager].totalRented == 0) revert Errors.NotUndebtedManager();
        if(pod == address(0)) revert Errors.AddressZero();
        if(addedAmount == 0) revert Errors.NullAmount();

        // Update the total amount rented & the amount rented for the specific
        // maanger with the amount claimed from Aave's Safety Module via the Pod
        podManagers[manager].totalRented += safe248(addedAmount);
        totalRentedAmount += addedAmount;

        // Add the part taken as fees to the Reserve
        reserveAmount += (addedAmount * reserveRatio) / MAX_BPS;

        emit NotifyRentedAmount(manager, pod, addedAmount);
    }

    /**
    * @notice Pull rented stkAAVE from a Pod
    * @dev Pull stkAAVE from a Pod & update the tracked rented amount
    * @param pod Address of the Pod
    * @param amount Amount to pull
    */
    function pullRentedStkAave(address pod, uint256 amount) external nonReentrant {
        address manager = msg.sender;
        if(podManagers[manager].totalRented == 0) revert Errors.NotUndebtedManager();
        if(pod == address(0)) revert Errors.AddressZero();
        if(amount == 0) revert Errors.NullAmount();
        if(amount > podManagers[manager].totalRented) revert Errors.AmountExceedsDebt();

        // Fetch Aave Safety Module rewards & stake them into stkAAVE
        _getStkAaveRewards();

        // Pull the tokens from the Pod, and update the tracked rented amount for the
        // corresponding Manager
        podManagers[manager].totalRented -= safe248(amount);
        totalRentedAmount -= amount;
        // We consider that pod give MAX_UINT256 allowance to this contract when created
        IERC20(STK_AAVE).safeTransferFrom(pod, address(this), amount);

        emit PullFromPod(manager, pod, amount);
    }


    // Internal functions

    /**
    * @dev Get the current index to convert between balance and scaled balances
    * @return uint256 : Current index
    */
    function _getCurrentIndex() internal view override returns (uint256) {
        if(_totalSupply == 0) return INITIAL_INDEX;
        return totalAssets().rayDiv(_totalSupply);
    }

    /**
    * @dev Pull assets to deposit in the Vault & mint shares
    * @param amount Amount to deposit
    * @param receiver Address to receive the shares
    * @param depositor Address depositing the assets
    * @return uint256 : Amount of assets deposited
    * @return uint256 : Amount of shares minted
    */
    function _deposit(
        uint256 amount,
        address receiver,
        address depositor
    ) internal returns (uint256, uint256) {
        if (receiver == address(0)) revert Errors.AddressZero();
        if (amount == 0) revert Errors.NullAmount();

        // Fetch Aave Safety Module rewards & stake them into stkAAVE
        _getStkAaveRewards();

        // We need to get the index before pulling the assets
        // so we can have the correct one based on previous stkAave claim
        uint256 _currentIndex = _getCurrentIndex();

        // Pull tokens from the depositor
        IERC20(STK_AAVE).safeTransferFrom(depositor, address(this), amount);

        // Mint the scaled balance of the user to match the deposited amount
        uint256 minted = _mint(receiver, amount, _currentIndex);

        afterDeposit(amount);

        return (amount, minted);
    }

    /**
    * @dev Withdraw assets from the Vault & send to the receiver & burn shares
    * @param amount Amount to withdraw
    * @param owner Address owning the shares
    * @param receiver Address to receive the assets
    * @param sender Address of the caller
    * @return uint256 : Amount of assets withdrawn
    * @return uint256 : Amount of shares burned
    */
    function _withdraw(
        uint256 amount, // if `MAX_UINT256`, just withdraw everything
        address owner,
        address receiver,
        address sender
    ) internal returns (uint256, uint256) {
        if (receiver == address(0) || owner == address(0)) revert Errors.AddressZero();
        if (amount == 0) revert Errors.NullAmount();

        // Fetch Aave Safety Module rewards & stake them into stkAAVE
        _getStkAaveRewards();

        // If the user wants to withdraw their full balance
        bool _maxWithdraw;
        if(amount == MAX_UINT256) {
            amount = balanceOf(owner);
            _maxWithdraw = true;
        }

        // Check that the caller has the allowance to withdraw for the given owner
        if (owner != sender) {
            uint256 allowed = _allowances[owner][sender];
            if (allowed < amount) revert Errors.ERC20_AmountOverAllowance();
            if (allowed != type(uint256).max)
                _allowances[owner][sender] = allowed - amount;
        }

        IERC20 _stkAave = IERC20(STK_AAVE);

        // Check that the Vault has enough stkAave to send
        uint256 availableBalance = _stkAave.balanceOf(address(this));
        availableBalance = reserveAmount >= availableBalance ? 0 : availableBalance - reserveAmount;
        if(amount > availableBalance) revert Errors.NotEnoughAvailableFunds();

        // Burn the scaled balance matching the amount to withdraw
        uint256 burned = _burn(owner, amount, _maxWithdraw);

        beforeWithdraw(amount);

        // Send the tokens to the given receiver
        _stkAave.safeTransfer(receiver, amount);

        return (amount, burned);
    }

    /**
    * @dev Hook exectued before withdrawing
    * @param amount Amount to withdraw
    */
    function beforeWithdraw(uint256 amount) internal {}

    /**
    * @dev Hook exectued after depositing
    * @param amount Amount deposited
    */
    function afterDeposit(uint256 amount) internal {}

    /**
    * @dev Hook executed before each transfer
    * @param from Sender address
    * @param to Receiver address
    * @param amount Amount to transfer
    */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal isInitialized whenNotPaused override virtual {
        super._beforeTokenTransfer(from, to, amount);
    }

    /**
    * @dev Hook executed after each transfer
    * @param from Sender address
    * @param to Receiver address
    * @param amount Amount to transfer
    */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override virtual {
        super._afterTokenTransfer(from, to, amount);
    }

    /**
    * @dev Claim AAVE rewards from the Safety Module & stake them to receive stkAAVE
    */
    function _getStkAaveRewards() internal {
        IStakedAave _stkAave = IStakedAave(STK_AAVE);

        // Get pending rewards amount
        uint256 pendingRewards = _stkAave.getTotalRewardsBalance(address(this));

        if (pendingRewards > 0) {
            // Claim the AAVE tokens
            _stkAave.claimRewards(address(this), pendingRewards);

            // Set a part of the claimed amount as the Reserve (protocol fees)
            reserveAmount += (pendingRewards * reserveRatio) / MAX_BPS;
        }

        IERC20 _aave = IERC20(AAVE);
        uint256 currentBalance = _aave.balanceOf(address(this));
        
        if(currentBalance > 0) {
            // Increase allowance for the Safety Module & stake AAVE into stkAAVE
            _aave.safeIncreaseAllowance(STK_AAVE, currentBalance);
            _stkAave.stake(address(this), currentBalance);
        }
    }


    // Admin 
    
    /**
     * @notice Pause the contract
     */
    function pause() external onlyAdmin {
        _pause();
    }

    /**
     * @notice Unpause the contract
     */
    function unpause() external onlyAdmin {
        _unpause();
    }

    /**
    * @notice Set a given address as the new pending admin
    * @param newAdmin Address to be the new admin
    */
    function transferAdmin(address newAdmin) external onlyAdmin {
        if (newAdmin == address(0)) revert Errors.AddressZero();
        if (newAdmin == admin) revert Errors.CannotBeAdmin();
        address oldPendingAdmin = pendingAdmin;

        pendingAdmin = newAdmin;

        emit NewPendingAdmin(oldPendingAdmin, newAdmin);
    }

    /**
    * @notice Accpet adminship of the contract (must be the pending admin)
    */
    function acceptAdmin() external {
        if (msg.sender != pendingAdmin) revert Errors.CallerNotPendingAdmin();
        address newAdmin = pendingAdmin;
        address oldAdmin = admin;
        admin = newAdmin;
        pendingAdmin = address(0);

        emit AdminTransferred(oldAdmin, newAdmin);
        emit NewPendingAdmin(newAdmin, address(0));
    }

    /**
    * @notice Add a new Pod Manager
    * @param newManager Address of the new manager
    */
    function addPodManager(address newManager) external onlyAdmin {
        if(newManager == address(0)) revert Errors.AddressZero();
        if(podManagers[newManager].rentingAllowed) revert Errors.ManagerAlreadyListed();

        podManagers[newManager].rentingAllowed = true;

        emit NewPodManager(newManager);
    }

    /**
    * @notice Block a Pod Manager
    * @param manager Address of the manager
    */
    function blockPodManager(address manager) external onlyAdmin {
        if(manager == address(0)) revert Errors.AddressZero();
        if(!podManagers[manager].rentingAllowed) revert Errors.ManagerNotListed();

        podManagers[manager].rentingAllowed = false;

        emit BlockedPodManager(manager);
    }

    /**
    * @notice Update the Vault's voting power manager & delegate the voting power to it
    * @param newManager Address of the new manager
    */
    function updateVotingPowerManager(address newManager) external onlyAdmin {
        if(newManager == address(0)) revert Errors.AddressZero();
        if(newManager == votingPowerManager) revert Errors.SameAddress();

        address oldManager = votingPowerManager;
        votingPowerManager = newManager;

        IGovernancePowerDelegationToken(STK_AAVE).delegateByType(
            newManager,
            IGovernancePowerDelegationToken.DelegationType.VOTING_POWER
        );

        emit UpdatedVotingPowerManager(oldManager, newManager);
    }

    /**
    * @notice Update the Vault's proposal power manager & delegate the proposal power to it
    * @param newManager Address of the new manager
    */
    function updateProposalPowerManager(address newManager) external onlyAdmin {
        if(newManager == address(0)) revert Errors.AddressZero();
        if(newManager == proposalPowerManager) revert Errors.SameAddress();

        address oldManager = proposalPowerManager;
        proposalPowerManager = newManager;

        IGovernancePowerDelegationToken(STK_AAVE).delegateByType(
            newManager,
            IGovernancePowerDelegationToken.DelegationType.PROPOSITION_POWER
        );

        emit UpdatedProposalPowerManager(oldManager, newManager);
    }

    /**
    * @notice Update the Vault's Reserve manager
    * @param newManager Address of the new manager
    */
    function updateReserveManager(address newManager) external onlyAdmin {
        if(newManager == address(0)) revert Errors.AddressZero();
        if(newManager == reserveManager) revert Errors.SameAddress();

        address oldManager = reserveManager;
        reserveManager = newManager;

        emit UpdatedReserveManager(oldManager, newManager);
    }

    /**
    * @notice Uodate the reserve ratio parameter
    * @param newRatio New ratio value
    */
    function updateReserveRatio(uint256 newRatio) external onlyAdmin {
        if(newRatio > 1500) revert Errors.InvalidParameter();

        uint256 oldRatio = reserveRatio;
        reserveRatio = newRatio;

        emit UpdatedReserveRatio(oldRatio, newRatio);
    }

    /**
    * @notice Uodate the buffer ratio parameter
    * @param newRatio New ratio value
    */
    function updateBufferRatio(uint256 newRatio) external onlyAdmin {
        if(newRatio > 1500) revert Errors.InvalidParameter();

        uint256 oldRatio = bufferRatio;
        bufferRatio = newRatio;

        emit UpdatedBufferRatio(oldRatio, newRatio);
    }

    /**
     * @notice Deposit token in the reserve
     * @param amount Amount of token to deposit
     */
    function depositToReserve(uint256 amount) external nonReentrant onlyAllowed returns(bool) {
        if(amount == 0) revert Errors.NullAmount();

        // Fetch Aave Safety Module rewards & stake them into stkAAVE
        _getStkAaveRewards();

        IERC20(STK_AAVE).safeTransferFrom(msg.sender, address(this), amount);
        reserveAmount += amount;

        emit ReserveDeposit(msg.sender, amount);

        return true;
    }

    /**
     * @notice Withdraw tokens from the reserve to send to the given receiver
     * @param amount Amount of token to withdraw
     * @param receiver Address to receive the tokens
     */
    function withdrawFromReserve(uint256 amount, address receiver) external nonReentrant onlyAllowed returns(bool) {
        if(amount == 0) revert Errors.NullAmount();
        if(receiver == address(0)) revert Errors.AddressZero();
        if(amount > reserveAmount) revert Errors.ReserveTooLow();

        // Fetch Aave Safety Module rewards & stake them into stkAAVE
        _getStkAaveRewards();

        IERC20(STK_AAVE).safeTransfer(receiver, amount);
        reserveAmount -= amount;

        emit ReserveWithdraw(receiver, amount);

        return true;
    }

    /**
    * @notice Recover ERC2O tokens sent by mistake to the contract
    * @dev Recover ERC2O tokens sent by mistake to the contract
    * @param token Address of the ERC2O token
    * @return bool: success
    */
    function recoverERC20(address token) external onlyAdmin returns(bool) {
        if(token == AAVE || token == STK_AAVE) revert Errors.CannotRecoverToken();

        uint256 amount = IERC20(token).balanceOf(address(this));
        if(amount == 0) revert Errors.NullAmount();
        IERC20(token).safeTransfer(admin, amount);

        emit TokenRecovered(token, amount);

        return true;
    }

    // Maths

    function safe248(uint256 n) internal pure returns (uint248) {
        if(n > type(uint248).max) revert Errors.NumberExceed248Bits();
        return uint248(n);
    }

}
// File: contracts/interfaces/IERC4626.sol



pragma solidity 0.8.16;
//SPDX-License-Identifier: MIT

import "../oz/interfaces/IERC20.sol";

interface IERC4626 is IERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Deposit(
        address indexed caller,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    event Withdraw(
        address indexed caller,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /*//////////////////////////////////////////////////////////////
                               IMMUTABLES
    //////////////////////////////////////////////////////////////*/

    function asset() external view returns (address);

    /*//////////////////////////////////////////////////////////////
                        DEPOSIT/WITHDRAWAL LOGIC
    //////////////////////////////////////////////////////////////*/

    function deposit(uint256 assets, address receiver)
        external
        returns (uint256 shares);

    function mint(uint256 shares, address receiver)
        external
        returns (uint256 assets);

    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) external returns (uint256 shares);

    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) external returns (uint256 assets);

    /*//////////////////////////////////////////////////////////////
                            ACCOUNTING LOGIC
    //////////////////////////////////////////////////////////////*/

    function totalAssets() external view returns (uint256);

    function convertToShares(uint256 assets) external view returns (uint256);

    function convertToAssets(uint256 shares) external view returns (uint256);

    function previewDeposit(uint256 assets) external view returns (uint256);

    function previewMint(uint256 shares) external view returns (uint256);

    function previewWithdraw(uint256 assets) external view returns (uint256);

    function previewRedeem(uint256 shares) external view returns (uint256);

    /*//////////////////////////////////////////////////////////////
                     DEPOSIT/WITHDRAWAL LIMIT LOGIC
    //////////////////////////////////////////////////////////////*/

    function maxDeposit(address) external view returns (uint256);

    function maxMint(address) external view returns (uint256);

    function maxWithdraw(address owner) external view returns (uint256);

    function maxRedeem(address owner) external view returns (uint256);

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HOOKS LOGIC
    //////////////////////////////////////////////////////////////*/
    /*
    function beforeWithdraw(uint256 assets, uint256 shares) internal {}
    function afterDeposit(uint256 assets, uint256 shares) internal {}
    */
}

// File: contracts/interfaces/IGovernancePowerDelegationToken.sol



//Aave Governance Token Interface

// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.16;

interface IGovernancePowerDelegationToken {
    enum DelegationType {
        VOTING_POWER,
        PROPOSITION_POWER
    }

    /**
     * @dev emitted when a user delegates to another
     * @param delegator the delegator
     * @param delegatee the delegatee
     * @param delegationType the type of delegation (VOTING_POWER, PROPOSITION_POWER)
     **/
    event DelegateChanged(
        address indexed delegator,
        address indexed delegatee,
        DelegationType delegationType
    );

    /**
     * @dev emitted when an action changes the delegated power of a user
     * @param user the user which delegated power has changed
     * @param amount the amount of delegated power for the user
     * @param delegationType the type of delegation (VOTING_POWER, PROPOSITION_POWER)
     **/
    event DelegatedPowerChanged(
        address indexed user,
        uint256 amount,
        DelegationType delegationType
    );

    /**
     * @dev delegates the specific power to a delegatee
     * @param delegatee the user which delegated power has changed
     * @param delegationType the type of delegation (VOTING_POWER, PROPOSITION_POWER)
     **/
    function delegateByType(address delegatee, DelegationType delegationType)
        external;

    /**
     * @dev delegates all the powers to a specific user
     * @param delegatee the user to which the power will be delegated
     **/
    function delegate(address delegatee) external;

    /**
     * @dev returns the delegatee of an user
     * @param delegator the address of the delegator
     **/
    function getDelegateeByType(
        address delegator,
        DelegationType delegationType
    ) external view returns (address);

    /**
     * @dev returns the current delegated power of a user. The current power is the
     * power delegated at the time of the last snapshot
     * @param user the user
     **/
    function getPowerCurrent(address user, DelegationType delegationType)
        external
        view
        returns (uint256);

    /**
     * @dev returns the delegated power of a user at a certain block
     * @param user the user
     **/
    function getPowerAtBlock(
        address user,
        uint256 blockNumber,
        DelegationType delegationType
    ) external view returns (uint256);

    /**
     * @dev returns the total supply at a certain block number
     **/
    function totalSupplyAt(uint256 blockNumber) external view returns (uint256);
}

// File: contracts/interfaces/IStakedAave.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.16;

interface IStakedAave {

  event Staked(
    address indexed from,
    address indexed to,
    uint256 assets,
    uint256 shares
  );
  event Redeem(
    address indexed from,
    address indexed to,
    uint256 assets,
    uint256 shares
  );

  event RewardsAccrued(address user, uint256 amount);
  event RewardsClaimed(address indexed from, address indexed to, uint256 amount);

  event Cooldown(address indexed user);

  function stake(address to, uint256 amount) external;

  function redeem(address to, uint256 amount) external;

  function cooldown() external;

  function claimRewards(address to, uint256 amount) external;

  function getTotalRewardsBalance(address staker) external view returns (uint256);
}
// File: contracts/oz/interfaces/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev 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);

    /**
     * @dev 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);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev 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.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * 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.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}
// File: contracts/oz/libraries/SafeERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../interfaces/IERC20.sol";
import "../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;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    /**
     * @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, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}
// File: contracts/oz/utils/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}
// File: contracts/oz/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}
// File: contracts/oz/utils/Pausable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)

pragma solidity ^0.8.0;

import "./Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}
// File: contracts/oz/utils/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}
// File: contracts/utils/Errors.sol



pragma solidity 0.8.16;
//SPDX-License-Identifier: MIT

library Errors {

    // Common Errors
    error AddressZero();
    error NullAmount();
    error IncorrectRewardToken();
    error SameAddress();
    error InequalArraySizes();
    error EmptyArray();
    error EmptyParameters();
    error NotInitialized();
    error AlreadyInitialized();
    error CannotInitialize();
    error InvalidParameter();
    error CannotRecoverToken();
    error NullWithdraw();
    error AlreadyListedManager();
    error NotListedManager();

    // Access Control Erros
    error CallerNotAdmin();
    error CannotBeAdmin();
    error CallerNotPendingAdmin();
    error CallerNotAllowed();

    // ERC20 Errors
    error ERC20_ApproveAddressZero();
    error ERC20_AllowanceUnderflow();
    error ERC20_AmountOverAllowance();
    error ERC20_AddressZero();
    error ERC20_SelfTransfer();
    error ERC20_NullAmount();
    error ERC20_AmountExceedBalance();

    // Maths Errors
    error NumberExceed96Bits();
    error NumberExceed128Bits();
    error NumberExceed248Bits();

    // Vault Errors
    error ManagerAlreadyListed();
    error ManagerNotListed();
    error NotEnoughAvailableFunds();
    error WithdrawBuffer();
    error ReserveTooLow();
    error CallerNotAllowedManager();
    error NotUndebtedManager();
    error AmountExceedsDebt();

    // Vaults Rewards Errors
    error NullScaledAmount();
    error AlreadyListedDepositor();
    error NotListedDepositor();
    error ClaimNotAllowed();

    // Pods Errors
    error NotPodOwner();
    error NotPodManager();
    error FailPodStateUpdate();
    error MintAmountUnderMinimum();
    error RepayFailed();

    // Pods Manager Errors
    error CallerNotValidPod();
    error CollateralBlocked();
    error MintingAllowanceFailed();
    error FreeingStkAaveFailed();
    error CollateralAlreadyListed();
    error CollateralNotListed();
    error CollateralNotAllowed();
    error PodInvalid();
    error FailStateUpdate();
    error PodNotLiquidable();

    // Registry Errors
    error VaultAlreadySet();

    // Zap Errors
    error InvalidSourceToken();
    error DepositFailed();

    // Wrapper Errors
    error NullConvertedAmount();

}
// File: contracts/utils/WadRayMath.sol



// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.0;

/// @title WadRayMath.
/// @author Morpho Labs.
/// @custom:contact security@morpho.xyz
/// @notice Optimized version of Aave V3 math library WadRayMath to conduct wad and ray manipulations: https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/libraries/math/WadRayMath.sol
library WadRayMath {
    /// CONSTANTS ///

    uint256 internal constant WAD = 1e18;
    uint256 internal constant HALF_WAD = 0.5e18;
    uint256 internal constant RAY = 1e27;
    uint256 internal constant HALF_RAY = 0.5e27;
    uint256 internal constant WAD_RAY_RATIO = 1e9;
    uint256 internal constant HALF_WAD_RAY_RATIO = 0.5e9;
    uint256 internal constant MAX_UINT256 = 2**256 - 1; // Not possible to use type(uint256).max in yul.
    uint256 internal constant MAX_UINT256_MINUS_HALF_WAD = 2**256 - 1 - 0.5e18;
    uint256 internal constant MAX_UINT256_MINUS_HALF_RAY = 2**256 - 1 - 0.5e27;

    /// INTERNAL ///

    /// @dev Multiplies two wad, rounding half up to the nearest wad.
    /// @param x Wad.
    /// @param y Wad.
    /// @return z The result of x * y, in wad.
    function wadMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        // Let y > 0
        // Overflow if (x * y + HALF_WAD) > type(uint256).max
        // <=> x * y > type(uint256).max - HALF_WAD
        // <=> x > (type(uint256).max - HALF_WAD) / y
        assembly {
            if mul(y, gt(x, div(MAX_UINT256_MINUS_HALF_WAD, y))) {
                revert(0, 0)
            }

            z := div(add(mul(x, y), HALF_WAD), WAD)
        }
    }

    /// @dev Divides two wad, rounding half up to the nearest wad.
    /// @param x Wad.
    /// @param y Wad.
    /// @return z The result of x / y, in wad.
    function wadDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        // Overflow if y == 0
        // Overflow if (x * WAD + y / 2) > type(uint256).max
        // <=> x * WAD > type(uint256).max - y / 2
        // <=> x > (type(uint256).max - y / 2) / WAD
        assembly {
            z := div(y, 2)
            if iszero(mul(y, iszero(gt(x, div(sub(MAX_UINT256, z), WAD))))) {
                revert(0, 0)
            }

            z := div(add(mul(WAD, x), z), y)
        }
    }

    /// @dev Multiplies two ray, rounding half up to the nearest ray.
    /// @param x Ray.
    /// @param y Ray.
    /// @return z The result of x * y, in ray.
    function rayMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        // Let y > 0
        // Overflow if (x * y + HALF_RAY) > type(uint256).max
        // <=> x * y > type(uint256).max - HALF_RAY
        // <=> x > (type(uint256).max - HALF_RAY) / y
        assembly {
            if mul(y, gt(x, div(MAX_UINT256_MINUS_HALF_RAY, y))) {
                revert(0, 0)
            }

            z := div(add(mul(x, y), HALF_RAY), RAY)
        }
    }

    /// @dev Divides two ray, rounding half up to the nearest ray.
    /// @param x Ray.
    /// @param y Ray.
    /// @return z The result of x / y, in ray.
    function rayDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        // Overflow if y == 0
        // Overflow if (x * RAY + y / 2) > type(uint256).max
        // <=> x * RAY > type(uint256).max - y / 2
        // <=> x > (type(uint256).max - y / 2) / RAY
        assembly {
            z := div(y, 2)
            if iszero(mul(y, iszero(gt(x, div(sub(MAX_UINT256, z), RAY))))) {
                revert(0, 0)
            }

            z := div(add(mul(RAY, x), z), y)
        }
    }

    /// @dev Casts ray down to wad.
    /// @param x Ray.
    /// @return y = x converted to wad, rounded half up to the nearest wad.
    function rayToWad(uint256 x) internal pure returns (uint256 y) {
        assembly {
            // If x % WAD_RAY_RATIO >= HALF_WAD_RAY_RATIO, round up.
            y := add(div(x, WAD_RAY_RATIO), iszero(lt(mod(x, WAD_RAY_RATIO), HALF_WAD_RAY_RATIO)))
        }
    }

    /// @dev Converts wad up to ray.
    /// @param x Wad.
    /// @return y = x converted in ray.
    function wadToRay(uint256 x) internal pure returns (uint256 y) {
        assembly {
            y := mul(WAD_RAY_RATIO, x)
            // Revert if y / WAD_RAY_RATIO != x
            if iszero(eq(div(y, WAD_RAY_RATIO), x)) {
                revert(0, 0)
            }
        }
    }
}