// Contract: AggregatorDataProvider
// Target function: function getVault(
MaxOracleDeviation
    );

    /// @notice The ```setOracleInfo``` function sets the oracle data
    /// @param _newOracle The new oracle address
    /// @param _newMaxOracleDeviation The new max oracle deviation
    function setOracle(address _newOracle, uint32 _newMaxOracleDeviation) external {
        _requireTimelock();
        ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
        emit SetOracleInfo(
            _exchangeRateInfo.oracle,
            _exchangeRateInfo.maxOracleDeviation,
            _newOracle,
            _newMaxOracleDeviation
        );
        _exchangeRateInfo.oracle = _newOracle;
        _exchangeRateInfo.maxOracleDeviation = _newMaxOracleDeviation;
        exchangeRateInfo = _exchangeRateInfo;
    }

    bool public isMaxLTVSetterRevoked;

    /// @notice The ```RevokeMaxLTVSetter``` event is emitted when the max LTV setter is revoked
    event RevokeMaxLTVSetter();

    /// @notice The ```revokeMaxLTVSetter``` function revokes the max LTV setter
    function revokeMaxLTVSetter() external {
        _requireTimelock();
        isMaxLTVSetterRevoked = true;
        emit RevokeMaxLTVSetter();
    }

    /// @notice The ```SetMaxLTV``` event is emitted when the max LTV is set
    /// @param oldMaxLTV The old max LTV
    /// @param newMaxLTV The new max LTV
    event SetMaxLTV(uint256 oldMaxLTV, uint256 newMaxLTV);

    /// @notice The ```setMaxLTV``` function sets the max LTV
    /// @param _newMaxLTV The new max LTV
    function setMaxLTV(uint256 _newMaxLTV) external {
        _requireTimelock();
        if (isMaxLTVSetterRevoked) revert SetterRevoked();
        emit SetMaxLTV(maxLTV, _newMaxLTV);
        maxLTV = _newMaxLTV;
    }

    /// @notice The ```SetRateContract``` event is emitted when the rate contract is set
    /// @param oldRateContract The old rate contract
    /// @param newRateContract The new rate contract
    event SetRateContract(address oldRateContract, address newRateContract);

    /// @notice The ```setRateContract``` function sets the rate contract address
    /// @param _newRateContract The new rate contract address
    function setRateContract(address _newRateContract) external {
        _requireTimelock();
        emit SetRateContract(address(rateContract), _newRateContract);
        rateContract = IRateCalculatorV2(_newRateContract);
    }

    /// @notice The ```SetLiquidationFees``` event is emitted when the liquidation fees are set
    /// @param oldCleanLiquidationFee The old clean liquidation fee
    /// @param oldDirtyLiquidationFee The old dirty liquidation fee
    /// @param oldProtocolLiquidationFee The old protocol liquidation fee
    /// @param newCleanLiquidationFee The new clean liquidation fee
    /// @param newDirtyLiquidationFee The new dirty liquidation fee
    /// @param newProtocolLiquidationFee The new protocol liquidation fee
    event SetLiquidationFees(
        uint256 oldCleanLiquidationFee,
        uint256 oldDirtyLiquidationFee,
        uint256 oldProtocolLiquidationFee,
        uint256 newCleanLiquidationFee,
        uint256 newDirtyLiquidationFee,
        uint256 newProtocolLiquidationFee
    );

    /// @notice The ```setLiquidationFees``` function sets the liquidation fees
    /// @param _newCleanLiquidationFee The new clean liquidation fee
    /// @param _newDirtyLiquidationFee The new dirty liquidation fee
    function setLiquidationFees(
        uint256 _newCleanLiquidationFee,
        uint256 _newDirtyLiquidationFee,
        uint256 _newProtocolLiquidationFee
    ) external {
        _requireTimelock();
        emit SetLiquidationFees(
            cleanLiquidationFee,
            dirtyLiquidationFee,
            protocolLiquidationFee,
            _newCleanLiquidationFee,
            _newDirtyLiquidationFee,
            _newProtocolLiquidationFee
        );
        cleanLiquidationFee = _newCleanLiquidationFee;
        dirtyLiquidationFee = _newDirtyLiquidationFee;
        protocolLiquidationFee = _newProtocolLiquidationFee;
    }

    /// @notice The ```ChangeFee``` event first when the fee is changed
    /// @param newFee The new fee
    event ChangeFee(uint32 newFee);

    /// @notice The ```changeFee``` function changes the protocol fee, max 50%
    /// @param _newFee The new fee
    function changeFee(uint32 _newFee) external {
        _requireTimelock();
        if (isInterestPaused) revert InterestPaused();
        if (_newFee > MAX_PROTOCOL_FEE) {
            revert BadProtocolFee();
        }
        _addInterest();
        currentRateInfo.feeToProtocolRate = _newFee;
        emit ChangeFee(_newFee);
    }

    /// @notice The ```WithdrawFees``` event fires when the fees are withdrawn
    /// @param shares Number of shares (fTokens) redeemed
    /// @param recipient To whom the assets were sent
    /// @param amountToTransfer The amount of fees redeemed
    event WithdrawFees(uint128 shares, address recipient, uint256 amountToTransfer, uint256 collateralAmount);

    /// @notice The ```withdrawFees``` function withdraws fees accumulated
    /// @param _shares Number of fTokens to redeem
    /// @param _recipient Address to send the assets
    /// @return _amountToTransfer Amount of assets sent to recipient
    function withdrawFees(uint128 _shares, address _recipient) external onlyOwner returns (uint256 _amountToTransfer) {
        if (_recipient == address(0)) revert InvalidReceiver();

        // Grab some data from state to save gas
        VaultAccount memory _totalAsset = totalAsset;

        // Take all available if 0 value passed
        if (_shares == 0) _shares = uint128(balanceOf(address(this)));

        // We must calculate this before we subtract from _totalAsset or invoke _burn
        _amountToTransfer = _totalAsset.toAmount(_shares, true);

        _approve(address(this), msg.sender, _shares);
        _redeem(_totalAsset, _amountToTransfer.toUint128(), _shares, _recipient, address(this));
        uint256 _collateralAmount = userCollateralBalance[address(this)];
        _removeCollateral(_collateralAmount, _recipient, address(this));
        emit WithdrawFees(_shares, _recipient, _amountToTransfer, _collateralAmount);
    }

    /// @notice The ```SetSwapper``` event fires whenever a swapper is black or whitelisted
    /// @param swapper The swapper address
    /// @param approval The approval
    event SetSwapper(address swapper, bool approval);

    /// @notice The ```setSwapper``` function is called to black or whitelist a given swapper address
    /// @dev
    /// @param _swapper The swapper address
    /// @param _approval The approval
    function setSwapper(address _swapper, bool _approval) external onlyOwner {
        swappers[_swapper] = _approval;
        emit SetSwapper(_swapper, _approval);
    }

    // ============================================================================================
    // Functions: Access Control
    // ============================================================================================

    /// @notice The ```pause``` function is called to pause all contract functionality
    function pause() external {
        _requireProtocolOrOwner();

        _setBorrowLimit(0);
        _setDepositLimit(0);
        if (!isRepayAccessControlRevoked) _pauseRepay(true);
        if (!isWithdrawAccessControlRevoked) _pauseWithdraw(true);
        if (!isLiquidateAccessControlRevoked) _pauseLiquidate(true);

        _addInterest();
        _pauseInterest(true);
    }

    /// @notice The ```unpause``` function is called to unpause all contract functionality
    function unpause() external {
        _requireTimelockOrOwner();

        _setBorrowLimit(type(uint256).max);
        _setDepositLimit(type(uint256).max);
        if (!isRepayAccessControlRevoked) _pauseRepay(false);
        if (!isWithdrawAccessControlRevoked) _pauseWithdraw(false);
        if (!isLiquidateAccessControlRevoked) _pauseLiquidate(false);

        _addInterest();
        _pauseInterest(false);
    }

    /// @notice The ```pauseBorrow``` function sets borrow limit to 0
    function pauseBorrow() external {
        _requireProtocolOrOwner();
        _setBorrowLimit(0);
    }

    /// @notice The ```setBorrowLimit``` function sets the borrow limit
    /// @param _limit The new borrow limit
    function setBorrowLimit(uint256 _limit) external {
        _requireTimelockOrOwner();
        _setBorrowLimit(_limit);
    }

    /// @notice The ```pauseDeposit``` function pauses deposit functionality
    function pauseDeposit() external {
        _requireProtocolOrOwner();
        _setDepositLimit(0);
    }

    /// @notice The ```setDepositLimit``` function sets the deposit limit
    /// @param _limit The new deposit limit
    function setDepositLimit(uint256 _limit) external {
        _requireTimelockOrOwner();
        _setDepositLimit(_limit);
    }

    /// @notice The ```pauseRepay``` function pauses repay functionality
    /// @param _isPaused The new pause state
    function pauseRepay(bool _isPaused) external {
        if (_isPaused) {
            _requireProtocolOrOwner();
        } else {
            _requireTimelockOrOwner();
        }
        if (isRepayAccessControlRevoked) revert AccessControlRevoked();
        _pauseRepay(_isPaused);
    }

    /// @notice The ```revokeRepayAccessControl``` function revokes repay access control
    function revokeRepayAccessControl() external {
        _requireTimelock();
        _revokeRepayAccessControl();
    }

    /// @notice The ```pauseWithdraw``` function pauses withdraw functionality
    /// @param _isPaused The new pause state
    function pauseWithdraw(bool _isPaused) external {
        if (_isPaused) {
            _requireProtocolOrOwner();
        } else {
            _requireTimelockOrOwner();
        }
        if (isWithdrawAccessControlRevoked) revert AccessControlRevoked();
        _pauseWithdraw(_isPaused);
    }

    /// @notice The ```revokeWithdrawAccessControl``` function revokes withdraw access control
    function revokeWithdrawAccessControl() external {
        _requireTimelock();
        _revokeWithdrawAccessControl();
    }

    /// @notice The ```pauseLiquidate``` function pauses liquidate functionality
    /// @param _isPaused The new pause state
    function pauseLiquidate(bool _isPaused) external {
        if (_isPaused) {
            _requireProtocolOrOwner();
        } else {
            _requireTimelockOrOwner();
        }
        if (isLiquidateAccessControlRevoked) revert AccessControlRevoked();
        _pauseLiquidate(_isPaused);
    }

    /// @notice The ```revokeLiquidateAccessControl``` function revokes liquidate access control
    function revokeLiquidateAccessControl() external {
        _requireTimelock();
        _revokeLiquidateAccessControl();
    }

    /// @notice The ```pauseInterest``` function pauses interest functionality
    /// @param _isPaused The new pause state
    function pauseInterest(bool _isPaused) external {
        if (_isPaused) {
            _requireProtocolOrOwner();
        } else {
            _requireTimelockOrOwner();
        }
        // Resets the lastTimestamp which has the effect of no interest accruing over the pause period
        _addInterest();
        _pauseInterest(_isPaused);
    }
}

