// Contract: Controller
// Target function: function beforeBorrow(
    /**
     * @notice Sets the closeFactor used when liquidating borrows
     * @dev Admin function to set closeFactor
     * @param _newCloseFactorMantissa New close factor, scaled by 1e18
     */
    function _setCloseFactor(uint256 _newCloseFactorMantissa)
        external
        override
        onlyOwner
    {
        require(
            _newCloseFactorMantissa >= closeFactorMinMantissa &&
                _newCloseFactorMantissa <= closeFactorMaxMantissa,
            "Close factor invalid"
        );

        uint256 _oldCloseFactorMantissa = closeFactorMantissa;
        closeFactorMantissa = _newCloseFactorMantissa;
        emit NewCloseFactor(_oldCloseFactorMantissa, _newCloseFactorMantissa);
    }

    /**
     * @notice Sets liquidationIncentive
     * @dev Admin function to set liquidationIncentive
     * @param _newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18
     */
    function _setLiquidationIncentive(uint256 _newLiquidationIncentiveMantissa)
        external
        override
        onlyOwner
    {
        require(
            _newLiquidationIncentiveMantissa >=
                liquidationIncentiveMinMantissa &&
                _newLiquidationIncentiveMantissa <=
                liquidationIncentiveMaxMantissa,
            "Liquidation incentive invalid"
        );

        uint256 _oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa;
        liquidationIncentiveMantissa = _newLiquidationIncentiveMantissa;

        emit NewLiquidationIncentive(
            _oldLiquidationIncentiveMantissa,
            _newLiquidationIncentiveMantissa
        );
    }

    /**
     * @notice Sets the collateralFactor for a iToken
     * @dev Admin function to set collateralFactor for a iToken
     * @param _iToken The token to set the factor on
     * @param _newCollateralFactorMantissa The new collateral factor, scaled by 1e18
     */
    function _setCollateralFactor(
        address _iToken,
        uint256 _newCollateralFactorMantissa
    ) external override onlyOwner {
        _checkiTokenListed(_iToken);

        require(
            _newCollateralFactorMantissa <= collateralFactorMaxMantissa,
            "Collateral factor invalid"
        );

        // Its value will be taken into account when calculate account equity
        // Check if the price is available for the calculation
        require(
            IPriceOracle(priceOracle).getUnderlyingPrice(_iToken) != 0,
            "Failed to set collateral factor, underlying price is unavailable"
        );

        Market storage _market = markets[_iToken];
        uint256 _oldCollateralFactorMantissa = _market.collateralFactorMantissa;
        _market.collateralFactorMantissa = _newCollateralFactorMantissa;

        emit NewCollateralFactor(
            _iToken,
            _oldCollateralFactorMantissa,
            _newCollateralFactorMantissa
        );
    }

    /**
     * @notice Sets the borrowFactor for a iToken
     * @dev Admin function to set borrowFactor for a iToken
     * @param _iToken The token to set the factor on
     * @param _newBorrowFactorMantissa The new borrow factor, scaled by 1e18
     */
    function _setBorrowFactor(address _iToken, uint256 _newBorrowFactorMantissa)
        external
        override
        onlyOwner
    {
        _checkiTokenListed(_iToken);

        require(
            _newBorrowFactorMantissa > 0 &&
                _newBorrowFactorMantissa <= borrowFactorMaxMantissa,
            "Borrow factor invalid"
        );

        // Its value will be taken into account when calculate account equity
        // Check if the price is available for the calculation
        require(
            IPriceOracle(priceOracle).getUnderlyingPrice(_iToken) != 0,
            "Failed to set borrow factor, underlying price is unavailable"
        );

        Market storage _market = markets[_iToken];
        uint256 _oldBorrowFactorMantissa = _market.borrowFactorMantissa;
        _market.borrowFactorMantissa = _newBorrowFactorMantissa;

        emit NewBorrowFactor(
            _iToken,
            _oldBorrowFactorMantissa,
            _newBorrowFactorMantissa
        );
    }

    /**
     * @notice Sets the borrowCapacity for a iToken
     * @dev Admin function to set borrowCapacity for a iToken
     * @param _iToken The token to set the capacity on
     * @param _newBorrowCapacity The new borrow capacity
     */
    function _setBorrowCapacity(address _iToken, uint256 _newBorrowCapacity)
        external
        override
        onlyOwner
    {
        _checkiTokenListed(_iToken);

        Market storage _market = markets[_iToken];
        uint256 oldBorrowCapacity = _market.borrowCapacity;
        _market.borrowCapacity = _newBorrowCapacity;

        emit NewBorrowCapacity(_iToken, oldBorrowCapacity, _newBorrowCapacity);
    }

    /**
     * @notice Sets the supplyCapacity for a iToken
     * @dev Admin function to set supplyCapacity for a iToken
     * @param _iToken The token to set the capacity on
     * @param _newSupplyCapacity The new supply capacity
     */
    function _setSupplyCapacity(address _iToken, uint256 _newSupplyCapacity)
        external
        override
        onlyOwner
    {
        _checkiTokenListed(_iToken);

        Market storage _market = markets[_iToken];
        uint256 oldSupplyCapacity = _market.supplyCapacity;
        _market.supplyCapacity = _newSupplyCapacity;

        emit NewSupplyCapacity(_iToken, oldSupplyCapacity, _newSupplyCapacity);
    }

    /**
     * @notice Sets the pauseGuardian
     * @dev Admin function to set pauseGuardian
     * @param _newPauseGuardian The new pause guardian
     */
    function _setPauseGuardian(address _newPauseGuardian)
        external
        override
        onlyOwner
    {
        address _oldPauseGuardian = pauseGuardian;

        require(
            _newPauseGuardian != address(0) &&
                _newPauseGuardian != _oldPauseGuardian,
            "Pause guardian address invalid"
        );

        pauseGuardian = _newPauseGuardian;

        emit NewPauseGuardian(_oldPauseGuardian, _newPauseGuardian);
    }

    /**
     * @notice pause/unpause mint() for all iTokens
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _paused whether to pause or unpause
     */
    function _setAllMintPaused(bool _paused)
        external
        override
        checkPauser(_paused)
    {
        EnumerableSetUpgradeable.AddressSet storage _iTokens = iTokens;
        uint256 _len = _iTokens.length();

        for (uint256 i = 0; i < _len; i++) {
            _setMintPausedInternal(_iTokens.at(i), _paused);
        }
    }

    /**
     * @notice pause/unpause mint() for the iToken
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _iToken The iToken to pause/unpause
     * @param _paused whether to pause or unpause
     */
    function _setMintPaused(address _iToken, bool _paused)
        external
        override
        checkPauser(_paused)
    {
        _checkiTokenListed(_iToken);

        _setMintPausedInternal(_iToken, _paused);
    }

    function _setMintPausedInternal(address _iToken, bool _paused) internal {
        markets[_iToken].mintPaused = _paused;
        emit MintPaused(_iToken, _paused);
    }

    /**
     * @notice pause/unpause redeem() for all iTokens
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _paused whether to pause or unpause
     */
    function _setAllRedeemPaused(bool _paused)
        external
        override
        checkPauser(_paused)
    {
        EnumerableSetUpgradeable.AddressSet storage _iTokens = iTokens;
        uint256 _len = _iTokens.length();

        for (uint256 i = 0; i < _len; i++) {
            _setRedeemPausedInternal(_iTokens.at(i), _paused);
        }
    }

    /**
     * @notice pause/unpause redeem() for the iToken
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _iToken The iToken to pause/unpause
     * @param _paused whether to pause or unpause
     */
    function _setRedeemPaused(address _iToken, bool _paused)
        external
        override
        checkPauser(_paused)
    {
        _checkiTokenListed(_iToken);

        _setRedeemPausedInternal(_iToken, _paused);
    }

    function _setRedeemPausedInternal(address _iToken, bool _paused) internal {
        markets[_iToken].redeemPaused = _paused;
        emit RedeemPaused(_iToken, _paused);
    }

    /**
     * @notice pause/unpause borrow() for all iTokens
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _paused whether to pause or unpause
     */
    function _setAllBorrowPaused(bool _paused)
        external
        override
        checkPauser(_paused)
    {
        EnumerableSetUpgradeable.AddressSet storage _iTokens = iTokens;
        uint256 _len = _iTokens.length();

        for (uint256 i = 0; i < _len; i++) {
            _setBorrowPausedInternal(_iTokens.at(i), _paused);
        }
    }

    /**
     * @notice pause/unpause borrow() for the iToken
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _iToken The iToken to pause/unpause
     * @param _paused whether to pause or unpause
     */
    function _setBorrowPaused(address _iToken, bool _paused)
        external
        override
        checkPauser(_paused)
    {
        _checkiTokenListed(_iToken);

        _setBorrowPausedInternal(_iToken, _paused);
    }

    function _setBorrowPausedInternal(address _iToken, bool _paused) internal {
        markets[_iToken].borrowPaused = _paused;
        emit BorrowPaused(_iToken, _paused);
    }

    /**
     * @notice pause/unpause global transfer()
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _paused whether to pause or unpause
     */
    function _setTransferPaused(bool _paused)
        external
        override
        checkPauser(_paused)
    {
        _setTransferPausedInternal(_paused);
    }

    function _setTransferPausedInternal(bool _paused) internal {
        transferPaused = _paused;
        emit TransferPaused(_paused);
    }

    /**
     * @notice pause/unpause global seize()
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _paused whether to pause or unpause
     */
    function _setSeizePaused(bool _paused)
        external
        override
        checkPauser(_paused)
    {
        _setSeizePausedInternal(_paused);
    }

    function _setSeizePausedInternal(bool _paused) internal {
        seizePaused = _paused;
        emit SeizePaused(_paused);
    }

    /**
     * @notice pause/unpause all actions iToken, including mint/redeem/borrow
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _paused whether to pause or unpause
     */
    function _setiTokenPaused(address _iToken, bool _paused)
        external
        override
        checkPauser(_paused)
    {
        _checkiTokenListed(_iToken);

        _setiTokenPausedInternal(_iToken, _paused);
    }

    function _setiTokenPausedInternal(address _iToken, bool _paused) internal {
        Market storage _market = markets[_iToken];

        _market.mintPaused = _paused;
        emit MintPaused(_iToken, _paused);

        _market.redeemPaused = _paused;
        emit RedeemPaused(_iToken, _paused);

        _market.borrowPaused = _paused;
        emit BorrowPaused(_iToken, _paused);
    }

    /**
     * @notice pause/unpause entire protocol, including mint/redeem/borrow/seize/transfer
     * @dev Admin function, only owner and pauseGuardian can call this
     * @param _paused whether to pause or unpause
     */
    function _setProtocolPaused(bool _paused)
        external
        override
        checkPauser(_paused)
    {
        EnumerableSetUpgradeable.AddressSet storage _iTokens = iTokens;
        uint256 _len = _iTokens.length();

        for (uint256 i = 0; i < _len; i++) {
            address _iToken = _iTokens.at(i);

            _setiTokenPausedInternal(_iToken, _paused);
        }

        _setTransferPausedInternal(_paused);
        _setSeizePausedInternal(_paused);
    }

    /**
     * @notice Sets Reward Distributor
     * @dev Admin function to set reward distributor
     * @param _newRewardDistributor new reward distributor
     */
    function _setRewardDistributor(address _newRewardDistributor)
        external
        override
        onlyOwner
    {
        address _oldRewardDistributor = rewardDistributor;
        require(
            _newRewardDistributor != address(0) &&
                _newRewardDistributor != _oldRewardDistributor,
            "Reward Distributor address invalid"
        );

        rewardDistributor = _newRewardDistributor;
        emit NewRewardDistributor(_oldRewardDistributor, _newRewardDistributor);
    }

    /*********************************/
    /******** Policy Hooks **********/
    /*********************************/

    /**
     * @notice Hook function before iToken `mint()`
     * Checks if the account should be allowed to mint the given iToken
     * Will `revert()` if any check fails
     * @param _iToken The iToken to check the mint against
     * @param _minter The account which would get the minted tokens
     * @param _mintAmount The amount of underlying being minted to iToken
     */
    function beforeMint(
        address _iToken,
        address _minter,
        uint256 _mintAmount
    ) external override {
        _checkiTokenListed(_iToken);

        Market storage _market = markets[_iToken];
        require(!_market.mintPaused, "Token mint has been paused");

        // Check the iToken's supply capacity, -1 means no limit
        uint256 _totalSupplyUnderlying =
            IERC20Upgradeable(_iToken).totalSupply().rmul(
                IiToken(_iToken).exchangeRateStored()
            );
        require(
            _totalSupplyUnderlying.add(_mintAmount) <= _market.supplyCapacity,
            "Token supply capacity reached"
        );

        // Update the Reward Distribution Supply state and distribute reward to suppplier
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            false
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _minter,
            false
        );
    }

    /**
     * @notice Hook function after iToken `mint()`
     * Will `revert()` if any operation fails
     * @param _iToken The iToken being minted
     * @param _minter The account which would get the minted tokens
     * @param _mintAmount The amount of underlying being minted to iToken
     * @param _mintedAmount The amount of iToken being minted
     */
    function afterMint(
        address _iToken,
        address _minter,
        uint256 _mintAmount,
        uint256 _mintedAmount
    ) external override {
        _iToken;
        _minter;
        _mintAmount;
        _mintedAmount;
    }

    /**
     * @notice Hook function before iToken `redeem()`
     * Checks if the account should be allowed to redeem the given iToken
     * Will `revert()` if any check fails
     * @param _iToken The iToken to check the redeem against
     * @param _redeemer The account which would redeem iToken
     * @param _redeemAmount The amount of iToken to redeem
     */
    function beforeRedeem(
        address _iToken,
        address _redeemer,
        uint256 _redeemAmount
    ) external virtual override {
        // _redeemAllowed below will check whether _iToken is listed

        require(!markets[_iToken].redeemPaused, "Token redeem has been paused");

        _redeemAllowed(_iToken, _redeemer, _redeemAmount);

        // Update the Reward Distribution Supply state and distribute reward to suppplier
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            false
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _redeemer,
            false
        );
    }

    /**
     * @notice Hook function after iToken `redeem()`
     * Will `revert()` if any operation fails
     * @param _iToken The iToken being redeemed
     * @param _redeemer The account which redeemed iToken
     * @param _redeemAmount  The amount of iToken being redeemed
     * @param _redeemedUnderlying The amount of underlying being redeemed
     */
    function afterRedeem(
        address _iToken,
        address _redeemer,
        uint256 _redeemAmount,
        uint256 _redeemedUnderlying
    ) external virtual override {
        _iToken;
        _redeemer;
        _redeemAmount;
        _redeemedUnderlying;
    }