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



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


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

import "./interfaces/IScalingERC20.sol";
import "./oz/interfaces/IERC20.sol";
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 {Errors} from "./utils/Errors.sol";
import {WadRayMath} from  "./utils/WadRayMath.sol";

/** @title DullahanRewardsStaking contract
 *  @author Paladin
 *  @notice Staking system for Dullahan share holders to receive the rewards
 *          generated by Dullahan modules.
 */
contract DullahanRewardsStaking is ReentrancyGuard, Pausable, Owner {
    using SafeERC20 for IERC20;
    using WadRayMath for uint256;


    // Constants

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

    /** @notice Duration in second of a reward distribution */
    uint256 private constant DISTRIBUTION_DURATION = 604800; // 1 week
    /** @notice 1e27 - RAY - Initial Index for balance to scaled balance */
    uint256 private constant INITIAL_INDEX = 1e27;
    /** @notice Ratio of the total reward amount to be in the queue before moving it to distribution */
    uint256 private constant UPDATE_REWARD_RATIO = 8500; // 85 %
    /** @notice Amount to deposit to seed the contract during initialization */
    uint256 private constant SEED_DEPOSIT = 0.001 ether;


    // Structs

    /** @notice UserRewardState struct 
    *   lastRewardPerToken: last update reward per token value
    *   accruedRewards: total amount of rewards accrued
    */
    struct UserRewardState {
        uint256 lastRewardPerToken;
        uint256 accruedRewards;
    }

    /** @notice RewardState struct 
    *   rewardPerToken: current reward per token value
    *   lastUpdate: last state update timestamp 
    *   distributionEndTimestamp: timestamp of the end of the current distribution
    *   ratePerSecond: current disitrbution rate per second
    *   currentRewardAmount: current amount of rewards in the distribution
    *   queuedRewardAmount: current amount of reward queued for the distribution
    *   userStates: users reward state for the reward token
    */
    struct RewardState { // to pack better - gas opti
        uint256 rewardPerToken;
        uint128 lastUpdate;
        uint128 distributionEndTimestamp;
        uint256 ratePerSecond;
        uint256 currentRewardAmount;
        uint256 queuedRewardAmount;
        // user address => user reward state
        mapping(address => UserRewardState) userStates;
    }

    /** @notice UserClaimableRewards struct 
    *   reward: address of the reward token
    *   claimableAmount: amount of rewards accrued by the user
    */
    struct UserClaimableRewards {
        address reward;
        uint256 claimableAmount;
    }

    /** @notice UserClaimableRewards struct 
    *   reward: address of the reward token
    *   amount: amount of rewards claimed by the user
    */
    struct UserClaimedRewards {
        address reward;
        uint256 amount;
    }


    // Storage

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

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

    /** @notice Total scaled deposited amount */
    uint256 public totalScaledAmount;
    /** @notice User scaled deposits */
    mapping(address => uint256) public userScaledBalances;

    /** @notice Address of tokens used in reward distributions */
    address[] public rewardList;

    /** @notice Reward state for each reward token */
    mapping(address => RewardState) public rewardStates;

    /** @notice Addresses allowed to deposit rewards */
    mapping(address => bool) public rewardDepositors;

    /** @notice Addresses allowed to claim for another user */
    mapping(address => address) public allowedClaimer;


    // Events

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

    /** @notice Event emitted when staking */
    event Staked(address indexed caller, address indexed receiver, uint256 amount, uint256 scaledAmount);
    /** @notice Event emitted when unstaking */
    event Unstaked(address indexed owner, address indexed receiver, uint256 amount, uint256 scaledAmount);
    
    /** @notice Event emitted when rewards are claimed */
    event ClaimedRewards(address indexed reward, address indexed user, address indexed receiver, uint256 amount);

    /** @notice Event emitted when a new Claimer is set for an user */
    event SetUserAllowedClaimer(address indexed user, address indexed claimer);

    /** @notice Event emitted when a new reward is added */
    event NewRewards(address indexed rewardToken, uint256 amount, uint256 endTimestamp);

    /** @notice Event emitted when a new reward depositor is added */
    event AddedRewardDepositor(address indexed depositor);
    /** @notice Event emitted when a reward depositor is removed */
    event RemovedRewardDepositor(address indexed depositor);


    // Modifers

    /** @notice Check that the caller is allowed to deposit rewards */
    modifier onlyRewardDepositors() {
        if(!rewardDepositors[msg.sender]) revert Errors.CallerNotAllowed();
        _;
    }

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


    // Constructor

    constructor(
        address _vault
    ) {
        if(_vault == address(0)) revert Errors.AddressZero();

        vault = _vault;
    }

    function init() external onlyOwner {
        if(initialized) revert Errors.AlreadyInitialized();

        initialized = true;

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

        emit Initialized();
    }


    // View functions

    /**
    * @notice Get the last update timestamp for a reward token
    * @param reward Address of the reward token
    * @return uint256 : Last update timestamp
    */
    function lastRewardUpdateTimestamp(address reward) public view returns(uint256) {
        uint256 rewardEndTimestamp = rewardStates[reward].distributionEndTimestamp;
        // If the distribution is already over, return the timestamp of the end of distribution
        // to prevent from accruing rewards that do not exist
        return block.timestamp > rewardEndTimestamp ? rewardEndTimestamp : block.timestamp;
    }

    /**
    * @notice Get the total amount of assets staked
    * @return uint256 : Total amount of assets staked
    */
    function totalAssets() public view returns(uint256) {
        return IScalingERC20(vault).balanceOf(address(this));
    }

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

    /**
    * @notice Get the list of all reward tokens
    * @return address[] : List of reward tokens
    */
    function getRewardList() public view returns(address[] memory) {
        return rewardList;
    }

    /**
    * @notice Get the current amount staked by an user
    * @param user Address of the user
    * @return uint256 : Current amount staked
    */
    function userCurrentStakedAmount(address user) public view returns(uint256) {
        return userScaledBalances[user].rayMul(_getCurrentIndex());
    }

    /**
    * @notice Get the current reward state of an user for a given reward token
    * @param reward Address of the reward token
    * @param user Address of the user
    * @return UserRewardState : User reward state
    */
    function getUserRewardState(address reward, address user) external view returns(UserRewardState memory) {
        return rewardStates[reward].userStates[user];
    }

    /**
    * @notice Get the current amount of rewards accrued by an user for a given reward token
    * @param reward Address of the reward token
    * @param user Address of the user
    * @return uint256 : amount of rewards accured
    */
    function getUserAccruedRewards(address reward, address user) external view returns(uint256) {
        return rewardStates[reward].userStates[user].accruedRewards + _getUserEarnedRewards(reward, user, _getNewRewardPerToken(reward));
    }

    /**
    * @notice Get all current claimable amount of rewards for all reward tokens for a given user
    * @param user Address of the user
    * @return UserClaimableRewards[] : Amounts of rewards claimable by reward token
    */
    function getUserTotalClaimableRewards(address user) external view returns(UserClaimableRewards[] memory){
        address[] memory rewards = rewardList;
        uint256 rewardsLength = rewards.length;
        UserClaimableRewards[] memory rewardAmounts = new UserClaimableRewards[](rewardsLength);

        // For each listed reward
        for(uint256 i; i < rewardsLength;){
            // Add the reward token to the list
            rewardAmounts[i].reward = rewards[i];
            // And add the calculated claimable amount of the given reward
            rewardAmounts[i].claimableAmount = rewardStates[rewards[i]].userStates[user].accruedRewards + _getUserEarnedRewards(rewards[i], user, _getNewRewardPerToken(rewards[i]));

            unchecked { ++i; }
        }
        return rewardAmounts;
    }



    // State-changing functions

    // Can give MAX_UINT256 to stake full balance
    /**
    * @notice Stake Vault shares
    * @param amount Amount to stake
    * @param receiver Address of the address to stake for
    * @return uint256 : scaled amount for the deposit 
    */
    function stake(uint256 amount, address receiver) external nonReentrant isInitialized whenNotPaused returns(uint256) {
        if(amount == 0) revert Errors.NullAmount();
        if(receiver == address(0)) revert Errors.AddressZero();

        return _stake(msg.sender, amount, receiver);
    }

    /**
    * @dev Pull the ScalingERC20 token & stake in this contract & tracks the correct scaled amount
    * @param receiver Address of the caller to pull token from
    * @param amount Amount to stake
    * @param receiver Address of the address to stake for
    * @return uint256 : scaled amount for the deposit 
    */
    function _stake(address caller, uint256 amount, address receiver) internal returns(uint256) {
        // We just want to update the reward states for the user who's balance gonna change
        _updateAllUserRewardStates(receiver);

        // If given MAX_UINT256, we want to deposit the full user balance
        if(amount == MAX_UINT256) amount = IERC20(vault).balanceOf(caller);

        // Calculate the scaled amount corresponding to the user deposit
        // based on the total tokens held by this contract (because of the Scaling ERC20 logic)
        uint256 scaledAmount = amount.rayDiv(_getCurrentIndex());
        if(scaledAmount == 0) revert Errors.NullScaledAmount();

        // Pull the tokens from the user
        IERC20(vault).safeTransferFrom(caller, address(this), amount);

        // Update storage
        userScaledBalances[receiver] += scaledAmount;
        totalScaledAmount += scaledAmount;

        emit Staked(caller, receiver, amount, scaledAmount);

        return scaledAmount;
    }

    // Can give MAX_UINT256 to unstake full balance
    /**
    * @notice Unstake Vault shares
    * @dev Unstake ScalingERC20 shares based on the given scaled amount & send them to the receiver
    * @param scaledAmount Scaled amount ot unstake
    * @param receiver Address to receive the shares
    * @return uint256 : amount unstaked
    */
    function unstake(uint256 scaledAmount, address receiver) external nonReentrant isInitialized returns(uint256) {
        if(scaledAmount == 0) revert Errors.NullScaledAmount();
        if(receiver == address(0)) revert Errors.AddressZero();

        // We just want to update the reward states for the user who's balance gonna change
        _updateAllUserRewardStates(msg.sender);

        // If given MAX_UINT256, we want to withdraw the full user balance
        if(scaledAmount == MAX_UINT256) scaledAmount = userScaledBalances[msg.sender];

        // Calculate the amount to receive based on the given scaled amount
        uint256 amount = scaledAmount.rayMul(_getCurrentIndex());
        if(amount == 0) revert Errors.NullAmount();

        // Update storage
        userScaledBalances[msg.sender] -= scaledAmount;
        totalScaledAmount -= scaledAmount;

        // And send the tokens to the given receiver
        IERC20(vault).safeTransfer(receiver, amount);

        emit Unstaked(msg.sender, receiver, amount, scaledAmount);

        return amount;
    }

    /**
    * @notice Claim the accrued rewards for a given reward token
    * @param reward Address of the reward token
    * @param receiver Address to receive the rewards
    * @return uint256 : Amount of rewards claimed
    */
    function claimRewards(address reward, address receiver) external nonReentrant isInitialized whenNotPaused returns(uint256) {
        if(receiver == address(0)) revert Errors.AddressZero();

        return _claimRewards(reward, msg.sender, receiver);
    }

    /**
    * @notice Claim the accrued rewards for a given reward token on behalf of a given user
    * @param reward Address of the reward token
    * @param user Address that accrued the rewards
    * @param receiver Address to receive the rewards
    * @return uint256 : Amount of rewards claimed
    */
    function claimRewardsForUser(address reward, address user, address receiver) external nonReentrant isInitialized whenNotPaused returns(uint256) {
        if(receiver == address(0) || user == address(0)) revert Errors.AddressZero();
        if(msg.sender != allowedClaimer[user]) revert Errors.ClaimNotAllowed();

        return _claimRewards(reward, user, receiver);
    }

    /**
    * @notice Claim all accrued rewards for all reward tokens
    * @param receiver Address to receive the rewards
    * @return UserClaimedRewards[] : Amounts of reward claimed
    */
    function claimAllRewards(address receiver) external nonReentrant isInitialized whenNotPaused returns(UserClaimedRewards[] memory) {
        if(receiver == address(0)) revert Errors.AddressZero();

        return _claimAllRewards(msg.sender, receiver);
    }

    /**
    * @notice Claim all accrued rewards for all reward tokens on behalf of a given user
    * @param user Address that accrued the rewards
    * @param receiver Address to receive the rewards
    * @return UserClaimedRewards[] : Amounts of reward claimed
    */
    function claimAllRewardsForUser(address user, address receiver) external nonReentrant isInitialized whenNotPaused returns(UserClaimedRewards[] memory) {
        if(receiver == address(0) || user == address(0)) revert Errors.AddressZero();
        if(msg.sender != allowedClaimer[user]) revert Errors.ClaimNotAllowed();

        return _claimAllRewards(user, receiver);
    }

    /**
    * @notice Update the reward state for a given reward token
    * @param reward Address of the reward token
    */
    function updateRewardState(address reward) external isInitialized whenNotPaused {
        if(reward == address(0)) revert Errors.AddressZero();
        _updateRewardState(reward);
    }

    /**
    * @notice Update the reward state for all reward tokens
    */
    function updateAllRewardState() external isInitialized whenNotPaused {
        _updateAllRewardStates();
    }


    // Reward Managers functions

    /**
    * @notice Add rewards to the disitribution queue
    * @dev Set the amount of reward in the queue & push it to distribution if reaching the ratio
    * @param rewardToken Address of the reward token
    * @param amount Amount to queue
    * @return bool : success
    */
    function queueRewards(address rewardToken, uint256 amount) 
        external
        nonReentrant
        isInitialized
        whenNotPaused
        onlyRewardDepositors
        returns(bool) 
    {
        if(amount == 0) revert Errors.NullAmount();
        if(rewardToken == address(0)) revert Errors.AddressZero();

        RewardState storage state = rewardStates[rewardToken];

        // If the given reward token is new (no previous distribution),
        // add it to the reward list
        if(state.lastUpdate == 0) {
            rewardList.push(rewardToken);
        }

        // Update the reward token state before queueing new rewards
        _updateRewardState(rewardToken);

        // Get the total queued amount (previous queued amount + new amount)
        uint256 totalQueued = amount + state.queuedRewardAmount;

        // If there is no current disitrbution (previous is over or new reward token):
        // Start the new distribution directly without queueing the rewards
        if(block.timestamp >= state.distributionEndTimestamp){
            _updateRewardDistribution(rewardToken, state, totalQueued);
            state.queuedRewardAmount = 0;

            return true;
        }

        // Calculate the reamining duration for the current distribution
        // and the ratio of queued rewards compared to total rewards (queued + reamining in current distribution)
        // state.distributionEndTimestamp - block.timestamp => remaining time in the current distribution
        uint256 currentRemainingAmount =  state.ratePerSecond * (state.distributionEndTimestamp - block.timestamp);
        uint256 queuedAmountRatio =  (totalQueued * MAX_BPS) / (totalQueued + currentRemainingAmount);

        // If 85% or more of the total rewards are queued, move them to distribution
        if(queuedAmountRatio >= UPDATE_REWARD_RATIO) {
            _updateRewardDistribution(rewardToken, state, totalQueued);
            state.queuedRewardAmount = 0;
        } else {
            state.queuedRewardAmount = totalQueued;
        }

        return true;
    }

    /**
    * @dev Update the disitrubtion parameters for a given reward token
    * @param rewardToken Address of the reward token
    * @param state State of the reward token
    * @param rewardAmount Total amount ot distribute
    */
    function _updateRewardDistribution(address rewardToken, RewardState storage state, uint256 rewardAmount) internal {
        // Calculate the remaining duration of the current distribution (if not already over)
        // to calculate the amount fo rewards not yet distributed, and add them to the new amount to distribute
        if(block.timestamp < state.distributionEndTimestamp) {
            uint256 remainingRewards = state.ratePerSecond * (state.distributionEndTimestamp - block.timestamp);
            rewardAmount += remainingRewards;
        }
        // Calculate the new rate per second
        // & update the storage for the new distribution state
        state.ratePerSecond = rewardAmount / DISTRIBUTION_DURATION;
        state.currentRewardAmount = rewardAmount;
        state.lastUpdate = safe128(block.timestamp);
        uint256 distributionEnd = block.timestamp + DISTRIBUTION_DURATION;
        state.distributionEndTimestamp = safe128(distributionEnd);

        emit NewRewards(rewardToken, rewardAmount, distributionEnd);
    }


    // Internal functions

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

    /**
    * @dev Calculate the new rewardPerToken value for a reward token distribution
    * @param reward Address of the reward token
    * @return uint256 : new rewardPerToken value
    */
    function _getNewRewardPerToken(address reward) internal view returns(uint256) {
        RewardState storage state = rewardStates[reward];

        // If no fudns are deposited, we don't want to distribute rewards
        if(totalScaledAmount == 0) return state.rewardPerToken;

        // Get the last update timestamp
        uint256 lastRewardTimetamp = lastRewardUpdateTimestamp(reward);
        if(state.lastUpdate == lastRewardTimetamp) return state.rewardPerToken;

        // Calculate the increase since the last update
        return state.rewardPerToken + (
            (((lastRewardTimetamp - state.lastUpdate) * state.ratePerSecond) * UNIT) / totalScaledAmount
        );
    }

    /**
    * @dev Calculate the amount of rewards accrued by an user since last update for a reward token
    * @param reward Address of the reward token
    * @param user Address of the user
    * @return uint256 : Accrued rewards amount for the user
    */
    function _getUserEarnedRewards(address reward, address user, uint256 currentRewardPerToken) internal view returns(uint256) {
        UserRewardState storage userState = rewardStates[reward].userStates[user];

        // Get the user scaled balance
        uint256 userScaledBalance = userScaledBalances[user];

        if(userScaledBalance == 0) return 0;

        // If the user has a previous deposit (scaled balance is not null), calcualte the
        // earned rewards based on the increase of the rewardPerToken value
        return (userScaledBalance * (currentRewardPerToken - userState.lastRewardPerToken)) / UNIT;
    }

    /**
    * @dev Update the reward token distribution state
    * @param reward Address of the reward token
    */
    function _updateRewardState(address reward) internal {
        RewardState storage state = rewardStates[reward];

        // Update the storage with the new reward state 
        state.rewardPerToken = _getNewRewardPerToken(reward);
        state.lastUpdate = safe128(lastRewardUpdateTimestamp(reward));
    }

    /**
    * @dev Update the user reward state for a given reward token
    * @param reward Address of the reward token
    * @param user Address of the user
    */
    function _updateUserRewardState(address reward, address user) internal {
        // Update the reward token state before the user's state
        _updateRewardState(reward);

        UserRewardState storage userState = rewardStates[reward].userStates[user];

        // Update the storage with the new reward state
        uint256 currentRewardPerToken = rewardStates[reward].rewardPerToken;
        userState.accruedRewards += _getUserEarnedRewards(reward, user, currentRewardPerToken);
        userState.lastRewardPerToken = currentRewardPerToken;
    }

    /**
    * @dev Update the reward state for all the reward tokens
    */
    function _updateAllRewardStates() internal {
        address[] memory _rewards = rewardList;
        uint256 length = _rewards.length;

        // For all reward token in the list, update the reward state
        for(uint256 i; i < length;){
            _updateRewardState(_rewards[i]);

            unchecked{ ++i; }
        }
    }

    /**
    * @dev Update the reward state of the given user for all the reward tokens
    * @param user Address of the user
    */
    function _updateAllUserRewardStates(address user) internal {
        address[] memory _rewards = rewardList;
        uint256 length = _rewards.length;

        // For all reward token in the list, update the user's reward state
        for(uint256 i; i < length;){
            _updateUserRewardState(_rewards[i], user);

            unchecked{ ++i; }
        }
    }

    /**
    * @dev Claims rewards of an user for a given reward token and sends them to the receiver address
    * @param reward Address of reward token
    * @param user Address of the user
    * @param receiver Address to receive the rewards
    * @return uint256 : claimed amount
    */
    function _claimRewards(address reward, address user, address receiver) internal returns(uint256) {
        // Update all user states to get all current claimable rewards
        _updateUserRewardState(reward, user);

        UserRewardState storage userState = rewardStates[reward].userStates[user];
        
        // Fetch the amount of rewards accrued by the user
        uint256 rewardAmount = userState.accruedRewards;

        if(rewardAmount == 0) return 0;
        
        // Reset user's accrued rewards
        userState.accruedRewards = 0;

        // If the user accrued rewards, send them to the given receiver
        IERC20(reward).safeTransfer(receiver, rewardAmount);

        emit ClaimedRewards(reward, user, receiver, rewardAmount);

        return rewardAmount;
    }

    /**
    * @dev Claims all rewards of an user and sends them to the receiver address
    * @param user Address of the user
    * @param receiver Address to receive the rewards
    * @return UserClaimedRewards[] : list of claimed rewards
    */
    function _claimAllRewards(address user, address receiver) internal returns(UserClaimedRewards[] memory) {
        address[] memory rewards = rewardList;
        uint256 rewardsLength = rewards.length;

        UserClaimedRewards[] memory rewardAmounts = new UserClaimedRewards[](rewardsLength);

        // Update all user states to get all current claimable rewards
        _updateAllUserRewardStates(user);

        // For each reward token in the reward list
        for(uint256 i; i < rewardsLength; ++i){
            UserRewardState storage userState = rewardStates[rewards[i]].userStates[user];
            
            // Fetch the amount of rewards accrued by the user
            uint256 rewardAmount = userState.accruedRewards;

            // If the user accrued no rewards, skip
            if(rewardAmount == 0) continue;

            // Track the claimed amount for the reward token
            rewardAmounts[i].reward = rewards[i];
            rewardAmounts[i].amount = rewardAmount;
            
            // Reset user's accrued rewards
            userState.accruedRewards = 0;
            
            // For each reward token, send the accrued rewards to the given receiver
            IERC20(rewards[i]).safeTransfer(receiver, rewardAmount);

            emit ClaimedRewards(rewards[i], user, receiver, rewardAmounts[i].amount);
        }

        return rewardAmounts;
    }


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

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

    /**
    * @notice Add an address to the lsit of allowed reward depositors
    * @param depositor Address to deposit rewards
    */
    function addRewardDepositor(address depositor) external onlyOwner {
        if(depositor == address(0)) revert Errors.AddressZero();
        if(rewardDepositors[depositor]) revert Errors.AlreadyListedDepositor();

        rewardDepositors[depositor] = true;

        emit AddedRewardDepositor(depositor);
    }

    /**
    * @notice Remove an address from the lsit of allowed reward depositors
    * @param depositor Address to deposit rewards
    */
    function removeRewardDepositor(address depositor) external onlyOwner {
        if(depositor == address(0)) revert Errors.AddressZero();
        if(!rewardDepositors[depositor]) revert Errors.NotListedDepositor();

        rewardDepositors[depositor] = false;

        emit RemovedRewardDepositor(depositor);
    }

    /**
    * @notice Sets a given address as allowed to claim rewards for a given user
    * @dev Sets a given address as allowed to claim rewards for a given user
    * @param user Address of the user
    * @param claimer Address of the allowed claimer
    */
    function setUserAllowedClaimer(address user, address claimer) external onlyOwner {
        if(user == address(0) || claimer == address(0)) revert Errors.AddressZero();

        // Set the given address as the claimer for the given user
        allowedClaimer[user] = claimer;

        emit SetUserAllowedClaimer(user, claimer);
    }


    // Maths

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


}
// File: contracts/DullahanVault.sol



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


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

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

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

    // Constants

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

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

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


    // Struct

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


    // Storage

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

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

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

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

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

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

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


    // Events

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

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

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

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

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

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

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


    // Modifers

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

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

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


    // Constructor

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

        admin = _admin;

        reserveRatio = _reserveRatio;
        reserveManager = _reserveManager;

        AAVE = _aave;
        STK_AAVE = _stkAave;
    }

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

        initialized = true;

        votingPowerManager = _votingPowerManager;
        proposalPowerManager = _proposalPowerManager;

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

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

        emit Initialized();
    }


    // View functions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


    // State-changing functions

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

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

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

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

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

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

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

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

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


    // Pods Manager functions

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

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

        IERC20 _stkAave = IERC20(STK_AAVE);

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

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

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

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

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

        emit NotifyRentedAmount(manager, pod, addedAmount);
    }

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

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

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

        emit PullFromPod(manager, pod, amount);
    }


    // Internal functions

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

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

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

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

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

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

        afterDeposit(amount);

        return (amount, minted);
    }

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

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

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

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

        IERC20 _stkAave = IERC20(STK_AAVE);

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

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

        beforeWithdraw(amount);

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

        return (amount, burned);
    }

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

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

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

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

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

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

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

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

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


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

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

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

        pendingAdmin = newAdmin;

        emit NewPendingAdmin(oldPendingAdmin, newAdmin);
    }

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

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

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

        podManagers[newManager].rentingAllowed = true;

        emit NewPodManager(newManager);
    }

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

        podManagers[manager].rentingAllowed = false;

        emit BlockedPodManager(manager);
    }

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

        address oldManager = votingPowerManager;
        votingPowerManager = newManager;

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

        emit UpdatedVotingPowerManager(oldManager, newManager);
    }

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

        address oldManager = proposalPowerManager;
        proposalPowerManager = newManager;

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

        emit UpdatedProposalPowerManager(oldManager, newManager);
    }

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

        address oldManager = reserveManager;
        reserveManager = newManager;

        emit UpdatedReserveManager(oldManager, newManager);
    }

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

        uint256 oldRatio = reserveRatio;
        reserveRatio = newRatio;

        emit UpdatedReserveRatio(oldRatio, newRatio);
    }

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

        uint256 oldRatio = bufferRatio;
        bufferRatio = newRatio;

        emit UpdatedBufferRatio(oldRatio, newRatio);
    }

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

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

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

        emit ReserveDeposit(msg.sender, amount);

        return true;
    }

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

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

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

        emit ReserveWithdraw(receiver, amount);

        return true;
    }

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

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

        emit TokenRecovered(token, amount);

        return true;
    }

    // Maths

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

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



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

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

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

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

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

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

    function asset() external view returns (address);

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

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

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

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

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

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

    function totalAssets() external view returns (uint256);

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

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

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

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

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

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

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

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

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

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

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

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

