// Contract: Controller
// Target function: function calcAccountEquity(address _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;
    }

    /**
     * @notice Hook function before iToken `borrow()`
     * Checks if the account should be allowed to borrow the given iToken
     * Will `revert()` if any check fails
     * @param _iToken The iToken to check the borrow against
     * @param _borrower The account which would borrow iToken
     * @param _borrowAmount The amount of underlying to borrow
     */
    function beforeBorrow(
        address _iToken,
        address _borrower,
        uint256 _borrowAmount
    ) external virtual override {
        _checkiTokenListed(_iToken);

        Market storage _market = markets[_iToken];
        require(!_market.borrowPaused, "Token borrow has been paused");

        if (!hasBorrowed(_borrower, _iToken)) {
            // Unlike collaterals, borrowed asset can only be added by iToken,
            // rather than enabled by user directly.
            require(msg.sender == _iToken, "sender must be iToken");

            // Have checked _iToken is listed, just add it
            _addToBorrowed(_borrower, _iToken);
        }

        // Check borrower's equity
        (, uint256 _shortfall, , ) =
            calcAccountEquityWithEffect(_borrower, _iToken, 0, _borrowAmount);

        require(_shortfall == 0, "Account has some shortfall");

        // Check the iToken's borrow capacity, -1 means no limit
        uint256 _totalBorrows = IiToken(_iToken).totalBorrows();
        require(
            _totalBorrows.add(_borrowAmount) <= _market.borrowCapacity,
            "Token borrow capacity reached"
        );

        // Update the Reward Distribution Borrow state and distribute reward to borrower
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            true
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _borrower,
            true
        );
    }

    /**
     * @notice Hook function after iToken `borrow()`
     * Will `revert()` if any operation fails
     * @param _iToken The iToken being borrewd
     * @param _borrower The account which borrowed iToken
     * @param _borrowedAmount  The amount of underlying being borrowed
     */
    function afterBorrow(
        address _iToken,
        address _borrower,
        uint256 _borrowedAmount
    ) external virtual override {
        _iToken;
        _borrower;
        _borrowedAmount;
    }

    /**
     * @notice Hook function before iToken `repayBorrow()`
     * Checks if the account should be allowed to repay the given iToken
     * for the borrower. Will `revert()` if any check fails
     * @param _iToken The iToken to verify the repay against
     * @param _payer The account which would repay iToken
     * @param _borrower The account which has borrowed
     * @param _repayAmount The amount of underlying to repay
     */
    function beforeRepayBorrow(
        address _iToken,
        address _payer,
        address _borrower,
        uint256 _repayAmount
    ) external override {
        _checkiTokenListed(_iToken);

        // Update the Reward Distribution Borrow state and distribute reward to borrower
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            true
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _borrower,
            true
        );

        _payer;
        _repayAmount;
    }

    /**
     * @notice Hook function after iToken `repayBorrow()`
     * Will `revert()` if any operation fails
     * @param _iToken The iToken being repaid
     * @param _payer The account which would repay
     * @param _borrower The account which has borrowed
     * @param _repayAmount  The amount of underlying being repaied
     */
    function afterRepayBorrow(
        address _iToken,
        address _payer,
        address _borrower,
        uint256 _repayAmount
    ) external override {
        _checkiTokenListed(_iToken);

        // Remove _iToken from borrowed list if new borrow balance is 0
        if (IiToken(_iToken).borrowBalanceStored(_borrower) == 0) {
            // Only allow called by iToken as we are going to remove this token from borrower's borrowed list
            require(msg.sender == _iToken, "sender must be iToken");

            // Have checked _iToken is listed, just remove it
            _removeFromBorrowed(_borrower, _iToken);
        }

        _payer;
        _repayAmount;
    }

    /**
     * @notice Hook function before iToken `liquidateBorrow()`
     * Checks if the account should be allowed to liquidate the given iToken
     * for the borrower. Will `revert()` if any check fails
     * @param _iTokenBorrowed The iToken was borrowed
     * @param _iTokenCollateral The collateral iToken to be liqudate with
     * @param _liquidator The account which would repay the borrowed iToken
     * @param _borrower The account which has borrowed
     * @param _repayAmount The amount of underlying to repay
     */
    function beforeLiquidateBorrow(
        address _iTokenBorrowed,
        address _iTokenCollateral,
        address _liquidator,
        address _borrower,
        uint256 _repayAmount
    ) external override {
        // Tokens must have been listed
        require(
            iTokens.contains(_iTokenBorrowed) &&
                iTokens.contains(_iTokenCollateral),
            "Tokens have not been listed"
        );

        (, uint256 _shortfall, , ) = calcAccountEquity(_borrower);

        require(_shortfall > 0, "Account does not have shortfall");

        // Only allowed to repay the borrow balance's close factor
        uint256 _borrowBalance =
            IiToken(_iTokenBorrowed).borrowBalanceStored(_borrower);
        uint256 _maxRepay = _borrowBalance.rmul(closeFactorMantissa);

        require(_repayAmount <= _maxRepay, "Repay exceeds max repay allowed");

        _liquidator;
    }

    /**
     * @notice Hook function after iToken `liquidateBorrow()`
     * Will `revert()` if any operation fails
     * @param _iTokenBorrowed The iToken was borrowed
     * @param _iTokenCollateral The collateral iToken to be seized
     * @param _liquidator The account which would repay and seize
     * @param _borrower The account which has borrowed
     * @param _repaidAmount  The amount of underlying being repaied
     * @param _seizedAmount  The amount of collateral being seized
     */
    function afterLiquidateBorrow(
        address _iTokenBorrowed,
        address _iTokenCollateral,
        address _liquidator,
        address _borrower,
        uint256 _repaidAmount,
        uint256 _seizedAmount
    ) external override {
        _iTokenBorrowed;
        _iTokenCollateral;
        _liquidator;
        _borrower;
        _repaidAmount;
        _seizedAmount;

        // Unlike repayBorrow, liquidateBorrow does not allow to repay all borrow balance
        // No need to check whether should remove from borrowed asset list
    }

    /**
     * @notice Hook function before iToken `seize()`
     * Checks if the liquidator should be allowed to seize the collateral iToken
     * Will `revert()` if any check fails
     * @param _iTokenCollateral The collateral iToken to be seize
     * @param _iTokenBorrowed The iToken was borrowed
     * @param _liquidator The account which has repaid the borrowed iToken
     * @param _borrower The account which has borrowed
     * @param _seizeAmount The amount of collateral iToken to seize
     */
    function beforeSeize(
        address _iTokenCollateral,
        address _iTokenBorrowed,
        address _liquidator,
        address _borrower,
        uint256 _seizeAmount
    ) external override {
        require(!seizePaused, "Seize has been paused");

        // Markets must have been listed
        require(
            iTokens.contains(_iTokenBorrowed) &&
                iTokens.contains(_iTokenCollateral),
            "Tokens have not been listed"
        );

        // Sanity Check the controllers
        require(
            IiToken(_iTokenBorrowed).controller() ==
                IiToken(_iTokenCollateral).controller(),
            "Controller mismatch between Borrowed and Collateral"
        );

        // Update the Reward Distribution Supply state on collateral
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iTokenCollateral,
            false
        );

        // Update reward of liquidator and borrower on collateral
        IRewardDistributor(rewardDistributor).updateReward(
            _iTokenCollateral,
            _liquidator,
            false
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iTokenCollateral,
            _borrower,
            false
        );

        _seizeAmount;
    }

    /**
     * @notice Hook function after iToken `seize()`
     * Will `revert()` if any operation fails
     * @param _iTokenCollateral The collateral iToken to be seized
     * @param _iTokenBorrowed The iToken was borrowed
     * @param _liquidator The account which has repaid and seized
     * @param _borrower The account which has borrowed
     * @param _seizedAmount  The amount of collateral being seized
     */
    function afterSeize(
        address _iTokenCollateral,
        address _iTokenBorrowed,
        address _liquidator,
        address _borrower,
        uint256 _seizedAmount
    ) external override {
        _iTokenBorrowed;
        _iTokenCollateral;
        _liquidator;
        _borrower;
        _seizedAmount;
    }

    /**
     * @notice Hook function before iToken `transfer()`
     * Checks if the transfer should be allowed
     * Will `revert()` if any check fails
     * @param _iToken The iToken to be transfered
     * @param _from The account to be transfered from
     * @param _to The account to be transfered to
     * @param _amount The amount to be transfered
     */
    function beforeTransfer(
        address _iToken,
        address _from,
        address _to,
        uint256 _amount
    ) external override {
        // _redeemAllowed below will check whether _iToken is listed

        require(!transferPaused, "Transfer has been paused");

        // Check account equity with this amount to decide whether the transfer is allowed
        _redeemAllowed(_iToken, _from, _amount);

        // Update the Reward Distribution supply state
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            false
        );

        // Update reward of from and to
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _from,
            false
        );
        IRewardDistributor(rewardDistributor).updateReward(_iToken, _to, false);
    }

    /**
     * @notice Hook function after iToken `transfer()`
     * Will `revert()` if any operation fails
     * @param _iToken The iToken was transfered
     * @param _from The account was transfer from
     * @param _to The account was transfer to
     * @param _amount  The amount was transfered
     */
    function afterTransfer(
        address _iToken,
        address _from,
        address _to,
        uint256 _amount
    ) external override {
        _iToken;
        _from;
        _to;
        _amount;
    }

    /**
     * @notice Hook function before iToken `flashloan()`
     * Checks if the flashloan should be allowed
     * Will `revert()` if any check fails
     * @param _iToken The iToken to be flashloaned
     * @param _to The account flashloaned transfer to
     * @param _amount The amount to be flashloaned
     */
    function beforeFlashloan(
        address _iToken,
        address _to,
        uint256 _amount
    ) external override {
        // Flashloan share the same pause state with borrow
        require(!markets[_iToken].borrowPaused, "Token borrow has been paused");

        _checkiTokenListed(_iToken);

        _to;
        _amount;

        // Update the Reward Distribution Borrow state
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            true
        );
    }

    /**
     * @notice Hook function after iToken `flashloan()`
     * Will `revert()` if any operation fails
     * @param _iToken The iToken was flashloaned
     * @param _to The account flashloan transfer to
     * @param _amount  The amount was flashloaned
     */
    function afterFlashloan(
        address _iToken,
        address _to,
        uint256 _amount
    ) external override {
        _iToken;
        _to;
        _amount;
    }

    /*********************************/
    /***** Internal  Functions *******/
    /*********************************/

    function _redeemAllowed(
        address _iToken,
        address _redeemer,
        uint256 _amount
    ) private view {
        _checkiTokenListed(_iToken);

        // No need to check liquidity if _redeemer has not used _iToken as collateral
        if (!accountsData[_redeemer].collaterals.contains(_iToken)) {
            return;
        }

        (, uint256 _shortfall, , ) =
            calcAccountEquityWithEffect(_redeemer, _iToken, _amount, 0);

        require(_shortfall == 0, "Account has some shortfall");
    }

    /**
     * @dev Check if _iToken is listed
     */
    function _checkiTokenListed(address _iToken) internal view {
        require(iTokens.contains(_iToken), "Token has not been listed");
    }

    /*********************************/
    /** Account equity calculation ***/
    /*********************************/