contract WrappedERC4626YearnCRVAave is ApyFlowVault {
    using SafeERC20 for IERC20Metadata;
    using PricesLibrary for ChainlinkPriceFeedAggregator;
    using AaveLibrary for AaveLibrary.Data;
    using SafeAssetConverter for AssetConverter;

    IERC20Metadata public immutable crv;
    WrappedERC4626YearnCRVAave public immutable crvVault;
    AaveLibrary.Data public aave;
    AssetConverter public immutable assetConverter;
    ChainlinkPriceFeedAggregator public immutable pricesOracle;

    constructor(
        AaveLibrary.Data memory _aave,
        IERC20Metadata asset_,
        WrappedERC4626YearnCRVAave _crvVault,
        ChainlinkPriceFeedAggregator _pricesOracle,
        AssetConverter _assetConverter,
        string memory name,
        string memory symbol
    ) ApyFlowVault(asset_) ERC20(name, symbol) {
        crvVault = _crvVault;
        crv = IERC20Metadata(crvVault.asset());
        aave = _aave;
        assetConverter = _assetConverter;
        pricesOracle = _pricesOracle;

        require(aave.tokenToBorrow == address(crv), "Crv should be borrowed");
        require(asset() == aave.collateral, "Asset should be collateral");
        aave.performApprovals();
        Utils.approveIfZeroAllowance(address(crv), address(crvVault));
        Utils.approveIfZeroAllowance(address(crv), address(assetConverter));
        Utils.approveIfZeroAllowance(asset(), address(assetConverter));
    }

    function _totalAssets() internal view override returns (uint256 assets) {
        uint256 valueInUSD;

        uint256 crvAtVault = crvVault.convertToAssets(
            crvVault.balanceOf(address(this))
        );
        valueInUSD += pricesOracle.convertToUSD(address(crv), crvAtVault);
        valueInUSD += pricesOracle.convertToUSD(
            asset(),
            aave.getCurrentCollateralSupply()
        );
        valueInUSD -= pricesOracle.convertToUSD(
            address(crv),
            aave.getCurrentDebt()
        );
        assets = pricesOracle.convertFromUSD(valueInUSD, asset());
    }

    function _deposit(uint256 assets) internal override {
        aave.supply(assets);
        uint256 neededDebt = aave.getNeededDebt(
            aave.getCurrentCollateralSupply(),
            aave.ltv
        );
        uint256 currentDebt = aave.getCurrentDebt();
        if (currentDebt < neededDebt) {
            uint256 amount = neededDebt - currentDebt;
            aave.borrow(amount);
            crvVault.deposit(amount, address(this));
        }
    }

    function _redeem(
        uint256 shares
    ) internal override returns (uint256 assets) {
        uint256 lps = (crvVault.balanceOf(address(this)) * shares) /
            totalSupply();
        uint256 crvAmount = crvVault.redeem(lps, address(this), address(this));
        uint256 amountToRepay = (aave.getCurrentDebt() * shares) /
            totalSupply();
        uint256 currentCollateral = aave.getCurrentCollateralSupply();
        uint256 amountToWithdraw = (currentCollateral * shares) / totalSupply();
        if (crvAmount >= amountToRepay) {
            aave.repay(amountToRepay);
            assets += assetConverter.safeSwap(
                address(crv),
                asset(),
                crvAmount - amountToRepay
            );
            aave.withdraw(amountToWithdraw);
            assets += amountToWithdraw;
        } else {
            aave.repay(crvAmount);

            // see executeOperation() function
            aave.flashloan(
                asset(),
                pricesOracle.convert(
                    address(crv),
                    asset(),
                    amountToRepay - crvAmount
                )
            );
            uint256 neededCollateralAmount = currentCollateral -
                amountToWithdraw;

            // we withdraw some collateral during flashloan, so we need to update this value
            currentCollateral = aave.getCurrentCollateralSupply();

            if (currentCollateral > neededCollateralAmount) {
                amountToWithdraw = currentCollateral - neededCollateralAmount;
            }
        }
        aave.withdraw(amountToWithdraw);
        assets += amountToWithdraw;
    }

    function executeOperation(
        address _asset,
        uint256 _amount,
        uint256 _premium,
        address _initiator,
        bytes calldata
    ) external returns (bool) {
        require(_initiator == address(this), "invalid flashloan initiator");
        require(_asset == asset(), "invalid flashloan asset");

        uint256 amountToRepay = assetConverter.safeSwap(
            asset(),
            address(crv),
            _amount
        );
        aave.repay(amountToRepay);

        uint256 amountToWithdraw = _amount + _premium;
        aave.withdraw(amountToWithdraw);

        return true;
    }
}
[{"inputs":[{"components":[{"internalType":"contract IAavePool","name":"aavePool","type":"address"},{"internalType":"contract IAavePriceOracle","name":"aavePriceOracle","type":"address"},{"internalType":"address","name":"tokenToBorrow","type":"address"},{"internalType":"address","name":"collateral","type":"address"},{"internalType":"uint256","name":"ltv","type":"uint256"}],"internalType":"struct AaveLibrary.Data","name":"_aave","type":"tuple"},{"internalType":"contract IERC20Metadata","name":"asset_","type":"address"},{"internalType":"contract WrappedERC4626YearnCRVAave","name":"_crvVault","type":"address"},{"internalType":"contract ChainlinkPriceFeedAggregator","name":"_pricesOracle","type":"address"},{"internalType":"contract AssetConverter","name":"_assetConverter","type":"address"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"aave","outputs":[{"internalType":"contract IAavePool","name":"aavePool","type":"address"},{"internalType":"contract IAavePriceOracle","name":"aavePriceOracle","type":"address"},{"internalType":"address","name":"tokenToBorrow","type":"address"},{"internalType":"address","name":"collateral","type":"address"},{"internalType":"uint256","name":"ltv","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetConverter","outputs":[{"internalType":"contract AssetConverter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"crv","outputs":[{"internalType":"contract IERC20Metadata","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"crvVault","outputs":[{"internalType":"contract WrappedERC4626YearnCRVAave","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_premium","type":"uint256"},{"internalType":"address","name":"_initiator","type":"address"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"executeOperation","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastTotalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pricePerToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pricesOracle","outputs":[{"internalType":"contract ChainlinkPriceFeedAggregator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]