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