// 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/DullahanPod.sol



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


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

import "./oz/interfaces/IERC20.sol";
import "./oz/libraries/SafeERC20.sol";
import "./oz/utils/ReentrancyGuard.sol";
import "./interfaces/IDullahanPodManager.sol";
import "./DullahanVault.sol";
import "./modules/DullahanRegistry.sol";
import "./interfaces/IStakedAave.sol";
import "./interfaces/IAavePool.sol";
import "./interfaces/IGovernancePowerDelegationToken.sol";
import "./interfaces/IAaveRewardsController.sol";
import {Errors} from "./utils/Errors.sol";

/** @title Dullahan Pod contract
 *  @author Paladin
 *  @notice Dullahan Pod, unique to each user, allowing to depoist collateral,
 *          rent stkAAVE from the Dullahan Vault & borrow GHO
 */
contract DullahanPod is ReentrancyGuard {
    using SafeERC20 for IERC20;

    // Constants

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

    /** @notice Minimum allowed amount of GHO to mint */
    uint256 public constant MIN_MINT_AMOUNT = 1e9;

    // Storage

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

    /** @notice Address of the Pod manager */
    address public manager;
    /** @notice Address of the Vault */
    address public vault;
    /** @notice Address of the Registry */
    address public registry;

    /** @notice Address of the Pod owner */
    address public podOwner;

    /** @notice Address of the delegate receiving the Pod voting power */
    address public votingPowerDelegate;
    /** @notice Address of the delegate receiving the Pod proposal power */
    address public proposalPowerDelegate;

    /** @notice Address of the collateral in the Pod */
    address public collateral;
    /** @notice Address of the aToken for the collateral */
    address public aToken;

    /** @notice Address of the AAVE token */
    address public aave;
    /** @notice Address of the stkAAVE token */
    address public stkAave;


    // Events

    /** @notice Event emitted when the Pod is initialized */
    event PodInitialized(
        address indexed podManager,
        address indexed collateral,
        address indexed podOwner,
        address vault,
        address registry
    );

    /** @notice Event emitted when collateral is deposited */
    event CollateralDeposited(address indexed collateral, uint256 amount);
    /** @notice Event emitted when collateral is withdrawn */
    event CollateralWithdrawn(address indexed collateral, uint256 amount);
    /** @notice Event emitted when collateral is liquidated */
    event CollateralLiquidated(address indexed collateral, uint256 amount);

    /** @notice Event emitted when GHO is minted */
    event GhoMinted(uint256 mintedAmount);
    /** @notice Event emitted when GHO is repayed */
    event GhoRepayed(uint256 amountToRepay);

    /** @notice Event emitted when stkAAVE is rented by the Pod */
    event RentedStkAave();

    /** @notice Event emitted when the Pod delegates are updated */
    event UpdatedDelegate(address indexed newVotingDelegate, address indexed newProposalDelegate);
    /** @notice Event emitted when the Pod registry is updated */
    event UpdatedRegistry(address indexed oldRegistry, address indexed newRegistry);


    // Modifers

    /** @notice Check that the caller is the Pod owner */
    modifier onlyPodOwner() {
        if(msg.sender != podOwner) revert Errors.NotPodOwner();
        _;
    }

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

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


    // Constructor

    constructor() {
        manager = address(0xdEaD);
        vault = address(0xdEaD);
        registry = address(0xdEaD);
        collateral = address(0xdEaD);
        podOwner = address(0xdEaD);
        votingPowerDelegate = address(0xdEaD);
        proposalPowerDelegate = address(0xdEaD);
    }

    /**
    * @notice Initialize the Pod with the given parameters
    * @param _manager Address of the Manager
    * @param _vault Address of the Vault
    * @param _registry Address of the Registry
    * @param _podOwner Address of the Pod owner
    * @param _collateral Address of the collateral
    * @param _aToken Address of the aToken for the collateral
    * @param _votingPowerDelegate Address of the delegate for the voting power
    * @param _proposalPowerDelegate Address of the delegate for the proposal power
    */
    function init(
        address _manager,
        address _vault,
        address _registry,
        address _podOwner,
        address _collateral,
        address _aToken,
        address _votingPowerDelegate,
        address _proposalPowerDelegate
    ) external {
        if(initialized) revert Errors.AlreadyInitialized();
        if(manager == address(0xdEaD)) revert Errors.CannotInitialize();
        if(
            _manager == address(0)
            || _vault == address(0)
            || _registry == address(0)
            || _podOwner == address(0)
            || _collateral == address(0)
            || _aToken == address(0)
            || _votingPowerDelegate == address(0)
            || _proposalPowerDelegate == address(0)
        ) revert Errors.AddressZero();

        initialized = true;
        
        manager = _manager;
        vault = _vault;
        registry = _registry;
        podOwner = _podOwner;
        collateral = _collateral;
        votingPowerDelegate = _votingPowerDelegate;
        proposalPowerDelegate = _proposalPowerDelegate;

        aToken = _aToken;

        // Fetch the stkAAVE address from the Registry
        address _stkAave = DullahanRegistry(_registry).STK_AAVE();
        stkAave = _stkAave;
        aave = DullahanRegistry(registry).AAVE();

        // Set full allowance for the Vault to be able to pull back the stkAAVE rented to this Pod
        IERC20(_stkAave).safeIncreaseAllowance(_vault, type(uint256).max);

        // Set the Delegates for this Pod's voting power
        IGovernancePowerDelegationToken(_stkAave).delegateByType(
            _votingPowerDelegate,
            IGovernancePowerDelegationToken.DelegationType.VOTING_POWER
        );
        IGovernancePowerDelegationToken(_stkAave).delegateByType(
            _proposalPowerDelegate,
            IGovernancePowerDelegationToken.DelegationType.PROPOSITION_POWER
        );

        emit PodInitialized(_manager, _collateral, _podOwner, _vault, _registry);
    }


    // View functions

    /**
    * @notice Get the Pod's current collateral balance
    * @return uint256 : Current collateral balance
    */
    function podCollateralBalance() external view returns(uint256) {
        return IERC20(aToken).balanceOf(address(this));
    }

    /**
    * @notice Get the Pod's current GHO debt balance
    * @return uint256 : Current GHO debt balance
    */
    function podDebtBalance() public view returns(uint256) {
        return IERC20(DullahanRegistry(registry).DEBT_GHO()).balanceOf(address(this));
    }

    /**
    * @notice Get the stored amount of fees owed by this Pod
    * @return uint256 : Stored amount of fees owed
    */
    function podOwedFees() external view returns(uint256) {
        return IDullahanPodManager(manager).podOwedFees(address(this));
    }


    // State-changing functions

    /**
    * @notice Deposit collateral
    * @dev Pull collateral in the Pod to deposit it in the Aave Pool
    * @param amount Amount to deposit
    */
    function depositCollateral(uint256 amount) external nonReentrant isInitialized onlyPodOwner {
        if(amount == 0) revert Errors.NullAmount();
        if(!IDullahanPodManager(manager).updatePodState(address(this))) revert Errors.FailPodStateUpdate();

        IERC20 _collateral = IERC20(collateral);
        // Pull the collateral from the Pod Owner
        _collateral.safeTransferFrom(msg.sender, address(this), amount);

        // And deposit it in the Aave Pool
        address _aavePool = DullahanRegistry(registry).AAVE_POOL_V3();
        _collateral.safeIncreaseAllowance(_aavePool, amount);
        IAavePool(_aavePool).supply(address(_collateral), amount, address(this), 0);

        emit CollateralDeposited(address(_collateral), amount);
    }

    /**
    * @notice Withdraw collateral
    * @param amount Amount to withdraw
    * @param receiver Address to receive the collateral
    */
    // Can give MAX_UINT256 to withdraw full balance
    function withdrawCollateral(uint256 amount, address receiver) external nonReentrant isInitialized onlyPodOwner {
        if(amount == 0) revert Errors.NullAmount();
        if(receiver == address(0)) revert Errors.AddressZero();
        if(!IDullahanPodManager(manager).updatePodState(address(this))) revert Errors.FailPodStateUpdate();
        
        _withdrawCollateral(amount, receiver);
    }

    /**
    * @notice Claim any existing rewards from the Aave Rewards Controller for this Pod
    * @param receiver Address to receive the rewards
    */
    function claimAaveExtraRewards(address receiver) external nonReentrant isInitialized onlyPodOwner {
        if(receiver == address(0)) revert Errors.AddressZero();
        address[] memory assets = new address[](2);
        assets[0] = aToken;
        assets[1] = DullahanRegistry(registry).DEBT_GHO();
        // Claim any rewards accrued via the Aave Pool & send them directly to the given receiver
        IAaveRewardsController(DullahanRegistry(registry).AAVE_REWARD_COONTROLLER()).claimAllRewards(assets, receiver);
    }

    /**
    * @notice Claim Safety Module rewards & stake them in stkAAVE
    */
    function compoundStkAave() external nonReentrant isInitialized {
        // Claim Aave Safety Module rewards for this Pod & stake them into stkAAVE directly
        _getStkAaveRewards();
    }

    /**
    * @notice Mint GHO & rent stkAAVE
    * @dev Rent stkAAVE from the Vault & mint GHO with the best interest rate discount possible
    * @param amountToMint Amount of GHO to be minted
    * @param receiver Address to receive the minted GHO
    * @return mintedAmount - uint256 : amount of GHO minted after fees
    */
    function mintGho(uint256 amountToMint, address receiver) external nonReentrant isInitialized onlyPodOwner returns(uint256 mintedAmount) {
        if(amountToMint == 0) revert Errors.NullAmount();
        if(receiver == address(0)) revert Errors.AddressZero();
        if(amountToMint < MIN_MINT_AMOUNT) revert Errors.MintAmountUnderMinimum();
        IDullahanPodManager _manager = IDullahanPodManager(manager);
        if(!_manager.updatePodState(address(this))) revert Errors.FailPodStateUpdate();

        // Update this contract stkAAVE current balance is there is one
        _getStkAaveRewards();

        // Ask to rent stkAAVE based on amount wanted to be minted / already minted
        // (will also take care of reverting if the Pod is not allowed to mint or can't receive stkAAVE)
        if(!_manager.getStkAave(amountToMint)) revert Errors.MintingAllowanceFailed();

        emit RentedStkAave();

        // Mint GHO from the Aave Pool, with the Variable mode
        address _ghoAddress = DullahanRegistry(registry).GHO();
        IAavePool(DullahanRegistry(registry).AAVE_POOL_V3()).borrow(_ghoAddress, amountToMint, 2, 0, address(this)); // 2 => variable mode (might need to change that)

        // Take the protocol minting fees & send them to the Pod Manager & notify it
        // & Send the rest of the minted GHO to the given receiver
        IERC20 _gho = IERC20(_ghoAddress);
        uint256 mintFeeRatio = _manager.mintFeeRatio();
        uint256 mintFeeAmount = (amountToMint * mintFeeRatio) / MAX_BPS;
        _gho.safeTransfer(manager, mintFeeAmount);
        _manager.notifyMintingFee(mintFeeAmount);

        mintedAmount = amountToMint - mintFeeAmount;
        _gho.safeTransfer(receiver, mintedAmount);

        emit GhoMinted(mintedAmount);
    }

    /**
    * @notice Repay GHO fees and debt
    * @param amountToRepay Amount of GHO to de repaid
    * @return bool : Success
    */
    // Can give MAX_UINT256 to repay everything (needs max allowance)
    function repayGho(uint256 amountToRepay) external nonReentrant isInitialized onlyPodOwner returns(bool) {
        if(amountToRepay == 0) revert Errors.NullAmount();
        if(!IDullahanPodManager(manager).updatePodState(address(this))) revert Errors.FailPodStateUpdate();
        
        return _repayGho(amountToRepay);
    }

    /**
    * @notice Repay GHO fees and debt & withdraw collateral
    * @dev Repay GHO fees & debt to be allowed to withdraw collateral
    * @param repayAmount Amount of GHO to de repaid
    * @param withdrawAmount Amount to withdraw
    * @param receiver Address to receive the collateral
    * @return bool : Success
    */
    function repayGhoAndWithdrawCollateral(
        uint256 repayAmount,
        uint256 withdrawAmount,
        address receiver
    ) external nonReentrant isInitialized onlyPodOwner returns(bool) {
        if(repayAmount == 0 || withdrawAmount == 0) revert Errors.NullAmount();
        if(receiver == address(0)) revert Errors.AddressZero();
        if(!IDullahanPodManager(manager).updatePodState(address(this))) revert Errors.FailPodStateUpdate();

        bool repaySuccess = _repayGho(repayAmount);
        if(!repaySuccess) revert Errors.RepayFailed();
        
        _withdrawCollateral(withdrawAmount, receiver);

        return repaySuccess;
    }

    /**
    * @notice Rent stkAAVE from the Vault to get the best interest rate reduction
    * @return bool : Success
    */
    function rentStkAave() external nonReentrant isInitialized onlyPodOwner returns(bool) {
        IDullahanPodManager _manager = IDullahanPodManager(manager);
        if(!_manager.updatePodState(address(this))) revert Errors.FailPodStateUpdate();

        // Update this contract stkAAVE current balance is there is one
        _getStkAaveRewards();

        // Ask to rent stkAAVE based on amount wanted to be minted / already minted
        // (will also take care of reverting if the Pod is not allowed to mint or can't receive stkAAVE)
        if(!IDullahanPodManager(manager).getStkAave(0)) revert Errors.MintingAllowanceFailed();

        emit RentedStkAave();

        return true;
    }


    // Manager only functions

    /**
    * @notice Liquidate Pod collateral to repay owed fees
    * @dev Liquidate Pod collateral to repay owed fees, in the case the this Pod got liquidated on Aave market, and fees are still owed to Dullahan
    * @param amount Amount of collateral to liquidate
    * @param receiver Address to receive the collateral
    */
    function liquidateCollateral(uint256 amount, address receiver) external nonReentrant isInitialized onlyManager {
        if(amount == 0) return;

        // Withdraw the amount to be liquidated from the aave Pool
        // (Using MAX_UINT256 here will withdraw everything)
        IAavePool(DullahanRegistry(registry).AAVE_POOL_V3()).withdraw(collateral, amount, address(this));

        // Send the tokens to the liquidator (here the given receiver)
        IERC20(collateral).safeTransfer(receiver, amount);

        emit CollateralLiquidated(collateral, amount);
    }

    /**
    * @notice Update the Pod's delegate address & delegate the voting power to it
    * @param newVotingDelegate Address of the new voting power delegate
    * @param newProposalDelegate Address of the new proposal power delegate
    */
    function updateDelegation(address newVotingDelegate, address newProposalDelegate) external isInitialized onlyManager {
        if(newVotingDelegate == address(0) || newProposalDelegate == address(0)) revert Errors.AddressZero();

        votingPowerDelegate = newVotingDelegate;
        proposalPowerDelegate = newProposalDelegate;

        // Update the delegation to the new Delegate
        IGovernancePowerDelegationToken(stkAave).delegateByType(
            newVotingDelegate,
            IGovernancePowerDelegationToken.DelegationType.VOTING_POWER
        );
        IGovernancePowerDelegationToken(stkAave).delegateByType(
            newProposalDelegate,
            IGovernancePowerDelegationToken.DelegationType.PROPOSITION_POWER
        );

        emit UpdatedDelegate(newVotingDelegate, newProposalDelegate);
    }

    /**
    * @notice Update the Pod's Registry address
    * @param newRegistry Address of the new Registry
    */
    function updateRegistry(address newRegistry) external isInitialized onlyManager {
        if(newRegistry == address(0)) revert Errors.AddressZero();
        if(newRegistry == registry) revert Errors.SameAddress();

        address oldRegistry = registry;
        registry = newRegistry;

        emit UpdatedRegistry(oldRegistry, newRegistry);
    }


    // Internal functions

    /**
    * @dev Withdraw collateral from the Aave Pool directly to the given receiver (only if Pod fees are fully repaid)
    * @param amount Amount to withdraw
    * @param receiver Address to receive the collateral
    */
    function _withdrawCollateral(uint256 amount, address receiver) internal {
        // Not allowed to withdraw collateral before paying the owed fees,
        // in case we need to liquidate part if this collateral to pay
        // the fees owed by this Pod.
        if(
            IDullahanPodManager(manager).podOwedFees(address(this)) > 0
        ) revert Errors.CollateralBlocked();

        // Withdraw from the Aave Pool & send directly to the given receiver
        // If given MAX_UINT256, we want to withdraw all the collateral
        uint256 withdrawnAmount = IAavePool(DullahanRegistry(registry).AAVE_POOL_V3()).withdraw(collateral, amount, receiver);

        emit CollateralWithdrawn(collateral, withdrawnAmount);
    }
    
    /**
    * @dev Repay GHO owed fees & debt (fees in priority)
    * @param amountToRepay Amount of GHO to be repayed
    * @return bool : Success
    */
    function _repayGho(uint256 amountToRepay) internal returns(bool) {
        IDullahanPodManager _manager = IDullahanPodManager(manager);

        // Update this contract stkAAVE current balance is there is one
        _getStkAaveRewards();

        // Fetch the current owed fees for this Pod from the Pod Manager
        uint256 owedFees = _manager.podOwedFees(address(this));
        uint256 variableDebt = podDebtBalance();

        // If given the MAX_UINT256, we want to repay the fees and all the debt
        if(amountToRepay == MAX_UINT256) {
            amountToRepay = owedFees + variableDebt;
        }

        // Pull the GHO from the Pod Owner
        IERC20 _gho = IERC20(DullahanRegistry(registry).GHO());
        _gho.safeTransferFrom(msg.sender, address(this), amountToRepay);

        uint256 realRepayAmount;
        uint256 feesToPay;

        // Repay in priority the owed fees, and then the debt to the Aave Pool
        if(owedFees >= amountToRepay) {
            feesToPay = amountToRepay;
        } else {
            realRepayAmount = amountToRepay == MAX_UINT256 ? MAX_UINT256 : amountToRepay - owedFees;
            feesToPay = owedFees;
        }

        // If there is owed fees to pay, transfer the needed amount to the Pod Manager & notify it
        if(feesToPay > 0) {
            _gho.safeTransfer(manager, feesToPay);
            _manager.notifyPayFee(feesToPay);
        }

        // If there is GHO debt to be repayed, increase allowance to the Aave Pool and repay the debt
        if(realRepayAmount > 0) {
            address _aavePool = DullahanRegistry(registry).AAVE_POOL_V3();
            if(_gho.allowance(address(this), _aavePool) != 0) _gho.safeApprove(_aavePool, 0);
            _gho.safeIncreaseAllowance(_aavePool, realRepayAmount);
            IAavePool(_aavePool).repay(address(_gho), realRepayAmount, 2, address(this)); // 2 => variable mode (might need to change that)
        }

        // Notify the Pod Manager, so not needed stkAave in this Pod
        // can be freed & pull back by the Vaut
        if(!_manager.freeStkAave(address(this))) revert Errors.FreeingStkAaveFailed();

        // Send back any remaining GHO from in the Pod to the caller
        uint256 remainingGho = _gho.balanceOf(address(this));
        if(remainingGho > 0) {
            _gho.safeTransfer(msg.sender, remainingGho);
        }

        emit GhoRepayed(amountToRepay);

        return true;
    }

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

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

        if(pendingRewards == 0) return;

        // Claim the AAVE tokens
        _stkAave.claimRewards(address(this), pendingRewards);

        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(address(_stkAave), currentBalance);
            _stkAave.stake(address(this), currentBalance);

            // Notify the Pod Manager fro the new amount staked, so the tracking of
            // the Pod rented amount & fees on that claim can be updated in the Vault
            IDullahanPodManager(manager).notifyStkAaveClaim(currentBalance);
        }
    }



}
// File: contracts/DullahanPodManager.sol



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


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

