// Contract: AggregatorDataProvider
// Target function: function addVault(address _vault) external onlyAdmin
 <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

struct VaultAccount {
    uint128 amount; // Total amount, analogous to market cap
    uint128 shares; // Total shares, analogous to shares outstanding
}

interface IFraxLendV3 {
    struct CurrentRateInfo {
        uint32 lastBlock;
        uint32 feeToProtocolRate; // Fee amount 1e5 precision
        uint64 lastTimestamp;
        uint64 ratePerSec;
        uint64 fullUtilizationRate;
    }

    function isInterestPaused() external view returns (bool);

    function toAssetAmount(
        uint256 _shares,
        bool _roundUp,
        bool _previewInterest
    ) external view returns (uint256);

    function toAssetShares(
        uint256 _amount,
        bool _roundUp,
        bool _previewInterest
    ) external view returns (uint256);

    function getConstants()
        external
        pure
        returns (
            uint256 _LTV_PRECISION,
            uint256 _LIQ_PRECISION,
            uint256 _UTIL_PREC,
            uint256 _FEE_PRECISION,
            uint256 _EXCHANGE_PRECISION,
            uint256 _DEVIATION_PRECISION,
            uint256 _RATE_PRECISION,
            uint256 _MAX_PROTOCOL_FEE
        );

    function addInterest(
        bool _returnAccounting
    )
        external
        returns (
            uint256 _interestEarned,
            uint256 _feesAmount,
            uint256 _feesShare,
            CurrentRateInfo memory _currentRateInfo,
            VaultAccount memory _totalAsset,
            VaultAccount memory _totalBorrow
        );
}

