Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace e2e fork node with anvil #3781

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions .env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ 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=
58 changes: 33 additions & 25 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ 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
Expand All @@ -202,9 +202,9 @@ jobs:
|| 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 --chain-id $MAINNET_FORK_CHAIN_ID --port 8545 \
--fork-block-number $FORKING_BLOCK --fork-url $CHAIN_API_URL \
--preserve-historical-states --fork-chain-id 1 \
--gas-limit 65000000 &
sleep 10
- 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 }}
5 changes: 4 additions & 1 deletion background/lib/priceOracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ import { toFixedPoint } from "./fixed-point"
import SerialFallbackProvider from "../services/chain/serial-fallback-provider"
import { EVMNetwork } from "../networks"
import logger, { logRejectedAndReturnFulfilledResults } from "./logger"
import { FeatureFlags } 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.
const BATCH_SIZE = 20
// On mainnet fork, even though the node caches evm states, local execution
// creates a strict limit on gas per call
const BATCH_SIZE = FeatureFlags.USE_MAINNET_FORK ? 2 : 20

// Oracle Documentation and Address references can be found
// at https://docs.1inch.io/docs/spot-price-aggregator/introduction/
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
2 changes: 2 additions & 0 deletions background/services/chain/asset-data-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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
33 changes: 16 additions & 17 deletions background/services/indexing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default class IndexingService extends BaseService<Events> {
*/
private scheduledTokenRefresh = false

private lastPriceAlarmTime = 0
#pricesUpdateTimer: NodeJS.Timer | null = null

private cachedAssets: Record<EVMNetwork["chainID"], AnyAsset[]> =
Object.fromEntries(
Expand Down Expand Up @@ -178,9 +178,9 @@ export default class IndexingService extends BaseService<Events> {
prices: {
schedule: {
delayInMinutes: 1,
periodInMinutes: 10,
periodInMinutes: 1,
},
handler: () => this.handlePriceAlarm(),
handler: () => this.scheduleUpdateAssetsPrices(),
},
})
}
Expand All @@ -194,7 +194,7 @@ export default class IndexingService extends BaseService<Events> {
const tokenListLoad = this.fetchAndCacheTokenLists()

this.chainService.emitter.once("serviceStarted").then(async () => {
this.handlePriceAlarm()
this.scheduleUpdateAssetsPrices()

const trackedNetworks = await this.chainService.getTrackedNetworks()

Expand Down Expand Up @@ -476,7 +476,7 @@ export default class IndexingService extends BaseService<Events> {
await this.loadAccountBalancesFor(addressOnNetwork)

// FIXME Refactor this to only update prices for tokens with balances.
this.handlePriceAlarm()
this.scheduleUpdateAssetsPrices()
},
)

Expand Down Expand Up @@ -939,21 +939,20 @@ export default class IndexingService extends BaseService<Events> {
}
}

private async handlePriceAlarm(): Promise<void> {
if (Date.now() < this.lastPriceAlarmTime + 5 * SECOND) {
// If this is quickly called multiple times (for example when
// using a network for the first time with a wallet loaded
// with many accounts) only fetch prices once.
private async scheduleUpdateAssetsPrices(): Promise<void> {
// If there's a pending update do nothing
if (this.#pricesUpdateTimer) {
return
}

this.lastPriceAlarmTime = Date.now()

// Avoid awaiting here so price fetching can happen in the background
// and the extension can go on doing whatever it needs to do while waiting
// for prices to come back.
this.getBaseAssetsPrices()
this.getTrackedAssetsPrices()
this.#pricesUpdateTimer = setTimeout(() => {
this.#pricesUpdateTimer = null
// Avoid awaiting here so price fetching can happen in the background
// and the extension can go on doing whatever it needs to do while waiting
// for prices to come back.
this.getBaseAssetsPrices()
this.getTrackedAssetsPrices()
}, 5 * SECOND)
}

private async fetchAndCacheTokenLists(): Promise<void> {
Expand Down
21 changes: 12 additions & 9 deletions background/services/indexing/tests/index.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ describe("IndexingService", () => {
// Check that we're using proper token ids for built in network assets
// TODO: Remove once we add an e2e test for balances
it("should query builtin network asset prices", async () => {
jest.useFakeTimers()

const indexingDb = await getIndexingDB()

const smartContractAsset = createSmartContractAsset()
Expand All @@ -266,10 +268,9 @@ describe("IndexingService", () => {

await indexingDb.addAssetToTrack(smartContractAsset)

const spy = getPrivateMethodSpy<IndexingService["handlePriceAlarm"]>(
indexingService,
"handlePriceAlarm",
)
const spy = getPrivateMethodSpy<
IndexingService["scheduleUpdateAssetsPrices"]
>(indexingService, "scheduleUpdateAssetsPrices")

await Promise.all([
chainService.startService(),
Expand All @@ -280,7 +281,9 @@ describe("IndexingService", () => {

expect(spy).toHaveBeenCalled()

await spy.mock.results[0].value
jest.advanceTimersByTime(5000)
await jest.advanceTimersToNextTimerAsync()
jest.useRealTimers()

expect(
fetchJsonStub
Expand All @@ -306,9 +309,9 @@ describe("IndexingService", () => {
await indexingDb.addAssetToTrack(smartContractAsset)

// Skip loading prices at service init
getPrivateMethodSpy<IndexingService["handlePriceAlarm"]>(
getPrivateMethodSpy<IndexingService["scheduleUpdateAssetsPrices"]>(
indexingService,
"handlePriceAlarm",
"scheduleUpdateAssetsPrices",
).mockResolvedValue(Promise.resolve())

await Promise.all([
Expand Down Expand Up @@ -359,9 +362,9 @@ describe("IndexingService", () => {
await indexingDb.addAssetToTrack(smartContractAsset)

// Skip loading prices at service init
getPrivateMethodSpy<IndexingService["handlePriceAlarm"]>(
getPrivateMethodSpy<IndexingService["scheduleUpdateAssetsPrices"]>(
indexingService,
"handlePriceAlarm",
"scheduleUpdateAssetsPrices",
).mockResolvedValue(Promise.resolve())

await Promise.all([
Expand Down
15 changes: 15 additions & 0 deletions ci/stop-anvil.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

# This script sends a SIGINT to anvil so it flushes its cache
# to disk and terminates properly

PID=$(pgrep -o anvil)
while kill -2 "$PID" 2>/dev/null; do
sleep 1;
done;

echo "Process $PID has exited";

SIZE=$(du -sh $HOME/.foundry/cache | awk '{print $1}')

echo "Cache size: $SIZE"
Loading
Loading