contract AggregatorDataProvider is Ownable, IAggregatorDataProvider {
    // vault addresses
    address[] private _vaults;
    mapping(address => bool) private _vaultFlags;
    // strategy addresses of vault
    mapping(address => address[]) private _strategiesOfVault;
    // all strategy addresses
    address[] private _strategies;
    mapping(address => bool) private _strategyFlags;
    // vault factory address
    address private _factory;

    error AG_CALLER_NOT_ADMIN();

    event AddVault(address vault);
    event RemoveVault(address vault);
    event AddStrategy(address strategy);
    event RemoveStrategy(address strategy);

    modifier onlyAdmin() {
        if (msg.sender != owner() && msg.sender != _factory)
            revert AG_CALLER_NOT_ADMIN();
        _;
    }

    /**
     * @dev Add new vault to list
     * - Caller is owner or factory
     * @param _vault The new vault address
     */
    function addVault(address _vault) external onlyAdmin {
        _addVault(_vault);
    }

    /**
     * @dev Remove the specified vault from the list
     * @param _vault The address of the vault
     */
    function removeVault(address _vault) external onlyAdmin {
        _removeVault(_vault);
    }

    /**
     * @dev Add multiple new vaults to list
     * - Caller is owner or factory
     * @param vaults_ The new vault addresses
     */
    function addVaults(address[] memory vaults_) external onlyAdmin {
        uint256 count = vaults_.length;
        for (uint256 i; i < count; ++i) {
            _addVault(vaults_[i]);
        }
    }

    /**
     * @dev Add new strategy to vault
     * - Caller is owner or factory
     * @param _vault The vault address
     * @param _strategy The strategy address
     */
    function addVaultStrategy(
        address _vault,
        address _strategy
    ) external onlyAdmin {
        _linkStrategyToVault(_vault, _strategy);
    }

    function removeStrategyFromVault(
        address _vault,
        address _strategy
    ) external onlyAdmin {
        _unlinkStrategyFromVault(_vault, _strategy);
    }

    /**
     * @dev Add multiple new strategies to vault
     * - Caller is owner or factory
     * @param _vault The new vault addresse
     * @param strategies_ The new strategy addresses
     */
    function addVaultStrategies(
        address _vault,
        address[] memory strategies_
    ) external onlyAdmin {
        uint256 count = strategies_.length;
        for (uint256 i; i < count; ++i) {
            _addStrategy(strategies_[i]);
            _linkStrategyToVault(_vault, strategies_[i]);
        }
    }

    /**
     * @dev Add new strategy to the list
     * - Caller is owner or factory
     * @param _strategy The new strategy address
     */
    function addStrategy(address _strategy) external onlyAdmin {
        _addStrategy(_strategy);
    }

    /**
     * @dev Remove strategy from the list
     * - Caller is owner or factory
     * @param _strategy The new strategy address
     */
    function removeStrategy(address _strategy) external onlyAdmin {
        _removeStrategy(_strategy);
    }

    /**
     * @dev Add multiple new strategies to list
     * - Caller is owner or factory
     * @param strategies_ The new strategy addresses
     */
    function addStrategies(address[] memory strategies_) external onlyAdmin {
        uint256 count = strategies_.length;
        for (uint256 i; i < count; ++i) {
            _addStrategy(strategies_[i]);
        }
    }

    function _addVault(address _vault) internal {
        if (!_vaultFlags[_vault]) {
            _vaults.push(_vault);
            _vaultFlags[_vault] = true;

            emit AddVault(_vault);
        }
    }

    function _removeVault(address _vault) internal {
        uint256 length = _vaults.length;
        if (_vaultFlags[_vault]) {
            for (uint i = 0; i < length; i++) {
                if (_vault == _vaults[i]) {
                    if (i < length - 1) {
                        _vaults[i] = _vaults[length - 1];
                    }
                    _vaults.pop();
                    break;
                }
            }
            _vaultFlags[_vault] = false;

            emit RemoveVault(_vault);
        }
    }

    function _addStrategy(address _strategy) internal {
        if (!_strategyFlags[_strategy]) {
            _strategies.push(_strategy);
            _strategyFlags[_strategy] = true;

            emit AddStrategy(_strategy);
        }
    }

    function _removeStrategy(address _strategy) internal {
        uint256 length = _strategies.length;
        if (_strategyFlags[_strategy]) {
            for (uint i = 0; i < length; i++) {
                if (_strategy == _strategies[i]) {
                    if (i < length - 1) {
                        _strategies[i] = _strategies[length - 1];
                    }
                    _strategies.pop();
                    break;
                }
            }
            _strategyFlags[_strategy] = false;

            emit RemoveStrategy(_strategy);
        }
    }

    function _linkStrategyToVault(address _vault, address _strategy) internal {
        address[] memory _alreadyLinked = _strategiesOfVault[_vault];
        for (uint256 i = 0; i < _alreadyLinked.length; i++) {
            if (_strategy == _alreadyLinked[i]) {
                return;
            }
        }
        _strategiesOfVault[_vault].push(_strategy);
    }

    function _unlinkStrategyFromVault(
        address _vault,
        address _strategy
    ) internal {
        uint256 length = _strategiesOfVault[_vault].length;
        for (uint i = 0; i < length; i++) {
            if (_strategy == _strategiesOfVault[_vault][i]) {
                if (i < length - 1) {
                    _strategiesOfVault[_vault][i] = _strategiesOfVault[_vault][
                        length - 1
                    ];
                }
                _strategiesOfVault[_vault].pop();
                return;
            }
        }
    }

    /**
     * @dev Set vault factory contract address
     * - Caller is owner
     * @param factory_ The factory contract address
     */
    function setFactory(address factory_) external onlyOwner {
        _factory = factory_;
    }

    /**
     * @dev Get the factory contract address
     * @return the factory contract address
     */
    function getFactory() external view returns (address) {
        return _factory;
    }