contract FraxLendStrategy is BaseStrategy {
    using SafeERC20 for ERC20;

    IFraxLend public immutable pair;
    // FraxLend pair version
    uint256 public immutable version;

    // Amount of deposits we need before depositing
    uint256 public immutable depositThreshold;
    // Bool if we have met the depositThreshold
    bool public thresholdMet;

    // Timestamp of when withdraws will be enabled
    uint256 public timeToUnlock;
    // If the timeToUnlock is still able to be set. (false == can still be set.)
    bool public freezeTimeToUnlock;

    constructor(
        address _asset,
        address _pair,
        string memory _name,
        uint256 _version,
        uint256 _depositThreshold,
        uint256 _timeToUnlock
    ) BaseStrategy(_asset, _name) {
        require(_version != 0, "invalid version");
        // Set pair address
        pair = IFraxLend(_pair);
        // Set pair version
        version = _version;
        // Set depositThreshold value
        depositThreshold = _depositThreshold;
        // Set initial time to unlock
        timeToUnlock = _timeToUnlock;
        // Approve the pair for FRAX
        ERC20(_asset).safeApprove(address(pair), type(uint256).max);
    }

    /*//////////////////////////////////////////////////////////////
                NEEDED TO BE OVERRIDEN BY STRATEGIST
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Should deploy up to '_amount' of 'asset' in the yield source.
     *
     * This function is called at the end of a {deposit} or {mint}
     * call. Meaning that unless a whitelist is implemented it will
     * be entirely permsionless and thus can be sandwhiched or otherwise
     * manipulated.
     *
     * @param _amount The amount of 'asset' that the strategy should attemppt
     * to deposit in the yield source.
     */
    function _deployFunds(uint256 _amount) internal override {
        // We don't deposit until the threshold is met.
        if (thresholdMet) {
            pair.deposit(_amount, address(this));

            // Amount will include all idle funds if not deposited yet.
        } else if (_amount > depositThreshold) {
            // Set the bool for easy checks in the future.
            thresholdMet = true;
            pair.deposit(_amount, address(this));
        }
    }

    /**
     * @dev Will attempt to free the '_amount' of 'asset'.
     *
     * The amount of 'asset' that is already loose has already
     * been accounted for.
     *
     * This function is called during {withdraw} and {redeem} calls.
     * Meaning that unless a whitelist is implemented it will be
     * entirely permsionless and thus can be sandwhiched or otherwise
     * manipulated.
     *
     * Should not rely on asset.balanceOf(address(this)) calls other than
     * for diff accounting puroposes.
     *
     * Any difference between `_amount` and what is actually freed will be
     * counted as a loss and passed on to the withdrawer. This means
     * care should be taken in times of illiquidity. It may be better to revert
     * if withdraws are simply illiquid so not to realize incorrect losses.
     *
     * @param _amount, The amount of 'asset' to be freed.
     */
    function _freeFunds(uint256 _amount) internal override {
        // Only proccess withdraws if we have unlocked the stratey
        if (block.timestamp >= timeToUnlock) {
            // Accrue interest for conversion.
            if (version < 3) {
                pair.addInterest();
                pair.redeem(
                    pair.toAssetShares(_amount, false),
                    address(this),
                    address(this)
                );
            } else {
                IFraxLendV3(address(pair)).addInterest(false);
                pair.redeem(
                    IFraxLendV3(address(pair)).toAssetShares(_amount, false, false),
                    address(this),
                    address(this)
                );
            }
        } else {
            // This should never happen but we check just in case and
            // revert so we don't record an incorrect loss.
            require(false, "Locked!");
        }
    }

    /**
     * @dev Internal function to harvest all rewards, redeploy any idle
     * funds and return an accurate accounting of all funds currently
     * held by the Strategy.
     *
     * This should do any needed harvesting, rewards selling, accrual,
     * redepositing etc. to get the most accurate view of current assets.
     *
     * NOTE: All applicable assets including loose assets should be
     * accounted for in this function.
     *
     * Care should be taken when relying on oracles or swap values rather
     * than actual amounts as all Strategy profit/loss accounting will
     * be done based on this returned value.
     *
     * This can still be called post a shutdown, a strategist can check
     * `TokenizedStrategy.isShutdown()` to decide if funds should be
     * redeployed or simply realize any profits/losses.
     *
     * @return _totalAssets A trusted and accurate account for the total
     * amount of 'asset' the strategy currently holds including idle funds.
     */
    function _harvestAndReport()
        internal
        override
        returns (uint256 _totalAssets)
    {
        if (version < 3) {
            // Acccrue interest
            pair.addInterest();
            _totalAssets =
                // Return total balance including idle.
                asset.balanceOf(address(this)) +
                pair.toAssetAmount(pair.balanceOf(address(this)), false);
        } else {
            // Acccrue interest
            IFraxLendV3(address(pair)).addInterest(false);
            _totalAssets =
                // Return total balance including idle.
                asset.balanceOf(address(this)) +
                IFraxLendV3(address(pair)).toAssetAmount(pair.balanceOf(address(this)), false, false);
        }
    }

    /*//////////////////////////////////////////////////////////////
                    OPTIONAL TO OVERRIDE BY STRATEGIST
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Gets the max amount of `asset` that can be withdrawn.
     * @dev Defaults to an unlimited amount for any address. But can
     * be overriden by strategists.
     *
     * This function will be called before any withdraw or redeem to enforce
     * any limits desired by the strategist. This can be used for illiquid
     * or sandwhichable strategies. It should never be lower than `totalIdle`.
     *
     *   EX:
     *       return TokenIzedStrategy.totalIdle();
     *
     * This does not need to take into account the `_owner`'s share balance
     * or conversion rates from shares to assets.
     *
     * @param . The address that is withdrawing from the strategy.
     * @return . The avialable amount that can be withdrawn in terms of `asset`
     */
    function availableWithdrawLimit(
        address _owner
    ) public view override returns (uint256) {
        // If we have passed the unlock time we have no limit.
        if (block.timestamp >= timeToUnlock) {
            return
                TokenizedStrategy.totalIdle() +
                asset.balanceOf(address(pair));
        } else {
            // Otherwise just the amount of idle can be withdrawn.
            return TokenizedStrategy.totalIdle();
        }
    }

    /**
     * @dev Optional function for a strategist to override that will
     * allow management to manually withdraw deployed funds from the
     * yield source if a strategy is shutdown.
     *
     * This should attempt to free `_amount`, noting that `_amount` may
     * be more than is currently deployed.
     *
     * NOTE: This will not realize any profits or losses. A seperate
     * {report} will be needed in order to record any profit/loss. If
     * a report may need to be called after a shutdown it is important
     * to check if the strategy is shutdown during {_harvestAndReport}
     * so that it does not simply re-deploy all funds that had been freed.
     *
     * EX:
     *   if(freeAsset > 0 && !TokenizedStrategy.isShutdown()) {
     *       depositFunds...
     *    }
     *
     * @param _amount The amount of asset to attempt to free.
     */
    function _emergencyWithdraw(uint256 _amount) internal override {
        // Round up and check against actual balance
        uint256 shares;
        if (version < 3) {
            pair.addInterest();
            shares = pair.toAssetShares(_amount, true);
        } else {
            IFraxLendV3(address(pair)).addInterest(false);
            shares = IFraxLendV3(address(pair)).toAssetShares(_amount, true, false);
        }
        shares = Math.min(shares, pair.balanceOf(address(this)));
        pair.redeem(shares, address(this), address(this));
    }

    // Management can update the unlock time unless it has been frozen.
    function setTimeToUnlock(uint256 _newTime) external onlyManagement {
        require(!freezeTimeToUnlock, "lock frozen");
        timeToUnlock = _newTime;
    }

    // One way switch to freeze the unlock time.
    function freezeUnlock() external onlyManagement {
        freezeTimeToUnlock = true;
    }

    function maxLTV() external view override returns (uint256) {
        return pair.maxLTV();
    }

    function collateral() external view override returns (address) {
        return pair.collateralContract();
    }
}

contract AggregatorDataProvider is Ownable, IAggregatorDataProvider {
    // vault addresses
    address[] private _vaults;
    // 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();

    modifier onlyAdmin() {
        if (msg.sender != owner() && msg.sender != _factory) revert AG_CALLER_NOT_ADMIN();
        _;
    }