From 042aa39bd57dc25b753eff25407f79b95afeaa87 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 24 Nov 2025 15:57:23 +0100 Subject: [PATCH] e2e: test loan management --- .../mutations/create-loan.mutation.ts | 35 +++-- .../create-loan-expected-collateral.query.ts | 82 +++++----- .../create-loan-max-receive.query.ts | 6 +- .../src/lib/model/query/factory.ts | 3 +- ...rpc.cy.tsx => create-loan-form.rpc.cy.tsx} | 86 ++--------- .../llamalend/manage-loan-forms.rpc.cy.tsx | 140 ++++++++++++++++++ .../llamalend/manage-soft-liq.rpc.cy.tsx | 20 +-- .../support/helpers/ComponentTestWrapper.tsx | 41 ++++- tests/cypress/support/helpers/loan.ts | 95 ++++++++++++ .../support/helpers/tenderly/vnet-fund.ts | 10 +- 10 files changed, 366 insertions(+), 152 deletions(-) rename tests/cypress/component/llamalend/{borrow-tab-contents.rpc.cy.tsx => create-loan-form.rpc.cy.tsx} (51%) create mode 100644 tests/cypress/component/llamalend/manage-loan-forms.rpc.cy.tsx create mode 100644 tests/cypress/support/helpers/loan.ts diff --git a/apps/main/src/llamalend/mutations/create-loan.mutation.ts b/apps/main/src/llamalend/mutations/create-loan.mutation.ts index 58b7e89d3e..fce3e755a4 100644 --- a/apps/main/src/llamalend/mutations/create-loan.mutation.ts +++ b/apps/main/src/llamalend/mutations/create-loan.mutation.ts @@ -1,5 +1,5 @@ import { useCallback } from 'react' -import { useConfig } from 'wagmi' +import { Config as WagmiConfig, useConfig } from 'wagmi' import { formatTokenAmounts } from '@/llamalend/llama.utils' import type { LlamaMarketTemplate } from '@/llamalend/llamalend.types' import { type LlammaMutationOptions, useLlammaMutation } from '@/llamalend/mutations/useLlammaMutation' @@ -32,7 +32,7 @@ export type CreateLoanOptions = { userAddress: Address | undefined } -const approve = async ( +export const approveLoan = async ( market: LlamaMarketTemplate, { userCollateral, userBorrowed, leverageEnabled }: BorrowMutation, ) => @@ -42,7 +42,7 @@ const approve = async ( : await market.leverage.createLoanApprove(userCollateral, userBorrowed) : await market.createLoanApprove(userCollateral)) as Address[] -const create = async ( +export const createLoan = async ( market: LlamaMarketTemplate, { debt, userCollateral, userBorrowed, leverageEnabled, range, slippage }: BorrowMutation, ) => { @@ -55,6 +55,24 @@ const create = async ( return (await parent.createLoan(userCollateral, debt, range, +slippage)) as Address } +const approveAndCreateLoan = async ({ + config, + market, + ...mutation +}: BorrowMutation & { + chainId: IChainId + market: MintMarketTemplate | LendMarketTemplate + config: WagmiConfig +}) => { + await waitForApproval({ + isApproved: () => fetchBorrowCreateLoanIsApproved({ ...mutation, marketId: market.id }), + onApprove: () => approveLoan(market, mutation), + message: t`Approved loan creation`, + config, + }) + return { hash: await createLoan(market, mutation) } +} + export const useCreateLoanMutation = ({ network, network: { chainId }, @@ -69,16 +87,7 @@ export const useCreateLoanMutation = ({ network, marketId, mutationKey: [...rootKeys.userMarket({ chainId, marketId, userAddress }), 'create-loan'] as const, - mutationFn: async (mutation, { market }) => { - const params = { ...mutation, chainId, marketId } - await waitForApproval({ - isApproved: () => fetchBorrowCreateLoanIsApproved(params), - onApprove: () => approve(market, mutation), - message: t`Approved loan creation`, - config, - }) - return { hash: await create(market, mutation) } - }, + mutationFn: async (mutation, { market }) => await approveAndCreateLoan({ ...mutation, chainId, market, config }), validationSuite: borrowFormValidationSuite, pendingMessage: (mutation, { market }) => t`Creating loan... ${formatTokenAmounts(market, mutation)}`, successMessage: (mutation, { market }) => t`Loan created! ${formatTokenAmounts(market, mutation)}`, diff --git a/apps/main/src/llamalend/queries/create-loan/create-loan-expected-collateral.query.ts b/apps/main/src/llamalend/queries/create-loan/create-loan-expected-collateral.query.ts index be8698d837..4f560505e1 100644 --- a/apps/main/src/llamalend/queries/create-loan/create-loan-expected-collateral.query.ts +++ b/apps/main/src/llamalend/queries/create-loan/create-loan-expected-collateral.query.ts @@ -37,47 +37,43 @@ const convertNumbers = ({ collateralFromDebt: decimal(collateralFromDebt), }) -export const { useQuery: useCreateLoanExpectedCollateral, queryKey: createLoanExpectedCollateralQueryKey } = - queryFactory({ - queryKey: ({ - chainId, - marketId, - userBorrowed = '0', - userCollateral = '0', - debt, - slippage, - }: BorrowFormQueryParams) => - [ - ...rootKeys.market({ chainId, marketId }), - 'createLoanExpectedCollateral', - { userCollateral }, - { userBorrowed }, - { debt }, - { slippage }, - ] as const, - queryFn: async ({ - marketId, - userBorrowed = '0', - userCollateral = '0', - debt, - slippage, - }: BorrowFormQuery): Promise => { - const market = getLlamaMarket(marketId) - if (market instanceof LendMarketTemplate) { - return convertNumbers( - await market.leverage.createLoanExpectedCollateral(userCollateral, userBorrowed, debt, +slippage), - ) - } - if (market.leverageV2.hasLeverage()) { - return convertNumbers( - await market.leverageV2.createLoanExpectedCollateral(userCollateral, userBorrowed, debt, +slippage), - ) - } +export const { + useQuery: useCreateLoanExpectedCollateral, + queryKey: createLoanExpectedCollateralQueryKey, + fetchQuery: fetchLoanExpectedCollateral, +} = queryFactory({ + queryKey: ({ chainId, marketId, userBorrowed = '0', userCollateral = '0', debt, slippage }: BorrowFormQueryParams) => + [ + ...rootKeys.market({ chainId, marketId }), + 'createLoanExpectedCollateral', + { userCollateral }, + { userBorrowed }, + { debt }, + { slippage }, + ] as const, + queryFn: async ({ + marketId, + userBorrowed = '0', + userCollateral = '0', + debt, + slippage, + }: BorrowFormQuery): Promise => { + const market = getLlamaMarket(marketId) + if (market instanceof LendMarketTemplate) { + return convertNumbers( + await market.leverage.createLoanExpectedCollateral(userCollateral, userBorrowed, debt, +slippage), + ) + } + if (market.leverageV2.hasLeverage()) { + return convertNumbers( + await market.leverageV2.createLoanExpectedCollateral(userCollateral, userBorrowed, debt, +slippage), + ) + } - assert(!+userBorrowed, `userBorrowed must be 0 for non-leverage mint markets`) - const { collateral, leverage, routeIdx } = await market.leverage.createLoanCollateral(userCollateral, debt) - return convertNumbers({ userCollateral, leverage, totalCollateral: collateral }) - }, - staleTime: '1m', - validationSuite: borrowQueryValidationSuite, - }) + assert(!+userBorrowed, `userBorrowed must be 0 for non-leverage mint markets`) + const { collateral, leverage, routeIdx } = await market.leverage.createLoanCollateral(userCollateral, debt) + return convertNumbers({ userCollateral, leverage, totalCollateral: collateral }) + }, + staleTime: '1m', + validationSuite: borrowQueryValidationSuite, +}) diff --git a/apps/main/src/llamalend/queries/create-loan/create-loan-max-receive.query.ts b/apps/main/src/llamalend/queries/create-loan/create-loan-max-receive.query.ts index 2d56ebf4a9..9226ae6946 100644 --- a/apps/main/src/llamalend/queries/create-loan/create-loan-max-receive.query.ts +++ b/apps/main/src/llamalend/queries/create-loan/create-loan-max-receive.query.ts @@ -50,7 +50,11 @@ export const maxReceiveValidation = createValidationSuite( }, ) -export const { useQuery: useCreateLoanMaxReceive, queryKey: createLoanMaxReceiveKey } = queryFactory({ +export const { + useQuery: useCreateLoanMaxReceive, + queryKey: createLoanMaxReceiveKey, + fetchQuery: fetchLoanMaxReceive, +} = queryFactory({ queryKey: ({ chainId, marketId, diff --git a/packages/curve-ui-kit/src/lib/model/query/factory.ts b/packages/curve-ui-kit/src/lib/model/query/factory.ts index 760fa823d0..05e8abd3cc 100644 --- a/packages/curve-ui-kit/src/lib/model/query/factory.ts +++ b/packages/curve-ui-kit/src/lib/model/query/factory.ts @@ -60,7 +60,8 @@ export function queryFactory< getQueryData: (params) => queryClient.getQueryData(queryKey(params)), setQueryData: (params, data) => queryClient.setQueryData(queryKey(params), data), prefetchQuery: (params, staleTime = 0) => queryClient.prefetchQuery({ ...getQueryOptions(params), staleTime }), - fetchQuery: (params, options) => queryClient.fetchQuery({ ...getQueryOptions(params), ...options }), + fetchQuery: (params, options = { staleTime: 0 }) => + queryClient.fetchQuery({ ...getQueryOptions(params), ...options }), /** * Function that is like fetchQuery, but sets staleTime to 0 to ensure fresh data is fetched. * Primary use case is for Zustand stores where want to both use queries and ensure freshness. diff --git a/tests/cypress/component/llamalend/borrow-tab-contents.rpc.cy.tsx b/tests/cypress/component/llamalend/create-loan-form.rpc.cy.tsx similarity index 51% rename from tests/cypress/component/llamalend/borrow-tab-contents.rpc.cy.tsx rename to tests/cypress/component/llamalend/create-loan-form.rpc.cy.tsx index 1200e08861..f72bdf2fe5 100644 --- a/tests/cypress/component/llamalend/borrow-tab-contents.rpc.cy.tsx +++ b/tests/cypress/component/llamalend/create-loan-form.rpc.cy.tsx @@ -1,55 +1,27 @@ -import React, { useMemo } from 'react' +import React from 'react' import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' -import { prefetchMarkets } from '@/lend/entities/chain/chain-query' import { CreateLoanForm } from '@/llamalend/features/borrow/components/CreateLoanForm' import type { OnBorrowFormUpdate } from '@/llamalend/features/borrow/types' import type { CreateLoanOptions } from '@/llamalend/mutations/create-loan.mutation' import networks from '@/loan/networks' import { oneBool, oneValueOf } from '@cy/support/generators' -import { ComponentTestWrapper } from '@cy/support/helpers/ComponentTestWrapper' -import { createTestWagmiConfigFromVNet, createVirtualTestnet } from '@cy/support/helpers/tenderly' +import { LlamalendComponentWrapper } from '@cy/support/helpers/ComponentTestWrapper' +import { checkAccordion, getActionValue, TEST_LLAMA_MARKETS, useTestLlamaMarket } from '@cy/support/helpers/loan' +import { createVirtualTestnet } from '@cy/support/helpers/tenderly' import { getRpcUrls } from '@cy/support/helpers/tenderly/vnet' import { fundErc20, fundEth } from '@cy/support/helpers/tenderly/vnet-fund' import { LOAD_TIMEOUT } from '@cy/support/ui' -import Box from '@mui/material/Box' import Skeleton from '@mui/material/Skeleton' -import { useConnection } from '@ui-kit/features/connect-wallet/lib/ConnectionContext' -import { ConnectionProvider } from '@ui-kit/features/connect-wallet/lib/ConnectionProvider' import { LlamaMarketType } from '@ui-kit/types/market' import { Chain } from '@ui-kit/utils' const chainId = Chain.Ethereum -const MARKETS = { - [LlamaMarketType.Mint]: { - id: 'lbtc', - collateralAddress: '0x8236a87084f8b84306f72007f36f2618a5634494' as const, // lbtc - collateral: '1', - borrow: '100', - }, - [LlamaMarketType.Lend]: { - id: 'one-way-market-14', - collateralAddress: '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3' as const, // USDe - collateral: '1', - borrow: '0.9', - }, -} -const oneEthInWei = '0xde0b6b3a7640000' // 1 ETH=1e18 wei - const onUpdate: OnBorrowFormUpdate = async (form) => console.info('form updated', form) type BorrowTabTestProps = { type: LlamaMarketType } & Pick -const prefetch = () => prefetchMarkets({}) - function BorrowTabTest({ type, onCreated }: BorrowTabTestProps) { - const { isHydrated, llamaApi } = useConnection() - const { id } = MARKETS[type] - const market = useMemo( - () => - isHydrated && - { [LlamaMarketType.Mint]: llamaApi?.getMintMarket, [LlamaMarketType.Lend]: llamaApi?.getLendMarket }[type]?.(id), - [isHydrated, id, llamaApi?.getLendMarket, llamaApi?.getMintMarket, type], - ) + const market = useTestLlamaMarket(type) return market ? ( ) : ( @@ -57,7 +29,7 @@ function BorrowTabTest({ type, onCreated }: BorrowTabTestProps) { ) } -describe('BorrowTabContents Component Tests', () => { +describe('Create loan form component tests', () => { const privateKey = generatePrivateKey() const { address } = privateKeyToAccount(privateKey) const getVirtualNetwork = createVirtualTestnet((uuid) => ({ @@ -68,34 +40,23 @@ describe('BorrowTabContents Component Tests', () => { })) const marketType = oneValueOf(LlamaMarketType) - const { collateralAddress: tokenAddress, collateral, borrow } = MARKETS[marketType] + const { collateralAddress, collateral, borrow } = TEST_LLAMA_MARKETS[marketType] const leverageEnabled = oneBool() // test with and without leverage beforeEach(() => { const vnet = getVirtualNetwork() const { adminRpcUrl } = getRpcUrls(vnet) - fundEth({ adminRpcUrl, amountWei: oneEthInWei, recipientAddresses: [address] }) - fundErc20({ adminRpcUrl, amountWei: oneEthInWei, tokenAddress, recipientAddresses: [address] }) + fundEth({ adminRpcUrl, recipientAddresses: [address] }) + fundErc20({ adminRpcUrl, tokenAddress: collateralAddress, recipientAddresses: [address] }) cy.log(`Funded some eth and collateral to ${address} in vnet ${vnet.slug}`) }) const BorrowTabTestWrapper = (props: BorrowTabTestProps) => ( - - - - - - - + + + ) - const getActionValue = (name: string) => cy.get(`[data-testid="${name}-value"]`, LOAD_TIMEOUT) - it(`calculates max debt and health for ${marketType} market ${leverageEnabled ? 'with' : 'without'} leverage`, () => { const onCreated = cy.stub() cy.mount() @@ -109,28 +70,7 @@ describe('BorrowTabContents Component Tests', () => { if (leverageEnabled) { cy.get('[data-testid="leverage-checkbox"]').click() } - - // open borrow advanced settings and check all fields - cy.contains('button', 'Health').click() - - getActionValue('borrow-band-range') - .invoke(LOAD_TIMEOUT, 'text') - .should('match', /(\d(\.\d+)?) to (\d(\.\d+)?)/) - getActionValue('borrow-price-range') - .invoke(LOAD_TIMEOUT, 'text') - .should('match', /(\d(\.\d+)?) - (\d(\.\d+)?)/) - getActionValue('borrow-apr').contains('%') - getActionValue('borrow-apr-previous').contains('%') - getActionValue('borrow-ltv').contains('%') - getActionValue('borrow-n').contains('50') - - if (leverageEnabled) { - getActionValue('borrow-price-impact').contains('%') - getActionValue('borrow-slippage').contains('%') - } else { - getActionValue('borrow-price-impact').should('not.exist') - getActionValue('borrow-slippage').should('not.exist') - } + checkAccordion(leverageEnabled) cy.get('[data-testid="loan-form-errors"]').should('not.exist') cy.get('[data-testid="create-loan-submit-button"]').click() diff --git a/tests/cypress/component/llamalend/manage-loan-forms.rpc.cy.tsx b/tests/cypress/component/llamalend/manage-loan-forms.rpc.cy.tsx new file mode 100644 index 0000000000..5720f8336c --- /dev/null +++ b/tests/cypress/component/llamalend/manage-loan-forms.rpc.cy.tsx @@ -0,0 +1,140 @@ +import React, { type ReactNode } from 'react' +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' +import { BORROW_PRESET_RANGES } from '@/llamalend/constants' +import { AddCollateralForm } from '@/llamalend/features/manage-loan/components/AddCollateralForm' +import { RemoveCollateralForm } from '@/llamalend/features/manage-loan/components/RemoveCollateralForm' +import { RepayForm } from '@/llamalend/features/manage-loan/components/RepayForm' +import type { LlamaMarketTemplate } from '@/llamalend/llamalend.types' +import networks from '@/loan/networks' +import { oneBool, oneValueOf } from '@cy/support/generators' +import { LlamalendComponentWrapper } from '@cy/support/helpers/ComponentTestWrapper' +import { approveAndCreateLoan, TEST_LLAMA_MARKETS, useTestLlamaMarket } from '@cy/support/helpers/loan' +import { createVirtualTestnet } from '@cy/support/helpers/tenderly' +import { getRpcUrls } from '@cy/support/helpers/tenderly/vnet' +import { fundErc20, fundEth } from '@cy/support/helpers/tenderly/vnet-fund' +import { LOAD_TIMEOUT } from '@cy/support/ui' +import Skeleton from '@mui/material/Skeleton' +import { LlamaMarketType } from '@ui-kit/types/market' +import { Chain } from '@ui-kit/utils' + +const chainId = Chain.Ethereum + +const getVirtualNetwork = createVirtualTestnet((uuid) => ({ + slug: `manage-loan-forms-${uuid}`, + display_name: `ManageLoanForms (${uuid})`, + fork_config: { block_number: 'latest' }, +})) + +type Children = (market: LlamaMarketTemplate) => ReactNode + +function ManageLoanFormTest({ type, children }: { type: LlamaMarketType; children: Children }) { + const market = useTestLlamaMarket(type) + return market ? children(market) : +} + +describe('Llamalend manage loan forms', () => { + const privateKey = generatePrivateKey() + const { address } = privateKeyToAccount(privateKey) + + const marketType = oneValueOf(LlamaMarketType) + const { collateralAddress: collateralAddress } = TEST_LLAMA_MARKETS[marketType] + + before(() => { + const vnet = getVirtualNetwork() + const { adminRpcUrl } = getRpcUrls(vnet) + fundEth({ adminRpcUrl, recipientAddresses: [address] }) + fundErc20({ adminRpcUrl, tokenAddress: collateralAddress, recipientAddresses: [address] }) + }) + + it('should allow adding collateral to an existing loan', () => { + cy.mount( + + {(market) => ( + {}} /> + )} + , + ) + + cy.get('[data-testid="add-collateral-input"] [data-testid="balance-value"]', LOAD_TIMEOUT).should('exist') + + cy.get('[data-testid="add-collateral-input"] input[type="text"]').first().type('0.1') + + cy.get('[data-testid="add-collateral-submit-button"]').click() + cy.get('[data-testid="add-collateral-submit-button"]').should('be.disabled') + + cy.get('[data-testid="loan-form-success-alert"]', LOAD_TIMEOUT).should('exist') + }) + + it('should allow repaying part of the loan from borrowed token', () => { + cy.mount( + + {(market) => ( + {}} /> + )} + , + ) + + cy.get('[data-testid="repay-user-borrowed-input"] [data-testid="balance-value"]', LOAD_TIMEOUT).should('exist') + + cy.get('[data-testid="repay-user-borrowed-input"] input[type="text"]').first().type('1') + + cy.get('[data-testid="repay-submit-button"]').click() + cy.get('[data-testid="repay-submit-button"]').should('be.disabled') + + cy.get('[data-testid="loan-form-success-alert"]', LOAD_TIMEOUT).should('exist') + }) + + it('should allow removing a small amount of collateral', () => { + cy.mount( + + {(market) => ( + {}} + /> + )} + , + ) + + cy.get('[data-testid="remove-collateral-input"] [data-testid="balance-value"]', LOAD_TIMEOUT).should('exist') + + cy.get('[data-testid="remove-collateral-input"] input[type="text"]').first().type('0.01') + + cy.get('[data-testid="remove-collateral-submit-button"]').click() + cy.get('[data-testid="remove-collateral-submit-button"]').should('be.disabled') + + cy.get('[data-testid="loan-form-success-alert"]', LOAD_TIMEOUT).should('exist') + }) +}) + +const ManageLoanFormTestWrapper = ({ children }: { children: Children }) => { + const marketType = LlamaMarketType.Mint + // const marketType = oneValueOf(LlamaMarketType) + const { id: marketId } = TEST_LLAMA_MARKETS[marketType] + return ( + { + await approveAndCreateLoan(lib, { + marketId, + marketType, + chainId, + mutation: { + userCollateral: '1', + userBorrowed: '0', + debt: '5', + range: BORROW_PRESET_RANGES.Safe, + slippage: '0.1', + leverageEnabled: oneBool(), + }, + }) + }} + > + {children} + + ) +} diff --git a/tests/cypress/component/llamalend/manage-soft-liq.rpc.cy.tsx b/tests/cypress/component/llamalend/manage-soft-liq.rpc.cy.tsx index dd8c593164..fa5c92d7a2 100644 --- a/tests/cypress/component/llamalend/manage-soft-liq.rpc.cy.tsx +++ b/tests/cypress/component/llamalend/manage-soft-liq.rpc.cy.tsx @@ -1,11 +1,10 @@ import { useMemo } from 'react' -import { prefetchMarkets } from '@/lend/entities/chain/chain-query' import { ManageSoftLiquidation } from '@/llamalend/features/manage-soft-liquidation' import networks from '@/loan/networks' -import { ComponentTestWrapper } from '@cy/support/helpers/ComponentTestWrapper' -import { createTestWagmiConfigFromVNet, forkVirtualTestnet } from '@cy/support/helpers/tenderly' +import { LlamalendComponentWrapper } from '@cy/support/helpers/ComponentTestWrapper' +import { forkVirtualTestnet } from '@cy/support/helpers/tenderly' import Skeleton from '@mui/material/Skeleton' -import { ConnectionProvider, useConnection } from '@ui-kit/features/connect-wallet' +import { useConnection } from '@ui-kit/features/connect-wallet' import { Chain } from '@ui-kit/utils' describe('Manage soft liquidation', () => { @@ -29,16 +28,9 @@ describe('Manage soft liquidation', () => { } const TestComponentWrapper = () => ( - - prefetchMarkets({}) }} - > - - - + + + ) beforeEach(() => { diff --git a/tests/cypress/support/helpers/ComponentTestWrapper.tsx b/tests/cypress/support/helpers/ComponentTestWrapper.tsx index 6ba3e918b8..53a20b194f 100644 --- a/tests/cypress/support/helpers/ComponentTestWrapper.tsx +++ b/tests/cypress/support/helpers/ComponentTestWrapper.tsx @@ -1,7 +1,11 @@ -import { type ReactElement } from 'react' -import { WagmiProvider, type ResolvedRegister } from 'wagmi' +import { type ReactElement, useMemo } from 'react' +import { type ResolvedRegister, WagmiProvider } from 'wagmi' +import { prefetchMarkets } from '@/lend/entities/chain/chain-query' +import { NetworkConfig } from '@/loan/types/loan.types' +import { createTestWagmiConfigFromVNet } from '@cy/support/helpers/tenderly' +import Box from '@mui/material/Box' import { createMemoryHistory, createRootRoute, createRouter, RouterProvider } from '@tanstack/react-router' -import { WalletToast } from '@ui-kit/features/connect-wallet' +import { ConnectionProvider, type LlamaApi, requireLib, WalletToast } from '@ui-kit/features/connect-wallet' import { persister, queryClient, QueryProvider } from '@ui-kit/lib/api' import { ThemeProvider } from '@ui-kit/shared/ui/ThemeProvider' import { WithWrapper } from '@ui-kit/shared/ui/WithWrapper' @@ -40,3 +44,34 @@ export function ComponentTestWrapper({ config, children, autoConnect }: Props) { ) } + +export const LlamalendComponentWrapper = ({ + network, + wagmi, + children, + onHydrated, +}: { + network: NetworkConfig + wagmi: Parameters[0] + children: ReactElement + onHydrated?: (lib: LlamaApi) => Promise +}) => ( + + ({ + llamalend: async () => { + await prefetchMarkets({}) + await onHydrated?.(requireLib('llamaApi')) + }, + }), + [onHydrated], + )} + > + {children} + + +) diff --git a/tests/cypress/support/helpers/loan.ts b/tests/cypress/support/helpers/loan.ts new file mode 100644 index 0000000000..5c493e4c2b --- /dev/null +++ b/tests/cypress/support/helpers/loan.ts @@ -0,0 +1,95 @@ +import { useMemo } from 'react' +import { approveLoan, type BorrowMutation, createLoan } from '@/llamalend/mutations/create-loan.mutation' +import { fetchBorrowCreateLoanIsApproved } from '@/llamalend/queries/create-loan/borrow-create-loan-approved.query' +import { fetchLoanExpectedCollateral } from '@/llamalend/queries/create-loan/create-loan-expected-collateral.query' +import { fetchLoanMaxReceive } from '@/llamalend/queries/create-loan/create-loan-max-receive.query' +import type { IChainId as LlamaChainId } from '@curvefi/llamalend-api/lib/interfaces' +import { LOAD_TIMEOUT } from '@cy/support/ui' +import { type LlamaApi, useConnection } from '@ui-kit/features/connect-wallet' +import { LlamaMarketType } from '@ui-kit/types/market' +import { waitFor } from '@ui-kit/utils/time.utils' + +export const getActionValue = (name: string) => cy.get(`[data-testid="${name}-value"]`, LOAD_TIMEOUT) + +export const checkAccordion = (leverageEnabled: boolean) => { + // open borrow advanced settings and check all fields + cy.contains('button', 'Health').click() + + getActionValue('borrow-band-range') + .invoke(LOAD_TIMEOUT, 'text') + .should('match', /(\d(\.\d+)?) to (\d(\.\d+)?)/) + getActionValue('borrow-price-range') + .invoke(LOAD_TIMEOUT, 'text') + .should('match', /(\d(\.\d+)?) - (\d(\.\d+)?)/) + getActionValue('borrow-apr').contains('%') + getActionValue('borrow-apr-previous').contains('%') + getActionValue('borrow-ltv').contains('%') + getActionValue('borrow-n').contains('50') + + if (leverageEnabled) { + getActionValue('borrow-price-impact').contains('%') + getActionValue('borrow-slippage').contains('%') + } else { + getActionValue('borrow-price-impact').should('not.exist') + getActionValue('borrow-slippage').should('not.exist') + } +} + +export const TEST_LLAMA_MARKETS = { + [LlamaMarketType.Mint]: { + id: 'lbtc', + collateralAddress: '0x8236a87084f8b84306f72007f36f2618a5634494' as const, // lbtc + collateral: '1', + borrow: '100', + }, + [LlamaMarketType.Lend]: { + id: 'one-way-market-14', + collateralAddress: '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3' as const, // USDe + collateral: '1', + borrow: '0.9', + }, +} + +export const getLlamaMarket = (llamaApi: LlamaApi | undefined, type: LlamaMarketType, id: string) => + ({ + [LlamaMarketType.Mint]: llamaApi?.getMintMarket, + [LlamaMarketType.Lend]: llamaApi?.getLendMarket, + })[type]?.(id) + +export function useTestLlamaMarket(type: LlamaMarketType) { + const { isHydrated, llamaApi } = useConnection() + const { id } = TEST_LLAMA_MARKETS[type] + return useMemo(() => isHydrated && getLlamaMarket(llamaApi, type, id), [isHydrated, llamaApi, type, id]) +} + +/** + * Approves and creates a loan in a single flow + */ +export async function approveAndCreateLoan( + lib: LlamaApi, + { + marketType, + marketId, + mutation, + chainId, + }: { marketId: string; chainId: LlamaChainId; marketType: LlamaMarketType; mutation: BorrowMutation }, +) { + const params = { ...mutation, chainId, marketId } + const market = getLlamaMarket(lib, marketType, marketId)! + + // first approve + await approveLoan(market, mutation) + + // wait for approval to go through + await waitFor(() => fetchBorrowCreateLoanIsApproved(params), { + ...LOAD_TIMEOUT, + step: 300, + }) + + // now fetch the max receive and expected collateral, that's required by llamalend-js + const { maxDebt } = await fetchLoanMaxReceive(params) + if (mutation.leverageEnabled) await fetchLoanExpectedCollateral(params) + + // finally, create the loan + return await createLoan(market, { ...mutation, debt: maxDebt }) +} diff --git a/tests/cypress/support/helpers/tenderly/vnet-fund.ts b/tests/cypress/support/helpers/tenderly/vnet-fund.ts index df7613b4b7..c0c00bc58d 100644 --- a/tests/cypress/support/helpers/tenderly/vnet-fund.ts +++ b/tests/cypress/support/helpers/tenderly/vnet-fund.ts @@ -1,6 +1,8 @@ import type { Hex } from 'viem' import { oneInt } from '@cy/support/generators' +const thousandEthInWei = '0x3635c9adc5dea00000' // 1000 ETH in wei + /** * Funds ETH to multiple addresses in a Tenderly virtual testnet. * @param adminRpcUrl Admin RPC URL of the Tenderly virtual testnet @@ -10,11 +12,11 @@ import { oneInt } from '@cy/support/generators' export const fundEth = ({ adminRpcUrl, recipientAddresses, - amountWei, + amountWei = thousandEthInWei, }: { adminRpcUrl: string recipientAddresses: Hex[] - amountWei: Hex + amountWei?: Hex }) => cy.request({ method: 'POST', @@ -39,12 +41,12 @@ export const fundErc20 = ({ adminRpcUrl, tokenAddress, recipientAddresses, - amountWei, + amountWei = thousandEthInWei, }: { adminRpcUrl: string tokenAddress: Hex recipientAddresses: Hex[] - amountWei: Hex + amountWei?: Hex }) => cy.request({ method: 'POST',