From cc5f4c6ef759a661f5c8900462c091099d848598 Mon Sep 17 00:00:00 2001 From: Wodann Date: Fri, 26 Jul 2024 16:06:05 +0000 Subject: [PATCH] fix: prevent crash when returning large JSON responses --- .changeset/two-starfishes-obey.md | 5 ++ crates/edr_napi/index.d.ts | 3 +- crates/edr_napi/package.json | 5 +- crates/edr_napi/src/provider.rs | 37 +++++++--- crates/edr_napi/test/helpers.ts | 17 +++++ crates/edr_napi/test/issues.ts | 109 +++++++++++++++++++++++++++++ crates/edr_napi/test/provider.ts | 25 +++---- crates/tools/js/benchmark/index.js | 28 ++++---- package.json | 3 + patches/hardhat@2.22.7.patch | 77 ++++++++++++++++++++ pnpm-lock.yaml | 36 +++++++--- 11 files changed, 293 insertions(+), 52 deletions(-) create mode 100644 .changeset/two-starfishes-obey.md create mode 100644 crates/edr_napi/test/issues.ts create mode 100644 patches/hardhat@2.22.7.patch diff --git a/.changeset/two-starfishes-obey.md b/.changeset/two-starfishes-obey.md new file mode 100644 index 000000000..629d241f5 --- /dev/null +++ b/.changeset/two-starfishes-obey.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/edr": patch +--- + +Fixed crash when returning large JSON responses diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 621ee25fd..d19f66225 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -418,7 +418,8 @@ export class Provider { setVerboseTracing(verboseTracing: boolean): void } export class Response { - get json(): string + /** Returns the response data as a JSON string or a JSON object. */ + get data(): string | any get solidityTrace(): RawTrace | null get traces(): Array } diff --git a/crates/edr_napi/package.json b/crates/edr_napi/package.json index 32953ad90..a460e5bea 100644 --- a/crates/edr_napi/package.json +++ b/crates/edr_napi/package.json @@ -38,6 +38,7 @@ "@types/node": "^18.0.0", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", + "json-stream-stringify": "^3.1.4", "mocha": "^10.0.0", "ts-node": "^10.8.0", "typescript": "~4.5.2" @@ -55,8 +56,8 @@ "universal": "napi universal", "version": "napi version", "pretest": "pnpm build", - "test": "pnpm tsc && mocha --recursive \"test/**/*.ts\"", - "testNoBuild": "pnpm tsc && mocha --recursive \"test/**/*.ts\"", + "test": "pnpm tsc && node --max-old-space-size=8192 node_modules/mocha/bin/_mocha --recursive \"test/**/*.ts\"", + "testNoBuild": "pnpm tsc && node --max-old-space-size=8192 node_modules/mocha/bin/_mocha --recursive \"test/**/*.ts\"", "clean": "rm -rf @nomicfoundation/edr.node" } } diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 7116b5091..062fb3fa4 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use edr_provider::{time::CurrentTime, InvalidRequestReason}; use edr_rpc_eth::jsonrpc; -use napi::{tokio::runtime, Env, JsFunction, JsObject, Status}; +use napi::{tokio::runtime, Either, Env, JsFunction, JsObject, Status}; use napi_derive::napi; use self::config::ProviderConfig; @@ -120,9 +120,9 @@ impl Provider { format!("Invalid JSON `{json_request}` due to: {error}"), ) }) - .map(|json_response| Response { + .map(|json| Response { solidity_trace: None, - json: json_response, + data: Either::A(json), traces: Vec::new(), }); } @@ -168,10 +168,25 @@ impl Provider { let response = jsonrpc::ResponseData::from(response.map(|response| response.result)); serde_json::to_string(&response) - .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) - .map(|json_response| Response { + .and_then(|json| { + // We experimentally determined that 500_000_000 was the maximum string length + // that can be returned without causing the error: + // + // > Failed to convert rust `String` into napi `string` + // + // To be safe, we're limiting string lengths to half of that. + const MAX_STRING_LENGTH: usize = 250_000_000; + + if json.len() <= MAX_STRING_LENGTH { + Ok(Either::A(json)) + } else { + serde_json::to_value(response).map(Either::B) + } + }) + .map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string())) + .map(|data| Response { solidity_trace, - json: json_response, + data, traces: traces.into_iter().map(Arc::new).collect(), }) } @@ -209,7 +224,10 @@ impl Provider { #[napi] pub struct Response { - json: String, + // N-API is known to be slow when marshalling `serde_json::Value`s, so we try to return a + // `String`. If the object is too large to be represented as a `String`, we return a `Buffer` + // instead. + data: Either, /// When a transaction fails to execute, the provider returns a trace of the /// transaction. solidity_trace: Option>, @@ -219,9 +237,10 @@ pub struct Response { #[napi] impl Response { + /// Returns the response data as a JSON string or a JSON object. #[napi(getter)] - pub fn json(&self) -> String { - self.json.clone() + pub fn data(&self) -> Either { + self.data.clone() } #[napi(getter)] diff --git a/crates/edr_napi/test/helpers.ts b/crates/edr_napi/test/helpers.ts index 537c93811..ba2b1aa81 100644 --- a/crates/edr_napi/test/helpers.ts +++ b/crates/edr_napi/test/helpers.ts @@ -1,5 +1,22 @@ import { TracingMessage, TracingMessageResult, TracingStep } from ".."; +function getEnv(key: string): string | undefined { + const variable = process.env[key]; + if (variable === undefined || variable === "") { + return undefined; + } + + const trimmed = variable.trim(); + + return trimmed.length === 0 ? undefined : trimmed; +} + +export const ALCHEMY_URL = getEnv("ALCHEMY_URL"); + +export function isCI(): boolean { + return getEnv("CI") === "true"; +} + /** * Given a trace, return only its steps. */ diff --git a/crates/edr_napi/test/issues.ts b/crates/edr_napi/test/issues.ts new file mode 100644 index 000000000..fa3fe82aa --- /dev/null +++ b/crates/edr_napi/test/issues.ts @@ -0,0 +1,109 @@ +import chai, { assert } from "chai"; +import { JsonStreamStringify } from "json-stream-stringify"; + +import { + ContractAndFunctionName, + EdrContext, + MineOrdering, + Provider, + SpecId, + SubscriptionEvent, +} from ".."; +import { ALCHEMY_URL, isCI } from "./helpers"; + +describe("Provider", () => { + const context = new EdrContext(); + const providerConfig = { + allowBlocksWithSameTimestamp: false, + allowUnlimitedContractSize: true, + bailOnCallFailure: false, + bailOnTransactionFailure: false, + blockGasLimit: 300_000_000n, + chainId: 1n, + chains: [], + coinbase: Buffer.from("0000000000000000000000000000000000000000", "hex"), + enableRip7212: false, + genesisAccounts: [ + { + secretKey: + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + balance: 1000n * 10n ** 18n, + }, + ], + hardfork: SpecId.Latest, + initialBlobGas: { + gasUsed: 0n, + excessGas: 0n, + }, + initialParentBeaconBlockRoot: Buffer.from( + "0000000000000000000000000000000000000000000000000000000000000000", + "hex", + ), + minGasPrice: 0n, + mining: { + autoMine: true, + memPool: { + order: MineOrdering.Priority, + }, + }, + networkId: 123n, + }; + + const loggerConfig = { + enable: false, + decodeConsoleLogInputsCallback: (inputs: Buffer[]): string[] => { + return []; + }, + getContractAndFunctionNameCallback: ( + _code: Buffer, + _calldata?: Buffer, + ): ContractAndFunctionName => { + return { + contractName: "", + }; + }, + printLineCallback: (message: string, replace: boolean) => {}, + }; + + it("issue 543", async function () { + if (ALCHEMY_URL === undefined || !isCI()) { + this.skip(); + } + + // This test is slow because the debug_traceTransaction is performed on a large transaction. + this.timeout(240_000); + + const provider = await Provider.withConfig( + context, + { + fork: { + jsonRpcUrl: ALCHEMY_URL, + }, + initialBaseFeePerGas: 0n, + ...providerConfig, + }, + loggerConfig, + (_event: SubscriptionEvent) => {}, + ); + + const debugTraceTransaction = `{ + "jsonrpc": "2.0", + "method": "debug_traceTransaction", + "params": ["0x7e460f200343e5ab6653a8857cc5ef798e3f5bea6a517b156f90c77ef311a57c"], + "id": 1 + }`; + + const response = await provider.handleRequest(debugTraceTransaction); + + let responseData; + + if (typeof response.data === "string") { + responseData = JSON.parse(response.data); + } else { + responseData = response.data; + } + + // Validate that we can query the response data without crashing. + const _json = new JsonStreamStringify(responseData); + }); +}); diff --git a/crates/edr_napi/test/provider.ts b/crates/edr_napi/test/provider.ts index e2dd97a53..7a43e48b0 100644 --- a/crates/edr_napi/test/provider.ts +++ b/crates/edr_napi/test/provider.ts @@ -9,23 +9,10 @@ import { SpecId, SubscriptionEvent, } from ".."; -import { collectMessages, collectSteps } from "./helpers"; +import { collectMessages, collectSteps, ALCHEMY_URL } from "./helpers"; chai.use(chaiAsPromised); -function getEnv(key: string): string | undefined { - const variable = process.env[key]; - if (variable === undefined || variable === "") { - return undefined; - } - - const trimmed = variable.trim(); - - return trimmed.length === 0 ? undefined : trimmed; -} - -const ALCHEMY_URL = getEnv("ALCHEMY_URL"); - describe("Provider", () => { const context = new EdrContext(); const providerConfig = { @@ -350,7 +337,15 @@ describe("Provider", () => { }), ); - const txHash = JSON.parse(sendTxResponse.json).result; + let responseData; + + if (typeof sendTxResponse.data === "string") { + responseData = JSON.parse(sendTxResponse.data); + } else { + responseData = sendTxResponse.data; + } + + const txHash = responseData.result; const traceTransactionResponse = await provider.handleRequest( JSON.stringify({ diff --git a/crates/tools/js/benchmark/index.js b/crates/tools/js/benchmark/index.js index ee64fe488..df57cf853 100644 --- a/crates/tools/js/benchmark/index.js +++ b/crates/tools/js/benchmark/index.js @@ -90,10 +90,9 @@ async function report(benchmarkResultPath) { async function verify(benchmarkResultPath) { let success = true; const benchmarkResult = require(benchmarkResultPath); - const snapshotResult = require(path.join( - getScenariosDir(), - SCENARIO_SNAPSHOT_NAME - )); + const snapshotResult = require( + path.join(getScenariosDir(), SCENARIO_SNAPSHOT_NAME), + ); for (let scenarioName in snapshotResult) { // TODO https://github.com/NomicFoundation/edr/issues/365 @@ -107,7 +106,7 @@ async function verify(benchmarkResultPath) { if (ratio > NEPTUNE_MAX_MIN_FAILURES) { console.error( `Snapshot failure for ${scenarioName} with max/min failure ratio`, - ratio + ratio, ); success = false; } @@ -129,16 +128,16 @@ async function verify(benchmarkResultPath) { if (shouldFail.size > 0) { console.error( `Scenario ${scenarioName} should fail at indexes ${Array.from( - shouldFail - ).sort()}` + shouldFail, + ).sort()}`, ); } if (shouldNotFail.size > 0) { console.error( `Scenario ${scenarioName} should not fail at indexes ${Array.from( - shouldNotFail - ).sort()}` + shouldNotFail, + ).sort()}`, ); } } @@ -207,7 +206,7 @@ async function benchmarkAllScenarios(outPath, useAnvil) { console.error( `Total time ${ Math.round(100 * (totalTime / 1000)) / 100 - } seconds with ${totalFailures} failures.` + } seconds with ${totalFailures} failures.`, ); console.error(`Benchmark results written to ${outPath}`); @@ -275,7 +274,7 @@ async function benchmarkScenario(scenarioFileName, useAnvil) { console.error( `${name} finished in ${ Math.round(100 * (timeMs / 1000)) / 100 - } seconds with ${failures.length} failures.` + } seconds with ${failures.length} failures.`, ); const result = { @@ -331,11 +330,11 @@ function preprocessConfig(config) { config = removeNull(config); config.providerConfig.initialDate = new Date( - config.providerConfig.initialDate.secsSinceEpoch * 1000 + config.providerConfig.initialDate.secsSinceEpoch * 1000, ); config.providerConfig.hardfork = normalizeHardfork( - config.providerConfig.hardfork + config.providerConfig.hardfork, ); // "accounts" in EDR are "genesisAccounts" in Hardhat @@ -345,7 +344,7 @@ function preprocessConfig(config) { config.providerConfig.genesisAccounts = config.providerConfig.accounts.map( ({ balance, secretKey }) => { return { balance, privateKey: secretKey }; - } + }, ); delete config.providerConfig.accounts; @@ -380,6 +379,7 @@ function preprocessConfig(config) { } config.providerConfig.minGasPrice = BigInt(config.providerConfig.minGasPrice); + config.providerConfig.enableRip7212 = false; return config; } diff --git a/package.json b/package.json index 59af888f9..1baeb65d4 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,9 @@ "pnpm": { "overrides": { "hardhat>@nomicfoundation/edr": "workspace:*" + }, + "patchedDependencies": { + "hardhat@2.22.7": "patches/hardhat@2.22.7.patch" } }, "engines": { diff --git a/patches/hardhat@2.22.7.patch b/patches/hardhat@2.22.7.patch new file mode 100644 index 000000000..392e91d18 --- /dev/null +++ b/patches/hardhat@2.22.7.patch @@ -0,0 +1,77 @@ +diff --git a/internal/hardhat-network/provider/provider.js b/internal/hardhat-network/provider/provider.js +index 720cbe438e2a09b210db4135c0e87a4edb5b69f0..5954096feb5bc4ab1c546e6acc9d765bdfb7e95d 100644 +--- a/internal/hardhat-network/provider/provider.js ++++ b/internal/hardhat-network/provider/provider.js +@@ -215,7 +215,14 @@ class EdrProviderWrapper extends events_1.EventEmitter { + params, + }); + const responseObject = await this._provider.handleRequest(stringifiedArgs); +- const response = JSON.parse(responseObject.json); ++ ++ let response; ++ if (typeof responseObject.data === "string") { ++ response = JSON.parse(responseObject.data); ++ } else { ++ response = responseObject.data; ++ } ++ + const needsTraces = this._node._vm.evm.events.eventNames().length > 0 || + this._node._vm.events.eventNames().length > 0 || + this._vmTracer !== undefined; +diff --git a/internal/hardhat-network/provider/vm/minimal-vm.js b/internal/hardhat-network/provider/vm/minimal-vm.js +index 7308105d1718d54afac5a87402a6c2250ae8115c..5e054633ac9dcc7ec1872d1527e637a18a0db550 100644 +--- a/internal/hardhat-network/provider/vm/minimal-vm.js ++++ b/internal/hardhat-network/provider/vm/minimal-vm.js +@@ -26,7 +26,14 @@ function getMinimalEthereumJsVm(provider) { + method: "eth_getStorageAt", + params: [address.toString(), `0x${slotHash.toString("hex")}`], + })); +- const response = JSON.parse(responseObject.json); ++ ++ let response; ++ if (typeof responseObject.data === "string") { ++ response = JSON.parse(responseObject.data); ++ } else { ++ response = responseObject.data; ++ } ++ + return Buffer.from(response.result.slice(2), "hex"); + }, + putContractStorage: async (address, slotHash, slotValue) => { +diff --git a/src/internal/hardhat-network/provider/provider.ts b/src/internal/hardhat-network/provider/provider.ts +index 939ca6b3b7968e9b98cc2b4d1edfc906ef92897c..15418fdb0902728cefc6433dce152a08efc5ba70 100644 +--- a/src/internal/hardhat-network/provider/provider.ts ++++ b/src/internal/hardhat-network/provider/provider.ts +@@ -354,7 +354,13 @@ export class EdrProviderWrapper + const responseObject: Response = await this._provider.handleRequest( + stringifiedArgs + ); +- const response = JSON.parse(responseObject.json); ++ ++ let response; ++ if (typeof responseObject.data === "string") { ++ response = JSON.parse(responseObject.data); ++ } else { ++ response = responseObject.data; ++ } + + const needsTraces = + this._node._vm.evm.events.eventNames().length > 0 || +diff --git a/src/internal/hardhat-network/provider/vm/minimal-vm.ts b/src/internal/hardhat-network/provider/vm/minimal-vm.ts +index bf75d7d30adf1845201a8841fdc1c4db17e63d90..0c947e8a74c7dedcd8e6831773cbaaee432284dc 100644 +--- a/src/internal/hardhat-network/provider/vm/minimal-vm.ts ++++ b/src/internal/hardhat-network/provider/vm/minimal-vm.ts +@@ -82,7 +82,12 @@ export function getMinimalEthereumJsVm( + }) + ); + +- const response = JSON.parse(responseObject.json); ++ let response; ++ if (typeof responseObject.data === "string") { ++ response = JSON.parse(responseObject.data); ++ } else { ++ response = responseObject.data; ++ } + + return Buffer.from(response.result.slice(2), "hex"); + }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68a57d357..3ed6e43c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,11 @@ settings: overrides: hardhat>@nomicfoundation/edr: workspace:* +patchedDependencies: + hardhat@2.22.7: + hash: umhdsnfwxfqbbniyljx6a4ic5i + path: patches/hardhat@2.22.7.patch + importers: .: @@ -41,6 +46,9 @@ importers: chai-as-promised: specifier: ^7.1.1 version: 7.1.1(chai@4.4.1) + json-stream-stringify: + specifier: ^3.1.4 + version: 3.1.4 mocha: specifier: ^10.0.0 version: 10.3.0 @@ -61,7 +69,7 @@ importers: version: 4.4.1 hardhat: specifier: 2.22.7 - version: 2.22.7(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) lodash: specifier: ^4.17.11 version: 4.17.21 @@ -181,7 +189,7 @@ importers: version: 7.0.1 hardhat: specifier: 2.22.7 - version: 2.22.7(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -211,10 +219,10 @@ importers: devDependencies: '@defi-wonderland/smock': specifier: ^2.4.0 - version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.7(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.7(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) + version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2)(hardhat@2.22.7(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) chai: specifier: ^4.3.6 version: 4.4.1 @@ -223,7 +231,7 @@ importers: version: 5.7.2 hardhat: specifier: 2.22.7 - version: 2.22.7(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -1979,6 +1987,10 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stream-stringify@3.1.4: + resolution: {integrity: sha512-oGoz05ft577LolnXFQHD2CjnXDxXVA5b8lHwfEZgRXQUZeCMo6sObQQRq+NXuHQ3oTeMZHHmmPY2rjVwyqR62A==} + engines: {node: '>=7.10.1'} + json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -3083,16 +3095,16 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.7(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.7(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4))': + '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/abstract-provider': 5.7.0 '@ethersproject/abstract-signer': 5.7.0 '@nomicfoundation/ethereumjs-util': 9.0.4 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.7(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) diff: 5.0.0 ethers: 5.7.2 - hardhat: 2.22.7(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) lodash.isequal: 4.5.0 lodash.isequalwith: 4.4.0 rxjs: 7.8.1 @@ -3603,10 +3615,10 @@ snapshots: '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.7(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4))': dependencies: ethers: 5.7.2 - hardhat: 2.22.7(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) '@scure/base@1.1.5': {} @@ -4873,7 +4885,7 @@ snapshots: hard-rejection@2.1.0: {} - hardhat@2.22.7(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4): + hardhat@2.22.7(patch_hash=umhdsnfwxfqbbniyljx6a4ic5i)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 @@ -5141,6 +5153,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stream-stringify@3.1.4: {} + json5@1.0.2: dependencies: minimist: 1.2.8