Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d53e01f
Update e2e test tags
hyphenized Feb 11, 2025
9efb838
Add test-id to asset list item
hyphenized Feb 11, 2025
aac5559
Add test-id to account list slide menu
hyphenized Feb 11, 2025
c263158
Add helper to change first account name
hyphenized Feb 11, 2025
bdea96a
Schedule price updates more often
hyphenized Feb 11, 2025
643bed8
Limit price lookups batch size on e2e tests
hyphenized Feb 12, 2025
7ffeb9a
Replace hardhat with anvil
hyphenized Feb 14, 2025
2b840ff
Update CI e2e commands
hyphenized Feb 24, 2025
96b6961
Fix anvil ci cache persistance
hyphenized Feb 24, 2025
905ab2a
Add testid to wallet balance loader
hyphenized Feb 24, 2025
7d40e4d
Add asset id to wallet asset list item
hyphenized Feb 24, 2025
3686020
Set timeout on extension page monitoring
hyphenized Feb 24, 2025
512a684
Fix asset prices test
hyphenized Feb 24, 2025
41328bd
Do not aggregate balances of infinite value
hyphenized Feb 16, 2025
50c35ac
Remove value check from shared assertion
hyphenized Feb 24, 2025
407e903
Add test id to asset list item prices
hyphenized Feb 24, 2025
402a6d9
Add utility to reset erc20 balance
hyphenized Feb 24, 2025
52c15b0
Merge branch 'main' into testing-tests-e2e-fork
hyphenized Dec 4, 2025
e7210f0
Chunk balance lookups
hyphenized Dec 4, 2025
da31038
Fix balance lookup results
hyphenized Dec 4, 2025
03b48e3
Fix broken assertion
hyphenized Dec 5, 2025
f2e3b46
Disable NFT e2e tests
hyphenized Dec 5, 2025
68dc1f9
Improve assertions
hyphenized Dec 5, 2025
b16f881
Skip login.xyz test
hyphenized Dec 5, 2025
efdc0fe
Remove redundant assertions
hyphenized Dec 6, 2025
a168b5f
Fallback balance lookups if alchemy fails
hyphenized Dec 6, 2025
96f5017
Prioritize drpc.org for polygon on new installs
hyphenized Dec 6, 2025
2e71f03
Fix broken uniswap tokenlist
hyphenized Dec 6, 2025
afe6eb6
Reduce initialLoadWaitExpired time
hyphenized Dec 6, 2025
d3d96ba
Bump playwright to 1.57
hyphenized Dec 6, 2025
ef071fd
Re-structure balance-refresh alarms
hyphenized Dec 6, 2025
db8141e
Fix snackbar assertions
hyphenized Dec 6, 2025
635aafb
Disable blocknative on e2e fork env
hyphenized Dec 6, 2025
b6bb840
Fix wallet balance assertion
hyphenized Dec 6, 2025
c0bd38a
Retry connect popup locator
hyphenized Dec 6, 2025
04bc2fe
Fix transaction helper assertions
hyphenized Dec 7, 2025
861b7f1
Setup the global error timeout on window load
hyphenized Dec 7, 2025
19dd827
Lower base timeouts on e2e tests
hyphenized Dec 7, 2025
7095eb6
Add sepolia to eip1559 chains
hyphenized Dec 7, 2025
179b423
Add Sepolia RPCs
hyphenized Dec 7, 2025
d68aeb2
Remove enable testnet step
hyphenized Dec 7, 2025
111f40c
Change Anvil runtime settings
hyphenized Dec 7, 2025
522dea6
Schedule price lookups if new balances are discovered
hyphenized Dec 7, 2025
9133b62
Retry price load assertion
hyphenized Dec 7, 2025
2322167
Reduce batch size for multicall price lookups
hyphenized Dec 8, 2025
e73004c
Fix delayed balance display for custom assets
hyphenized Dec 9, 2025
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
3 changes: 2 additions & 1 deletion .env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ USE_MAINNET_FORK=false
ARBITRUM_FORK_RPC=https://rpc.tenderly.co/fork/2fc2cf12-5c58-439f-9b5e-967bfd02191a
TESTNET_TAHO_DEPLOYER_ADDRESS=0x55B180c3470dA8E31761d45468e4E61DbE13Eb9B
TESTNET_TAHO_ADDRESS=0x78f04eC76df38Fcb37971Efa8EcbcB33f52dae0F
FORK_TEST_WALLET_JSON_BODY=
FORK_TEST_WALLET_JSON_PASSWORD=
USE_CAMPAIGN_NFT_CONTRACT=

