// File: contracts/interfaces/IDullahanVault.sol



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


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

interface IDullahanVault {

    function totalAssets() external view returns (uint256);
    function totalRentedAmount() external view returns (uint256);

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



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


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

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

/** @title Dullahan Fee Module
 *  @author Paladin
 *  @notice Module handling the calculation of fee rate for renting stkaave from the Dullahan Vault
 */
contract DullahanFeeModule is IFeeModule, Owner {

    // Constants

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

    /** @notice Threshold ratio to apply the extra multiplier */
    uint256 public constant TRESHOLD = 0.90 ether;
    /** @notice Base extra multiplier */
    uint256 public constant BASE_MULTIPLIER = 1e18;
    /** @notice Multplier increase for extra ratio over the treshold  */
    uint256 public constant EXTRA_MULTIPLIER_STEP = 10e18;


    // Storage

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

    /** @notice Amount of GHO fees per second per stkAAVE */
    uint256 public feePerStkAavePerSecond;


    // Events

    /** @notice Event emitted when the fee per second value is updated */
    event UpdatedFeePerStkAavePerSecond(uint256 oldFee, uint256 newFee);


    // Constructor

    constructor(
        address _vault,
        uint256 _startFee
    ) {
        if(_vault == address(0)) revert Errors.AddressZero();
        if(_startFee == 0) revert Errors.NullAmount();

        vault = _vault;
        feePerStkAavePerSecond = _startFee;
    }


    // Functions

    /**
    * @notice Get the current utilization rate
    * @dev Calculates the current utilization rate based on the Vault rented amount & total assets
    * @return uint256 : Current utilization rate
    */
    function utilizationRate() public view returns(uint256) {
        IDullahanVault _vault = IDullahanVault(vault);
        uint256 vaultTotalAssets = _vault.totalAssets();
        uint256 currentlyRented = _vault.totalRentedAmount();

        if(vaultTotalAssets == 0) return 0;

        // Utilization Rate = Current Rented amount / Total Vault Assets
        return (currentlyRented * UNIT) / vaultTotalAssets;
    }

    /**
    * @notice Get the current fee per second
    * @dev Calculates the current fee per second based on the current utilization rate
    * @return currentFee - uint256 : Current fee per second
    */
    function getCurrentFeePerSecond() external view returns(uint256 currentFee) {
        uint256 utilRate = utilizationRate();
        currentFee = feePerStkAavePerSecond;

        // If the Utilization Rate is over the Threshold, increase the fee per second
        // using the calculated multiplier
        if(utilRate >= TRESHOLD) {
            uint256 multiplier = BASE_MULTIPLIER + ((EXTRA_MULTIPLIER_STEP * (utilRate - TRESHOLD) / UNIT));
            currentFee = (currentFee * multiplier) / UNIT;
        }
    }


    // Admin Functions

    /**
    * @notice Updates the feePerStkAavePerSecond parameter
    * @dev Updates the feePerStkAavePerSecond in storage with the given value
    * @param newFee New value tu set
    */
    function updateFeePerStkAavePerSecond(uint256 newFee) external onlyOwner {
        if(newFee == 0) revert Errors.NullAmount();

        uint256 oldFee = feePerStkAavePerSecond;
        feePerStkAavePerSecond = newFee;

        emit UpdatedFeePerStkAavePerSecond(oldFee, newFee);
    }

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

}