import "./oz/interfaces/IERC20.sol";
import "./oz/libraries/SafeERC20.sol";
import "./oz/utils/ReentrancyGuard.sol";
import "./oz/utils/Pausable.sol";
import "./utils/Owner.sol";
import "./oz/libraries/Clones.sol";
import "./DullahanVault.sol";
import "./DullahanPod.sol";
import "./modules/DullahanRegistry.sol";
import "./interfaces/IDullahanRewardsStaking.sol";
import "./interfaces/IFeeModule.sol";
import "./interfaces/IOracleModule.sol";
import "./interfaces/IDiscountCalculator.sol";
import {Errors} from "./utils/Errors.sol";

/** @title DullahanPodManager contract
 *  @author Paladin
 *  @notice Dullahan Pod Manager: allows to deploy new Pods & handles the stkAAVE renting
 *          allocations & fee system
 */
contract DullahanPodManager is ReentrancyGuard, Pausable, Owner {
    using SafeERC20 for IERC20;

    // Constants

    /** @notice 1e18 scale */
    uint256 public constant UNIT = 1e18;
    /** @notice Max value for BPS - 100% */
    uint256 public constant MAX_BPS = 10000;


    // Struct

    /** @notice Pod struct 
    *   podAddress: Address of the Pod contract
    *   podOwner: Address of the Pod owner
    *   collateral: Address of the collateral token
    *   lastUpdate: Last update timestamp for the Pod state
    *   lastIndex: Last updated index for the Pod state
    *   rentedAmount: Current total amount of stkAAVE rented to the Pod
    *   accruedFees: Current amount of fees owed by the Pod
    */
    struct Pod {
        address podAddress;
        address podOwner;
        address collateral;
        uint96 lastUpdate;
        uint256 lastIndex;
        uint256 rentedAmount;
        uint256 accruedFees;
    }


    // Storage

    /** @notice Address of the Dullahan Vault */
    address public immutable vault;

    /** @notice Address of the Dullahan Staking contract */
    address public immutable rewardsStaking;

    /** @notice Address of the Pod implementation */
    address public immutable podImplementation;

    /** @notice Address of the Chest to receive fees */
    address public protocolFeeChest;

    /** @notice Address of the Dullahan Registry */
    address public registry;

    /** @notice Allowed token to be used as collaterals */
    mapping(address => bool) public allowedCollaterals;
    /** @notice Address of aToken from the Aave Market for each collateral */
    mapping(address => address) public aTokenForCollateral;

    /** @notice State for Pods */
    mapping(address => Pod) public pods;
    /** @notice List of all created Pods */
    address[] public allPods;
    /** @notice List of Pods created by an user */
    mapping(address => address[]) public ownerPods;

    /** @notice Address of the Fee Module */
    address public feeModule;
    /** @notice Address of the Oracle Module */
    address public oracleModule;
    /** @notice Address of the Discount Calculator Module */
    address public discountCalculator;

    /** @notice Last updated value of the Index */
    uint256 public lastUpdatedIndex;
    /** @notice Last update timestamp for the Index */
    uint256 public lastIndexUpdate;

    /** @notice Extra ratio applied during liquidations */
    uint256 public extraLiquidationRatio = 500; // BPS: 5%
    /** @notice Ratio of minted amount taken as minting fees */
    uint256 public mintFeeRatio = 25; // BPS: 0.25%
    /** @notice Ratio of renting fees taken as protocol fees */
    uint256 public protocolFeeRatio = 1000; // BPS: 10%

    /** @notice Total amount set as reserve (holding Vault renting fees) */
    uint256 public reserveAmount;
    /** @notice Min amount in the reserve to be processed */
    uint256 public processThreshold = 500e18;


    // Events

    /** @notice Event emitted when a new Pod is created */
    event PodCreation(
        address indexed collateral,
        address indexed podOwner,
        address indexed pod
    );

    /** @notice Event emitted when stkAAVE is clawed back from a Pod */
    event FreedStkAave(address indexed pod, uint256 pullAmount);
    /** @notice Event emitted when stkAAVE is rented to a Pod */
    event RentedStkAave(address indexed pod, uint256 rentAmount);

    /** @notice Event emitted when a Pod is liquidated */
    event LiquidatedPod(address indexed pod, address indexed collateral, uint256 collateralAmount, uint256 receivedFeeAmount);

    /** @notice Event emitted when renting fees are paid */
    event PaidFees(address indexed pod, uint256 feeAmount);
    /** @notice Event emitted when minting fees are paid */
    event MintingFees(address indexed pod, uint256 feeAmount);

    /** @notice Event emitted when the Reserve is processed */
    event ReserveProcessed(uint256 stakingRewardsAmount);

    /** @notice Event emitted when a new collateral is added */
    event NewCollateral(address indexed collateral, address indexed aToken);
    /** @notice Event emitted when a colalteral is updated */
    event CollateralUpdated(address indexed collateral, bool allowed);

    /** @notice Event emitted when the Registry is updated */
    event RegistryUpdated(address indexed oldRegistry, address indexed newRegistry);
    /** @notice Event emitted when the Fee Module is updated */
    event FeeModuleUpdated(address indexed oldMoldule, address indexed newModule);
    /** @notice Event emitted when the Oracle Module is updated */
    event OracleModuleUpdated(address indexed oldMoldule, address indexed newModule);
    /** @notice Event emitted when the Discount Calculator Module is updated */
    event DiscountCalculatorUpdated(address indexed oldCalculator, address indexed newCalculator);
    /** @notice Event emitted when the Fee Chest is updated */
    event FeeChestUpdated(address indexed oldFeeChest, address indexed newFeeChest);

    /** @notice Event emitted when the Mint Fee Ratio is updated */
    event MintFeeRatioUpdated(uint256 oldRatio, uint256 newRatio);
    /** @notice Event emitted when the Protocol Fee Ratio is updated */
    event ProtocolFeeRatioUpdated(uint256 oldRatio, uint256 newRatio);
    /** @notice Event emitted when the Extra Liquidation Ratio is updated */
    event ExtraLiquidationRatioUpdated(uint256 oldRatio, uint256 newRatio);
    /** @notice Event emitted when the Mint Fee Ratio is updated */
    event ProcessThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);


    // Modifers

    /** @notice Check that the caller is a valid Pod */
    modifier isValidPod() {
        if(pods[msg.sender].podAddress == address(0)) revert Errors.CallerNotValidPod();
        _;
    }


    // Constructor

    constructor(
        address _vault,
        address _rewardsStaking,
        address _protocolFeeChest,
        address _podImplementation,
        address _registry,
        address _feeModule,
        address _oracleModule,
        address _discountCalculator
    ) {
        if(
            _vault == address(0)
            || _rewardsStaking == address(0)
            || _protocolFeeChest == address(0)
            || _podImplementation == address(0)
            || _registry == address(0)
            || _feeModule == address(0)
            || _oracleModule == address(0)
            || _discountCalculator == address(0)
        ) revert Errors.AddressZero();

        vault = _vault;
        rewardsStaking = _rewardsStaking;
        protocolFeeChest = _protocolFeeChest;
        podImplementation = _podImplementation;
        registry = _registry;
        feeModule = _feeModule;
        oracleModule = _oracleModule;
        discountCalculator = _discountCalculator;

        lastIndexUpdate = block.timestamp;
    }


    // View functions

    /**
    * @notice Get the current fee index
    * @return uint256 : Current index
    */
    function getCurrentIndex() public view returns(uint256) {
        return lastUpdatedIndex + _accruedIndex();
    }

    /**
    * @notice Get the current amount of fees owed by a Pod
    * @param pod Address of the Pod
    * @return uint256 : Current amount of fees owed
    */
    function podCurrentOwedFees(address pod) public view returns(uint256) {
        if(pods[pod].lastIndex == 0) return 0;
        return pods[pod].accruedFees + (((getCurrentIndex() - pods[pod].lastIndex) * pods[pod].rentedAmount) / UNIT);
    }

    /**
    * @notice Get the stored amount of fees owed by a Pod
    * @param pod Address of the Pod
    * @return uint256 : Stored amount of fees owed
    */
    function podOwedFees(address pod) public view returns(uint256) {
        return pods[pod].accruedFees;
    }

    /**
    * @notice Get all Pods created by this contract
    * @return address[] : List of Pods
    */
    function getAllPods() external view returns(address[] memory) {
        return allPods;
    }

    /**
    * @notice Get the list of Pods owned by a given account
    * @param account Address of the Pods owner
    * @return address[] : List of Pods
    */
    function getAllOwnerPods(address account) external view returns(address[] memory) {
        return ownerPods[account];
    }

    /**
    * @notice Check if the given Pod is liquidable
    * @param pod Address of the Pod
    * @return bool : True if liquidable
    */
    function isPodLiquidable(address pod) public view returns(bool) {
        // We consider the Pod liquidable since the Pod has no more GHO debt from Aave,
        // but still owes fees to Dullahan, but the Pod logic forces to pay fees before
        // repaying debt to Aave.
        return IERC20(DullahanRegistry(registry).DEBT_GHO()).balanceOf(pod) == 0 && pods[pod].accruedFees > 0;
    }

    /**
    * @notice Estimate the amount of fees to repay to liquidate a Pod & the amount of collaterla to receive after liquidation
    * @param pod Address of the Pod
    * @return feeAmount - uint256 : Amount of fees to pay to liquidate
    * @return collateralAmount - uint256 : Amount of collateral to receive after liquidation
    */
    function estimatePodLiquidationexternal(address pod) external view returns(
        uint256 feeAmount,
        uint256 collateralAmount
    ) {
        if(pods[pod].podAddress == address(0)) revert Errors.PodInvalid();
        // Check if Pod can be liquidated
        if(!isPodLiquidable(pod)) revert Errors.PodNotLiquidable();

        Pod storage _pod = pods[pod];
        uint256 owedFees = podCurrentOwedFees(pod);

        address _collateral = _pod.collateral;

        // Get the current amount of collateral left in the Pod (from the aToken balance of the Pod, since 1:1 with collateral)
        // (should not have conversion issues since aTokens have the same amount of decimals than the asset)
        uint256 podCollateralBalance = IERC20(aTokenForCollateral[_collateral]).balanceOf(pod);
        // Get amount of collateral to liquidate
        collateralAmount = IOracleModule(oracleModule).getCollateralAmount(_collateral, owedFees);
        // Extra ratio on amount to liquidate: Penality + liquidation bonus
        collateralAmount += (collateralAmount * extraLiquidationRatio) / MAX_BPS;

        // If the Pod doesn't have enough collateral left to cover all the fees owed,
        // take all the collateral (the whole aToken balance).
        feeAmount = owedFees;
        if(collateralAmount > podCollateralBalance) {
            collateralAmount = podCollateralBalance;

            // Calculate the reduced amount of fees to be received based on real collateral amount we can get
            feeAmount = IOracleModule(oracleModule).getFeeAmount(
                _collateral,
                (collateralAmount * MAX_BPS) / (MAX_BPS + extraLiquidationRatio)
            );
        }
    }


    // State-changing functions

    /**
    * @notice Create a new Pod
    * @dev Clone the Pod implementation, initialize it & store the paremeters
    * @param collateral Address of the collateral for the new Pod
    * @return address : Address of the newly deployed Pod
    */
    function createPod(
        address collateral
    ) external nonReentrant whenNotPaused returns(address) {
        if(collateral == address(0)) revert Errors.AddressZero();
        if(!allowedCollaterals[collateral]) revert Errors.CollateralNotAllowed();
        if(!_updateGlobalState()) revert Errors.FailStateUpdate();

        address podOwner = msg.sender;

        // Clone to create new Pod
        address newPod = Clones.clone(podImplementation);

        (address votingPowerDelegate, address proposalPowerDelegate) = DullahanVault(vault).getDelegates();

        // Initialize the newly created Pod
        DullahanPod(newPod).init(
            address(this),
            vault,
            registry,
            podOwner,
            collateral,
            aTokenForCollateral[collateral],
            votingPowerDelegate,
            proposalPowerDelegate
        );

        // Write the new Pod data in storage
        pods[newPod].podAddress = newPod;
        pods[newPod].podOwner = podOwner;
        pods[newPod].collateral = collateral;
        allPods.push(newPod);
        ownerPods[podOwner].push(newPod);

        emit PodCreation(collateral, podOwner, newPod);

        return newPod;
    }

    /**
    * @notice Update the global state
    * @return bool : Success
    */
    function updateGlobalState() external whenNotPaused returns(bool) {
        return _updateGlobalState();
    }

    /**
    * @notice Update a Pod state
    * @param pod Address of the Pod
    * @return bool : Success
    */
    function updatePodState(address pod) external nonReentrant whenNotPaused returns(bool) {
        if(pods[pod].podAddress == address(0)) revert Errors.PodInvalid();

        return _updatePodState(pod);
    }

    /**
    * @notice Free all stkAAVE not currently needed by a Pod
    * @dev Calculate the needed amount of stkAAVE for a Pod & free any extra stkAAVE held by the Pod
    * @param pod Address of the Pod
    * @return bool : Success
    */
    function freeStkAave(address pod) external nonReentrant returns(bool) {
        if(!_updatePodState(pod)) revert Errors.FailPodStateUpdate();
        if(pods[pod].podAddress == address(0)) revert Errors.PodInvalid();
        
        // Make the Pod claim any pending stkAAVE rewards
        if(msg.sender != pod) {
            // Otherwise the Pod will already call the internal _getStkAaveRewards(); method
            // And will block the method because of the nonReentrant modifier
            DullahanPod(pod).compoundStkAave();
        }

        // Calculate the amount of stkAave the Pod needs based on the amount of GHO debt it has
        uint256 neededStkAaveAmount = _calculatedNeededStkAave(pod, 0);
        uint256 currentStkAaveBalance = IERC20(DullahanRegistry(registry).STK_AAVE()).balanceOf(pod);

        // In case a pod receives direct stkAAVE transfer, we want to track that received amount
        if(currentStkAaveBalance > pods[pod].rentedAmount) {
            uint256 balanceDiff = currentStkAaveBalance - pods[pod].rentedAmount;
            pods[pod].rentedAmount += balanceDiff;

            // And notify the Vault of the balance increase
            DullahanVault(vault).notifyRentedAmount(pod, balanceDiff);
        }

        // If the Pod holds more stkAave than needed
        if(currentStkAaveBalance > neededStkAaveAmount) {
            uint256 pullAmount = currentStkAaveBalance - neededStkAaveAmount;

            // Make the Vault pull the stkAave from the Pod
            DullahanVault(vault).pullRentedStkAave(pod, pullAmount);

            // Update the tracked rented amount
            if(pullAmount == currentStkAaveBalance) {
                // We pull all the Pod stkAAVE, we can reset the rentedAmount to 0
                pods[pod].rentedAmount = 0;
            } else {
                pods[pod].rentedAmount -= pullAmount;
            }

            emit FreedStkAave(pod, pullAmount);
        }

        return true;
    }

    /**
    * @notice Liquidate a Pod that owes fees & has no GHO debt
    * @dev Repay the fees owed by the Pod & receive some of the Pod colleteral (with an extra ratio)
    * @param pod Address of the Pod
    * @return bool : Success
    */
    function liquidatePod(address pod) external nonReentrant returns(bool) {
        if(pods[pod].podAddress == address(0)) revert Errors.PodInvalid();

        address liquidator = msg.sender;

        // Update the Pod state to get the actual mount of fees owed
        _updatePodState(pod);

        // Check if Pod can be liquidated
        if(!isPodLiquidable(pod)) revert Errors.PodNotLiquidable();

        // Free any remaining stkAave in the Pod
        DullahanPod(pod).compoundStkAave();
        uint256 currentStkAaveBalance = IERC20(DullahanRegistry(registry).STK_AAVE()).balanceOf(pod);

        // In case a pod receives direct stkAAVE transfer, we want to track that received amount
        if(currentStkAaveBalance > pods[pod].rentedAmount) {
            uint256 balanceDiff = currentStkAaveBalance - pods[pod].rentedAmount;
            pods[pod].rentedAmount += balanceDiff;

            // And notify the Vault of the balance increase
            DullahanVault(vault).notifyRentedAmount(pod, balanceDiff);
        }

        if(currentStkAaveBalance > 0) {
            // Update the tracked rented amount
            pods[pod].rentedAmount = 0;

            // And make the Vault pull the stkAave from the Pod
            DullahanVault(vault).pullRentedStkAave(pod, currentStkAaveBalance);

            emit FreedStkAave(pod, currentStkAaveBalance);
        }

        Pod storage _pod = pods[pod];
        uint256 owedFees = _pod.accruedFees;
        address _collateral = _pod.collateral;

        // Get the current amount of collateral left in the Pod (from the aToken balance of the Pod, since 1:1 with collateral)
        // (should not have conversion issues since aTokens have the same amount of decimals than the asset)
        uint256 podCollateralBalance = IERC20(aTokenForCollateral[_collateral]).balanceOf(pod);
        // Get amount of collateral to liquidate
        uint256 collateralAmount = IOracleModule(oracleModule).getCollateralAmount(_collateral, owedFees);
        // Extra ratio on amount to liquidate: Penality + liquidation bonus
        collateralAmount += (collateralAmount * extraLiquidationRatio) / MAX_BPS;

        // If the Pod doesn't have enough collateral left to cover all the fees owed,
        // take all the collateral (the whole aToken balance).
        uint256 paidFees = owedFees;
        if(collateralAmount > podCollateralBalance) {
            collateralAmount = podCollateralBalance;

            // Calculate the reduced amount of fees to be received based on real collateral amount we can get
            paidFees = IOracleModule(oracleModule).getFeeAmount(
                _collateral,
                (collateralAmount * MAX_BPS) / (MAX_BPS + extraLiquidationRatio)
            );
        }

        // Reset owed fees for the Pod & add fees to Reserve
        _pod.accruedFees = 0;
        reserveAmount += paidFees;

        // Process the reserve
        if(reserveAmount >= processThreshold) {
            _processReserve();
        }

        // Pull the GHO fees from the liquidator
        IERC20(DullahanRegistry(registry).GHO()).safeTransferFrom(liquidator, address(this), paidFees);

        // Liquidate & send to the liquidator
        DullahanPod(pod).liquidateCollateral(collateralAmount, liquidator);

        emit LiquidatedPod(pod, _collateral, collateralAmount, paidFees);

        return true;
    }

    /**
    * @notice Update the delegator of a Pod
    * @param pod Address of the Pod
    */
    function updatePodDelegation(address pod) public whenNotPaused {
        if(pods[pod].podAddress == address(0)) revert Errors.PodInvalid();

        (address votingPowerDelegate, address proposalPowerDelegate) = DullahanVault(vault).getDelegates();
        DullahanPod(pod).updateDelegation(votingPowerDelegate, proposalPowerDelegate);
    }

    /**
    * @notice Update the delegator for a list of Pods
    * @param podList List of Pod addresses
    */
    function updateMultiplePodsDelegation(address[] calldata podList) external {
        uint256 length = podList.length;
        for(uint256 i; i < length;){
            updatePodDelegation(podList[i]);
            unchecked { ++i; }
        }
    }

    /**
    * @notice Process the Reserve
    * @dev Send the Reserve to the staking contract to be queued for distribution & take a part as protocol fees
    * @return bool : Success
    */
    function processReserve() external nonReentrant whenNotPaused returns(bool) {
        return _processReserve();
    }


    // Pods only functions

    /**
    * @notice Get the needed amount of stkAAVE for a Pod based on the GHO amount minted
    * @dev Calculate the amount of stkAAVE a Pod need based on its GHO debt & amount ot be minted & request the needed amount to the Vault for renting
    * @param amountToMint Amount of GHO to be minted
    * @return bool : Success
    */
    function getStkAave(uint256 amountToMint) external nonReentrant whenNotPaused isValidPod returns(bool){
        address pod = msg.sender;

        // Update the Pod state with the previous stkAave rented amount
        _updatePodState(pod);

        // Caculate the needed amount of stkaave based on current GHO debt + amount of GHO wanted for minting
        // & Fetch the current Pod stkAave balance
        uint256 neededStkAaveAmount = _calculatedNeededStkAave(pod, amountToMint);
        uint256 currentStkAaveBalance = IERC20(DullahanRegistry(registry).STK_AAVE()).balanceOf(pod);

        // In case a pod receives direct stkAAVE transfer, we want to track that received amount
        if(currentStkAaveBalance > pods[pod].rentedAmount) {
            uint256 balanceDiff = currentStkAaveBalance - pods[pod].rentedAmount;
            pods[pod].rentedAmount += balanceDiff;

            // And notify the Vault of the balance increase
            DullahanVault(vault).notifyRentedAmount(pod, balanceDiff);
        }

        // Get the amount of stkAave to rent from the Vault
        uint256 rentAmount = neededStkAaveAmount > currentStkAaveBalance ? neededStkAaveAmount - currentStkAaveBalance : 0;

        // Check with the Vault if there is enough to rent, otherwise take all available
        DullahanVault _vault = DullahanVault(vault);
        uint256 availableStkAaveAmount = _vault.totalAvailable();
        if(rentAmount > availableStkAaveAmount) rentAmount = availableStkAaveAmount;

        if(rentAmount > 0) {
            // Update the tracked rented amount for the Pod
            pods[pod].rentedAmount += rentAmount;

            // And make the Vault send the stkAave to the Pod
            _vault.rentStkAave(pod, rentAmount);

            emit RentedStkAave(pod, rentAmount);
        }

        return true;
    }

    /**
    * @notice Notify the Vault for claimed rewards from the Safety Module for a Pod
    * @param claimedAmount Amount of rewards claimed
    */
    function notifyStkAaveClaim(uint256 claimedAmount) external isValidPod {
        address _pod = msg.sender;

        // Update the Pod state with the previous stkAave rented amount
        _updatePodState(_pod);

        // Update the tracked rented amount for the Pod
        pods[_pod].rentedAmount += claimedAmount;

        // And notify the Vault of the newly claimed amount
        DullahanVault(vault).notifyRentedAmount(_pod, claimedAmount);

        emit RentedStkAave(_pod, claimedAmount);
    }

    /**
    * @notice Notify fees paid by a Pod
    * @param feeAmount Amount of fees paid
    */
    function notifyPayFee(uint256 feeAmount) external isValidPod {
        address _pod = msg.sender;
        // Update the amount of fees owed by the Pod
        pods[_pod].accruedFees -= feeAmount;

        // And set the received fees as Reserve
        reserveAmount += feeAmount;

        // Process the reserve
        if(reserveAmount >= processThreshold) {
            _processReserve();
        }

        emit PaidFees(_pod, feeAmount);
    }

    /**
    * @notice Notify minting fees paid by a Pod
    * @param feeAmount Amount of fees paid
    */
    function notifyMintingFee(uint256 feeAmount) external isValidPod {
        address _pod = msg.sender;

        // Set the received minting fees as Reserve
        reserveAmount += feeAmount;

        // Process the reserve
        if(reserveAmount >= processThreshold) {
            _processReserve();
        }

        emit MintingFees(_pod, feeAmount);
    }


    // Internal functions

    /**
    * @dev Calculates the amount of stkAAVE needed by a Pod based on its GHO debt & the amount of GHO to be minted
    * @param pod Address of the Pod
    * @param addedDebtAmount Amount of GHO to be minted
    * @return uint256 : Amount of stkAAVE needed
    */
    function _calculatedNeededStkAave(address pod, uint256 addedDebtAmount) internal view returns(uint256) {
        uint256 totalDebtBalance = IERC20(DullahanRegistry(registry).DEBT_GHO()).balanceOf(pod) + addedDebtAmount;
        return IDiscountCalculator(discountCalculator).calculateAmountForMaxDiscount(totalDebtBalance);
    }

    /**
    * @dev Calculate the index accrual based on the current fee per second
    * @return uint256 : index accrual
    */
    function _accruedIndex() internal view returns(uint256) {
        if(block.timestamp <= lastIndexUpdate) return 0;

        // Time since the last index update
        uint256 elapsedTime = block.timestamp - lastIndexUpdate;

        // Fee (in GHO) per rented stkAave per second
        uint256 currentFeePerSec = IFeeModule(feeModule).getCurrentFeePerSecond();
        return currentFeePerSec * elapsedTime;
    }

    /**
    * @dev Update the global state by updating the fee index
    * @return bool : Success
    */
    function _updateGlobalState() internal returns(bool) {
        // Get the new index
        uint256 accruedIndex = _accruedIndex();

        // Update the storage
        lastIndexUpdate = block.timestamp;
        lastUpdatedIndex = lastUpdatedIndex + accruedIndex;

        return true;
    }

    /**
    * @dev Update a Pod's state & accrued owed fees based on the last updated index
    * @param podAddress Address of the Pod
    * @return bool : Success
    */
    function _updatePodState(address podAddress) internal returns(bool) {
        if(!_updateGlobalState()) revert Errors.FailStateUpdate();

        Pod storage _pod = pods[podAddress];

        // Get the lastest index & the Pod last index
        // to calculate the accrued owed fees based on the Pod's rented amount
        uint256 _lastUpdatedIndex = lastUpdatedIndex;
        uint256 _oldPodIndex = _pod.lastIndex;
        _pod.lastIndex = _lastUpdatedIndex;
        _pod.lastUpdate = safe96(block.timestamp);

        if(_pod.rentedAmount != 0 && _oldPodIndex != _lastUpdatedIndex){
            _pod.accruedFees += ((_lastUpdatedIndex - _oldPodIndex) * _pod.rentedAmount) / UNIT;
        }

        return true;
    }

    /**
    * @dev Send the Reserve to the staking contract to be queued for distribution & take a part as protocol fees
    * @return bool : Success
    */
    function _processReserve() internal returns(bool) {
        if(!_updateGlobalState()) revert Errors.FailStateUpdate();
        uint256 currentReserveAmount = reserveAmount;
        if(currentReserveAmount == 0) return true;

        // Reset the Reserve
        reserveAmount = 0;

        address _ghoAddress = DullahanRegistry(registry).GHO();
        IERC20 _gho = IERC20(_ghoAddress);
        // Take the DAO fees based on current amount to process
        uint256 protocolFees = (currentReserveAmount * protocolFeeRatio) / MAX_BPS;
        _gho.safeTransfer(protocolFeeChest, protocolFees);

        // And send the rest of the fees to the Staking module to be queued for distribution
        uint256 stakingRewardsAmount = currentReserveAmount - protocolFees;
        IDullahanRewardsStaking(rewardsStaking).queueRewards(_ghoAddress, stakingRewardsAmount);
        _gho.safeTransfer(rewardsStaking, stakingRewardsAmount);

        emit ReserveProcessed(stakingRewardsAmount);

        return true;
    }


    // Admin functions

    /**
    * @notice Update the Registry for a given Pod
    * @param pod Address of the Pod
    */
    function updatePodRegistry(address pod) public onlyOwner {
        if(pods[pod].podAddress == address(0)) revert Errors.PodInvalid();

        DullahanPod(pod).updateRegistry(registry);
    }

    /**
    * @notice Update the Registry for a given list of Pods
    * @param podList List of Pod addresses
    */
    function updateMultiplePodsRegistry(address[] calldata podList) external onlyOwner {
        uint256 length = podList.length;
        for(uint256 i; i < length;){
            updatePodRegistry(podList[i]);
            unchecked { ++i; }
        }
    }

    /**
    * @notice Update the Registry for all Pods
    */
    function updateAllPodsRegistry() external onlyOwner {
        address[] memory _pods = allPods;
        uint256 length = _pods.length;
        for(uint256 i; i < length;){
            updatePodRegistry(_pods[i]);
            unchecked { ++i; }
        }
    }
    
    /**
     * @notice Pause the contract
     */
    function pause() external onlyOwner {
        _pause();
    }

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

    /**
    * @notice Add a new collateral for Pod creation
    * @param collateral Address of the collateral
    * @param aToken Address of the aToken associated to the collateral
    */
    function addCollateral(address collateral, address aToken) external onlyOwner {
        if(collateral == address(0) || aToken == address(0)) revert Errors.AddressZero();
        if(aTokenForCollateral[collateral] != address(0)) revert Errors.CollateralAlreadyListed();

        allowedCollaterals[collateral] = true;
        aTokenForCollateral[collateral] = aToken;

        emit NewCollateral(collateral, aToken);
    }

    /**
    * @notice Update a collateral for Pod creation
    * @param collateral Address of the collateral
    * @param allowed Is the collateral allowed ofr Pod creation
    */
    function updateCollateral(address collateral, bool allowed) external onlyOwner {
        if(collateral == address(0)) revert Errors.AddressZero();
        if(aTokenForCollateral[collateral] == address(0)) revert Errors.CollateralNotListed();

        allowedCollaterals[collateral] = allowed;

        emit CollateralUpdated(collateral, allowed);
    }

    /**
    * @notice Uodate the FeeChest
    * @param newFeeChest Address of the new FeeChest
    */
    function updateFeeChest(address newFeeChest) external onlyOwner {
        if(newFeeChest == address(0)) revert Errors.AddressZero();
        if(newFeeChest == protocolFeeChest) revert Errors.SameAddress();

        address oldFeeChest = protocolFeeChest;
        protocolFeeChest = newFeeChest;

        emit FeeChestUpdated(oldFeeChest, newFeeChest);
    }

    /**
    * @notice Uodate the Registry
    * @param newRegistry Address of the new Registry
    */
    function updateRegistry(address newRegistry) external onlyOwner {
        if(newRegistry == address(0)) revert Errors.AddressZero();
        if(newRegistry == registry) revert Errors.SameAddress();

        address oldRegistry = registry;
        registry = newRegistry;

        emit RegistryUpdated(oldRegistry, newRegistry);
    }

    /**
    * @notice Uodate the Fee Module
    * @param newModule Address of the new Module
    */
    function updateFeeModule(address newModule) external onlyOwner {
        if(newModule == address(0)) revert Errors.AddressZero();
        if(newModule == feeModule) revert Errors.SameAddress();

        address oldMoldule = feeModule;
        feeModule = newModule;

        emit FeeModuleUpdated(oldMoldule, newModule);
    }

    /**
    * @notice Uodate the Oracle Module
    * @param newModule Address of the new Module
    */
    function updateOracleModule(address newModule) external onlyOwner {
        if(newModule == address(0)) revert Errors.AddressZero();
        if(newModule == oracleModule) revert Errors.SameAddress();

        address oldMoldule = oracleModule;
        oracleModule = newModule;

        emit OracleModuleUpdated(oldMoldule, newModule);
    }

    /**
    * @notice Uodate the Discount Calculator Module
    * @param newCalculator Address of the new Calculator
    */
    function updateDiscountCalculator(address newCalculator) external onlyOwner {
        if(newCalculator == address(0)) revert Errors.AddressZero();
        if(newCalculator == discountCalculator) revert Errors.SameAddress();

        address oldCalculator = discountCalculator;
        discountCalculator = newCalculator;

        emit DiscountCalculatorUpdated(oldCalculator, newCalculator);
    }

    /**
    * @notice Uodate the mint fee ratio parameter
    * @param newRatio New ratio value
    */
    function updateMintFeeRatio(uint256 newRatio) external onlyOwner {
        if(newRatio > 500) revert Errors.InvalidParameter();

        uint256 oldRatio = mintFeeRatio;
        mintFeeRatio = newRatio;

        emit MintFeeRatioUpdated(oldRatio, newRatio);
    }

    /**
    * @notice Uodate the protocol fee ratio parameter
    * @param newRatio New ratio value
    */
    function updateProtocolFeeRatio(uint256 newRatio) external onlyOwner {
        if(newRatio > 2500) revert Errors.InvalidParameter();

        uint256 oldRatio = protocolFeeRatio;
        protocolFeeRatio = newRatio;

        emit ProtocolFeeRatioUpdated(oldRatio, newRatio);
    }

    /**
    * @notice Uodate the extra liquidation ratio parameter
    * @param newRatio New ratio value
    */
    function updateExtraLiquidationRatio(uint256 newRatio) external onlyOwner {
        if(newRatio > 2500) revert Errors.InvalidParameter();

        uint256 oldRatio = extraLiquidationRatio;
        extraLiquidationRatio = newRatio;

        emit ExtraLiquidationRatioUpdated(oldRatio, newRatio);
    }

    /**
    * @notice Uodate the process threshold parameter
    * @param newThreshold New treshold value
    */
    function updateProcessThreshold(uint256 newThreshold) external onlyOwner {
        if(newThreshold < 10e18) revert Errors.InvalidParameter();

        uint256 oldThreshold = processThreshold;
        processThreshold = newThreshold;

        emit ProcessThresholdUpdated(oldThreshold, newThreshold);
    }


    // Maths

    function safe96(uint256 n) internal pure returns (uint96) {
        if(n > type(uint96).max) revert Errors.NumberExceed96Bits();
        return uint96(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/IAavePool.sol



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

interface IAavePool {

    function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
    function withdraw(address asset, uint256 amount, address to) external returns (uint256);

    function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external;
    function repay(address asset, uint256 amount, uint256 rateMode, address onBehalfOf) external returns (uint256);
    function repayWithATokens(address asset,uint256 amount,uint256 interestRateMode) external returns (uint256);

    function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;

    function getUserAccountData(address user) external view returns(
        uint256 totalCollateralBase,
        uint256 totalDebtBase,
        uint256 availableBorrowsBase,
        uint256 currentLiquidationThreshold,
        uint256 ltv,
        uint256 healthFactor
    );

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



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

interface IAaveRewardsController {

    function getUserAccruedRewards(address user, address reward) external view returns (uint256);

    function getUserRewards(
        address[] calldata assets,
        address user,
        address reward
    ) external view returns (uint256);

    function getAllUserRewards(
        address[] calldata assets,
        address user
    ) external view returns (
        address[] memory rewardsList,
        uint256[] memory unclaimedAmounts
    );

    function claimAllRewards(
        address[] calldata assets,
        address to
    ) external returns (
        address[] memory rewardsList,
        uint256[] memory claimedAmounts
    );

}

// File: contracts/interfaces/IDiscountCalculator.sol



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


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

interface IDiscountCalculator {

    function calculateAmountForMaxDiscount(uint256 totalDebtAmount) external view returns (uint256 neededAmount);

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



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


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

interface IDullahanPodManager {

    function mintFeeRatio() external view returns(uint256);

    function podOwedFees(address pod) external view returns(uint256);

    function updatePodState(address pod) external returns(bool);

    function getStkAave(uint256 amountToMint) external returns(bool);
    function freeStkAave(address pod) external returns(bool);

    function notifyStkAaveClaim(uint256 claimedAmount) external;
    function notifyPayFee(uint256 feeAmount) external;
    function notifyMintingFee(uint256 feeAmount) external;

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



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


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

interface IDullahanRewardsStaking {

    function queueRewards(address rewardToken, uint256 amount) external returns(bool);

}
// 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/IFeeModule.sol



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


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

interface IFeeModule {

    function getCurrentFeePerSecond() external view returns(uint256 currentFee);

}
// 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/IOracleModule.sol



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


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

interface IOracleModule {

    function getCollateralAmount(address collateral, uint256 feeAmount) external view returns(uint256 collateralAmount);

    function getFeeAmount(address collateral, uint256 collateralAmount) external view returns(uint256 feeAmount);

}
// 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/modules/DullahanRegistry.sol



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


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

import "../utils/Owner.sol";
import {Errors} from "../utils/Errors.sol";

/** @title Dullahan Registry contract
 *  @author Paladin
 *  @notice Registry, for all Aave related addresses & some Dullahan addresses
 */
contract DullahanRegistry is Owner {

    // Storage

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

    /** @notice Address of the GHO token */
    address public immutable GHO;
    /** @notice Address of the GHO debt token */
    address public immutable DEBT_GHO;

    /** @notice Address of the Aave v3 Pool */
    address public immutable AAVE_POOL_V3;

    /** @notice Address of the Aave rewards controller */
    address public immutable AAVE_REWARD_COONTROLLER;

    /** @notice Address of the Dullahan Vault */
    address public dullahanVault;

    /** @notice Address of Dullahan Pod Managers */
    address[] public dullahanPodManagers;

    // Events

    /** @notice Event emitted when the Vault is set */
    event SetVault(address indexed vault);
    /** @notice Event emitted when a Manager is added */
    event AddPodManager(address indexed newManager);


    // Constructor
    constructor(
        address _aave,
        address _stkAave,
        address _gho,
        address _ghoDebt,
        address _aavePool,
        address _aaveRewardController
    ) {
        if(
            _aave == address(0)
            || _stkAave == address(0)
            || _gho == address(0)
            || _ghoDebt == address(0)
            || _aavePool == address(0)
            || _aaveRewardController == address(0)
        ) revert Errors.AddressZero();

        STK_AAVE = _stkAave;
        AAVE = _aave;

        GHO = _gho;
        DEBT_GHO = _ghoDebt;

        AAVE_POOL_V3 = _aavePool;

        AAVE_REWARD_COONTROLLER = _aaveRewardController;
    }

    /**
    * @notice Set the Dullahan Vault
    * @param vault address of the vault
    */
    function setVault(address vault) external onlyOwner {
        if(vault == address(0)) revert Errors.AddressZero();
        if(dullahanVault != address(0)) revert Errors.VaultAlreadySet();

        dullahanVault = vault;

        emit SetVault(vault);
    }

    /**
    * @notice Add a Pod Manager
    * @param manager Address of the new manager
    */
    function addPodManager(address manager) external onlyOwner {
        if(manager == address(0)) revert Errors.AddressZero();

        // Check in the Manager list that it's not already present
        address[] memory _managers = dullahanPodManagers;
        uint256 length = _managers.length;
        for(uint256 i; i < length;){
            if(_managers[i] == manager) revert Errors.AlreadyListedManager();
            unchecked { ++i; }
        }

        dullahanPodManagers.push(manager);

        emit AddPodManager(manager);
    }

    /**
    * @notice Get the list of Pod Managers
    * @return address[] : List of Pod Managers
    */
    function getPodManagers() external view returns(address[] memory) {
        return dullahanPodManagers;
    }

}
// 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/Clones.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (proxy/Clones.sol)

pragma solidity ^0.8.0;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 *
 * _Available since v3.4._
 */
library Clones {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(0, 0x09, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(0, 0x09, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := keccak256(add(ptr, 0x43), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}
// 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/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "./Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
// 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/Owner.sol



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

import "../oz/utils/Ownable.sol";

/** @title Extend OZ Ownable contract  */
/// @author Paladin

contract Owner is Ownable {

    address public pendingOwner;

    event NewPendingOwner(address indexed previousPendingOwner, address indexed newPendingOwner);

    error CannotBeOwner();
    error CallerNotPendingOwner();
    error OwnerAddressZero();

    function transferOwnership(address newOwner) public override virtual onlyOwner {
        if(newOwner == address(0)) revert OwnerAddressZero();
        if(newOwner == owner()) revert CannotBeOwner();
        address oldPendingOwner = pendingOwner;

        pendingOwner = newOwner;

        emit NewPendingOwner(oldPendingOwner, newOwner);
    }

    function acceptOwnership() public virtual {
        if(msg.sender != pendingOwner) revert CallerNotPendingOwner();
        address newOwner = pendingOwner;
        _transferOwnership(pendingOwner);
        pendingOwner = address(0);

        emit NewPendingOwner(newOwner, address(0));
    }

}
// 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)
            }
        }
    }
}