60 changes: 34 additions & 26 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,18 @@ jobs:
env:
E2E_TEST_ONLY_WALLET_JSON_BODY: ${{ secrets.E2E_TEST_ONLY_WALLET_JSON_BODY }}
E2E_TEST_ONLY_WALLET_JSON_PASSWORD: ${{ secrets.E2E_TEST_ONLY_WALLET_JSON_PASSWORD }}
run: xvfb-run npx playwright test --grep-invert @expensive
run: xvfb-run yarn e2e:regular
#env:
# DEBUG: pw:api*
- name: Run costing Playwright tests
- name: Run testnet Playwright tests
if: |
github.ref == 'refs/heads/main'
|| contains(github.head_ref, 'e2e')
|| needs.detect-if-flag-changed.outputs.path-filter == 'true'
env:
TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }}
TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }}
run: xvfb-run npx playwright test --grep @expensive
TESTNET_TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }}
TESTNET_TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }}
run: xvfb-run yarn e2e:testnet
- uses: actions/upload-artifact@v4
if: ${{ failure() || cancelled() }}
with:
Expand Down Expand Up @@ -242,35 +242,39 @@ jobs:
name: extension-builds-fork-${{ github.event.number || github.event.head_commit.id }}
- name: Extract extension
run: unzip -o chrome-fork.zip -d dist/chrome
- name: Restore Hardhat cache
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
cache: false
- name: Restore Anvil cache
uses: actions/cache/restore@v3
with:
path: ci/cache
# 18291960 is a forking block used in the tests.
key: hardhat-18291960-${{ github.ref_name }}
path: /home/runner/.foundry/cache
key: anvil-21802314-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
hardhat-18291960-
hardhat-
- name: Run Hardhat
anvil-21802314-${{ github.ref_name }}-${{ github.sha }}
anvil-21802314-${{ github.ref_name }}-
anvil-21802314-
- name: Run Anvil
env:
CHAIN_API_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.DEV_ALCHEMY_API_KEY }}
MAINNET_FORK_CHAIN_ID: 1337
# We're using a fixed block number as a start of the fork to get
# consistent behavior in tests. The `18291960` block is a block at
# which the wallet used in tests (`testertesting.eth`) had the two
# assets used in e2e tests.
FORKING_BLOCK: 18291960
# consistent behavior in tests. The `21802314` block is a block at
# which the state of the chain allows the tests to pass
FORKING_BLOCK: 21802314
run: |
cd ci
yarn install
npx hardhat node --network hardhat &
sleep 20
anvil -q -s 10 --chain-id $MAINNET_FORK_CHAIN_ID --port 8545 \
--fork-block-number $FORKING_BLOCK --fork-url $CHAIN_API_URL \
--fork-chain-id 1 --fork-retry-backoff 100 --timeout 40000 \
--gas-limit 65000000 &
sleep 5
- name: Run Playwright tests designed for fork
env:
TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }}
TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }}
FORK_TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }}
FORK_TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }}
USE_MAINNET_FORK: true
run: xvfb-run npx playwright test
run: xvfb-run yarn e2e:fork
- uses: actions/upload-artifact@v4
if: ${{ failure() || cancelled() }}
with:
Expand All @@ -279,12 +283,16 @@ jobs:
test-results/
#videos/
retention-days: 30
- name: Save Hardhat cache
# Wait for anvil to finish and save its cache to disk
- name: Stop Anvil
if: ${{ !cancelled() }}
run: sh ./ci/stop-anvil.sh
- name: Save Anvil Cache
uses: actions/cache/save@v3
# We want to save the cache even if the tests failed. Without the cache
# the tests almost always fail, so without `if: always()` we would have
# problem with creating the first cache.
if: always()
with:
path: ci/cache
key: hardhat-18291960-${{ github.ref_name }}-${{ hashFiles('ci/cache/**/*.json') }}
path: /home/runner/.foundry/cache
key: anvil-21802314-${{ github.ref_name }}-${{ github.sha }}
12 changes: 9 additions & 3 deletions background/constants/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ export const FORK: EVMNetwork = {
}

export const EIP_1559_COMPLIANT_CHAIN_IDS = new Set(
[ETHEREUM, POLYGON, SEPOLIA, AVALANCHE].map((network) => network.chainID),
[ETHEREUM, POLYGON, SEPOLIA, AVALANCHE, SEPOLIA].map(
(network) => network.chainID,
),
)

