diff --git a/.circleci/config.yml b/.circleci/config.yml index 3178a687a617..27f2c57e701b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,6 +99,13 @@ aliases: circleci step halt fi +commands: + foundry-install: + steps: + - run: + name: Install Foundry package for Anvil Testing Dependency + command: yarn foundryup + workflows: test_and_release: when: @@ -1079,6 +1086,7 @@ jobs: - run: name: Move test build to dist command: mv ./dist-test-webpack ./dist + - foundry-install - run: name: test:e2e:chrome:webpack command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:webpack @@ -1102,6 +1110,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test ./builds + - foundry-install - gh/install - run: name: test:api-specs @@ -1134,6 +1143,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test ./builds + - foundry-install - run: name: test:e2e:chrome command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome @@ -1158,6 +1168,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test ./builds + - foundry-install - run: name: test:e2e:chrome:rpc command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:rpc @@ -1182,6 +1193,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test ./builds + - foundry-install - run: name: test:e2e:chrome:multi-provider command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:multi-provider @@ -1206,6 +1218,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test-mmi ./builds + - foundry-install - run: name: test:e2e:chrome:rpc command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:rpc --build-type=mmi @@ -1223,6 +1236,7 @@ jobs: - run: sudo corepack enable - attach_workspace: at: . + - foundry-install - run: name: test:e2e:single command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:single test/e2e/vault-decryption-chrome.spec.ts --browser chrome @@ -1247,6 +1261,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test-flask-mv2 ./builds + - foundry-install - run: name: test:e2e:firefox:flask command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:firefox:flask @@ -1271,6 +1286,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test-flask ./builds + - foundry-install - run: name: test:e2e:chrome:flask command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:flask @@ -1296,6 +1312,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test-mmi ./builds + - foundry-install - run: name: test:e2e:chrome:mmi command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:mmi --build-type=mmi @@ -1318,6 +1335,7 @@ jobs: - run: name: Move test build to dist command: mv ./dist-test-mmi-playwright ./dist + - foundry-install - run: name: Install chromium command: yarn playwright install chromium @@ -1358,6 +1376,7 @@ jobs: - run: name: Install chromium command: yarn playwright install chromium + - foundry-install - run: name: test:e2e:chrome:swap command: | @@ -1391,6 +1410,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test-mv2 ./builds + - foundry-install - run: name: test:e2e:firefox command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:firefox @@ -1462,6 +1482,7 @@ jobs: - run: name: Move test zips to builds command: mv ./builds-test ./builds + - foundry-install - run: name: Run page load benchmark command: | diff --git a/.depcheckrc.yml b/.depcheckrc.yml index 5260be984692..50c8b760c1f4 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -40,6 +40,7 @@ ignores: - 'wait-on' - 'tsx' # used in .devcontainer - 'prettier-eslint' # used by the Prettier ESLint VSCode extension + - 'tar' # storybook - '@storybook/cli' - '@storybook/core' diff --git a/.vscode/package.json-schema.json b/.vscode/package.json-schema.json index 458329d0741c..5ac21e3d5d1d 100644 --- a/.vscode/package.json-schema.json +++ b/.vscode/package.json-schema.json @@ -18,6 +18,10 @@ "type": "string", "description": "Deletes webpack's build cache. Useful to force a rebuild (webpack not detecting changes, node_modules have changed, etc)." }, + "foundryup": { + "type": "string", + "description": "Installs foundry's Anvil. Run `yarn foundryup --help` for advanced usage." + }, "postinstall": { "type": "string", "description": "Runs automatically after running `yarn` (`yarn install`) in order to prime the webpack dev build." diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index cf72074493e6..3d0e15a9862c 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -188,24 +188,11 @@ }, "packages": { "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true, - "@metamask/message-signing-snap>@noble/curves": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils>@scure/base": true + "viem>@scure/bip32": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { "TextEncoder": true, "crypto": true @@ -5661,6 +5648,19 @@ "msCrypto": true } }, + "viem>@scure/bip32": { + "packages": { + "@metamask/message-signing-snap>@noble/curves": true, + "@metamask/utils>@scure/base": true, + "viem>@scure/bip32>@noble/hashes": true + } + }, + "viem>@scure/bip32>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "wait-on>rxjs": { "globals": { "cancelAnimationFrame": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index cf72074493e6..3d0e15a9862c 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -188,24 +188,11 @@ }, "packages": { "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true, - "@metamask/message-signing-snap>@noble/curves": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils>@scure/base": true + "viem>@scure/bip32": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { "TextEncoder": true, "crypto": true @@ -5661,6 +5648,19 @@ "msCrypto": true } }, + "viem>@scure/bip32": { + "packages": { + "@metamask/message-signing-snap>@noble/curves": true, + "@metamask/utils>@scure/base": true, + "viem>@scure/bip32>@noble/hashes": true + } + }, + "viem>@scure/bip32>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "wait-on>rxjs": { "globals": { "cancelAnimationFrame": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index cf72074493e6..3d0e15a9862c 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -188,24 +188,11 @@ }, "packages": { "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true, - "@metamask/message-signing-snap>@noble/curves": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils>@scure/base": true + "viem>@scure/bip32": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { "TextEncoder": true, "crypto": true @@ -5661,6 +5648,19 @@ "msCrypto": true } }, + "viem>@scure/bip32": { + "packages": { + "@metamask/message-signing-snap>@noble/curves": true, + "@metamask/utils>@scure/base": true, + "viem>@scure/bip32>@noble/hashes": true + } + }, + "viem>@scure/bip32>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "wait-on>rxjs": { "globals": { "cancelAnimationFrame": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index e70102a63e51..cbddf9db0aa9 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -188,24 +188,11 @@ }, "packages": { "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true, - "@metamask/message-signing-snap>@noble/curves": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils>@scure/base": true + "viem>@scure/bip32": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { "TextEncoder": true, "crypto": true @@ -5729,6 +5716,19 @@ "msCrypto": true } }, + "viem>@scure/bip32": { + "packages": { + "@metamask/message-signing-snap>@noble/curves": true, + "@metamask/utils>@scure/base": true, + "viem>@scure/bip32>@noble/hashes": true + } + }, + "viem>@scure/bip32>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "wait-on>rxjs": { "globals": { "cancelAnimationFrame": true, diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 5338922720ef..c8f267abda51 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -1554,45 +1554,6 @@ "browserify>deps-sort>through2>readable-stream>safe-buffer": true } }, - "browserify>duplexer2": { - "packages": { - "browserify>duplexer2>readable-stream": true - } - }, - "browserify>duplexer2>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, - "packages": { - "browserify>duplexer2>readable-stream>isarray": true, - "browserify>duplexer2>readable-stream>safe-buffer": true, - "browserify>duplexer2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true - } - }, - "browserify>duplexer2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "browserify>duplexer2>readable-stream>string_decoder": { - "packages": { - "browserify>duplexer2>readable-stream>safe-buffer": true - } - }, "browserify>has": { "packages": { "browserify>has>function-bind": true @@ -1697,7 +1658,6 @@ "browserify>browser-resolve": true, "browserify>cached-path-relative": true, "browserify>concat-stream": true, - "browserify>duplexer2": true, "browserify>module-deps>detective": true, "browserify>module-deps>readable-stream": true, "browserify>module-deps>stream-combiner2": true, @@ -1706,6 +1666,7 @@ "depcheck>resolve": true, "loose-envify": true, "pumpify>inherits": true, + "unzipper>duplexer2": true, "watchify>defined": true, "watchify>xtend": true } @@ -1752,8 +1713,8 @@ }, "browserify>module-deps>stream-combiner2": { "packages": { - "browserify>duplexer2": true, - "browserify>module-deps>stream-combiner2>readable-stream": true + "browserify>module-deps>stream-combiner2>readable-stream": true, + "unzipper>duplexer2": true } }, "browserify>module-deps>stream-combiner2>readable-stream": { @@ -8871,6 +8832,45 @@ "terser>source-map-support": true } }, + "unzipper>duplexer2": { + "packages": { + "unzipper>duplexer2>readable-stream": true + } + }, + "unzipper>duplexer2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "pumpify>inherits": true, + "readable-stream-2>core-util-is": true, + "readable-stream-2>process-nextick-args": true, + "readable-stream>util-deprecate": true, + "unzipper>duplexer2>readable-stream>isarray": true, + "unzipper>duplexer2>readable-stream>safe-buffer": true, + "unzipper>duplexer2>readable-stream>string_decoder": true + } + }, + "unzipper>duplexer2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "unzipper>duplexer2>readable-stream>string_decoder": { + "packages": { + "unzipper>duplexer2>readable-stream>safe-buffer": true + } + }, "uri-js": { "globals": { "define": true diff --git a/package.json b/package.json index 5304df2c2ff8..c466bf7b18ae 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "scripts": { "webpack": "tsx ./development/webpack/launch.ts", "webpack:clearcache": "./development/clear-webpack-cache.js", + "foundryup": "tsx --tsconfig ./test/helpers/foundry/tsconfig.json ./test/helpers/foundry/foundryup.mts", "postinstall": "yarn webpack:clearcache", "env:e2e": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn", "start": "yarn build:dev dev --apply-lavamoat=false --snow=false", @@ -44,6 +45,7 @@ "forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010", "dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'", "test:unit": "jest", + "anvil": "npx anvil", "test:unit:watch": "jest --watch", "test:unit:coverage": "jest --coverage", "test:unit:webpack": "tsx --test development/webpack/test/*.test.ts", @@ -534,6 +536,7 @@ "@types/serve-handler": "^6.1.4", "@types/sinon": "^10.0.13", "@types/sprintf-js": "^1", + "@types/unzipper": "^0", "@types/w3c-web-hid": "^1.0.3", "@types/watchify": "^3.11.1", "@types/webextension-polyfill": "^0.10.4", @@ -542,6 +545,7 @@ "@types/yargs-parser": "^21.0.3", "@typescript-eslint/eslint-plugin": "^7.10.0", "@typescript-eslint/parser": "^7.10.0", + "@viem/anvil": "^0.0.10", "@welldone-software/why-did-you-render": "^8.0.3", "@whitespace/storybook-addon-html": "^5.1.6", "addons-linter": "^6.28.0", @@ -661,6 +665,7 @@ "style-loader": "^0.21.0", "stylelint": "^13.6.1", "superstruct": "^1.0.3", + "tar": "^7.4.3", "terser": "^5.7.0", "terser-webpack-plugin": "^5.3.10", "through2": "^4.0.2", @@ -668,6 +673,8 @@ "tsx": "^4.7.1", "ttest": "^2.1.1", "typescript": "~5.4.5", + "unzipper": "^0.12.3", + "viem": "^2.21.8", "vinyl": "^2.2.1", "vinyl-buffer": "^1.0.1", "vinyl-source-stream": "^2.0.0", @@ -684,7 +691,7 @@ "yargs-parser": "^21.1.1" }, "engines": { - "node": ">= 20", + "node": ">20.12.0 <20.15.0 || >=20.17.0", "yarn": "^4.4.1" }, "lavamoat": { diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 75eefb12f7c1..658a4b198490 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -6,11 +6,13 @@ const detectPort = require('detect-port'); const { difference } = require('lodash'); const createStaticServer = require('../../development/create-static-server'); const { setupMocking } = require('./mock-e2e'); +const { Anvil } = require('./seeder/anvil'); const { Ganache } = require('./seeder/ganache'); const FixtureServer = require('./fixture-server'); const PhishingWarningPageServer = require('./phishing-warning-page-server'); const { buildWebDriver } = require('./webdriver'); const { PAGES } = require('./webdriver/driver'); +const AnvilSeeder = require('./seeder/anvil-seeder'); const GanacheSeeder = require('./seeder/ganache-seeder'); const { Bundler } = require('./bundler'); const { SMART_CONTRACTS } = require('./seeder/smart-contracts'); @@ -60,6 +62,8 @@ async function withFixtures(options, testSuite) { dapp, fixtures, ganacheOptions, + anvilOptions, + useAnvil, smartContract, driverOptions, dappOptions, @@ -81,7 +85,12 @@ async function withFixtures(options, testSuite) { const fixtureServer = new FixtureServer(); let ganacheServer; - if (!disableGanache) { + let anvilServer; + + // Temporary logic for network management until we remove ganache from all specs + if (anvilOptions || useAnvil) { + anvilServer = new Anvil(); + } else if (!disableGanache) { ganacheServer = new Ganache(); } const bundlerServer = new Bundler(); @@ -100,21 +109,38 @@ async function withFixtures(options, testSuite) { let driver; let failed = false; try { - if (!disableGanache) { + if (ganacheServer) { await ganacheServer.start(ganacheOptions); } + + if (anvilServer) { + await anvilServer.start(anvilOptions || {}); + } + let contractRegistry; - if (smartContract && !disableGanache) { - const ganacheSeeder = new GanacheSeeder(ganacheServer.getProvider()); - const contracts = - smartContract instanceof Array ? smartContract : [smartContract]; - await Promise.all( - contracts.map((contract) => - ganacheSeeder.deploySmartContract(contract), - ), - ); - contractRegistry = ganacheSeeder.getContractRegistry(); + if (smartContract) { + if (ganacheServer) { + const ganacheSeeder = new GanacheSeeder(ganacheServer.getProvider()); + const contracts = + smartContract instanceof Array ? smartContract : [smartContract]; + await Promise.all( + contracts.map((contract) => + ganacheSeeder.deploySmartContract(contract), + ), + ); + contractRegistry = ganacheSeeder.getContractRegistry(); + } else if (anvilServer) { + const anvilSeeder = new AnvilSeeder(anvilServer.getProvider()); + const contracts = + smartContract instanceof Array ? smartContract : [smartContract]; + await Promise.all( + contracts.map((contract) => + anvilSeeder.deploySmartContract(contract), + ), + ); + contractRegistry = anvilSeeder.getContractRegistry(); + } } await fixtureServer.start(); @@ -217,13 +243,14 @@ async function withFixtures(options, testSuite) { console.log(`\nExecuting testcase: '${title}'\n`); await testSuite({ - driver: driverProxy ?? driver, + bundlerServer, contractRegistry, + driver: driverProxy ?? driver, ganacheServer, - secondaryGanacheServer, + anvilServer, mockedEndpoint, - bundlerServer, mockServer, + secondaryGanacheServer, }); const errorsAndExceptions = driver.summarizeErrorsAndExceptions(); @@ -307,6 +334,10 @@ async function withFixtures(options, testSuite) { await ganacheServer.quit(); } + if (anvilServer) { + await anvilServer.quit(); + } + if (ganacheOptions?.concurrent) { secondaryGanacheServer.forEach(async (server) => { await server.quit(); @@ -592,17 +623,17 @@ const TEST_SEED_PHRASE_TWO = * or after a transaction is made. * * @param {WebDriver} driver - The WebDriver instance. - * @param {Ganache} [ganacheServer] - The Ganache server instance (optional). + * @param {Ganache | Anvil} [localBlockchainServer] - The local server instance (optional). * @param {string} [address] - The address to check the balance for (optional). */ const locateAccountBalanceDOM = async ( driver, - ganacheServer, + localBlockchainServer, address = null, ) => { const balanceSelector = '[data-testid="eth-overview__primary-currency"]'; - if (ganacheServer) { - const balance = await ganacheServer.getBalance(address); + if (localBlockchainServer) { + const balance = await localBlockchainServer.getBalance(address); await driver.waitForSelector({ css: balanceSelector, text: `${balance} ETH`, @@ -640,10 +671,10 @@ async function unlockWallet( } } -const logInWithBalanceValidation = async (driver, ganacheServer) => { +const logInWithBalanceValidation = async (driver, localBlockchainServer) => { await unlockWallet(driver); // Wait for balance to load - await locateAccountBalanceDOM(driver, ganacheServer); + await locateAccountBalanceDOM(driver, localBlockchainServer); }; function roundToXDecimalPlaces(number, decimalPlaces) { diff --git a/test/e2e/page-objects/flows/login.flow.ts b/test/e2e/page-objects/flows/login.flow.ts index f5ed61946ce8..03b1fcafb12b 100644 --- a/test/e2e/page-objects/flows/login.flow.ts +++ b/test/e2e/page-objects/flows/login.flow.ts @@ -1,6 +1,7 @@ import LoginPage from '../pages/login-page'; import HomePage from '../pages/homepage'; import { Driver } from '../../webdriver/driver'; +import { Anvil } from '../../seeder/anvil'; import { Ganache } from '../../seeder/ganache'; /** @@ -29,7 +30,7 @@ export const loginWithoutBalanceValidation = async ( */ export const loginWithBalanceValidation = async ( driver: Driver, - localBlockchainServer?: Ganache, + localBlockchainServer?: Ganache | Anvil, password?: string, ) => { await loginWithoutBalanceValidation(driver, password); diff --git a/test/e2e/page-objects/pages/homepage.ts b/test/e2e/page-objects/pages/homepage.ts index 889ab344d91a..c7b09a148aa1 100644 --- a/test/e2e/page-objects/pages/homepage.ts +++ b/test/e2e/page-objects/pages/homepage.ts @@ -1,5 +1,6 @@ import { strict as assert } from 'assert'; import { Driver } from '../../webdriver/driver'; +import { Anvil } from '../../seeder/anvil'; import { Ganache } from '../../seeder/ganache'; import { getCleanAppState } from '../../helpers'; import HeaderNavbar from './header-navbar'; @@ -293,7 +294,7 @@ class HomePage { } async check_localBlockchainBalanceIsDisplayed( - localBlockchainServer?: Ganache, + localBlockchainServer?: Ganache | Anvil, address = null, ): Promise { let expectedBalance: string; diff --git a/test/e2e/seeder/anvil-clients.ts b/test/e2e/seeder/anvil-clients.ts new file mode 100644 index 000000000000..d1691d844e2d --- /dev/null +++ b/test/e2e/seeder/anvil-clients.ts @@ -0,0 +1,49 @@ +import { + createPublicClient, + createTestClient, + createWalletClient, + http, +} from 'viem'; + +const anvil = { + id: 1337, + name: 'Localhost', + nativeCurrency: { + decimals: 18, + name: 'Ether', + symbol: 'ETH', + }, + rpcUrls: { + default: { + http: ['http://127.0.0.1:8545'], + webSocket: ['ws://127.0.0.1:8545'], + }, + }, +}; + +type Instance = { + host: string; + port: number; +}; + +function createAnvilClients(instance: Instance) { + const publicClient = createPublicClient({ + chain: anvil, + transport: http(`http://${instance.host}:${instance.port}`), + }); + + const testClient = createTestClient({ + chain: anvil, + mode: 'anvil', + transport: http(`http://${instance.host}:${instance.port}`), + }); + + const walletClient = createWalletClient({ + chain: anvil, + transport: http(`http://${instance.host}:${instance.port}`), + }); + + return { publicClient, testClient, walletClient }; +} + +export { createAnvilClients }; diff --git a/test/e2e/seeder/anvil-seeder.js b/test/e2e/seeder/anvil-seeder.js new file mode 100644 index 000000000000..aef20326c566 --- /dev/null +++ b/test/e2e/seeder/anvil-seeder.js @@ -0,0 +1,149 @@ +const { DEFAULT_FIXTURE_ACCOUNT, ENTRYPOINT } = require('../constants'); +const ContractAddressRegistry = require('./contract-address-registry'); +const { contractConfiguration, SMART_CONTRACTS } = require('./smart-contracts'); + +/* + * Local network seeder is used to seed initial smart contract or set initial blockchain state. + */ +class AnvilSeeder { + constructor(provider) { + this.smartContractRegistry = new ContractAddressRegistry(); + this.provider = provider; + } + + /** + * Deploy initial smart contracts that can be used later within the e2e tests. + * + * @param contractName + */ + + async deploySmartContract(contractName) { + const { publicClient, testClient, walletClient } = this.provider; + const fromAddress = (await walletClient.getAddresses())[0]; + + const contractConfig = contractConfiguration[contractName]; + const deployArgs = this.getDeployArgs(contractName, contractConfig); + + const hash = await walletClient.deployContract({ + abi: contractConfig.abi, + account: fromAddress, + args: deployArgs, + bytecode: contractConfig.bytecode, + gasPrice: 2000000000, + }); + + await testClient.mine({ + blocks: 1, + }); + + const receipt = await publicClient.getTransactionReceipt({ hash }); + + console.log('Deployed smart contract', { + contractName, + contractAddress: receipt.contractAddress, + }); + + if (contractName === SMART_CONTRACTS.NFTS) { + const transaction = await walletClient.sendTransaction({ + from: fromAddress, + data: contractConfig.abi.encodeFunctionData('mintNFTs', [1]), + to: receipt.contractAddress, + }); + await publicClient.getTransactionReceipt({ hash: transaction.hash }); + } + + if (contractName === SMART_CONTRACTS.ERC1155) { + const transaction = await walletClient.sendTransaction({ + from: fromAddress, + data: contractConfig.abi.encodeFunctionData('mintBatch', [ + fromAddress, + [1, 2, 3], + [1, 1, 100000000000000], + '0x', + ]), + to: receipt.contractAddress, + }); + await publicClient.getTransactionReceipt({ hash: transaction.hash }); + } + + this.storeSmartContractAddress(contractName, receipt.contractAddress); + } + + async transfer(to, value) { + const { publicClient, walletClient } = this.provider; + const fromAddress = (await walletClient.getAddresses())[0]; + + const transaction = await walletClient.sendTransaction({ + from: fromAddress, + value, + to, + }); + + await publicClient.getTransactionReceipt({ hash: transaction.hash }); + + console.log('Completed transfer', { to, value }); + } + + async paymasterDeposit(amount) { + const paymasterAddress = this.smartContractRegistry.getContractAddress( + SMART_CONTRACTS.VERIFYING_PAYMASTER, + ); + + const { publicClient, walletClient } = this.provider; + const fromAddress = (await walletClient.getAddresses())[0]; + + const transaction = await walletClient.sendTransaction({ + from: fromAddress, + data: contractConfiguration[ + SMART_CONTRACTS.VERIFYING_PAYMASTER + ].abi.encodeFunctionData('deposit', []), + to: paymasterAddress, + value: amount, + }); + + await publicClient.getTransactionReceipt({ hash: transaction.hash }); + + console.log('Completed paymaster deposit', { amount }); + } + + /** + * Store deployed smart contract address within the environment variables + * to make it available everywhere. + * + * @param contractName + * @param contractAddress + */ + storeSmartContractAddress(contractName, contractAddress) { + this.smartContractRegistry.storeNewContractAddress( + contractName, + contractAddress, + ); + } + + /** + * Return an instance of the currently used smart contract registry. + * + * @returns ContractAddressRegistry + */ + getContractRegistry() { + return this.smartContractRegistry; + } + + getDeployArgs(contractName, contractConfig) { + if (contractName === SMART_CONTRACTS.HST) { + return [ + contractConfig.initialAmount, + contractConfig.tokenName, + contractConfig.decimalUnits, + contractConfig.tokenSymbol, + ]; + } else if (contractName === SMART_CONTRACTS.SIMPLE_ACCOUNT_FACTORY) { + return [ENTRYPOINT]; + } else if (contractName === SMART_CONTRACTS.VERIFYING_PAYMASTER) { + return [ENTRYPOINT, DEFAULT_FIXTURE_ACCOUNT]; + } + return []; + } +} + +module.exports = AnvilSeeder; diff --git a/test/e2e/seeder/anvil.ts b/test/e2e/seeder/anvil.ts new file mode 100644 index 000000000000..ed689f41cd60 --- /dev/null +++ b/test/e2e/seeder/anvil.ts @@ -0,0 +1,144 @@ +import { join } from 'path'; +import { execSync } from 'child_process'; +import { createAnvil, Anvil as AnvilType } from '@viem/anvil'; +import { createAnvilClients } from './anvil-clients'; + +type Hardfork = + | 'Frontier' + | 'Homestead' + | 'Dao' + | 'Tangerine' + | 'SpuriousDragon' + | 'Byzantium' + | 'Constantinople' + | 'Petersburg' + | 'Istanbul' + | 'Muirglacier' + | 'Berlin' + | 'London' + | 'ArrowGlacier' + | 'GrayGlacier' + | 'Paris' + | 'Shanghai' + | 'Latest'; + +const defaultOptions = { + balance: 25, + blockTime: 2, + chainId: 1337, + gasLimit: 30000000, + gasPrice: 2000000000, + hardfork: 'Muirglacier' as Hardfork, + host: '127.0.0.1', + mnemonic: + 'spread raise short crane omit tent fringe mandate neglect detail suspect cradle', + port: 8545, +}; + +export class Anvil { + #server: AnvilType | undefined; + + async start(opts = defaultOptions): Promise { + const options = { ...defaultOptions, ...opts }; + + // Determine the path to the anvil binary directory + const anvilBinaryDir = join(process.cwd(), 'node_modules', '.bin'); + + // Prepend the anvil binary directory to the PATH environment variable + process.env.PATH = `${anvilBinaryDir}:${process.env.PATH}`; + + // Verify that the anvil binary is accessible + try { + const versionOutput = execSync('anvil --version', { encoding: 'utf-8' }); + console.log(`Anvil version: ${versionOutput}`); + } catch (error) { + console.error('Failed to execute anvil:', error); + throw new Error('Anvil binary is not accessible.'); + } + + this.#server = createAnvil(options); + await this.#server.start(); + } + + getProvider() { + if (!this.#server) { + throw new Error('Server not running yet'); + } + const { walletClient, publicClient, testClient } = createAnvilClients( + this.#server, + ); + + return { walletClient, publicClient, testClient }; + } + + async getAccounts(): Promise { + const provider = this.getProvider(); + + const { walletClient } = provider; + const accounts = await walletClient.getAddresses(); + return accounts; + } + + async getBalance(address: `0x${string}` | null = null): Promise { + const provider = this.getProvider(); + + if (!provider) { + console.log('No provider found'); + return 0; + } + const { publicClient } = provider; + + const accountToUse = address || (await this.getAccounts())?.[0]; + + if (!accountToUse) { + console.log('No accounts found'); + return 0; + } + + const balanceInt = await publicClient.getBalance({ + address: accountToUse as `0x${string}`, + }); + const balanceFormatted = Number(balanceInt) / 10 ** 18; + + // Round to four decimal places, so we return the same value as ganache does + const balanceRounded = parseFloat(balanceFormatted.toFixed(4)); + return balanceRounded; + } + + async getFiatBalance(): Promise { + const balance = await this.getBalance(); + const currencyConversionRate = 1700.0; + const fiatBalance = (balance * currencyConversionRate).toFixed(2); + + return Number(fiatBalance); + } + + async setAccountBalance( + address: `0x${string}`, + balance: string, + ): Promise { + const provider = this.getProvider(); + const { testClient } = provider; + + if (!provider) { + throw new Error('No provider found'); + } + + const balanceInWei = BigInt(balance); + await testClient.setBalance({ + address, + value: balanceInWei, + }); + } + + async quit(): Promise { + if (!this.#server) { + throw new Error('Server not running yet'); + } + try { + await this.#server.stop(); + } catch (e) { + console.log('Caught error while closing Anvil network:', e); + } + } +} diff --git a/test/e2e/tests/transaction/send-eth.spec.js b/test/e2e/tests/transaction/send-eth.spec.js index 9ee1b58dc170..36296c53d16b 100644 --- a/test/e2e/tests/transaction/send-eth.spec.js +++ b/test/e2e/tests/transaction/send-eth.spec.js @@ -8,7 +8,6 @@ const { unlockWallet, editGasFeeForm, WINDOW_TITLES, - defaultGanacheOptions, tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -19,11 +18,11 @@ describe('Send ETH', function () { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), + useAnvil: true, }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); + async ({ driver, anvilServer }) => { + await logInWithBalanceValidation(driver, anvilServer); await openActionMenuAndStartSendFlow(driver); @@ -100,9 +99,8 @@ describe('Send ETH', function () { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - defaultGanacheOptions, title: this.test.fullTitle(), + useAnvil: true, }, async ({ driver }) => { await unlockWallet(driver); @@ -157,18 +155,17 @@ describe('Send ETH', function () { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: { - ...defaultGanacheOptions, + anvilOptions: { hardfork: 'london', }, smartContract, title: this.test.fullTitle(), }, - async ({ driver, contractRegistry, ganacheServer }) => { + async ({ driver, contractRegistry, anvilServer }) => { const contractAddress = await contractRegistry.getContractAddress( smartContract, ); - await logInWithBalanceValidation(driver, ganacheServer); + await logInWithBalanceValidation(driver, anvilServer); // Wait for balance to load await driver.delay(500); @@ -215,8 +212,8 @@ describe('Send ETH', function () { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), + useAnvil: true, }, async ({ driver }) => { await unlockWallet(driver); @@ -252,9 +249,8 @@ describe('Send ETH', function () { fixtures: new FixtureBuilder() .withPermissionControllerConnectedToTestDapp() .build(), - ganacheOptions: defaultGanacheOptions, - defaultGanacheOptions, title: this.test.fullTitle(), + useAnvil: true, }, async ({ driver }) => { await unlockWallet(driver); @@ -328,8 +324,7 @@ describe('Send ETH', function () { fixtures: new FixtureBuilder() .withPermissionControllerConnectedToTestDapp() .build(), - ganacheOptions: { - ...defaultGanacheOptions, + anvilOptions: { hardfork: 'london', }, title: this.test.fullTitle(), @@ -436,8 +431,8 @@ describe('Send ETH', function () { }) .withPreferencesControllerPetnamesDisabled() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), + useAnvil: true, }, async ({ driver }) => { await unlockWallet(driver); diff --git a/test/e2e/tests/transaction/send-hex-address.spec.js b/test/e2e/tests/transaction/send-hex-address.spec.js index b6ad969c6735..63c5fe4df677 100644 --- a/test/e2e/tests/transaction/send-hex-address.spec.js +++ b/test/e2e/tests/transaction/send-hex-address.spec.js @@ -1,5 +1,4 @@ const { - defaultGanacheOptions, withFixtures, logInWithBalanceValidation, openActionMenuAndStartSendFlow, @@ -18,11 +17,11 @@ describe('Send ETH to a 40 character hexadecimal address', function () { fixtures: new FixtureBuilder() .withPreferencesControllerPetnamesDisabled() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), + useAnvil: true, }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); + async ({ driver, anvilServer }) => { + await logInWithBalanceValidation(driver, anvilServer); // Send ETH await openActionMenuAndStartSendFlow(driver); @@ -63,11 +62,11 @@ describe('Send ETH to a 40 character hexadecimal address', function () { fixtures: new FixtureBuilder() .withPreferencesControllerPetnamesDisabled() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), + useAnvil: true, }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); + async ({ driver, anvilServer }) => { + await logInWithBalanceValidation(driver, anvilServer); // Send ETH await openActionMenuAndStartSendFlow(driver); @@ -114,19 +113,19 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { .withPreferencesControllerPetnamesDisabled() .withTokensControllerERC20() .build(), - ganacheOptions: defaultGanacheOptions, smartContract, title: this.test.fullTitle(), + useAnvil: true, }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - + async ({ driver, anvilServer }) => { + await logInWithBalanceValidation(driver, anvilServer); await tempToggleSettingRedesignedTransactionConfirmations(driver); // Send TST await driver.clickElement( '[data-testid="account-overview__asset-tab"]', ); + await driver.clickElement( '[data-testid="multichain-token-list-button"]', ); @@ -178,13 +177,12 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { .withPreferencesControllerPetnamesDisabled() .withTokensControllerERC20() .build(), - ganacheOptions: defaultGanacheOptions, smartContract, title: this.test.fullTitle(), + useAnvil: true, }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - + async ({ driver, anvilServer }) => { + await logInWithBalanceValidation(driver, anvilServer); await tempToggleSettingRedesignedTransactionConfirmations(driver); // Send TST diff --git a/test/e2e/tests/transaction/simple-send.spec.ts b/test/e2e/tests/transaction/simple-send.spec.ts index 7d2f4835cdca..671b6f5416e3 100644 --- a/test/e2e/tests/transaction/simple-send.spec.ts +++ b/test/e2e/tests/transaction/simple-send.spec.ts @@ -1,9 +1,8 @@ import { Suite } from 'mocha'; import { Driver } from '../../webdriver/driver'; -import { Ganache } from '../../seeder/ganache'; +import { Anvil } from '../../seeder/anvil'; import { withFixtures, - defaultGanacheOptions, tempToggleSettingRedesignedTransactionConfirmations, } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; @@ -16,17 +15,17 @@ describe('Simple send eth', function (this: Suite) { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), + useAnvil: true, }, async ({ driver, - ganacheServer, + anvilServer, }: { driver: Driver; - ganacheServer?: Ganache; + anvilServer?: Anvil; }) => { - await loginWithBalanceValidation(driver, ganacheServer); + await loginWithBalanceValidation(driver, anvilServer); await tempToggleSettingRedesignedTransactionConfirmations(driver); diff --git a/test/helpers/foundry/foundryup.mts b/test/helpers/foundry/foundryup.mts new file mode 100755 index 000000000000..0fb1f663d3c4 --- /dev/null +++ b/test/helpers/foundry/foundryup.mts @@ -0,0 +1,93 @@ +#!/usr/bin/env -S node --require "./node_modules/tsx/dist/preflight.cjs" --import "./node_modules/tsx/dist/loader.mjs" + +import { join, relative } from 'node:path'; +import { homedir } from 'node:os'; +import { Dir } from 'node:fs'; +import { opendir, symlink, unlink, copyFile, rm } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { exit, cwd } from 'node:process'; +import { + BinFormat, + Platform, + extractFrom, + getVersion, + printBanner, + say, + parseArgs, + isCodedError, + noop, +} from './helpers.mts'; + +const parsedArgs = parseArgs(); + +const CACHE_DIR = join(homedir(), '.cache', 'metamask-extension'); + +if (parsedArgs.command === 'cache clean') { + await rm(CACHE_DIR, { recursive: true, force: true }); + say('done!'); + exit(0); +} + +const { + repo, + version: { version, tag }, + arch, + platform, + binaries, +} = parsedArgs.options; + +printBanner(); +const bins = binaries.join(', '); +say(`fetching ${bins} ${version} for ${platform} ${arch}`); + +const ext = platform === Platform.Windows ? BinFormat.Zip : BinFormat.Tar; +const BIN_ARCHIVE_URL = `https://github.com/${repo}/releases/download/${tag}/foundry_${version}_${platform}_${arch}.${ext}`; +const BIN_DIR = join(cwd(), 'node_modules', '.bin'); + +const url = new URL(BIN_ARCHIVE_URL); +const cacheKey = createHash('sha256') + .update(`${BIN_ARCHIVE_URL}-${bins}`) + .digest('hex'); +const cachePath = join(CACHE_DIR, cacheKey); + +// check the cache, if the cache dir exists we assume the correct files do, too +let downloadedBinaries: Dir; +try { + say(`checking cache`); + downloadedBinaries = await opendir(cachePath); + say(`found binaries in cache`); +} catch (e: unknown) { + say(`binaries not in cache`); + if ((e as NodeJS.ErrnoException).code === 'ENOENT') { + say(`installing from ${url.toString()}`); + // directory doesn't exist, download and extract + await extractFrom(url, binaries, cachePath); + downloadedBinaries = await opendir(cachePath); + } else { + throw e; + } +} +for await (const file of downloadedBinaries) { + if (!file.isFile()) continue; + const target = join(file.parentPath, file.name); + const path = join(BIN_DIR, relative(cachePath, target)); + console.log("TARGET ===================", target); + console.log("PATH ===================", path); + // clean up any existing files or symlinks + await unlink(path).catch(noop); + try { + // create new symlink + await symlink(target, path); + } catch (e) { + if (!(isCodedError(e) && ['EPERM', 'EXDEV'].includes(e.code))) { + throw e; + } + // symlinking can fail if its a cross-device/filesystem link, or for + // permissions reasons, so we'll just copy the file instead + await copyFile(target, path); + } + // check that it works by logging the version + say(`installed - ${getVersion(target)}`); +} + +say('done!'); diff --git a/test/helpers/foundry/helpers.mts b/test/helpers/foundry/helpers.mts new file mode 100644 index 000000000000..5b2355761de0 --- /dev/null +++ b/test/helpers/foundry/helpers.mts @@ -0,0 +1,391 @@ +import { ok } from 'node:assert'; +import { execSync } from 'node:child_process'; +import { createWriteStream } from 'node:fs'; +import { rename, mkdir, rm } from 'node:fs/promises'; +import { + Agent as HttpAgent, + request as httpRequest, + type IncomingMessage, +} from 'node:http'; +import { Agent as HttpsAgent, request as httpsRequest } from 'node:https'; +import { join, basename, extname, relative } from 'node:path'; +import { argv, stdout } from 'node:process'; +import { arch, platform } from 'node:os'; +import { Stream } from 'node:stream'; +import { pipeline } from 'node:stream/promises'; +import { extract as extractTar } from 'tar'; +import { Source, Open as Unzip } from 'unzipper'; +import { InferredOptionTypes } from 'yargs'; +import yargs from 'yargs/yargs'; + +export function noop() {} + +export enum BinFormat { + Zip = 'zip', + Tar = 'tar.gz', +} + +export enum Platform { + Windows = 'win32', + Linux = 'linux', + Mac = 'darwin', +} + +export enum Binary { + Anvil = 'anvil', + Forge = 'forge', + Cast = 'cast', + Chisel = 'chisel', +} + +type Options = ReturnType; +type OptionsKeys = keyof Options; +type ParsedOptions = { + [key in OptionsKeys]: InferredOptionTypes[key]; +}; + +export function parseArgs(args: string[] = argv.slice(2)) { + const { $0, _, ...parsed } = yargs() + // Ensure unrecognized commands/options are reported as errors. + .strict() + // disable yargs's version, as it doesn't make sense here + .version(false) + // use the scriptName in `--help` output + .scriptName('yarn foundryup') + // wrap output at a maximum of 120 characters or `stdout.columns` + .wrap(Math.min(120, stdout.columns)) + .parserConfiguration({ + 'strip-aliased': true, + 'strip-dashed': true, + }) + // enable ENV parsing, which allows the user to specify foundryup options + // via environment variables prefixed with `FOUNDRYUP_` + .env('FOUNDRYUP') + .command(['$0', 'install'], 'Install foundry binaries', getOptions()) + .command('cache', '', (yargs) => { + yargs.command('clean', 'Remove the shared cache files').demandCommand(); + }) + .parseSync(args); + + const command = _.join(' '); + switch (command) { + case 'cache clean': + return { + command, + } as const; + case '': + case 'install': + return { + command: 'install', + options: parsed as ParsedOptions, + } as const; + } + throw new Error(`Unknown command: '${command}'`); +} + +function getOptions() { + return { + binaries: { + alias: 'b', + type: 'array' as const, + multiple: true, + description: 'Specify the binaries to install', + default: [Binary.Anvil], + choices: Object.values(Binary) as Binary[], + }, + repo: { + alias: 'r', + description: 'Specify the repository', + default: 'foundry-rs/foundry', + }, + version: { + alias: 'v', + description: 'Specify the version', + default: 'nightly', + coerce: ( + rawVersion: string, + ): { version: 'nightly' | `v${string}`; tag: string } => { + if (/^nightly/u.test(rawVersion)) { + return { version: 'nightly', tag: rawVersion }; + // we don't validate the version much, we just trust the user + } else if (/^\d/u.test(rawVersion)) { + return { version: `v${rawVersion}`, tag: rawVersion }; + } + throw new Error('Invalid version'); + }, + }, + arch: { + alias: 'a', + description: 'Specify the architecture', + default: getSystemArch(), + choices: ['amd64', 'arm64'] as const, + }, + platform: { + alias: 'p', + description: 'Specify the platform', + // if `osPlatform` is not a supported Platform yargs will throw an error + default: platform() as Platform, + choices: Object.values(Platform) as Platform[], + }, + }; +} + +function getSystemArch(): 'amd64' | 'arm64' { + const architecture = arch(); + if (architecture.startsWith('arm')) { + // if `arm*`, use `arm64` + return 'arm64'; + } else if (architecture === 'x64') { + // if `x64`, it _might_ be amd64 running via Rosetta on Apple Silicon + // (arm64). we can check this by running `sysctl.proc_translated` and + // checking the output; `1` === `arm64`. This can happen if the user is + // running an amd64 version of Node on Apple Silicon. We want to use the + // binaries native to the system for better performance. + try { + if (execSync('sysctl -n sysctl.proc_translated 2>/dev/null')[0] === 1) { + return 'arm64'; + } + } catch {} // if `sysctl` check fails, assume native `amd64` + } + + return 'amd64'; // Default for all other architectures +} + +export function printBanner() { + console.log(` +.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx + + ╔═╗ ╔═╗ ╦ ╦ ╔╗╔ ╔╦╗ ╦═╗ ╦ ╦ Portable and modular toolkit + ╠╣ ║ ║ ║ ║ ║║║ ║║ ╠╦╝ ╚╦╝ for Ethereum Application Development + ╚ ╚═╝ ╚═╝ ╝╚╝ ═╩╝ ╩╚═ ╩ written in Rust. + +.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx + +Repo : https://github.com/foundry-rs/ +Book : https://book.getfoundry.sh/ +Chat : https://t.me/foundry_rs/ +Support : https://t.me/foundry_support/ +Contribute : https://github.com/orgs/foundry-rs/projects/2/ + +.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx +`); +} + +/** + * Log a message to the console. + * + * @param message The message to log + */ +export function say(message: string) { + console.log(`[foundryup] ${message}`); +} + +/** + * Extracts the binaries from the given URL and writes them to the destination. + * + * @param url The URL of the archive to extract the binaries from + * @param binaries The list of binaries to extract + * @param dir The destination directory + * @returns The list of binaries extracted + */ +export async function extractFrom(url: URL, binaries: string[], dir: string) { + const extract = url.pathname.toLowerCase().endsWith(BinFormat.Tar) + ? extractFromTar + : extractFromZip; + // write all files to a temporary directory first, then rename to the final + // destination to avoid accidental partial extraction. We don't use + // `os.tmpdir` for this because `rename` will fail if the directories are on + // different file systems. + const tempDir = dir + '.downloading'; + const rmOpts = { recursive: true, maxRetries: 3, force: true }; + try { + // clean up any previous in-progress downloads + await rm(tempDir, rmOpts); + // make the temporary directory to extract the binaries to + await mkdir(tempDir, { recursive: true }); + const downloads = await extract(url, binaries, tempDir); + ok(downloads.length === binaries.length, 'Failed to extract all binaries'); + + // everything has been extracted; move the files to their final destination + await rename(tempDir, dir); + // return the list of extracted binaries + return downloads.map((file) => join(dir, relative(tempDir, file))); + } catch (e) { + // if things fail for any reason try to clean up a bit. it is very important + // to not leave `dir` behind, as its existence is a signal that the binaries + // are installed. + await Promise.all([rm(tempDir, rmOpts), rm(dir, rmOpts)]).catch(noop); + throw e; + } +} + +/** + * Extracts the binaries from a tar archive. + * + * @param url The URL of the archive to extract the binaries from + * @param binaries The list of binaries to extract + * @param dir The destination directory + * @returns The list of binaries extracted + */ +async function extractFromTar(url: URL, binaries: string[], dir: string) { + const downloads: string[] = []; + await pipeline( + startDownload(url), + extractTar({ cwd: dir }, binaries).on('entry', ({ absolute }) => + downloads.push(absolute), + ), + ); + return downloads; +} + +/** + * Extracts the binaries from a zip archive. + * + * @param url The URL of the archive to extract the binaries from + * @param binaries The list of binaries to extract + * @param dir The destination directory + * @returns The list of binaries extracted + */ +async function extractFromZip(url: URL, binaries: string[], dir: string) { + const agent = new (url.protocol === 'http:' ? HttpAgent : HttpsAgent)({ + keepAlive: true, + }); + const source: Source = { + async size() { + const download = startDownload(url, { agent, method: 'HEAD' }); + const response = await download.response(); + const contentLength = response.headers['content-length']; + return contentLength ? parseInt(contentLength, 10) : 0; + }, + stream(offset: number, bytes: number) { + const options = { + agent, + headers: { + range: `bytes=${offset}-${bytes ? offset + bytes : ''}`, + }, + }; + return startDownload(url, options); + }, + }; + + const { files } = await Unzip.custom(source); + const filtered = files.filter(({ path }) => + binaries.includes(basename(path, extname(path))), + ); + return await Promise.all( + filtered.map(async ({ stream, path }) => { + const dest = join(dir, path); + await pipeline(stream(), createWriteStream(dest)); + return dest; + }), + ); +} + +type DownloadOptions = { + method?: 'GET' | 'HEAD'; + headers?: Record; + agent?: HttpsAgent | HttpAgent; + maxRedirects?: number; +}; + +/** + * Starts a download from the given URL. + * + * @param url The URL to download from + * @param options The download options + * @param redirects The number of redirects that have occurred + * @returns A stream of the download + */ +function startDownload( + url: URL, + options: DownloadOptions = {}, + redirects: number = 0, +): DownloadStream { + const MAX_REDIRECTS = options.maxRedirects ?? 5; + const request = url.protocol === 'http:' ? httpRequest : httpsRequest; + const stream = new DownloadStream(); + request(url, options, async (response) => { + stream.once('close', () => { + response.destroy(); + }); + + const { statusCode, statusMessage, headers } = response; + // handle redirects + if ( + statusCode && + statusCode >= 300 && + statusCode < 400 && + headers.location + ) { + if (redirects >= MAX_REDIRECTS) { + stream.emit('error', new Error('Too many redirects')); + response.destroy(); + } else { + // note: we don't emit a response until we're done redirecting, because + // handlers only expect it to be emitted once. + await pipeline( + startDownload(new URL(headers.location, url), options, redirects + 1) + // remit the response event to the stream + .once('response', stream.emit.bind(stream, 'response')), + stream, + ).catch(stream.emit.bind(stream, 'error')); + response.destroy(); + } + } + // check for HTTP errors + else if (!statusCode || statusCode < 200 || statusCode >= 300) { + stream.emit( + 'error', + new Error( + `Request to ${url} failed. Status Code: ${statusCode} - ${statusMessage}`, + ), + ); + response.destroy(); + } else { + // resolve with response stream + stream.emit('response', response); + + response.once('error', stream.emit.bind(stream, 'error')); + await pipeline(response, stream).catch(stream.emit.bind(stream, 'error')); + } + }) + .once('error', stream.emit.bind(stream, 'error')) + .end(); + return stream; +} + +export class DownloadStream extends Stream.PassThrough { + async response(): Promise { + return new Promise((resolve, reject) => { + this.once('response', resolve); + this.once('error', reject); + }); + } +} + +/** + * Get the version of the binary at the given path. + * + * @param binPath + * @returns The `--version` reported by the binary + * @throws If the binary fails to report its version + */ +export function getVersion(binPath: string): Buffer { + try { + return execSync(`${binPath} --version`).subarray(0, -1); // ignore newline + } catch (error: unknown) { + const msg = `Failed to get version for ${binPath}`; + if (error instanceof Error) { + throw new Error(`${msg}: ${error.message}`); + } + throw new Error(msg); + } +} + +export function isCodedError( + error: unknown, +): error is Error & { code: string } { + return ( + error instanceof Error && 'code' in error && typeof error.code === 'string' + ); +} diff --git a/test/helpers/foundry/tsconfig.json b/test/helpers/foundry/tsconfig.json new file mode 100644 index 000000000000..6af8922ce66e --- /dev/null +++ b/test/helpers/foundry/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "Node" + }, + "extends": "../../../tsconfig.json" +} diff --git a/types/unzipper.d.ts b/types/unzipper.d.ts new file mode 100644 index 000000000000..fd665df66c73 --- /dev/null +++ b/types/unzipper.d.ts @@ -0,0 +1,17 @@ +import 'unzipper'; + +declare module 'unzipper' { + type Source = { + stream: (offset: number, length: number) => NodeJS.ReadableStream; + size: () => Promise; + }; + type Options = { + tailSize?: number; + }; + namespace Open { + function custom( + source: Source, + options?: Options, + ): Promise; + } +} diff --git a/yarn.lock b/yarn.lock index 88e84e6d37b1..13997b64e44a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,6 +50,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.10.0": + version: 1.10.0 + resolution: "@adraffy/ens-normalize@npm:1.10.0" + checksum: 10/5cdb5d2a9c9f8c0a71a7bb830967da0069cae1f1235cd41ae11147e4000f368f6958386e622cd4d52bf45c1ed3f8275056b387cba28902b83354e40ff323ecde + languageName: node + linkType: hard + "@adraffy/ens-normalize@npm:1.10.1": version: 1.10.1 resolution: "@adraffy/ens-normalize@npm:1.10.1" @@ -3677,6 +3684,15 @@ __metadata: languageName: node linkType: hard +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10/4412e9e6713c89c1e66d80bb0bb5a2a93192f10477623a27d08f228ba0316bb880affabc5bfe7f838f58a34d26c2c190da726e576cdfc18c49a72e89adabdcf5 + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.0.0 resolution: "@istanbuljs/load-nyc-config@npm:1.0.0" @@ -6929,7 +6945,16 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.4.2, @noble/curves@npm:^1.2.0, @noble/curves@npm:^1.4.2, @noble/curves@npm:~1.4.0": +"@noble/curves@npm:1.4.0": + version: 1.4.0 + resolution: "@noble/curves@npm:1.4.0" + dependencies: + "@noble/hashes": "npm:1.4.0" + checksum: 10/b21b30a36ff02bfcc0f5e6163d245cdbaf7f640511fff97ccf83fc207ee79cfd91584b4d97977374de04cb118a55eb63a7964c82596a64162bbc42bc685ae6d9 + languageName: node + linkType: hard + +"@noble/curves@npm:1.4.2, @noble/curves@npm:^1.2.0, @noble/curves@npm:^1.4.0, @noble/curves@npm:^1.4.2, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" dependencies: @@ -6952,7 +6977,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:^1.4.0": +"@noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.5.0": version: 1.5.0 resolution: "@noble/hashes@npm:1.5.0" checksum: 10/da7fc7af52af7afcf59810a7eea6155075464ff462ffda2572dc6d57d53e2669b1ea2ec774e814f6273f1697e567f28d36823776c9bf7068cba2a2855140f26e @@ -8090,7 +8115,7 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:^1.0.0, @scure/base@npm:^1.1.1, @scure/base@npm:^1.1.3, @scure/base@npm:~1.1.3, @scure/base@npm:~1.1.6": +"@scure/base@npm:^1.0.0, @scure/base@npm:^1.1.1, @scure/base@npm:^1.1.3, @scure/base@npm:~1.1.3, @scure/base@npm:~1.1.6, @scure/base@npm:~1.1.8": version: 1.1.9 resolution: "@scure/base@npm:1.1.9" checksum: 10/f0ab7f687bbcdee2a01377fe3cd808bf63977999672751295b6a92625d5322f4754a96d40f6bd579bc367aad48ecf8a4e6d0390e70296e6ded1076f52adb16bb @@ -8118,6 +8143,16 @@ __metadata: languageName: node linkType: hard +"@scure/bip39@npm:1.4.0": + version: 1.4.0 + resolution: "@scure/bip39@npm:1.4.0" + dependencies: + "@noble/hashes": "npm:~1.5.0" + "@scure/base": "npm:~1.1.8" + checksum: 10/f86e0e79768c95bc684ed6de92892b1a6f228db0f8fab836f091c0ec0f6d1e291b8c4391cfbeaa9ea83f41045613535b1940cd10e7d780a5b73db163b1e7f151 + languageName: node + linkType: hard + "@segment/loosely-validate-event@npm:^2.0.0": version: 2.0.0 resolution: "@segment/loosely-validate-event@npm:2.0.0" @@ -11542,6 +11577,15 @@ __metadata: languageName: node linkType: hard +"@types/unzipper@npm:^0": + version: 0.10.10 + resolution: "@types/unzipper@npm:0.10.10" + dependencies: + "@types/node": "npm:*" + checksum: 10/4ba5f6c4c5a892f5f5ce7724a4c3f2ea772a29043e296a28b725162ffff8fb25d2d0995c5536705e13bfbde7765d085f0da5408b25946457d050ac4b75aaefee + languageName: node + linkType: hard + "@types/uuid@npm:^10.0.0": version: 10.0.0 resolution: "@types/uuid@npm:10.0.0" @@ -11932,6 +11976,18 @@ __metadata: languageName: node linkType: hard +"@viem/anvil@npm:^0.0.10": + version: 0.0.10 + resolution: "@viem/anvil@npm:0.0.10" + dependencies: + execa: "npm:^7.1.1" + get-port: "npm:^6.1.2" + http-proxy: "npm:^1.18.1" + ws: "npm:^8.13.0" + checksum: 10/8e688b9b2a7f4f90b5b7d9669bc73b20b37f05cd09646bce0fd934e582ac067bf8006c23f49d7dc0af4aa4261d863fe52730372afc0e377931da78deb1eb68bf + languageName: node + linkType: hard + "@vue/compiler-core@npm:3.1.4": version: 3.1.4 resolution: "@vue/compiler-core@npm:3.1.4" @@ -12338,6 +12394,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:1.0.5": + version: 1.0.5 + resolution: "abitype@npm:1.0.5" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: 10/1acd0d9687945dd78442b71bd84ff3b9dceae27d15f0d8b14b16554a0c8c9518eeb971ff8e94d507f4d9f05a8a8b91eb8fafd735eaecebac37d5c5a4aac06d8e + languageName: node + linkType: hard + "abort-controller@npm:3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -14073,7 +14144,7 @@ __metadata: languageName: node linkType: hard -"bluebird@npm:^3.7.2": +"bluebird@npm:^3.7.2, bluebird@npm:~3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" checksum: 10/007c7bad22c5d799c8dd49c85b47d012a1fe3045be57447721e6afbd1d5be43237af1db62e26cb9b0d9ba812d2e4ca3bac82f6d7e016b6b88de06ee25ceb96e7 @@ -15194,6 +15265,13 @@ __metadata: languageName: node linkType: hard +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10/b63cb1f73d171d140a2ed8154ee6566c8ab775d3196b0e03a2a94b5f6a0ce7777ee5685ca56849403c8d17bd457a6540672f9a60696a6137c7a409097495b82c + languageName: node + linkType: hard + "chrome-trace-event@npm:^1.0.2": version: 1.0.2 resolution: "chrome-trace-event@npm:1.0.2" @@ -17680,7 +17758,7 @@ __metadata: languageName: node linkType: hard -"duplexer2@npm:^0.1.2, duplexer2@npm:~0.1.0, duplexer2@npm:~0.1.2": +"duplexer2@npm:^0.1.2, duplexer2@npm:~0.1.0, duplexer2@npm:~0.1.2, duplexer2@npm:~0.1.4": version: 0.1.4 resolution: "duplexer2@npm:0.1.4" dependencies: @@ -19370,6 +19448,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:^7.1.1": + version: 7.2.0 + resolution: "execa@npm:7.2.0" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.1" + human-signals: "npm:^4.3.0" + is-stream: "npm:^3.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^5.1.0" + onetime: "npm:^6.0.0" + signal-exit: "npm:^3.0.7" + strip-final-newline: "npm:^3.0.0" + checksum: 10/473feff60f9d4dbe799225948de48b5158c1723021d19c4b982afe37bcd111ae84e1b4c9dfe967fae5101b0894b1a62e4dd564a286dfa3e46d7b0cfdbf7fe62b + languageName: node + linkType: hard + "execall@npm:^2.0.0": version: 2.0.0 resolution: "execall@npm:2.0.0" @@ -20506,7 +20601,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.1.0": +"fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": version: 11.2.0 resolution: "fs-extra@npm:11.2.0" dependencies: @@ -20815,6 +20910,13 @@ __metadata: languageName: node linkType: hard +"get-port@npm:^6.1.2": + version: 6.1.2 + resolution: "get-port@npm:6.1.2" + checksum: 10/e3c3d591492a11393455ef220f24c812a28f7da56ec3e4a2512d931a1f196d42850b50ac6138349a44622eda6dc3c0ccd8495cd91376d968e2d9e6f6f849e0a9 + languageName: node + linkType: hard + "get-stdin@npm:^8.0.0": version: 8.0.0 resolution: "get-stdin@npm:8.0.0" @@ -20847,7 +20949,7 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^6.0.0": +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": version: 6.0.1 resolution: "get-stream@npm:6.0.1" checksum: 10/781266d29725f35c59f1d214aedc92b0ae855800a980800e2923b3fbc4e56b3cb6e462c42e09a1cf1a00c64e056a78fa407cbe06c7c92b7e5cd49b4b85c2a497 @@ -21352,7 +21454,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.0.0, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.0.0, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -22408,6 +22510,13 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^4.3.0": + version: 4.3.1 + resolution: "human-signals@npm:4.3.1" + checksum: 10/fa59894c358fe9f2b5549be2fb083661d5e1dff618d3ac70a49ca73495a72e873fbf6c0878561478e521e17d498292746ee391791db95ffe5747bfb5aef8765b + languageName: node + linkType: hard + "human-standard-token-abi@npm:^2.0.0": version: 2.0.0 resolution: "human-standard-token-abi@npm:2.0.0" @@ -23535,6 +23644,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 10/172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16 + languageName: node + linkType: hard + "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -23769,6 +23885,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.4": + version: 1.0.4 + resolution: "isows@npm:1.0.4" + peerDependencies: + ws: "*" + checksum: 10/a3ee62e3d6216abb3adeeb2a551fe2e7835eac87b05a6ecc3e7739259bf5f8e83290501f49e26137390c8093f207fc3378d4a7653aab76ad7bbab4b2dba9c5b9 + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.0.0-alpha.1, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -26979,6 +27104,7 @@ __metadata: "@types/serve-handler": "npm:^6.1.4" "@types/sinon": "npm:^10.0.13" "@types/sprintf-js": "npm:^1" + "@types/unzipper": "npm:^0" "@types/w3c-web-hid": "npm:^1.0.3" "@types/watchify": "npm:^3.11.1" "@types/webextension-polyfill": "npm:^0.10.4" @@ -26987,6 +27113,7 @@ __metadata: "@types/yargs-parser": "npm:^21.0.3" "@typescript-eslint/eslint-plugin": "npm:^7.10.0" "@typescript-eslint/parser": "npm:^7.10.0" + "@viem/anvil": "npm:^0.0.10" "@welldone-software/why-did-you-render": "npm:^8.0.3" "@whitespace/storybook-addon-html": "npm:^5.1.6" "@zxing/browser": "npm:^0.1.4" @@ -27175,6 +27302,7 @@ __metadata: style-loader: "npm:^0.21.0" stylelint: "npm:^13.6.1" superstruct: "npm:^1.0.3" + tar: "npm:^7.4.3" terser: "npm:^5.7.0" terser-webpack-plugin: "npm:^5.3.10" through2: "npm:^4.0.2" @@ -27184,8 +27312,10 @@ __metadata: ttest: "npm:^2.1.1" typescript: "npm:~5.4.5" unicode-confusables: "npm:^0.1.1" + unzipper: "npm:^0.12.3" uri-js: "npm:^4.4.1" uuid: "npm:^8.3.2" + viem: "npm:^2.21.8" vinyl: "npm:^2.2.1" vinyl-buffer: "npm:^1.0.1" vinyl-source-stream: "npm:^2.0.0" @@ -27686,6 +27816,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 10/995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56 + languageName: node + linkType: hard + "mimic-response@npm:^1.0.0, mimic-response@npm:^1.0.1": version: 1.0.1 resolution: "mimic-response@npm:1.0.1" @@ -27854,7 +27991,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 @@ -27871,6 +28008,16 @@ __metadata: languageName: node linkType: hard +"minizlib@npm:^3.0.1": + version: 3.0.1 + resolution: "minizlib@npm:3.0.1" + dependencies: + minipass: "npm:^7.0.4" + rimraf: "npm:^5.0.5" + checksum: 10/622cb85f51e5c206a080a62d20db0d7b4066f308cb6ce82a9644da112367c3416ae7062017e631eb7ac8588191cfa4a9a279b8651c399265202b298e98c4acef + languageName: node + linkType: hard + "mitt@npm:^3.0.1": version: 3.0.1 resolution: "mitt@npm:3.0.1" @@ -27925,7 +28072,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^3.0.0": +"mkdirp@npm:^3.0.0, mkdirp@npm:^3.0.1": version: 3.0.1 resolution: "mkdirp@npm:3.0.1" bin: @@ -28731,6 +28878,15 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^5.1.0": + version: 5.3.0 + resolution: "npm-run-path@npm:5.3.0" + dependencies: + path-key: "npm:^4.0.0" + checksum: 10/ae8e7a89da9594fb9c308f6555c73f618152340dcaae423e5fb3620026fefbec463618a8b761920382d666fa7a2d8d240b6fe320e8a6cdd54dc3687e2b659d25 + languageName: node + linkType: hard + "nth-check@npm:^2.0.1": version: 2.0.1 resolution: "nth-check@npm:2.0.1" @@ -29047,6 +29203,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: "npm:^4.0.0" + checksum: 10/0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 + languageName: node + linkType: hard + "only@npm:~0.0.2": version: 0.0.2 resolution: "only@npm:0.0.2" @@ -29686,6 +29851,13 @@ __metadata: languageName: node linkType: hard +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 10/8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7 + languageName: node + linkType: hard + "path-parse@npm:^1.0.7": version: 1.0.7 resolution: "path-parse@npm:1.0.7" @@ -34882,6 +35054,13 @@ __metadata: languageName: node linkType: hard +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 10/23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050 + languageName: node + linkType: hard + "strip-hex-prefix@npm:1.0.0": version: 1.0.0 resolution: "strip-hex-prefix@npm:1.0.0" @@ -35355,6 +35534,20 @@ __metadata: languageName: node linkType: hard +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: 10/12a2a4fc6dee23e07cc47f1aeb3a14a1afd3f16397e1350036a8f4cdfee8dcac7ef5978337a4e7b2ac2c27a9a6d46388fc2088ea7c80cb6878c814b1425f8ecf + languageName: node + linkType: hard + "telejson@npm:^7.2.0": version: 7.2.0 resolution: "telejson@npm:7.2.0" @@ -36750,6 +36943,19 @@ __metadata: languageName: node linkType: hard +"unzipper@npm:^0.12.3": + version: 0.12.3 + resolution: "unzipper@npm:0.12.3" + dependencies: + bluebird: "npm:~3.7.2" + duplexer2: "npm:~0.1.4" + fs-extra: "npm:^11.2.0" + graceful-fs: "npm:^4.2.2" + node-int64: "npm:^0.4.0" + checksum: 10/b210c421308e1913e01b54faad4ae79e758c674311892414a0697acacba9f82fa0051b677faa77e62fab422eef928c858f2d5cda9ddb47a2f3db95b0e9b36359 + languageName: node + linkType: hard + "upath@npm:2.0.1": version: 2.0.1 resolution: "upath@npm:2.0.1" @@ -37305,6 +37511,28 @@ __metadata: languageName: node linkType: hard +"viem@npm:^2.21.8": + version: 2.21.8 + resolution: "viem@npm:2.21.8" + dependencies: + "@adraffy/ens-normalize": "npm:1.10.0" + "@noble/curves": "npm:1.4.0" + "@noble/hashes": "npm:1.4.0" + "@scure/bip32": "npm:1.4.0" + "@scure/bip39": "npm:1.4.0" + abitype: "npm:1.0.5" + isows: "npm:1.0.4" + webauthn-p256: "npm:0.0.5" + ws: "npm:8.17.1" + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/158976108ed04de22cd01e2897b9fe4961c0f07c04eb8f3ab87f2f73aabc59e56cf83b49979508c90c45634cb1806dd2144428c9e27c19b674461211b8fa2959 + languageName: node + linkType: hard + "vinyl-buffer@npm:^1.0.1": version: 1.0.1 resolution: "vinyl-buffer@npm:1.0.1" @@ -37626,6 +37854,16 @@ __metadata: languageName: node linkType: hard +"webauthn-p256@npm:0.0.5": + version: 0.0.5 + resolution: "webauthn-p256@npm:0.0.5" + dependencies: + "@noble/curves": "npm:^1.4.0" + "@noble/hashes": "npm:^1.4.0" + checksum: 10/6bf5d1857dfb99ecb3b318af06eddea874c10135e6ebb9f046270f5cbb162933bc6caf77aedb033e14c09971dda544a5fb367ac545e4ec8001b309ba517555cf + languageName: node + linkType: hard + "webextension-polyfill@npm:>=0.10.0 <1.0, webextension-polyfill@npm:^0.12.0": version: 0.12.0 resolution: "webextension-polyfill@npm:0.12.0" @@ -38205,7 +38443,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:>=8.14.2, ws@npm:^8.0.0, ws@npm:^8.11.0, ws@npm:^8.16.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.2.3, ws@npm:^8.5.0, ws@npm:^8.8.0": +"ws@npm:*, ws@npm:>=8.14.2, ws@npm:^8.0.0, ws@npm:^8.11.0, ws@npm:^8.13.0, ws@npm:^8.16.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.2.3, ws@npm:^8.5.0, ws@npm:^8.8.0": version: 8.18.0 resolution: "ws@npm:8.18.0" peerDependencies: @@ -38357,6 +38595,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10/1884d272d485845ad04759a255c71775db0fac56308764b4c77ea56a20d56679fad340213054c8c9c9c26fcfd4c4b2a90df993b7e0aaf3cdb73c618d1d1a802a + languageName: node + linkType: hard + "yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": version: 1.10.2 resolution: "yaml@npm:1.10.2"