// File: contracts/interfaces/IGovernancePowerDelegationToken.sol



//Aave Governance Token Interface

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

interface IGovernancePowerDelegationToken {
    enum DelegationType {
        VOTING_POWER,
        PROPOSITION_POWER
    }

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

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

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

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

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

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

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

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

// File: contracts/interfaces/IScalingERC20.sol



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

interface IScalingERC20 {
    
    function totalSupply() external view returns (uint256);
    function totalScaledSupply() external view returns (uint256);

    function balanceOf(address user) external view returns (uint256);
    function scaledBalanceOf(address user) external view returns (uint256);
    
}
// File: contracts/interfaces/IStakedAave.sol



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

interface IStakedAave {

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

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

  event Cooldown(address indexed user);

  function stake(address to, uint256 amount) external;

  function redeem(address to, uint256 amount) external;

  function cooldown() external;

  function claimRewards(address to, uint256 amount) external;

  function getTotalRewardsBalance(address staker) external view returns (uint256);
}
// File: contracts/modules/DullahanZapDeposit.sol



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


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

import "../DullahanVault.sol";
import "../DullahanRewardsStaking.sol";
import "../interfaces/IStakedAave.sol";
import "../utils/Owner.sol";
import "../oz/utils/ReentrancyGuard.sol";
import "../oz/utils/Pausable.sol";
import "../oz/interfaces/IERC20.sol";
import "../oz/libraries/SafeERC20.sol";
import {Errors} from "../utils/Errors.sol";

/** @title Dullahan Zap Deposit contract
 *  @author Paladin
 *  @notice Contract to zap deposit in the Vault & stake in the Staking module
 */
contract DullahanZapDeposit is Owner, Pausable {
    using SafeERC20 for IERC20;

    // Storage

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

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


    // Events

    /** @notice Event emitted when a Zap Depsoit is performed */
    event ZapDeposit(address indexed caller, address indexed receiver, address indexed sourceToken, uint256 amount, bool staked);

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


    // Constructor

    constructor(
        address _aave,
        address _stkAave,
        address _vault,
        address _staking
    ) {
        if(_aave == address(0) || _stkAave == address(0) || _vault == address(0) || _staking == address(0)) revert Errors.AddressZero();

        aave = _aave;
        stkAave = _stkAave;

        vault = _vault;
        staking = _staking;
    }


    // User functions

    /**
    * @notice Zap deposit AAVE or stkAAVE into the Vault & stake them
    * @dev Pull AAVE or stkAAVE, deposit in the Vault, and stake if flag was given
    * @param sourceToken Address of the token to pull (AAVE or stkAAVE)
    * @param amount Amount to deposit
    * @param receiver Address to receive the share token / to be staked on behalf of
    * @param stake Flag to stake the received shares
    */
    function zapDeposit(address sourceToken, uint256 amount, address receiver, bool stake) external whenNotPaused {
        if(sourceToken == address(0) || receiver == address(0)) revert Errors.AddressZero();
        if(amount == 0) revert Errors.NullAmount();
        if(sourceToken != aave && sourceToken != stkAave) revert Errors.InvalidSourceToken();

        // Pull the tokens from the caller
        IERC20(sourceToken).safeTransferFrom(msg.sender, address(this), amount);

        // If the source tokens is AAVE, stake them into stkAAVE
        if(sourceToken == aave) {
            IERC20(aave).safeIncreaseAllowance(stkAave, amount);
            IStakedAave(stkAave).stake(address(this), amount);
        }

        IERC20(stkAave).safeIncreaseAllowance(vault, amount);

        if(stake) {
            // If the caller desires to stake their tokens, deposit the stkAAVE in the Vault
            // & stake them on behalf of the given receiver
            uint256 shares = DullahanVault(vault).deposit(amount, address(this));
            if(shares != amount) revert Errors.DepositFailed();

            IERC20(vault).safeIncreaseAllowance(staking, amount);
            DullahanRewardsStaking(staking).stake(amount, receiver);
        } else {
            // If the caller does not desire to stake their tokens, deposit
            // the stkAAVE in the Vault on behalf of the given receiver address directly
            uint256 shares = DullahanVault(vault).deposit(amount, receiver);
            if(shares != amount) revert Errors.DepositFailed();
        }

        emit ZapDeposit(msg.sender, receiver, sourceToken, amount, stake);
    }


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

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

    /**
    * @notice Recover ERC2O tokens in the contract
    * @dev Recover ERC2O tokens in the contract
    * @param token Address of the ERC2O token
    * @return bool: success
    */
    function recoverERC20(address token) external onlyOwner returns(bool) {
        uint256 amount = IERC20(token).balanceOf(address(this));
        if(amount == 0) revert Errors.NullAmount();
        IERC20(token).safeTransfer(msg.sender, amount);

        emit TokenRecovered(token, amount);

        return true;
    }

}
// File: contracts/oz/interfaces/IERC20.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

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

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

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

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../interfaces/IERC20.sol";
import "../utils/Address.sol";

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

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

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

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

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

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

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

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}
// File: contracts/oz/utils/Address.sol



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

pragma solidity ^0.8.1;

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

        return account.code.length > 0;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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

pragma solidity ^0.8.0;

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

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}
// File: contracts/oz/utils/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)
            }
        }
    }
}