export const CHAINS_WITH_MEMPOOL = new Set(
Expand Down Expand Up @@ -211,9 +213,9 @@ export const CHAIN_ID_TO_RPC_URLS: {
"wss://mezo-testnet.drpc.org",
],
[POLYGON.chainID]: [
"https://polygon.drpc.org",
// This one sometimes returns 0 for eth_getBalance
"https://polygon-rpc.com",
"https://polygon.drpc.org",
"https://1rpc.io/matic",
],
[OPTIMISM.chainID]: [
Expand All @@ -224,7 +226,11 @@ export const CHAIN_ID_TO_RPC_URLS: {
[ETHEREUM.chainID]: ["https://eth.drpc.org", "https://1rpc.io/eth"],
[ARBITRUM_ONE.chainID]: ["https://arbitrum.drpc.org", "https://1rpc.io/arb"],
[ARBITRUM_NOVA.chainID]: ["https://nova.arbitrum.io/rpc "],
[SEPOLIA.chainID]: ["https://endpoints.omniatech.io/v1/eth/sepolia/public"],
[SEPOLIA.chainID]: [
"https://sepolia.drpc.org",
"wss://sepolia.drpc.org",
"https://ethereum-sepolia-rpc.publicnode.com",
],
[ARBITRUM_SEPOLIA.chainID]: ["https://sepolia-rollup.arbitrum.io/rpc"],
[AVALANCHE.chainID]: [
"https://api.avax.network/ext/bc/C/rpc",
Expand Down
68 changes: 47 additions & 21 deletions background/lib/erc20.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BaseProvider, Provider } from "@ethersproject/providers"
import { BigNumber, ethers } from "ethers"
import _ from "lodash"

import {
EventFragment,
Expand All @@ -18,6 +19,7 @@ import {
MULTICALL_CONTRACT_ADDRESS,
} from "./multicall"
import logger from "./logger"
import { isFulfilledPromise } from "./utils/type-guards"

export const ERC20_FUNCTIONS = {
allowance: FunctionFragment.from(
Expand Down Expand Up @@ -181,7 +183,7 @@ export function parseLogsForERC20Transfers(logs: EVMLog[]): ERC20TransferLog[] {
senderAddress: decoded.from,
recipientAddress: decoded.to,
}
} catch (_) {
} catch (_err) {
return undefined
}
})
Expand All @@ -197,6 +199,9 @@ export const getTokenBalances = async (
CHAIN_SPECIFIC_MULTICALL_CONTRACT_ADDRESSES[network.chainID] ||
MULTICALL_CONTRACT_ADDRESS

// This helps limiting gas and payload size
const MAX_LOOKUPS_PER_CALL = 300

const contract = new ethers.Contract(
multicallAddress,
MULTICALL_ABI,
Expand All @@ -207,27 +212,48 @@ export const getTokenBalances = async (
address,
])

const response = (await contract.callStatic.tryBlockAndAggregate(
// false === don't require all calls to succeed
false,
tokenAddresses.map((tokenAddress) => [tokenAddress, balanceOfCallData]),
)) as AggregateContractResponse
// Looking up balances for N tokens in M batches on chain P
logger.info(
"Looking up balances for",
tokenAddresses.length,
"tokens in",
Math.ceil(tokenAddresses.length / MAX_LOOKUPS_PER_CALL),
"batches on chain",
network.chainID,
)

return response.returnData.flatMap((data, i) => {
if (data.success !== true) {
return []
}
const lookups = _.chunk(tokenAddresses, MAX_LOOKUPS_PER_CALL).map(
async (batch) => {
const response = (await contract.callStatic.tryBlockAndAggregate(
// false === don't require all calls to succeed
false,
batch.map((tokenAddress) => [tokenAddress, balanceOfCallData]),
)) as AggregateContractResponse

if (data.returnData === "0x00" || data.returnData === "0x") {
return []
}
return response.returnData.flatMap((data, i) => {
if (data.success !== true) {
return []
}

return {
amount: BigInt(BigNumber.from(data.returnData).toString()),
smartContract: {
contractAddress: tokenAddresses[i],
homeNetwork: network,
},
}
})
if (data.returnData === "0x00" || data.returnData === "0x") {
return []
}

return {
amount: ERC20_INTERFACE.decodeFunctionResult(
"balanceOf",
data.returnData,
)[0].toBigInt(),
smartContract: {
contractAddress: batch[i],
homeNetwork: network,
},
}
})
},
)

return (await Promise.allSettled(lookups))
.filter(isFulfilledPromise)
.flatMap((result) => result.value)
}
5 changes: 4 additions & 1 deletion background/lib/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "../constants/networks"
import { gweiToWei } from "./utils"
import { MINUTE } from "../constants"
import { FeatureFlags, isDisabled } from "../features"

// We can't use destructuring because webpack has to replace all instances of
// `process.env` variables in the bundled output
Expand Down Expand Up @@ -154,7 +155,9 @@ export default async function getBlockPrices(
// if BlockNative is configured and we're on mainnet, prefer their gas service.
if (
typeof BLOCKNATIVE_API_KEY !== "undefined" &&
network.chainID === ETHEREUM.chainID
network.chainID === ETHEREUM.chainID &&
// Don't use blocknative on mainnet fork
isDisabled(FeatureFlags.USE_MAINNET_FORK)
) {
// If the last attempt was a failure and we last succeeded less than
// BLOCKNATIVE_RETRY_INTERVAL ago, leave Blocknative alone and retry later.
Expand Down
4 changes: 3 additions & 1 deletion background/lib/priceOracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ import { toFixedPoint } from "./fixed-point"
import SerialFallbackProvider from "../services/chain/serial-fallback-provider"
import { EVMNetwork } from "../networks"
import logger, { logRejectedAndReturnFulfilledResults } from "./logger"
import { FeatureFlags, isEnabled } from "../features"

// The size of a batch of on-chain price lookups. Too high and the request will
// fail due to running out of gas, as eth_call is still subject to gas limits.
// Too low and we will make additional unnecessary RPC requests.
//
// Some public RPCS (such as ankr) have stricter limits on gas for eth_calls
// for now, this size appears to work fine
const BATCH_SIZE = 5
const BATCH_SIZE = isEnabled(FeatureFlags.USE_MAINNET_FORK) ? 2 : 4

// Oracle Documentation and Address references can be found
// at https://docs.1inch.io/docs/spot-price-aggregator/introduction/
Expand Down Expand Up @@ -267,6 +268,7 @@ export async function getUSDPriceForTokens(
}
return
}
// FIXME: If a lookup fails, retry?
if (
response.success !== true ||
ethers.BigNumber.from(response.returnData).isZero()
Expand Down
11 changes: 11 additions & 0 deletions background/lib/token-lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TokenList } from "@uniswap/token-lists"

import { memoize } from "lodash"
import { fetchJson } from "@ethersproject/web"
import { utils } from "ethers"
import {
FungibleAsset,
SmartContractFungibleAsset,
Expand Down Expand Up @@ -40,6 +41,16 @@ const cleanTokenListResponse = (json: any, url: string) => {
return { ...json, tokens }
}

if (json && Array.isArray(json?.tokens)) {
// Uniswap new token list format allows solana token addresses
return {
...json,
tokens: json.tokens.filter((token: { address: string }) =>
utils.isAddress(token.address),
),
}
}

return json
}

Expand Down
4 changes: 3 additions & 1 deletion background/redux-slices/selectors/accountsSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ const computeCombinedAssetAmountsData = (
combinedAssetAmounts.forEach((assetAmount) => {
if (typeof assetAmount.mainCurrencyAmount !== "undefined") {
totalMainCurrencyAmount ??= 0 // initialize if needed
totalMainCurrencyAmount += assetAmount.mainCurrencyAmount
if (Number.isFinite(assetAmount.mainCurrencyAmount)) {
totalMainCurrencyAmount += assetAmount.mainCurrencyAmount
}
}
})

Expand Down
11 changes: 11 additions & 0 deletions background/services/chain/asset-data-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ export default class AssetDataHelper {
if (provider.supportsAlchemy) {
return await getAlchemyTokenBalances(provider, addressOnNetwork)
}
} catch (error) {
logger.debug(
"Problem resolving asset balances on Alchemy supported network",
addressOnNetwork.network,
error,
)
}

try {
return await getTokenBalances(
addressOnNetwork,
smartContractAddresses || [],
Expand All @@ -98,6 +107,8 @@ export default class AssetDataHelper {
// Load balances of tokens on the mainnet fork
if (isEnabled(FeatureFlags.USE_MAINNET_FORK)) {
const tokens = [
"0x6b175474e89094c44da98b954eedeac495271d0f", // DAI
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
"0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", // AAVE
"0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", // UNI
"0x3A283D9c08E8b55966afb64C515f5143cf907611", // crvCVXETH
Expand Down
Loading
Loading