Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import { useMemo } from 'react'
import type { Chain } from 'viem'
import { generatePrivateKey } from 'viem/accounts'
import { mapRecord, recordValues } from '@curvefi/prices-api/objects.util'
import type { NetworkMapping } from '@ui/utils'
import { Chain as ChainEnum, isCypress, noCypressTestConnector } from '@ui-kit/utils'
import { createChainFromNetwork } from './chains'
import { defaultGetRpcUrls } from './rpc'
import { createTransportFromNetwork } from './transports'
import { createWagmiConfig } from './wagmi-config'
import { createTestConnector } from './wagmi-test'

export const useWagmiConfig = <T extends NetworkMapping>(networks: T | undefined) =>
useMemo(
() =>
networks &&
createWagmiConfig({
chains: recordValues(networks).map((network) => createChainFromNetwork(network, defaultGetRpcUrls)) as [
Chain,
...Chain[],
],
transports: mapRecord(networks, (_, network) => createTransportFromNetwork(network, defaultGetRpcUrls)),
}),
[networks],
)
useMemo(() => {
if (networks == null) return

const chains = recordValues(networks).map((network) => createChainFromNetwork(network, defaultGetRpcUrls)) as [
Chain,
...Chain[],
]

return createWagmiConfig({
chains,
transports: mapRecord(networks, (_, network) => createTransportFromNetwork(network, defaultGetRpcUrls)),
...(isCypress &&
!noCypressTestConnector && {
connectors: [
createTestConnector({
privateKey: generatePrivateKey(),
chain: chains.find((chain) => chain.id === ChainEnum.Ethereum)!,
})!,
],
}),
})
}, [networks])
Original file line number Diff line number Diff line change
@@ -1,69 +1,65 @@
import {
type Chain,
createWalletClient,
custom,
fallback,
type Hex,
http,
PrivateKeyAccount,
type RpcTransactionRequest,
type Address,
type Chain,
type CustomTransport,
type Hex,
type PrivateKeyAccount,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import type { TenderlyConfig } from '@cy/support/helpers/tenderly/account'
import { Address } from '@ui-kit/utils'
import { createConnector, type CreateConnectorFn } from '@wagmi/core'
import { sendVnetTransaction } from './tenderly/vnet-transaction'

type Options = {
/** A hexadecimal private key used to generate a test account */
privateKey: Hex
/** The testnet chain configuration */
chain: Chain
/** Tenderly configuration for the Virtual Testnet */
tenderly: TenderlyConfig
}
import { type CreateConnectorFn, createConnector } from 'wagmi'

type ConnectParams<T> = { chainId?: number; isReconnecting?: boolean; withCapabilities: T }
type ConnectResult<T> = { accounts: readonly T[]; chainId: number }
type Account = { address: Address; capabilities: Record<string, unknown> }

/**
* Creates a custom transport that intercepts JSON-RPC requests to handle account retrieval and
* transaction sending via Tenderly Virtual Testnet Admin API.
*
* This is necessary because our code under test uses a BrowserProvider with http transport,
* which relies on RPC methods to retrieve accounts and send transactions.
*/
const customTransport = (account: PrivateKeyAccount, tenderly: TenderlyConfig) =>
/** Default custom transport for Cypress E2E tests, read-only */
const cypressTransport = (account: PrivateKeyAccount) =>
custom({
request: async ({ method, params: [param] }): Promise<any> => {
request: async ({ method }): Promise<any> => {
if (method === 'eth_accounts') {
return [account.address]
}
if (method === 'eth_sendTransaction') {
return await sendVnetTransaction({ tenderly, tx: param as RpcTransactionRequest }).catch((err) => {
console.error(`Tenderly failed for ${method}(${JSON.stringify(param)}). Error: ${err}`)
throw err
})
}
throw new Error(`Unsupported method: ${method}, http fallback is used`)
},
})

export type CreateTestConnectorOptions = {
/** A hexadecimal private key used to generate a test account */
privateKey: Hex
/** The testnet chain configuration */
chain: Chain
/**
* Creates a custom transport that intercepts JSON-RPC requests to handle account retrieval and such.
*
* This is necessary because our code under test uses a BrowserProvider with http transport,
* which relies on RPC methods not always available to retrieve accounts and send transactions.
*
* Defaults to a read-only custom transport for Cypress.
*/
transport?: (account: PrivateKeyAccount) => CustomTransport
}

/**
* Cypress test connector for Wagmi.
* Creates a wagmi test connector for Cypress.
*
* This connector is designed for use in test environments (e.g., Cypress) with a testnet chain.
* It creates a wallet using a custom seed (private key) to allow testing contract write calls
* This connector is designed for use in test environments (e.g., Cypress) with optionally a testnet chain.
* It creates a wallet using a custom seed (private key) to allow testing contract read and write calls
* without relying on third-party browser extensions like MetaMask.
*/
export function createTestConnector({ privateKey, chain, tenderly }: Options): CreateConnectorFn {
export function createTestConnector({ privateKey, chain, transport }: CreateTestConnectorOptions): CreateConnectorFn {
const account = privateKeyToAccount(privateKey)

const client = createWalletClient({
account,
chain,
transport: fallback([customTransport(account, tenderly), ...chain.rpcUrls.default.http.map((url) => http(url))]),
transport: fallback([
(transport ?? cypressTransport)(account),
...chain.rpcUrls.default.http.map((url) => http(url)),
]),
})

// A connect function with overloads to satisfy Wagmi's conditional return type
Expand Down
1 change: 1 addition & 0 deletions packages/curve-ui-kit/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum ReleaseChannel {
}

export const isCypress = Boolean((window as { Cypress?: unknown }).Cypress)
export const noCypressTestConnector = Boolean((window as { CypressNoTestConnector?: unknown }).CypressNoTestConnector)

const isDefaultBeta =
process.env.NODE_ENV === 'development' ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import { useMemo } from 'react'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { prefetchMarkets } from '@/lend/entities/chain/chain-query'
import { CreateLoanForm } from '@/llamalend/features/borrow/components/CreateLoanForm'
Expand All @@ -7,7 +7,7 @@ import type { CreateLoanOptions } from '@/llamalend/mutations/create-loan.mutati
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 { createTenderlyWagmiConfigFromVNet, 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'
Expand Down Expand Up @@ -80,7 +80,10 @@ describe('BorrowTabContents Component Tests', () => {
})

const BorrowTabTestWrapper = (props: BorrowTabTestProps) => (
<ComponentTestWrapper config={createTestWagmiConfigFromVNet({ vnet: getVirtualNetwork(), privateKey })} autoConnect>
<ComponentTestWrapper
config={createTenderlyWagmiConfigFromVNet({ vnet: getVirtualNetwork(), privateKey })}
autoConnect
>
<ConnectionProvider
app="llamalend"
network={networks[chainId]}
Expand Down
7 changes: 5 additions & 2 deletions tests/cypress/component/llamalend/manage-soft-liq.rpc.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 { createTenderlyWagmiConfigFromVNet, forkVirtualTestnet } from '@cy/support/helpers/tenderly'
import Skeleton from '@mui/material/Skeleton'
import { ConnectionProvider, useConnection } from '@ui-kit/features/connect-wallet'
import { Chain } from '@ui-kit/utils'
Expand All @@ -29,7 +29,10 @@ describe('Manage soft liquidation', () => {
}

const TestComponentWrapper = () => (
<ComponentTestWrapper config={createTestWagmiConfigFromVNet({ vnet: getVirtualNetwork(), privateKey })} autoConnect>
<ComponentTestWrapper
config={createTenderlyWagmiConfigFromVNet({ vnet: getVirtualNetwork(), privateKey })}
autoConnect
>
<ConnectionProvider
app="llamalend"
network={network}
Expand Down
4 changes: 2 additions & 2 deletions tests/cypress/component/loan/peg-stability-reserve.rpc.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PegKeeper } from '@/loan/components/PagePegKeepers/components/PegKeeper'
import { PEG_KEEPERS } from '@/loan/components/PagePegKeepers/constants'
import { ComponentTestWrapper, type Config } from '@cy/support/helpers/ComponentTestWrapper'
import { createTestWagmiConfigFromVNet, createVirtualTestnet } from '@cy/support/helpers/tenderly'
import { createTenderlyWagmiConfigFromVNet, createVirtualTestnet } from '@cy/support/helpers/tenderly'

const getVirtualNetwork = createVirtualTestnet((uuid) => ({
slug: `pegkeepers-${uuid}`,
Expand All @@ -25,7 +25,7 @@ describe('Peg stability reserve', () => {
return
}

const config = createTestWagmiConfigFromVNet({ vnet })
const config = createTenderlyWagmiConfigFromVNet({ vnet })
cy.mount(<TestComponent config={config} />)

// Initial data when not connected
Expand Down
5 changes: 2 additions & 3 deletions tests/cypress/component/root-layout.rpc.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react'
import { useNetworksQuery } from '@/dex/entities/networks'
import { ComponentTestWrapper } from '@cy/support/helpers/ComponentTestWrapper'
import { createTestWagmiConfigFromVNet, createVirtualTestnet } from '@cy/support/helpers/tenderly'
import { createTenderlyWagmiConfigFromVNet, createVirtualTestnet } from '@cy/support/helpers/tenderly'
import Box from '@mui/material/Box'
import { ConnectionProvider } from '@ui-kit/features/connect-wallet/lib/ConnectionProvider'
import { usePathname } from '@ui-kit/hooks/router'
Expand Down Expand Up @@ -34,7 +33,7 @@ describe('RootLayout RPC Tests', () => {

it(`redirects to arbitrum when the wallet is connected to it`, () => {
cy.mount(
<ComponentTestWrapper config={createTestWagmiConfigFromVNet({ vnet: getVirtualNetwork() })} autoConnect>
<ComponentTestWrapper config={createTenderlyWagmiConfigFromVNet({ vnet: getVirtualNetwork() })} autoConnect>
<Test />
</ComponentTestWrapper>,
)
Expand Down
4 changes: 2 additions & 2 deletions tests/cypress/e2e/all/header.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('Header', () => {
viewport = oneDesktopViewport()
cy.viewport(...viewport)
route = oneAppRoute()
cy.visit(`/${route}`)
cy.visitWithoutTestConnector(route)
waitIsLoaded(route)
})

Expand Down Expand Up @@ -89,7 +89,7 @@ describe('Header', () => {
viewport = oneMobileOrTabletViewport()
cy.viewport(...viewport)
route = oneAppRoute()
cy.visit(`/${route}`)
cy.visitWithoutTestConnector(route)
waitIsLoaded(route)
})

Expand Down
5 changes: 5 additions & 0 deletions tests/cypress/e2e/lend/basic.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ describe('Basic Access Test', () => {
cy.url(LOAD_TIMEOUT).should('match', /http:\/\/localhost:\d+\/lend\/ethereum\/legal\/?\?tab=disclaimers$/)
cy.title().should('equal', 'Legal - Curve Lend')
})

it('should open a lend market page succesfully', () => {
cy.visit('/lend/ethereum/markets/0x23F5a668A9590130940eF55964ead9787976f2CC') // some WETH lend market on ethereum
cy.get('[data-testid^="detail-page-stack"]', LOAD_TIMEOUT).should('be.visible')
})
})
5 changes: 5 additions & 0 deletions tests/cypress/e2e/loan/basic.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ describe('Basic Access Test', () => {
cy.title(LOAD_TIMEOUT).should('equal', 'Savings crvUSD - Curve')
cy.url().should('match', /http:\/\/localhost:\d+\/crvusd\/ethereum\/scrvUSD\/?$/)
})

it('should open a loan market page succesfully', () => {
cy.visit('/crvusd/ethereum/markets/WBTC') // some WBTC mint market on ethereum
cy.get('[data-testid^="detail-page-stack"]', LOAD_TIMEOUT).should('be.visible')
})
})
2 changes: 1 addition & 1 deletion tests/cypress/e2e/main/basic.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('Basic Access Test', () => {
})

it('should load for lite networks', () => {
cy.visit('/dex/corn/pools')
cy.visitWithoutTestConnector('dex/corn/pools')
cy.title(LOAD_TIMEOUT).should('equal', 'Pools - Curve')
cy.url().should('include', '/dex/corn/pools')
cy.contains(/LBTC\/wBTCN/i, LOAD_TIMEOUT).should('be.visible')
Expand Down
4 changes: 2 additions & 2 deletions tests/cypress/e2e/main/dex-markets.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { setShowSmallPools } from '@cy/support/helpers/user-profile'
import { API_LOAD_TIMEOUT, type Breakpoint, LOAD_TIMEOUT, oneViewport } from '@cy/support/ui'
import { SMALL_POOL_TVL } from '@ui-kit/features/user-profile/store'

const PATH = '/dex/arbitrum/pools/'
const PATH = 'dex/arbitrum/pools/'

// Parse compact USD strings like "$1.2M", "$950K", "$0", "-"
function parseCompactUsd(value: string): number {
Expand All @@ -30,7 +30,7 @@ function visitAndWait(
) {
cy.viewport(width, height)
const { query } = options ?? {}
cy.visit(`${PATH}${query ? `?${new URLSearchParams(query)}` : ''}`, options)
cy.visitWithoutTestConnector(`${PATH}${query ? `?${new URLSearchParams(query)}` : ''}`, options)
cy.get('[data-testid^="data-table-row-"]', API_LOAD_TIMEOUT).should('have.length.greaterThan', 0)
if (query?.['page']) {
cy.get('[data-testid="table-pagination"]').should('be.visible')
Expand Down
5 changes: 3 additions & 2 deletions tests/cypress/e2e/main/pool-page.cy.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { oneFloat, oneOf } from '@cy/support/generators'
import type { AppRoute } from '@cy/support/routes'
import { LOAD_TIMEOUT } from '@cy/support/ui'

describe('Pool page', () => {
const path = `/dex/${oneOf(
const path: AppRoute = `dex/${oneOf(
'ethereum/pools/factory-tricrypto-0',
'ethereum/pools/factory-stable-ng-561',
'arbitrum/pools/2pool',
'corn/pools/factory-stable-ng-0',
)}/deposit`

it('should update slippage settings', () => {
cy.visit(path)
cy.visitWithoutTestConnector(path)
cy.get('[data-testid="tab-deposit"]', LOAD_TIMEOUT).should('have.class', 'Mui-selected')
cy.get('[data-testid="borrow-slippage-value"]').contains(path.includes('crypto') ? '0.10%' : '0.03%')
cy.get('[data-testid="slippage-settings-button"]').click()
Expand Down
2 changes: 1 addition & 1 deletion tests/cypress/e2e/main/swap.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('DEX Swap', () => {
const TO_ETH = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'

it('shows quotes via router API when disconnected', () => {
cy.visit(`/dex/ethereum/swap?from=${FROM_USDT}&to=${TO_ETH}`)
cy.visitWithoutTestConnector(`dex/ethereum/swap?from=${FROM_USDT}&to=${TO_ETH}`)
cy.get('[data-testid="btn-connect-wallet"]', LOAD_TIMEOUT).should('be.enabled')
cy.get(`[data-testid="token-icon-${FROM_USDT}"]`, LOAD_TIMEOUT).should('be.visible')

Expand Down
29 changes: 28 additions & 1 deletion tests/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -1 +1,28 @@
// This file is imported automatically before e2e tests run. Try to keep it empty if possible
import type { AppRoute } from './routes'

declare global {
interface Window {
CypressNoTestConnector?: string
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable<Subject = any> {
visitWithoutTestConnector(route: AppRoute, options?: Partial<Cypress.VisitOptions>): Chainable<AUTWindow>
}
}
}

/**
* For most of our e2e tests we have a wagmi test connect that auto-connects, so there's a wallet available.
* However, in some cases we want to test functionality without a wallet connected.
*/
Cypress.Commands.add('visitWithoutTestConnector', (route: AppRoute, options?: Partial<Cypress.VisitOptions>) =>
cy.visit(`/${route}`, {
...options,
onBeforeLoad(win) {
win.CypressNoTestConnector = 'true'
options?.onBeforeLoad?.(win)
},
}),
)
Loading
Loading