From dea4cdc4c1a28524b9be9a0fd9f96d8fd5907196 Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:45:54 -0500 Subject: [PATCH 1/7] chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. (#2013) * chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. * typo --- CHANGELOG.md | 1 + x/evm/precompile/funtoken.go | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6f536f43..b4103476e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2003](https://github.com/NibiruChain/nibiru/pull/2003) - fix(evm): fix FunToken conversions between Cosmos and EVM - [#2004](https://github.com/NibiruChain/nibiru/pull/2004) - refactor(evm)!: replace `HexAddr` with `EIP55Addr` - [#2008](https://github.com/NibiruChain/nibiru/pull/2008) - refactor(evm): clean up precompile setups +- [#2013](https://github.com/NibiruChain/nibiru/pull/2013) - chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. #### Dapp modules: perp, spot, oracle, etc diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 4e1f131cd..365cac8e1 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -12,6 +12,7 @@ import ( gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + gethparams "github.com/ethereum/go-ethereum/params" "github.com/NibiruChain/nibiru/v2/app/keepers" "github.com/NibiruChain/nibiru/v2/x/evm" @@ -32,12 +33,15 @@ func (p precompileFunToken) Address() gethcommon.Address { } func (p precompileFunToken) RequiredGas(input []byte) (gasPrice uint64) { - // TODO: https://github.com/NibiruChain/nibiru/issues/1990 - // We need to determine an appropriate gas value for the transaction to - // configure this function and add a assertions around the gas usage to - // the precompile's test suite. UD-DEBUG: not implemented yet. Currently - // set to 0 gasPrice - return 22 + // Since [gethparams.TxGas] is the cost per (Ethereum) transaction that does not create + // a contract, it's value can be used to derive an appropriate value for the + // precompile call. The FunToken precompile performs 3 operations, labeld 1-3 + // below: + // 0 | Call the precompile (already counted in gas calculation) + // 1 | Send ERC20 to EVM. + // 2 | Convert ERC20 to coin + // 3 | Send coin to recipient. + return gethparams.TxGas * 3 } const ( From 1f1e76b6f01be373b954dc03e9b8eec88c79d953 Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Fri, 23 Aug 2024 02:15:24 -0500 Subject: [PATCH 2/7] feat(evm): Emit block bloom event in EndBlock hook. (#2014) * feat(evm): Emit block bloom event in EndBlock hook. * chore: linter --- CHANGELOG.md | 1 + x/evm/keeper/hooks.go | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4103476e..a3e5a5b4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2004](https://github.com/NibiruChain/nibiru/pull/2004) - refactor(evm)!: replace `HexAddr` with `EIP55Addr` - [#2008](https://github.com/NibiruChain/nibiru/pull/2008) - refactor(evm): clean up precompile setups - [#2013](https://github.com/NibiruChain/nibiru/pull/2013) - chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. +- [#2014](https://github.com/NibiruChain/nibiru/pull/2014) - feat(evm): Emit block bloom event in EndBlock hook. #### Dapp modules: perp, spot, oracle, etc diff --git a/x/evm/keeper/hooks.go b/x/evm/keeper/hooks.go index ddf2eb1e7..48ab57704 100644 --- a/x/evm/keeper/hooks.go +++ b/x/evm/keeper/hooks.go @@ -4,19 +4,22 @@ package keeper import ( abci "github.com/cometbft/cometbft/abci/types" + "github.com/NibiruChain/nibiru/v2/x/evm" + sdk "github.com/cosmos/cosmos-sdk/types" + gethcoretypes "github.com/ethereum/go-ethereum/core/types" ) -// BeginBlock sets the sdk Context and EIP155 chain id to the Keeper. -func (k *Keeper) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { - // TODO: feat(evm): impl BeginBlock - // Is it necessary to set a local variable, or can we use ctx everywhere? -} +// BeginBlock hook for the EVM module. +func (k *Keeper) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {} // EndBlock also retrieves the bloom filter value from the transient store and commits it to the -// KVStore. The EVM end block logic doesn't update the validator set, thus it returns -// an empty slice. func (k *Keeper) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - // TODO: Do we care about bloom here? + ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + bloom := gethcoretypes.BytesToBloom(k.EvmState.GetBlockBloomTransient(ctx).Bytes()) + _ = ctx.EventManager().EmitTypedEvent(&evm.EventBlockBloom{ + Bloom: string(bloom.Bytes()), + }) + // The bloom logic doesn't update the validator set. return []abci.ValidatorUpdate{} } From 924f9c49eda76cea2255b55316d0ba790afebac4 Mon Sep 17 00:00:00 2001 From: Oleg Nikonychev Date: Fri, 23 Aug 2024 15:13:30 +0400 Subject: [PATCH 3/7] test(evm): e2e tests for eth_* endpoints (#2006) * test(evm): e2e tests for eth_* endpoints * test(evm): another series of e2e tests * chore: cleanup --------- Co-authored-by: Kevin Yang <5478483+k-yang@users.noreply.github.com> --- CHANGELOG.md | 1 + e2e/evm/jest.config.js | 5 +- e2e/evm/justfile | 2 +- e2e/evm/package-lock.json | 6630 ++++++++++++----- e2e/evm/package.json | 8 +- e2e/evm/test/basic_queries.test.ts | 354 +- .../test/contract_infinite_loop_gas.test.ts | 16 +- e2e/evm/test/contract_send_nibi.test.ts | 14 +- e2e/evm/test/erc20.test.ts | 18 +- e2e/evm/test/setup.ts | 5 +- e2e/evm/test/utils.ts | 34 + 11 files changed, 5166 insertions(+), 1921 deletions(-) create mode 100644 e2e/evm/test/utils.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e5a5b4c..313f0d2df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,6 +107,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2001](https://github.com/NibiruChain/nibiru/pull/2001) - refactor(evm): simplify FunToken methods and tests - [#2003](https://github.com/NibiruChain/nibiru/pull/2003) - fix(evm): fix FunToken conversions between Cosmos and EVM - [#2004](https://github.com/NibiruChain/nibiru/pull/2004) - refactor(evm)!: replace `HexAddr` with `EIP55Addr` +- [#2006](https://github.com/NibiruChain/nibiru/pull/2006) - test(evm): e2e tests for eth_* endpoints - [#2008](https://github.com/NibiruChain/nibiru/pull/2008) - refactor(evm): clean up precompile setups - [#2013](https://github.com/NibiruChain/nibiru/pull/2013) - chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. - [#2014](https://github.com/NibiruChain/nibiru/pull/2014) - feat(evm): Emit block bloom event in EndBlock hook. diff --git a/e2e/evm/jest.config.js b/e2e/evm/jest.config.js index 3ee1152ca..dcd46bd5f 100644 --- a/e2e/evm/jest.config.js +++ b/e2e/evm/jest.config.js @@ -1,6 +1,9 @@ module.exports = { testEnvironment: 'node', - testMatch: ['**/test/**/*.test.js'], + transform: { + '^.+\\.(ts|tsx)?$': 'ts-jest', + }, + testMatch: ['**/test/**/*.test.ts'], verbose: true, "maxWorkers": 1 }; diff --git a/e2e/evm/justfile b/e2e/evm/justfile index e99281f28..8ab9fb7d1 100644 --- a/e2e/evm/justfile +++ b/e2e/evm/justfile @@ -29,7 +29,7 @@ gen-types: # Runs the E2E tests test: - bun test + npm test # Format fmt: diff --git a/e2e/evm/package-lock.json b/e2e/evm/package-lock.json index 26a28e6fb..165b34429 100644 --- a/e2e/evm/package-lock.json +++ b/e2e/evm/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.1", "license": "ISC", "devDependencies": { - "@typechain/ethers-v6": "^0.5.1", + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.12", "bun": "^1.0.3", "bun-types": "^1.0.3", "dotenv": "^16.4.5", @@ -20,7 +21,8 @@ "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-prettier": "^5.1.3", "ethers": "^6.12.1", - "prettier": "^3.3.1" + "jest": "^29.7.0", + "ts-jest": "^29.2.4" }, "engines": { "node": ">=0.18.0" @@ -32,1881 +34,3925 @@ "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", "dev": true }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "node_modules/@babel/compat-data": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "bin": { + "json5": "lib/cli.js" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" }, "engines": { - "node": ">=10.10.0" + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, - "engines": { - "node": ">=12.22" + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true - }, - "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@noble/hashes": "1.3.2" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, "engines": { - "node": ">= 16" + "node": ">=6.9.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { - "node": ">= 8" + "node": ">=6.9.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" } }, - "node_modules/@oven/bun-darwin-aarch64": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.1.20.tgz", - "integrity": "sha512-9dZuhfkol/fgG9+ZcDfDFKamp6npraPQghutE5IJM8Y3w4+y5USvw+NbANuKLSLFOH06oXMT/necZ9HT0rzTlg==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "node_modules/@babel/helpers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" + }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, - "funding": { - "url": "https://opencollective.com/unts" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@typechain/ethers-v6": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@typechain/ethers-v6/-/ethers-v6-0.5.1.tgz", - "integrity": "sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==", + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { - "lodash": "^4.17.15", - "ts-essentials": "^7.0.1" + "color-convert": "^1.9.0" }, - "peerDependencies": { - "ethers": "6.x", - "typechain": "^8.3.2", - "typescript": ">=4.7.0" + "engines": { + "node": ">=4" } }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/@types/node": { - "version": "20.12.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", - "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "color-name": "1.1.3" } }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "peer": true + "engines": { + "node": ">=0.8.0" + } }, - "node_modules/@types/ws": { - "version": "8.5.11", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.11.tgz", - "integrity": "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==", + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "dependencies": { - "@types/node": "*" + "engines": { + "node": ">=4" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "node_modules/@babel/parser": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { - "acorn": "bin/acorn" + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=0.4.0" + "node": ">=6.0.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "dev": true - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "dev": true, - "peer": true, "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "peer": true, + "@babel/helper-plugin-utils": "^7.24.7" + }, "engines": { - "node": ">=6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" + "@babel/helper-plugin-utils": "^7.10.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "@babel/helper-plugin-utils": "^7.10.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "@babel/helper-plugin-utils": "^7.10.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "peer": true, "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "peer": true + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, - "peer": true, "dependencies": { - "possible-typed-array-names": "^1.0.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/axe-core": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", - "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, - "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": ">=4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/axobject-query": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", - "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, - "peer": true, "dependencies": { - "deep-equal": "^2.0.5" + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/@babel/traverse": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", "dev": true, "dependencies": { - "fill-range": "^7.1.1" + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", + "debug": "^4.3.1", + "globals": "^11.1.0" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/bun": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/bun/-/bun-1.1.20.tgz", - "integrity": "sha512-aqLmvaz0/vLUiCrOXtAsf7pCSOS/qXieYDsq8COa3+fIgMK05CjZt9m9r7DC+tjKy7hH8uKSNTapQOr/kX8gIA==", - "cpu": [ - "arm64", - "x64" - ], + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "hasInstallScript": true, - "os": [ - "darwin", - "linux", - "win32" - ], - "bin": { - "bun": "bin/bun.exe", - "bunx": "bin/bun.exe" - }, - "optionalDependencies": { - "@oven/bun-darwin-aarch64": "1.1.20", - "@oven/bun-darwin-x64": "1.1.20", - "@oven/bun-darwin-x64-baseline": "1.1.20", - "@oven/bun-linux-aarch64": "1.1.20", - "@oven/bun-linux-x64": "1.1.20", - "@oven/bun-linux-x64-baseline": "1.1.20", - "@oven/bun-windows-x64": "1.1.20", - "@oven/bun-windows-x64-baseline": "1.1.20" + "engines": { + "node": ">=4" } }, - "node_modules/bun-types": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.20.tgz", - "integrity": "sha512-2u84HciDR3E7Uc0t0AEeXHmQAWe9uzRKTz120D3silIJOQlbGIMJMJiGaM8Yx7nEvMyfV0LfSdkEGnb77AN5AA==", + "node_modules/@babel/types": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, "dependencies": { - "@types/node": "~20.12.8", - "@types/ws": "~8.5.10" + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">= 0.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, "engines": { - "node": ">=6" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=7.0.0" + "node": ">=10.10.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, - "node_modules/command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "peer": true, "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=4.0.0" + "node": ">=8" } }, - "node_modules/command-line-usage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "peer": true, "dependencies": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/command-line-usage/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "peer": true, "dependencies": { - "color-convert": "^1.9.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": ">=4" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, "engines": { "node": ">=8" } }, - "node_modules/command-line-usage/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "peer": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/command-line-usage/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "peer": true, "dependencies": { - "color-name": "1.1.3" + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/command-line-usage/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, - "node_modules/command-line-usage/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "peer": true, "engines": { - "node": ">=0.8.0" + "node": ">=8" } }, - "node_modules/command-line-usage/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "peer": true, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/command-line-usage/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, - "peer": true, "dependencies": { - "has-flag": "^3.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, - "peer": true, - "engines": { - "node": ">=8" + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, - "peer": true + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "jest-get-type": "^29.6.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { - "ms": "2.1.2" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "engines": { - "node": ">=6.0" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "node-notifier": { "optional": true } } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, - "peer": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "peer": true, + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=4.0.0" + "node": ">=10" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, - "peer": true, "dependencies": { - "esutils": "^2.0.2" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, - "engines": { - "node": ">=12" + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" }, - "funding": { - "url": "https://dotenvx.com" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "peer": true - }, - "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": ">=10.13.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, - "peer": true, "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">= 0.4" + "node": ">=6.0.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { - "node": ">= 0.4" + "node": ">=6.0.0" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/es-iterator-helpers": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", - "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, "dependencies": { - "es-errors": "^1.3.0" + "@noble/hashes": "1.3.2" }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, "engines": { - "node": ">= 0.4" + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "peer": true, "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">= 0.4" + "node": ">= 8" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "peer": true, - "dependencies": { - "hasown": "^2.0.0" + "engines": { + "node": ">= 8" } }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "peer": true, "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 8" + } + }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.1.24.tgz", + "integrity": "sha512-tT4omX5C9X4tlS0uLiypb6uECTbKjg3/dRNBjAROyL8tLJdndNR0SksfS7IQz0vD4zzifk98ZEsDML4gt+LFgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.1.24.tgz", + "integrity": "sha512-jSt9jL0mE1zh/8OEWR6fdnGaDF8iWnMwd2a4N2qRuWcr8/QCEjvfK4iJPaJ6f8W06z01CxO5+O4pMF94s6z3pw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.1.24.tgz", + "integrity": "sha512-jp7VhUDNA58scDHF8CKSClUdeWmWGiCWwxcrqs/81jNiARKCb+wjy+nEXWjc8KOB45NAhGaB3BBbl6EM3GzIDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.1.24.tgz", + "integrity": "sha512-yw/OHSEqygnWJelfuXJP3hSjNVzJ7adxL/Z9vxQPMYi7c7zdfMiggoH9V4U1VIcggfoMH1Gfc5lVpKowuJsSBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.1.24.tgz", + "integrity": "sha512-U8j716mLJFG5KsFypWgE+uNULo3Y5R0tgP7vEPnPAL4lvwt7IM4Jg1LyxzcEm0jDVrJU3TNaYsu4bsxVVAxmSg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-baseline": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.1.24.tgz", + "integrity": "sha512-L2p9MOY7ug1RzaMYcAknEefMPw+w0LPR9qv780G/SO0uBe1JGl3IzO6/hsPnjj65uT/nNuR+YJXeZbH6p1iwfQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-windows-x64": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.1.24.tgz", + "integrity": "sha512-8DWCMsavfxAYuJRMRV5M8Tkhttjh/vJo9DEIEBVOfKbKQHoqOnlmXgK7FBPNTqyVzKpmv+W80bTrRoxlnTnk7A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oven/bun-windows-x64-baseline": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.1.24.tgz", + "integrity": "sha512-Yznr5LJb8Q3MoyG9dM3UosnJSHwOe8AL1AA9fpkVC+TdTl6lmXxjk6JUsE4THQDloRYGN9eqpLLJbK7O6t0aUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "peer": true + }, + "node_modules/@types/node": { + "version": "20.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", + "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "dev": true + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "peer": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "peer": true + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", + "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "peer": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bun": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/bun/-/bun-1.1.24.tgz", + "integrity": "sha512-vZt5zNVaoNTSlNprdVdQVZInBcGC2QSik6o9ZsMzuYiiUEAEqpRwwMXAYDUN85mt2FNnsGvmk/8xVlUXS7IzEw==", + "cpu": [ + "arm64", + "x64" + ], + "dev": true, + "hasInstallScript": true, + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "bun": "bin/bun.exe", + "bunx": "bin/bun.exe" + }, + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "1.1.24", + "@oven/bun-darwin-x64": "1.1.24", + "@oven/bun-darwin-x64-baseline": "1.1.24", + "@oven/bun-linux-aarch64": "1.1.24", + "@oven/bun-linux-x64": "1.1.24", + "@oven/bun-linux-x64-baseline": "1.1.24", + "@oven/bun-windows-x64": "1.1.24", + "@oven/bun-windows-x64-baseline": "1.1.24" + } + }, + "node_modules/bun-types": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.24.tgz", + "integrity": "sha512-UNEcYawHWOVw9GYQdOgjJvTQduYlPckmRMqpBqfkWpUzeK2CZfEP/JOYBH6SMypyfkNg/Zsqf0olN6vMrSPg3w==", + "dev": true, + "dependencies": { + "@types/node": "~20.12.8", + "@types/ws": "~8.5.10" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "peer": true + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.11.tgz", + "integrity": "sha512-R1CccCDYqndR25CaXFd6hp/u9RaaMcftMkphmvuepXr5b1vfLkRml6aWVeBhXJ7rbevHkKEMJtz8XqPf7ffmew==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "peer": true + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz", + "integrity": "sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==", + "dev": true, + "peer": true, + "dependencies": { + "aria-query": "~5.1.3", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.9.1", + "axobject-query": "~3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.19", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "bin": { - "eslint": "bin/eslint.js" + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ethers": { + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", + "integrity": "sha512-9VkriTTed+/27BGuY1s0hf441kqwHJ1wtN2edksEtiRvXx+soxRX3iSXTfFqq2+YwrOqbDoTHjIhQnjJRlzKmg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", + "dev": true + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/eslint-config-airbnb": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", - "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { - "eslint-config-airbnb-base": "^15.0.0", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0" + "engines": { + "node": ">= 6" } }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" }, "engines": { "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" } }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "dependencies": { + "minimatch": "^5.0.1" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "peer": true, "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "balanced-match": "^1.0.0" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, - "peer": true, "dependencies": { - "ms": "^2.1.1" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", - "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { - "debug": "^4.3.4", - "enhanced-resolve": "^5.12.0", - "eslint-module-utils": "^2.7.4", - "fast-glob": "^3.3.1", - "get-tsconfig": "^4.5.0", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3" + "to-regex-range": "^5.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, - "funding": { - "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + "engines": { + "node": ">=10" }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "debug": "^3.2.7" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, + "peer": true, "dependencies": { - "ms": "^2.1.1" + "is-callable": "^1.1.3" } }, - "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, "peer": true, "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { - "node": ">=4" + "node": ">= 0.4" }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "peer": true, - "dependencies": { - "ms": "^2.1.1" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz", - "integrity": "sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "peer": true, - "dependencies": { - "aria-query": "~5.1.3", - "array-includes": "^3.1.8", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "^4.9.1", - "axobject-query": "~3.1.1", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "es-iterator-helpers": "^1.0.19", - "hasown": "^2.0.2", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.0" - }, "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "node": ">=6.9.0" } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": "*", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-react": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", - "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, - "peer": true, - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.2", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.19", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.8", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.0", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11", - "string.prototype.repeat": "^1.0.0" - }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + "node": ">=8.0.0" } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "peer": true, "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/get-tsconfig": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "resolve-pkg-maps": "^1.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "*" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint/node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "esutils": "^2.0.2" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=6.0.0" + "node": ">=10.13.0" } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "type-fest": "^0.20.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "estraverse": "^5.2.0" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">=4.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ethers": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", - "integrity": "sha512-9VkriTTed+/27BGuY1s0hf441kqwHJ1wtN2edksEtiRvXx+soxRX3iSXTfFqq2+YwrOqbDoTHjIhQnjJRlzKmg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@adraffy/ens-normalize": "1.10.1", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@types/node": "18.15.13", - "aes-js": "4.0.0-beta.5", - "tslib": "2.4.0", - "ws": "8.17.1" + "node": ">= 0.4" }, - "engines": { - "node": ">=14.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ethers/node_modules/@types/node": { - "version": "18.15.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "peer": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=8.6.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" + "engines": { + "node": ">= 4" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { - "flat-cache": "^3.0.4" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "dependencies": { - "to-regex-range": "^5.0.1" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "peer": true, - "dependencies": { - "array-back": "^3.0.1" - }, "engines": { - "node": ">=4.0.0" + "node": ">=0.8.19" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.4" } }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "dev": true, + "peer": true, "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "peer": true, "dependencies": { - "is-callable": "^1.1.3" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", "dev": true, "peer": true, "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, + "peer": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "peer": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -1915,27 +3961,26 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "peer": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dev": true, "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -1944,16 +3989,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", "dev": true, "peer": true, "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -1962,76 +4005,109 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-tsconfig": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", - "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, + "peer": true, "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, "engines": { - "node": "*" + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", "dev": true, + "peer": true, "dependencies": { - "is-glob": "^4.0.3" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "peer": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, "engines": { "node": ">= 0.4" }, @@ -2039,78 +4115,107 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, + "peer": true, "dependencies": { - "get-intrinsic": "^1.1.3" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "peer": true, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, + "peer": true, "dependencies": { - "es-define-property": "^1.0.0" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -2118,14 +4223,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "peer": true, "dependencies": { - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -2134,511 +4239,757 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, + "peer": true, "dependencies": { - "function-bind": "^1.1.2" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "peer": true, "engines": { - "node": ">= 4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, + "peer": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" + "call-bind": "^1.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, "engines": { - "node": ">=0.8.19" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } + "peer": true }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "peer": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, - "peer": true, "dependencies": { - "has-bigints": "^1.0.1" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/is-boolean-object": { + "node_modules/iterator.prototype": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", "dev": true, "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, - "engines": { - "node": ">= 0.4" + "bin": { + "jake": "bin/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=10" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, - "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { - "hasown": "^2.0.2" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "peer": true, "dependencies": { - "is-typed-array": "^1.1.13" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, - "peer": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.2" + "detect-newline": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, - "peer": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "is-extglob": "^2.1.1" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "peer": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=0.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "peer": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, - "peer": true, "engines": { - "node": ">= 0.4" + "node": ">=6" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.7" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, - "peer": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "peer": true, "dependencies": { - "has-symbols": "^1.0.2" + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, - "peer": true, "dependencies": { - "which-typed-array": "^1.1.14" + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=10" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.2" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "peer": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, - "peer": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/iterator.prototype": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", - "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "peer": true, "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "peer": true + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -2652,12 +5003,30 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2683,16 +5052,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -2718,6 +5077,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -2738,6 +5106,15 @@ "node": ">=0.10" } }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2751,6 +5128,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2766,19 +5149,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "peer": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2798,6 +5174,63 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2820,6 +5253,15 @@ "node": ">=8.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2842,19 +5284,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "peer": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2867,6 +5296,39 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3009,6 +5471,21 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3056,6 +5533,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3068,6 +5554,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3099,8 +5603,13 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "peer": true + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -3114,6 +5623,79 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -3138,6 +5720,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3160,6 +5743,51 @@ "node": ">=6.0.0" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -3181,6 +5809,22 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3208,16 +5852,6 @@ "dev": true, "peer": true }, - "node_modules/reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -3259,12 +5893,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, - "peer": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -3273,8 +5915,29 @@ "bin": { "resolve": "bin/resolve" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/resolve-from": { @@ -3295,6 +5958,15 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -3463,6 +6135,73 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -3476,12 +6215,38 @@ "node": ">= 0.4" } }, - "node_modules/string-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", - "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "peer": true + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/string.prototype.includes": { "version": "2.0.0", @@ -3606,6 +6371,15 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3635,7 +6409,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -3665,42 +6438,6 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true }, - "node_modules/table-layout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/table-layout/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table-layout/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -3710,12 +6447,41 @@ "node": ">=6" } }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3728,29 +6494,76 @@ "node": ">=8.0" } }, - "node_modules/ts-command-line-args": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz", - "integrity": "sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==", + "node_modules/ts-jest": { + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz", + "integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==", "dev": true, - "peer": true, "dependencies": { - "chalk": "^4.1.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^6.1.0", - "string-format": "^2.0.0" + "bs-logger": "0.x", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "bin": { - "write-markdown": "dist/write-markdown.js" + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, - "node_modules/ts-essentials": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", - "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", + "node_modules/ts-jest/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "peerDependencies": { - "typescript": ">=3.7.0" + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/tsconfig-paths": { @@ -3784,6 +6597,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -3796,47 +6618,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typechain": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.3.2.tgz", - "integrity": "sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==", - "dev": true, - "peer": true, - "dependencies": { - "@types/prettier": "^2.1.1", - "debug": "^4.3.1", - "fs-extra": "^7.0.0", - "glob": "7.1.7", - "js-sha3": "^0.8.0", - "lodash": "^4.17.15", - "mkdirp": "^1.0.4", - "prettier": "^2.3.1", - "ts-command-line-args": "^2.2.0", - "ts-essentials": "^7.0.1" - }, - "bin": { - "typechain": "dist/cli/cli.js" - }, - "peerDependencies": { - "typescript": ">=4.3.0" - } - }, - "node_modules/typechain/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "peer": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -3928,16 +6709,6 @@ "node": ">=14.17" } }, - "node_modules/typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -3960,14 +6731,34 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, - "peer": true, - "engines": { - "node": ">= 4.0.0" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, "node_modules/uri-js": { @@ -3979,6 +6770,29 @@ "punycode": "^2.1.0" } }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4086,28 +6900,21 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrapjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "peer": true, "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.2.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/wordwrapjs/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrappy": { @@ -4116,6 +6923,19 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -4137,6 +6957,48 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/e2e/evm/package.json b/e2e/evm/package.json index 28dbfb493..3415e6990 100644 --- a/e2e/evm/package.json +++ b/e2e/evm/package.json @@ -9,7 +9,8 @@ "node": ">=0.18.0" }, "devDependencies": { - "@typechain/ethers-v6": "^0.5.1", + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.12", "bun": "^1.0.3", "bun-types": "^1.0.3", "dotenv": "^16.4.5", @@ -20,10 +21,11 @@ "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-prettier": "^5.1.3", "ethers": "^6.12.1", - "prettier": "^3.3.1" + "jest": "^29.7.0", + "ts-jest": "^29.2.4" }, "scripts": { - "test": "bun test", + "test": "jest", "gen-types": "bun run typechain --target=ethers-v6 contracts/*Compiled.json" } } diff --git a/e2e/evm/test/basic_queries.test.ts b/e2e/evm/test/basic_queries.test.ts index 3ed1e2a8b..a974a70a1 100644 --- a/e2e/evm/test/basic_queries.test.ts +++ b/e2e/evm/test/basic_queries.test.ts @@ -1,12 +1,22 @@ -import { describe, expect, it } from "bun:test" // eslint-disable-line import/no-unresolved -import { toBigInt, Wallet } from "ethers" +import { describe, expect, it } from "@jest/globals" +import { + toBigInt, + Wallet, + parseEther, + keccak256, + AbiCoder, + ethers, +} from "ethers" import { account, provider } from "./setup" +import { + SendNibiCompiled__factory, + TestERC20Compiled__factory, +} from "../types/ethers-contracts" +import { alice, deployERC20, sendTestNibi } from "./utils" describe("Basic Queries", () => { it("Simple transfer, balance check", async () => { - const alice = Wallet.createRandom() const amountToSend = toBigInt(5e12) * toBigInt(1e6) // unibi - const senderBalanceBefore = await provider.getBalance(account) const recipientBalanceBefore = await provider.getBalance(alice) expect(senderBalanceBefore).toBeGreaterThan(0) @@ -40,4 +50,340 @@ describe("Basic Queries", () => { expect(senderBalanceAfter).toEqual(expectedSenderWei) expect(recipientBalanceAfter).toEqual(amountToSend) }, 20e3) + + it("eth_accounts", async () => { + const accounts = await provider.listAccounts() + expect(accounts).not.toHaveLength(0) + }) + + it("eth_estimateGas", async () => { + const tx = { + from: account.address, + to: alice, + value: parseEther("0.01"), // Sending 0.01 Ether + } + const estimatedGas = await provider.estimateGas(tx) + expect(estimatedGas).toBeGreaterThan(BigInt(0)) + }) + + it("eth_feeHistory", async () => { + const blockCount = 5 // Number of blocks in the requested history + const newestBlock = "latest" // Can be a block number or 'latest' + const rewardPercentiles = [25, 50, 75] // Example percentiles for priority fees + + const feeHistory = await provider.send("eth_feeHistory", [ + blockCount, + newestBlock, + rewardPercentiles, + ]) + expect(feeHistory).toBeDefined() + expect(feeHistory).toHaveProperty("baseFeePerGas") + expect(feeHistory).toHaveProperty("gasUsedRatio") + expect(feeHistory).toHaveProperty("oldestBlock") + expect(feeHistory).toHaveProperty("reward") + }) + + it("eth_gasPrice", async () => { + const gasPrice = await provider.send("eth_gasPrice", []) + expect(gasPrice).toBeDefined() + }) + + it("eth_getBalance", async () => { + const balance = await provider.getBalance(account.address) + expect(balance).toBeGreaterThan(0) + }) + + it("eth_getBlockByNumber, eth_getBlockByHash", async () => { + const blockNumber = 1 + const blockByNumber = await provider.send("eth_getBlockByNumber", [ + blockNumber, + false, + ]) + expect(blockByNumber).toBeDefined() + expect(blockByNumber).toHaveProperty("hash") + + const blockByHash = await provider.send("eth_getBlockByHash", [ + blockByNumber.hash, + false, + ]) + expect(blockByHash).toBeDefined() + expect(blockByHash.hash).toEqual(blockByNumber.hash) + expect(blockByHash.number).toEqual(blockByNumber.number) + }) + + it("eth_getBlockTransactionCountByHash", async () => { + const blockNumber = 1 + const block = await provider.send("eth_getBlockByNumber", [ + blockNumber, + false, + ]) + const txCount = await provider.send("eth_getBlockTransactionCountByHash", [ + block.hash, + ]) + expect(parseInt(txCount)).toBeGreaterThanOrEqual(0) + }) + + it("eth_getBlockTransactionCountByNumber", async () => { + const blockNumber = 1 + const txCount = await provider.send( + "eth_getBlockTransactionCountByNumber", + [blockNumber], + ) + expect(parseInt(txCount)).toBeGreaterThanOrEqual(0) + }) + + it("eth_getCode", async () => { + const factory = new SendNibiCompiled__factory(account) + const contract = await factory.deploy() + await contract.waitForDeployment() + const contractAddr = await contract.getAddress() + const code = await provider.send("eth_getCode", [contractAddr, "latest"]) + expect(code).toBeDefined() + }) + + it("eth_getFilterChanges", async () => { + // Deploy ERC-20 contract + const contract = await deployERC20() + const contractAddr = await contract.getAddress() + const filter = { + fromBlock: "latest", + address: contractAddr, + } + // Create the filter for a contract + const filterId = await provider.send("eth_newFilter", [filter]) + expect(filterId).toBeDefined() + + // Execute some contract TX + const tx = await contract.transfer(alice, parseEther("0.01")) + await tx.wait(1, 5e3) + await new Promise((resolve) => setTimeout(resolve, 3000)) + + // Assert logs + const changes = await provider.send("eth_getFilterChanges", [filterId]) + expect(changes.length).toBeGreaterThan(0) + expect(changes[0]).toHaveProperty("address") + expect(changes[0]).toHaveProperty("data") + expect(changes[0]).toHaveProperty("topics") + + const success = await provider.send("eth_uninstallFilter", [filterId]) + expect(success).toBeTruthy() + }, 20e3) + + // Skipping as the method is not implemented + it.skip("eth_getFilterLogs", async () => { + // Deploy ERC-20 contract + const contract = await deployERC20() + const contractAddr = await contract.getAddress() + const filter = { + fromBlock: "latest", + address: contractAddr, + } + // Execute some contract TX + const tx = await contract.transfer(alice, parseEther("0.01")) + await tx.wait(1, 5e3) + + // Create the filter for a contract + const filterId = await provider.send("eth_newFilter", [filter]) + expect(filterId).toBeDefined() + + // Assert logs + const changes = await provider.send("eth_getFilterLogs", [filterId]) + expect(changes.length).toBeGreaterThan(0) + expect(changes[0]).toHaveProperty("address") + expect(changes[0]).toHaveProperty("data") + expect(changes[0]).toHaveProperty("topics") + }, 20e3) + + // Skipping as the method is not implemented + it.skip("eth_getLogs", async () => { + // Deploy ERC-20 contract + const contract = await deployERC20() + const contractAddr = await contract.getAddress() + const filter = { + fromBlock: "latest", + address: contractAddr, + } + // Execute some contract TX + const tx = await contract.transfer(alice, parseEther("0.01")) + + // Assert logs + const changes = await provider.send("eth_getLogs", [filter]) + expect(changes.length).toBeGreaterThan(0) + expect(changes[0]).toHaveProperty("address") + expect(changes[0]).toHaveProperty("data") + expect(changes[0]).toHaveProperty("topics") + }, 20e3) + + it("eth_getProof", async () => { + // Deploy ERC-20 contract + const contract = await deployERC20() + const contractAddr = await contract.getAddress() + + const slot = 1 // Assuming balanceOf is at slot 1 + const storageKey = keccak256( + AbiCoder.defaultAbiCoder().encode( + ["address", "uint256"], + [account.address, slot], + ), + ) + const proof = await provider.send("eth_getProof", [ + contractAddr, + [storageKey], + "latest", + ]) + // Assert proof structure + expect(proof).toHaveProperty("address") + expect(proof).toHaveProperty("balance") + expect(proof).toHaveProperty("codeHash") + expect(proof).toHaveProperty("nonce") + expect(proof).toHaveProperty("storageProof") + + if (proof.storageProof.length > 0) { + expect(proof.storageProof[0]).toHaveProperty("key", storageKey) + expect(proof.storageProof[0]).toHaveProperty("value") + expect(proof.storageProof[0]).toHaveProperty("proof") + } + }, 20e3) + + // Skipping as the method is not implemented + it.skip("eth_getLogs", async () => { + // Deploy ERC-20 contract + const contract = await deployERC20() + const contractAddr = await contract.getAddress() + const filter = { + fromBlock: "latest", + address: contractAddr, + } + // Execute some contract TX + const tx = await contract.transfer(alice, parseEther("0.01")) + await tx.wait(1, 5e3) + + // Assert logs + const logs = await provider.send("eth_getLogs", [filter]) + expect(logs.length).toBeGreaterThan(0) + expect(logs[0]).toHaveProperty("address") + expect(logs[0]).toHaveProperty("data") + expect(logs[0]).toHaveProperty("topics") + }, 20e3) + + it("eth_getProof", async () => { + const contract = await deployERC20() + const contractAddr = await contract.getAddress() + + const slot = 1 // Assuming balanceOf is at slot 1 + const storageKey = keccak256( + AbiCoder.defaultAbiCoder().encode( + ["address", "uint256"], + [account.address, slot], + ), + ) + const proof = await provider.send("eth_getProof", [ + contractAddr, + [storageKey], + "latest", + ]) + // Assert proof structure + expect(proof).toHaveProperty("address") + expect(proof).toHaveProperty("balance") + expect(proof).toHaveProperty("codeHash") + expect(proof).toHaveProperty("nonce") + expect(proof).toHaveProperty("storageProof") + + if (proof.storageProof.length > 0) { + expect(proof.storageProof[0]).toHaveProperty("key", storageKey) + expect(proof.storageProof[0]).toHaveProperty("value") + expect(proof.storageProof[0]).toHaveProperty("proof") + } + }, 20e3) + + it("eth_getStorageAt", async () => { + const contract = await deployERC20() + const contractAddr = await contract.getAddress() + + const value = await provider.getStorage(contractAddr, 1) + expect(value).toBeDefined() + }, 20e3) + + it("eth_getTransactionByBlockHashAndIndex, eth_getTransactionByBlockNumberAndIndex", async () => { + // Execute EVM transfer + const txResponse = await sendTestNibi() + const block = await txResponse.getBlock() + + const txByBlockHash = await provider.send( + "eth_getTransactionByBlockHashAndIndex", + [block.hash, "0x0"], + ) + expect(txByBlockHash).toBeDefined() + expect(txByBlockHash).toHaveProperty("from") + expect(txByBlockHash).toHaveProperty("to") + expect(txByBlockHash).toHaveProperty("blockHash") + expect(txByBlockHash).toHaveProperty("blockNumber") + expect(txByBlockHash).toHaveProperty("value") + + const txByBlockNumber = await provider.send( + "eth_getTransactionByBlockNumberAndIndex", + [block.number, "0x0"], + ) + + expect(txByBlockNumber).toBeDefined() + expect(txByBlockNumber["from"]).toEqual(txByBlockHash["from"]) + expect(txByBlockNumber["to"]).toEqual(txByBlockHash["to"]) + expect(txByBlockNumber["value"]).toEqual(txByBlockHash["value"]) + }, 20e3) + + it("eth_getTransactionByHash", async () => { + const txResponse = await sendTestNibi() + const txByHash = await provider.getTransaction(txResponse.hash) + expect(txByHash).toBeDefined() + expect(txByHash.hash).toEqual(txResponse.hash) + }, 20e3) + + it("eth_getTransactionByHash", async () => { + const txCount = await provider.getTransactionCount(account.address) + expect(txCount).toBeGreaterThanOrEqual(0) + }) + + it("eth_getTransactionReceipt", async () => { + const txResponse = await sendTestNibi() + const txReceipt = await provider.getTransactionReceipt(txResponse.hash) + expect(txReceipt).toBeDefined() + expect(txReceipt.hash).toEqual(txResponse.hash) + }, 20e3) + + it("eth_getUncleCountByBlockHash", async () => { + const latestBlock = await provider.getBlockNumber() + const block = await provider.getBlock(latestBlock) + const uncleCount = await provider.send("eth_getUncleCountByBlockHash", [ + block.hash, + ]) + expect(parseInt(uncleCount)).toBeGreaterThanOrEqual(0) + }) + + it("eth_getUncleCountByBlockNumber", async () => { + const latestBlock = await provider.getBlockNumber() + const uncleCount = await provider.send("eth_getUncleCountByBlockNumber", [ + latestBlock, + ]) + expect(parseInt(uncleCount)).toBeGreaterThanOrEqual(0) + }) + + it("eth_maxPriorityFeePerGas", async () => { + const maxPriorityGas = await provider.send("eth_maxPriorityFeePerGas", []) + expect(parseInt(maxPriorityGas)).toBeGreaterThanOrEqual(0) + }) + + it("eth_newBlockFilter", async () => { + const filterId = await provider.send("eth_newBlockFilter", []) + expect(filterId).toBeDefined() + }) + + it("eth_newPendingTransactionFilter", async () => { + const filterId = await provider.send("eth_newPendingTransactionFilter", []) + expect(filterId).toBeDefined() + }) + + it("eth_syncing", async () => { + const syncing = await provider.send("eth_syncing", []) + expect(syncing).toBeFalsy() + }) }) diff --git a/e2e/evm/test/contract_infinite_loop_gas.test.ts b/e2e/evm/test/contract_infinite_loop_gas.test.ts index 2f992a0fe..15ed369e6 100644 --- a/e2e/evm/test/contract_infinite_loop_gas.test.ts +++ b/e2e/evm/test/contract_infinite_loop_gas.test.ts @@ -1,14 +1,14 @@ -import { describe, expect, it } from "bun:test"; // eslint-disable-line import/no-unresolved -import { toBigInt } from "ethers"; -import { InfiniteLoopGasCompiled__factory } from "../types/ethers-contracts"; -import { account } from "./setup"; +import { describe, expect, it } from "@jest/globals" +import { toBigInt } from "ethers" +import { InfiniteLoopGasCompiled__factory } from "../types/ethers-contracts" +import { account } from "./setup" describe("Infinite loop gas contract", () => { it("should fail due to out of gas error", async () => { - const factory = new InfiniteLoopGasCompiled__factory(account); - const contract = await factory.deploy(); + const factory = new InfiniteLoopGasCompiled__factory(account) + const contract = await factory.deploy() await contract.waitForDeployment() - + expect(contract.counter()).resolves.toBe(toBigInt(0)) try { @@ -18,7 +18,7 @@ describe("Infinite loop gas contract", () => { } catch (error) { expect(error.message).toContain("transaction execution reverted") } - + expect(contract.counter()).resolves.toBe(toBigInt(0)) }, 20e3) }) diff --git a/e2e/evm/test/contract_send_nibi.test.ts b/e2e/evm/test/contract_send_nibi.test.ts index 489f2fe31..6dcf7967e 100644 --- a/e2e/evm/test/contract_send_nibi.test.ts +++ b/e2e/evm/test/contract_send_nibi.test.ts @@ -1,15 +1,11 @@ -import { describe, expect, it } from "bun:test" // eslint-disable-line import/no-unresolved +import { describe, expect, it } from "@jest/globals" import { toBigInt, Wallet } from "ethers" -import { SendNibiCompiled__factory } from "../types/ethers-contracts" import { account, provider } from "./setup" +import { deploySendReceiveNibi } from "./utils" -describe("Send NIBI via smart contract", async () => { - const factory = new SendNibiCompiled__factory(account) - const contract = await factory.deploy() - await contract.waitForDeployment() - expect(contract.getAddress()).resolves.toBeDefined() - +describe("Send NIBI via smart contract", () => { it("should send via transfer method", async () => { + const contract = await deploySendReceiveNibi() const recipient = Wallet.createRandom() const weiToSend = toBigInt(5e12) * toBigInt(1e6) // 5 micro NIBI @@ -39,6 +35,7 @@ describe("Send NIBI via smart contract", async () => { }, 20e3) it("should send via send method", async () => { + const contract = await deploySendReceiveNibi() const recipient = Wallet.createRandom() const weiToSend = toBigInt(100e12) * toBigInt(1e6) // 100 NIBi @@ -68,6 +65,7 @@ describe("Send NIBI via smart contract", async () => { }, 20e3) it("should send via transfer method", async () => { + const contract = await deploySendReceiveNibi() const recipient = Wallet.createRandom() const weiToSend = toBigInt(100e12) * toBigInt(1e6) // 100 NIBI diff --git a/e2e/evm/test/erc20.test.ts b/e2e/evm/test/erc20.test.ts index fddb0e544..b5d87c3f7 100644 --- a/e2e/evm/test/erc20.test.ts +++ b/e2e/evm/test/erc20.test.ts @@ -1,13 +1,11 @@ -import { describe, expect, it } from "bun:test"; // eslint-disable-line import/no-unresolved -import { parseUnits, toBigInt, Wallet } from "ethers"; -import { TestERC20Compiled__factory } from "../types/ethers-contracts"; -import { account } from "./setup"; +import { describe, expect, it } from "@jest/globals" +import { parseUnits, toBigInt, Wallet } from "ethers" +import { account } from "./setup" +import { deployERC20 } from "./utils" describe("ERC-20 contract tests", () => { it("should send properly", async () => { - const factory = new TestERC20Compiled__factory(account); - const contract = await factory.deploy(); - await contract.waitForDeployment() + const contract = await deployERC20() expect(contract.getAddress()).resolves.toBeDefined() const ownerInitialBalance = parseUnits("1000000", 18) @@ -15,13 +13,15 @@ describe("ERC-20 contract tests", () => { expect(contract.balanceOf(account)).resolves.toEqual(ownerInitialBalance) expect(contract.balanceOf(alice)).resolves.toEqual(toBigInt(0)) - + // send to alice const amountToSend = parseUnits("1000", 18) // contract tokens let tx = await contract.transfer(alice, amountToSend) await tx.wait() - expect(contract.balanceOf(account)).resolves.toEqual(ownerInitialBalance - amountToSend) + expect(contract.balanceOf(account)).resolves.toEqual( + ownerInitialBalance - amountToSend, + ) expect(contract.balanceOf(alice)).resolves.toEqual(amountToSend) }, 20e3) }) diff --git a/e2e/evm/test/setup.ts b/e2e/evm/test/setup.ts index d3224016c..dd3232b55 100644 --- a/e2e/evm/test/setup.ts +++ b/e2e/evm/test/setup.ts @@ -1,10 +1,9 @@ import { config } from "dotenv" -import { getDefaultProvider, Wallet } from "ethers" +import { ethers, getDefaultProvider, Wallet } from "ethers" config() -const provider = getDefaultProvider(process.env.JSON_RPC_ENDPOINT) +const provider = new ethers.JsonRpcProvider(process.env.JSON_RPC_ENDPOINT) const account = Wallet.fromPhrase(process.env.MNEMONIC, provider) export { account, provider } - diff --git a/e2e/evm/test/utils.ts b/e2e/evm/test/utils.ts new file mode 100644 index 000000000..6029685b3 --- /dev/null +++ b/e2e/evm/test/utils.ts @@ -0,0 +1,34 @@ +import { + SendNibiCompiled__factory, + TestERC20Compiled__factory, +} from "../types/ethers-contracts" +import { account } from "./setup" +import { parseEther, toBigInt, Wallet } from "ethers" + +const alice = Wallet.createRandom() + +const deployERC20 = async () => { + const factory = new TestERC20Compiled__factory(account) + const contract = await factory.deploy() + await contract.waitForDeployment() + return contract +} +const deploySendReceiveNibi = async () => { + const factory = new SendNibiCompiled__factory(account) + const contract = await factory.deploy() + await contract.waitForDeployment() + return contract +} + +const sendTestNibi = async () => { + const transaction = { + gasLimit: toBigInt(100e3), + to: alice, + value: parseEther("0.01"), + } + const txResponse = await account.sendTransaction(transaction) + await txResponse.wait(1, 10e3) + return txResponse +} + +export { alice, deployERC20, deploySendReceiveNibi, sendTestNibi } From 95ba4af803c4775391850baea766f9eebc237377 Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:05:23 +0900 Subject: [PATCH 4/7] feat(evm-cli): Add the account query to the EVM command. Cover the CLI with tests. (#2002) * feat(evm-cli): expose account query * undo commit of events code * test(evm-cli): more tests * changelog * refactor: small edits after self-review * chore: typo --- CHANGELOG.md | 3 +- app/evmante/evmante_sigverify.go | 11 +- x/evm/cli/cli_setup_test.go | 115 ++++++++++++++++++++ x/evm/cli/cli_test.go | 173 +++++++++++++++++++++++++++++++ x/evm/cli/query.go | 68 +++++++++++- x/evm/cli/tx.go | 107 ++++++++----------- x/evm/evmtest/eth.go | 9 ++ x/evm/keeper/grpc_query.go | 4 +- x/evm/keeper/grpc_query_test.go | 21 +++- 9 files changed, 438 insertions(+), 73 deletions(-) create mode 100644 x/evm/cli/cli_setup_test.go create mode 100644 x/evm/cli/cli_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 313f0d2df..d79e5fe69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,10 +105,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1997](https://github.com/NibiruChain/nibiru/pull/1997) - refactor(evm): Remove unnecessary params: "enable_call", "enable_create". - [#2000](https://github.com/NibiruChain/nibiru/pull/2000) - refactor(evm): simplify ERC-20 keeper methods - [#2001](https://github.com/NibiruChain/nibiru/pull/2001) - refactor(evm): simplify FunToken methods and tests +- [#2002](https://github.com/NibiruChain/nibiru/pull/2002) - feat(evm): Add the account query to the EVM command. Cover the CLI with tests. - [#2003](https://github.com/NibiruChain/nibiru/pull/2003) - fix(evm): fix FunToken conversions between Cosmos and EVM - [#2004](https://github.com/NibiruChain/nibiru/pull/2004) - refactor(evm)!: replace `HexAddr` with `EIP55Addr` - [#2006](https://github.com/NibiruChain/nibiru/pull/2006) - test(evm): e2e tests for eth_* endpoints -- [#2008](https://github.com/NibiruChain/nibiru/pull/2008) - refactor(evm): clean up precompile setups +- [#2008](https://github.com/NibiruChain/nibiru/pull/2008) - refactor(evm): clean up precompile setups - [#2013](https://github.com/NibiruChain/nibiru/pull/2013) - chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. - [#2014](https://github.com/NibiruChain/nibiru/pull/2014) - feat(evm): Emit block bloom event in EndBlock hook. diff --git a/app/evmante/evmante_sigverify.go b/app/evmante/evmante_sigverify.go index 6b858066e..a19c664c8 100644 --- a/app/evmante/evmante_sigverify.go +++ b/app/evmante/evmante_sigverify.go @@ -24,11 +24,12 @@ func NewEthSigVerificationDecorator(k EVMKeeper) EthSigVerificationDecorator { } } -// AnteHandle validates checks that the registered chain id is the same as the one on the message, and -// that the signer address matches the one defined on the message. -// It's not skipped for RecheckTx, because it set `From` address which is critical from other ante handler to work. -// Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user -// won't see the error message. +// AnteHandle validates checks that the registered chain id is the same as the +// one on the message, and that the signer address matches the one defined on the +// message. It's not skipped for RecheckTx, because it set `From` address which +// is critical from other ante handler to work. Failure in RecheckTx will prevent +// tx to be included into block, especially when CheckTx succeed, in which case +// user won't see the error message. func (esvd EthSigVerificationDecorator) AnteHandle( ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler, ) (newCtx sdk.Context, err error) { diff --git a/x/evm/cli/cli_setup_test.go b/x/evm/cli/cli_setup_test.go new file mode 100644 index 000000000..21b11786e --- /dev/null +++ b/x/evm/cli/cli_setup_test.go @@ -0,0 +1,115 @@ +package cli_test + +import ( + "context" + "io" + "testing" + + "github.com/stretchr/testify/suite" + + rpcclientmock "github.com/cometbft/cometbft/rpc/client/mock" + sdkclient "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdktestutil "github.com/cosmos/cosmos-sdk/testutil" + sdktestutilcli "github.com/cosmos/cosmos-sdk/testutil/cli" + testutilmod "github.com/cosmos/cosmos-sdk/types/module/testutil" + + svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + + "github.com/NibiruChain/nibiru/v2/x/evm/cli" + "github.com/NibiruChain/nibiru/v2/x/evm/evmmodule" +) + +type Suite struct { + suite.Suite + + keyring keyring.Keyring + encCfg testutilmod.TestEncodingConfig + baseCtx sdkclient.Context + clientCtx sdkclient.Context + + testAcc sdktestutil.TestAccount +} + +func (s *Suite) SetupSuite() { + s.encCfg = testutilmod.MakeTestEncodingConfig(evmmodule.AppModuleBasic{}) + s.keyring = keyring.NewInMemory(s.encCfg.Codec) + s.baseCtx = sdkclient.Context{}. + WithKeyring(s.keyring). + WithTxConfig(s.encCfg.TxConfig). + WithCodec(s.encCfg.Codec). + WithClient(sdktestutilcli.MockTendermintRPC{Client: rpcclientmock.Client{}}). + WithAccountRetriever(sdkclient.MockAccountRetriever{}). + WithOutput(io.Discard). + WithChainID("test-chain") + + s.clientCtx = s.baseCtx + + testAccs := sdktestutil.CreateKeyringAccounts(s.T(), s.keyring, 1) + s.testAcc = testAccs[0] +} + +func TestSuite(t *testing.T) { + suite.Run(t, new(Suite)) +} + +// Flags for broadcasting transactions +func commonTxArgs() []string { + return []string{ + "--yes=true", // skip confirmation + "--broadcast-mode=sync", + "--fees=1unibi", + "--chain-id=test-chain", + } +} + +type TestCase struct { + name string + args []string + extraArgs []string + wantErr string +} + +func (tc TestCase) NewCtx(s *Suite) sdkclient.Context { + return s.baseCtx +} + +func (tc TestCase) RunTxCmd(s *Suite) { + s.Run(tc.name, func() { + ctx := svrcmd.CreateExecuteContext(context.Background()) + + cmd := cli.GetTxCmd() + cmd.SetContext(ctx) + args := append(tc.args, commonTxArgs()...) + cmd.SetArgs(append(args, tc.extraArgs...)) + + s.Require().NoError(sdkclient.SetCmdClientContextHandler(tc.NewCtx(s), cmd)) + + err := cmd.Execute() + if tc.wantErr != "" { + s.Require().ErrorContains(err, tc.wantErr) + return + } + s.Require().NoError(err) + }) +} + +func (tc TestCase) RunQueryCmd(s *Suite) { + s.Run(tc.name, func() { + ctx := svrcmd.CreateExecuteContext(context.Background()) + + cmd := cli.GetQueryCmd() + cmd.SetContext(ctx) + args := tc.args // don't append common tx args + cmd.SetArgs(append(args, tc.extraArgs...)) + + s.Require().NoError(sdkclient.SetCmdClientContextHandler(tc.NewCtx(s), cmd)) + + err := cmd.Execute() + if tc.wantErr != "" { + s.Require().ErrorContains(err, tc.wantErr) + return + } + s.Require().NoError(err) + }) +} diff --git a/x/evm/cli/cli_test.go b/x/evm/cli/cli_test.go new file mode 100644 index 000000000..99b8b684e --- /dev/null +++ b/x/evm/cli/cli_test.go @@ -0,0 +1,173 @@ +package cli_test + +import ( + "fmt" + "math/big" + + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/v2/x/evm" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" +) + +var ( + dummyAccs = evmtest.NewEthPrivAccs(3) + dummyEthAddr = dummyAccs[1].EthAddr.Hex() + dummyFuntoken = evm.NewFunToken( + gethcommon.BigToAddress(big.NewInt(123)), + "ibc/testtoken", + false, + ) +) + +func (s *Suite) TestCmdConvertCoinToEvm() { + testCases := []TestCase{ + { + name: "happy: convert-coin-to-evm", + args: []string{ + "convert-coin-to-evm", + dummyEthAddr, + fmt.Sprintf("%d%s", 123, dummyFuntoken.BankDenom), + }, + extraArgs: []string{fmt.Sprintf("--from=%s", s.testAcc.Address)}, + wantErr: "", + }, + { + name: "sad: coin format", + args: []string{ + "convert-coin-to-evm", + dummyAccs[1].EthAddr.Hex(), + fmt.Sprintf("%s %d", dummyFuntoken.BankDenom, 123), + }, + extraArgs: []string{fmt.Sprintf("--from=%s", s.testAcc.Address)}, + wantErr: "invalid decimal coin expression", + }, + } + + for _, tc := range testCases { + tc.RunTxCmd(s) + } +} + +func (s *Suite) TestCmdCreateFunToken() { + testCases := []TestCase{ + { + name: "happy: create-funtoken (erc20)", + args: []string{ + "create-funtoken", + fmt.Sprintf("--erc20=%s", dummyEthAddr), + }, + extraArgs: []string{fmt.Sprintf("--from=%s", s.testAcc.Address)}, + wantErr: "", + }, + { + name: "happy: create-funtoken (bank coin)", + args: []string{ + "create-funtoken", + fmt.Sprintf("--bank-denom=%s", dummyFuntoken.BankDenom), + }, + extraArgs: []string{fmt.Sprintf("--from=%s", s.testAcc.Address)}, + wantErr: "", + }, + { + name: "sad: too many args", + args: []string{ + "create-funtoken", + fmt.Sprintf("--erc20=%s", dummyEthAddr), + fmt.Sprintf("--bank-denom=%s", dummyFuntoken.BankDenom), + }, + extraArgs: []string{fmt.Sprintf("--from=%s", s.testAcc.Address)}, + wantErr: "exactly one of the flags --bank-denom or --erc20 must be specified", + }, + } + + for _, tc := range testCases { + tc.RunTxCmd(s) + } +} + +func (s *Suite) TestCmdQueryAccount() { + testCases := []TestCase{ + { + name: "happy: query account (bech32)", + args: []string{ + "account", + dummyAccs[0].NibiruAddr.String(), + }, + wantErr: "", + }, + { + name: "happy: query account (eth hex)", + args: []string{ + "account", + dummyAccs[0].EthAddr.Hex(), + }, + wantErr: "", + }, + { + name: "happy: query account (eth hex) --offline", + args: []string{ + "account", + dummyAccs[0].EthAddr.Hex(), + "--offline", + }, + wantErr: "", + }, + { + name: "happy: query account (bech32) --offline", + args: []string{ + "account", + dummyAccs[0].NibiruAddr.String(), + "--offline", + }, + wantErr: "", + }, + { + name: "sad: too many args", + args: []string{ + "funtoken", + "arg1", + "arg2", + }, + wantErr: "accepts 1 arg", + }, + } + + for _, tc := range testCases { + tc.RunQueryCmd(s) + } +} + +func (s *Suite) TestCmdQueryFunToken() { + testCases := []TestCase{ + { + name: "happy: query funtoken (bank coin denom)", + args: []string{ + "funtoken", + dummyFuntoken.BankDenom, + }, + wantErr: "", + }, + { + name: "happy: query funtoken (erc20 addr)", + args: []string{ + "funtoken", + dummyFuntoken.Erc20Addr.String(), + }, + wantErr: "", + }, + { + name: "sad: too many args", + args: []string{ + "funtoken", + "arg1", + "arg2", + }, + wantErr: "accepts 1 arg", + }, + } + + for _, tc := range testCases { + tc.RunQueryCmd(s) + } +} diff --git a/x/evm/cli/query.go b/x/evm/cli/query.go index 1c580e49e..349ee7bb2 100644 --- a/x/evm/cli/query.go +++ b/x/evm/cli/query.go @@ -9,6 +9,10 @@ import ( "github.com/cosmos/cosmos-sdk/version" "github.com/spf13/cobra" + sdk "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/x/evm" ) @@ -25,7 +29,8 @@ func GetQueryCmd() *cobra.Command { // Add subcommands cmds := []*cobra.Command{ - GetCmdFunToken(), + CmdQueryFunToken(), + CmdQueryAccount(), } for _, cmd := range cmds { moduleQueryCmd.AddCommand(cmd) @@ -33,8 +38,8 @@ func GetQueryCmd() *cobra.Command { return moduleQueryCmd } -// GetCmdFunToken returns fungible token mapping for either bank coin or erc20 addr -func GetCmdFunToken() *cobra.Command { +// CmdQueryFunToken returns fungible token mapping for either bank coin or erc20 addr +func CmdQueryFunToken() *cobra.Command { cmd := &cobra.Command{ Use: "funtoken [coin-or-erc20addr]", Short: "Query evm fungible token mapping", @@ -69,3 +74,60 @@ $ %s query %s get-fun-token 0x7D4B7B8CA7E1a24928Bb96D59249c7a5bd1DfBe6 flags.AddQueryFlagsToCmd(cmd) return cmd } + +func CmdQueryAccount() *cobra.Command { + cmd := &cobra.Command{ + Use: "account [address]", + Short: "Query account by its hex address or bech32", + Long: strings.TrimSpace(""), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := evm.NewQueryClient(clientCtx) + + req := &evm.QueryEthAccountRequest{ + Address: args[0], + } + + isBech32, err := req.Validate() + fmt.Printf("TODO: UD-DEBUG: req.String(): %v\n", req.String()) + fmt.Printf("TODO: UD-DEBUG: err: %v\n", err) + if err != nil { + return err + } + + offline, _ := cmd.Flags().GetBool("offline") + + if offline { + var addrEth gethcommon.Address + var addrBech32 sdk.AccAddress + + if isBech32 { + addrBech32 = sdk.MustAccAddressFromBech32(req.Address) + addrEth = eth.NibiruAddrToEthAddr(addrBech32) + } else { + addrEth = gethcommon.HexToAddress(req.Address) + addrBech32 = eth.EthAddrToNibiruAddr(addrEth) + } + + resp := new(evm.QueryEthAccountResponse) + resp.EthAddress = addrEth.Hex() + resp.Bech32Address = addrBech32.String() + return clientCtx.PrintProto(resp) + } + + resp, err := queryClient.EthAccount(cmd.Context(), req) + if err != nil { + return fmt.Errorf("consider using the \"--offline\" flag: %w", err) + } + + return clientCtx.PrintProto(resp) + }, + } + cmd.Flags().Bool("offline", false, "Skip the query and only return addresses.") + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/evm/cli/tx.go b/x/evm/cli/tx.go index c8a625a21..dbda5f7fd 100644 --- a/x/evm/cli/tx.go +++ b/x/evm/cli/tx.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/MakeNowJust/heredoc/v2" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" @@ -25,9 +26,8 @@ func GetTxCmd() *cobra.Command { } cmds := []*cobra.Command{ - CmdCreateFunTokenFromBankCoin(), - CmdCreateFunTokenFromERC20(), - ConvertCoinToEvm(), + CmdCreateFunToken(), + CmdConvertCoinToEvm(), } for _, cmd := range cmds { txCmd.AddCommand(cmd) @@ -36,87 +36,72 @@ func GetTxCmd() *cobra.Command { return txCmd } -// CmdCreateFunTokenFromBankCoin broadcast MsgCreateFunToken -func CmdCreateFunTokenFromBankCoin() *cobra.Command { +// CmdCreateFunToken broadcast MsgCreateFunToken +func CmdCreateFunToken() *cobra.Command { cmd := &cobra.Command{ - Use: "create-funtoken-from-bank-coin [denom] [flags]", - Short: `Create an erc20 fungible token from bank coin [denom]"`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - txFactory, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) - if err != nil { - return err - } - txFactory = txFactory. - WithTxConfig(clientCtx.TxConfig). - WithAccountRetriever(clientCtx.AccountRetriever) + Use: "create-funtoken [flags]", + Short: `Create a fungible token mapping between a bank coin and erc20 contract"`, + Long: heredoc.Doc(` + Example: Creating a fungible token mapping from bank coin. - msg := &evm.MsgCreateFunToken{ - Sender: clientCtx.GetFromAddress().String(), - FromBankDenom: args[0], - } - return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txFactory, msg) - }, - } - flags.AddTxFlagsToCmd(cmd) - return cmd -} + create-funtoken --bank-denom="ibc/..." -// CmdCreateFunTokenFromERC20 broadcast MsgCreateFunToken -func CmdCreateFunTokenFromERC20() *cobra.Command { - cmd := &cobra.Command{ - Use: "create-funtoken-from-erc20 [erc20addr] [flags]", - Short: `Create a fungible token from erc20 contract [erc20addr]"`, - Args: cobra.ExactArgs(1), + Example: Creating a fungible token mapping from an ERC20. + + create-funtoken --erc20=[erc20-address] + `), + Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } - txFactory, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) - if err != nil { - return err - } - txFactory = txFactory. - WithTxConfig(clientCtx.TxConfig). - WithAccountRetriever(clientCtx.AccountRetriever) - erc20Addr, err := eth.NewEIP55AddrFromStr(args[0]) - if err != nil { - return err + + bankDenom, _ := cmd.Flags().GetString("bank-denom") + erc20AddrStr, _ := cmd.Flags().GetString("erc20") + + if (bankDenom == "" && erc20AddrStr == "") || + (bankDenom != "" && erc20AddrStr != "") { + return fmt.Errorf("exactly one of the flags --bank-denom or --erc20 must be specified") } + msg := &evm.MsgCreateFunToken{ - Sender: clientCtx.GetFromAddress().String(), - FromErc20: &erc20Addr, + Sender: clientCtx.GetFromAddress().String(), } - return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txFactory, msg) + if bankDenom != "" { + if err := sdk.ValidateDenom(bankDenom); err != nil { + return err + } + msg.FromBankDenom = bankDenom + } else { + erc20Addr, err := eth.NewEIP55AddrFromStr(erc20AddrStr) + if err != nil { + return err + } + msg.FromErc20 = &erc20Addr + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) + cmd.Flags().String("bank-denom", "", "The bank denom to create a fungible token from") + cmd.Flags().String("erc20", "", "The ERC20 address to create a fungible token from") + return cmd } -// ConvertCoinToEvm broadcast MsgConvertCoinToEvm -func ConvertCoinToEvm() *cobra.Command { +// CmdConvertCoinToEvm broadcast MsgConvertCoinToEvm +func CmdConvertCoinToEvm() *cobra.Command { cmd := &cobra.Command{ - Use: "send-funtoken-to-erc20 [to_eth_addr] [coin] [flags]", - Short: `Send bank [coin] to its erc20 representation for the user [to_eth_addr]"`, + Use: "convert-coin-to-evm [to_eth_addr] [coin] [flags]", + Short: `Convert bank [coin] to its erc20 representation and send to the [to_eth_addr] account"`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } - txFactory, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) - if err != nil { - return err - } - txFactory = txFactory. - WithTxConfig(clientCtx.TxConfig). - WithAccountRetriever(clientCtx.AccountRetriever) eip55Addr, err := eth.NewEIP55AddrFromStr(args[0]) if err != nil { @@ -132,7 +117,7 @@ func ConvertCoinToEvm() *cobra.Command { BankCoin: coin, ToEthAddr: eip55Addr, } - return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txFactory, msg) + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) diff --git a/x/evm/evmtest/eth.go b/x/evm/evmtest/eth.go index 05792bb7e..f6037cfc5 100644 --- a/x/evm/evmtest/eth.go +++ b/x/evm/evmtest/eth.go @@ -36,6 +36,15 @@ func NewEthPrivAcc() EthPrivKeyAcc { } } +// NewEthPrivAccs calls [NewEthAccInfo] n times. +func NewEthPrivAccs(n int) []EthPrivKeyAcc { + infos := make([]EthPrivKeyAcc, n) + for idx := 0; idx < n; idx++ { + infos[idx] = NewEthPrivAcc() + } + return infos +} + type EthPrivKeyAcc struct { EthAddr gethcommon.Address NibiruAddr sdk.AccAddress diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index b0f703ae5..fae288468 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -67,12 +67,12 @@ func (k Keeper) EthAccount( acct := k.GetAccountOrEmpty(ctx, addrEth) return &evm.QueryEthAccountResponse{ + EthAddress: addrEth.Hex(), + Bech32Address: addrBech32.String(), Balance: acct.BalanceNative.String(), BalanceWei: evm.NativeToWei(acct.BalanceNative).String(), CodeHash: gethcommon.BytesToHash(acct.CodeHash).Hex(), Nonce: acct.Nonce, - EthAddress: addrEth.Hex(), - Bech32Address: addrBech32.String(), }, nil } diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index 2a1e2b86b..9234bbe6b 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -138,7 +138,7 @@ func (s *Suite) TestQueryEvmAccount() { wantErr: "not a valid ethereum hex addr", }, { - name: "happy: not existing account", + name: "happy: nonexistent account (hex addr input)", scenario: func(deps *evmtest.TestDeps) (req In, wantResp Out) { ethAcc := evmtest.NewEthPrivAcc() req = &evm.QueryEthAccountRequest{ @@ -156,6 +156,25 @@ func (s *Suite) TestQueryEvmAccount() { }, wantErr: "", }, + { + name: "happy: nonexistent account (bech32 input)", + scenario: func(deps *evmtest.TestDeps) (req In, wantResp Out) { + ethAcc := evmtest.NewEthPrivAcc() + req = &evm.QueryEthAccountRequest{ + Address: ethAcc.NibiruAddr.String(), + } + wantResp = &evm.QueryEthAccountResponse{ + Balance: "0", + BalanceWei: "0", + CodeHash: gethcommon.BytesToHash(evm.EmptyCodeHash).Hex(), + Nonce: 0, + EthAddress: ethAcc.EthAddr.String(), + Bech32Address: ethAcc.NibiruAddr.String(), + } + return req, wantResp + }, + wantErr: "", + }, } for _, tc := range testCases { From 7ae2443beac162193bc8872a500c6323b4779bf2 Mon Sep 17 00:00:00 2001 From: Oleg Nikonychev Date: Wed, 28 Aug 2024 18:00:25 +0400 Subject: [PATCH 5/7] chore(evm): enabled debug rpc api on localnet (#2019) * chore(evm): enabled debug rpc api on localnet * chore: changelog update --------- Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> --- CHANGELOG.md | 1 + contrib/make/build.mk | 4 +++- contrib/scripts/localnet.sh | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d79e5fe69..63b574cb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2008](https://github.com/NibiruChain/nibiru/pull/2008) - refactor(evm): clean up precompile setups - [#2013](https://github.com/NibiruChain/nibiru/pull/2013) - chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. - [#2014](https://github.com/NibiruChain/nibiru/pull/2014) - feat(evm): Emit block bloom event in EndBlock hook. +- [#2019](https://github.com/NibiruChain/nibiru/pull/2019) - chore(evm): enabled debug rpc api on localnet. #### Dapp modules: perp, spot, oracle, etc diff --git a/contrib/make/build.mk b/contrib/make/build.mk index 019ac6f6a..8a4c27d58 100644 --- a/contrib/make/build.mk +++ b/contrib/make/build.mk @@ -70,7 +70,9 @@ ldflags := $(strip $(ldflags)) BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' CGO_CFLAGS := -I$(TEMPDIR)/include CGO_LDFLAGS := -L$(TEMPDIR)/lib -ifneq ($(OS_NAME),darwin) +ifeq ($(OS_NAME),darwin) + CGO_LDFLAGS += -lrocksdb -lstdc++ -lz -lbz2 +else CGO_LDFLAGS += -static -lm -lbz2 endif diff --git a/contrib/scripts/localnet.sh b/contrib/scripts/localnet.sh index cc32dd28b..a33e7a3fd 100755 --- a/contrib/scripts/localnet.sh +++ b/contrib/scripts/localnet.sh @@ -162,6 +162,9 @@ sed -i $SEDOPTION '/\[grpc\]/,+3 s/enable = false/enable = true/' $CHAIN_DIR/con echo_info "config/app.toml: Enabling JSON API server" sed -i $SEDOPTION '/\[json\-rpc\]/,+3 s/enable = false/enable = true/' $CHAIN_DIR/config/app.toml +echo_info "config/app.toml: Enabling debug evm api" +sed -i $SEDOPTION '/\[json\-rpc\]/,+13 s/api = "eth,net,web3"/api = "eth,net,web3,debug"/' $CHAIN_DIR/config/app.toml + # Enable Swagger Docs echo_info "config/app.toml: Enabling Swagger Docs" sed -i $SEDOPTION 's/swagger = false/swagger = true/' $CHAIN_DIR/config/app.toml From 3e9bda9f737b5e531760b5e5298e5e65389348f8 Mon Sep 17 00:00:00 2001 From: Oleg Nikonychev Date: Thu, 29 Aug 2024 22:54:49 +0400 Subject: [PATCH 6/7] test(evm): e2e tests for debug namespace (#2020) * test(evm): e2e tests for debug namespace * chore: changelog update --- CHANGELOG.md | 1 + e2e/evm/README.md | 2 +- e2e/evm/test/debug_queries.test.ts | 76 +++++++++++++++++++ ...ic_queries.test.ts => eth_queries.test.ts} | 52 +------------ e2e/evm/test/native_transfer.test.ts | 42 ++++++++++ 5 files changed, 123 insertions(+), 50 deletions(-) create mode 100644 e2e/evm/test/debug_queries.test.ts rename e2e/evm/test/{basic_queries.test.ts => eth_queries.test.ts} (87%) create mode 100644 e2e/evm/test/native_transfer.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b574cb3..2a45cd410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2013](https://github.com/NibiruChain/nibiru/pull/2013) - chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. - [#2014](https://github.com/NibiruChain/nibiru/pull/2014) - feat(evm): Emit block bloom event in EndBlock hook. - [#2019](https://github.com/NibiruChain/nibiru/pull/2019) - chore(evm): enabled debug rpc api on localnet. +- [#2020](https://github.com/NibiruChain/nibiru/pull/2020) - test(evm): e2e tests for debug namespace #### Dapp modules: perp, spot, oracle, etc diff --git a/e2e/evm/README.md b/e2e/evm/README.md index 9e8d359d8..508956d15 100644 --- a/e2e/evm/README.md +++ b/e2e/evm/README.md @@ -41,7 +41,7 @@ bun test v1.1.12 (43f0913c) test/erc20.test.ts: ✓ ERC-20 contract tests > should send properly [8410.41ms] -test/basic_queries.test.ts: +test/native_transfer.test.ts: ✓ Basic Queries > Simple transfer, balance check [4251.02ms] test/contract_infinite_loop_gas.test.ts: diff --git a/e2e/evm/test/debug_queries.test.ts b/e2e/evm/test/debug_queries.test.ts new file mode 100644 index 000000000..2d6a4909a --- /dev/null +++ b/e2e/evm/test/debug_queries.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it, beforeAll } from "@jest/globals" +import { parseEther } from "ethers" +import { provider } from "./setup" +import { alice, deployERC20 } from "./utils" + +describe("debug queries", () => { + let contractAddress + let txHash + let txIndex + let blockNumber + let blockHash + + beforeAll(async () => { + // Deploy ERC-20 contract + const contract = await deployERC20() + contractAddress = await contract.getAddress() + + // Execute some contract TX + const txResponse = await contract.transfer(alice, parseEther("0.01")) + await txResponse.wait(1, 5e3) + + const receipt = await provider.getTransactionReceipt(txResponse.hash) + txHash = txResponse.hash + txIndex = txResponse.index + blockNumber = receipt.blockNumber + blockHash = receipt.blockHash + }, 20e3) + + it("debug_traceBlockByNumber", async () => { + const traceResult = await provider.send("debug_traceBlockByNumber", [ + blockNumber, + ]) + expectTrace(traceResult) + }) + + it("debug_traceBlockByHash", async () => { + const traceResult = await provider.send("debug_traceBlockByHash", [ + blockHash, + ]) + expectTrace(traceResult) + }) + + it("debug_traceTransaction", async () => { + const traceResult = await provider.send("debug_traceTransaction", [txHash]) + expectTrace([{ result: traceResult }]) + }) + + // TODO: implement that in EVM + it.skip("debug_getBadBlocks", async () => { + const traceResult = await provider.send("debug_getBadBlocks", [txHash]) + expect(traceResult).toBeDefined() + }) + + // TODO: implement that in EVM + it.skip("debug_storageRangeAt", async () => { + const traceResult = await provider.send("debug_storageRangeAt", [ + blockNumber, + txIndex, + contractAddress, + "0x0", + 100, + ]) + expect(traceResult).toBeDefined() + }) +}) + +const expectTrace = (traceResult: any[]) => { + expect(traceResult).toBeDefined() + expect(traceResult.length).toBeGreaterThan(0) + + const trace = traceResult[0]["result"] + expect(trace).toHaveProperty("failed", false) + expect(trace).toHaveProperty("gas") + expect(trace).toHaveProperty("returnValue") + expect(trace).toHaveProperty("structLogs") +} diff --git a/e2e/evm/test/basic_queries.test.ts b/e2e/evm/test/eth_queries.test.ts similarity index 87% rename from e2e/evm/test/basic_queries.test.ts rename to e2e/evm/test/eth_queries.test.ts index a974a70a1..a06e894ea 100644 --- a/e2e/evm/test/basic_queries.test.ts +++ b/e2e/evm/test/eth_queries.test.ts @@ -1,56 +1,10 @@ import { describe, expect, it } from "@jest/globals" -import { - toBigInt, - Wallet, - parseEther, - keccak256, - AbiCoder, - ethers, -} from "ethers" +import { parseEther, keccak256, AbiCoder } from "ethers" import { account, provider } from "./setup" -import { - SendNibiCompiled__factory, - TestERC20Compiled__factory, -} from "../types/ethers-contracts" +import { SendNibiCompiled__factory } from "../types/ethers-contracts" import { alice, deployERC20, sendTestNibi } from "./utils" -describe("Basic Queries", () => { - it("Simple transfer, balance check", async () => { - const amountToSend = toBigInt(5e12) * toBigInt(1e6) // unibi - const senderBalanceBefore = await provider.getBalance(account) - const recipientBalanceBefore = await provider.getBalance(alice) - expect(senderBalanceBefore).toBeGreaterThan(0) - expect(recipientBalanceBefore).toEqual(BigInt(0)) - - // Execute EVM transfer - const transaction = { - gasLimit: toBigInt(100e3), - to: alice, - value: amountToSend, - } - const txResponse = await account.sendTransaction(transaction) - await txResponse.wait(1, 10e3) - expect(txResponse).toHaveProperty("blockHash") - - const senderBalanceAfter = await provider.getBalance(account) - const recipientBalanceAfter = await provider.getBalance(alice) - - // Assert balances with logging - const tenPow12 = toBigInt(1e12) - const gasUsed = 50000n // 50k gas for the transaction - const txCostMicronibi = amountToSend / tenPow12 + gasUsed - const txCostWei = txCostMicronibi * tenPow12 - const expectedSenderWei = senderBalanceBefore - txCostWei - console.debug("DEBUG should send via transfer method %o:", { - senderBalanceBefore, - amountToSend, - expectedSenderWei, - senderBalanceAfter, - }) - expect(senderBalanceAfter).toEqual(expectedSenderWei) - expect(recipientBalanceAfter).toEqual(amountToSend) - }, 20e3) - +describe("eth queries", () => { it("eth_accounts", async () => { const accounts = await provider.listAccounts() expect(accounts).not.toHaveLength(0) diff --git a/e2e/evm/test/native_transfer.test.ts b/e2e/evm/test/native_transfer.test.ts new file mode 100644 index 000000000..d25ef4cc9 --- /dev/null +++ b/e2e/evm/test/native_transfer.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "@jest/globals" +import { toBigInt } from "ethers" +import { account, provider } from "./setup" +import { alice } from "./utils" + +describe("native transfer", () => { + it("simple transfer, balance check", async () => { + const amountToSend = toBigInt(5e12) * toBigInt(1e6) // unibi + const senderBalanceBefore = await provider.getBalance(account) + const recipientBalanceBefore = await provider.getBalance(alice) + expect(senderBalanceBefore).toBeGreaterThan(0) + expect(recipientBalanceBefore).toEqual(BigInt(0)) + + // Execute EVM transfer + const transaction = { + gasLimit: toBigInt(100e3), + to: alice, + value: amountToSend, + } + const txResponse = await account.sendTransaction(transaction) + await txResponse.wait(1, 10e3) + expect(txResponse).toHaveProperty("blockHash") + + const senderBalanceAfter = await provider.getBalance(account) + const recipientBalanceAfter = await provider.getBalance(alice) + + // Assert balances with logging + const tenPow12 = toBigInt(1e12) + const gasUsed = 50000n // 50k gas for the transaction + const txCostMicronibi = amountToSend / tenPow12 + gasUsed + const txCostWei = txCostMicronibi * tenPow12 + const expectedSenderWei = senderBalanceBefore - txCostWei + console.debug("DEBUG should send via transfer method %o:", { + senderBalanceBefore, + amountToSend, + expectedSenderWei, + senderBalanceAfter, + }) + expect(senderBalanceAfter).toEqual(expectedSenderWei) + expect(recipientBalanceAfter).toEqual(amountToSend) + }, 20e3) +}) From 9c46873c5b3811b98d175e497aad3c1311039a95 Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:18:43 +0900 Subject: [PATCH 7/7] fix(evm): Fix DynamicFeeTx gas cap parameters (#2017) --- CHANGELOG.md | 1 + app/evmante/evmante_can_transfer.go | 8 +- app/evmante/evmante_emit_event.go | 4 +- app/evmante/evmante_gas_consume.go | 19 +- app/evmante/evmante_increment_sender_seq.go | 8 +- app/evmante/evmante_mempool_fees.go | 25 +- app/evmante/evmante_setup_ctx.go | 4 +- app/evmante/evmante_sigverify.go | 8 +- app/evmante/evmante_validate_basic.go | 24 +- app/evmante/evmante_verify_eth_acc.go | 14 +- app/server/config/server_config.go | 40 +- app/server/start.go | 83 ++- e2e/evm/bun.lockb | Bin 129508 -> 216205 bytes e2e/evm/test/basic_queries.test.ts | 397 ++++++++++ .../test/contract_infinite_loop_gas.test.ts | 7 +- e2e/evm/test/contract_send_nibi.test.ts | 146 ++-- e2e/evm/test/debug_queries.test.ts | 33 +- e2e/evm/test/erc20.test.ts | 4 +- e2e/evm/test/eth_queries.test.ts | 105 ++- e2e/evm/test/utils.ts | 37 +- eth/rpc/backend/call_tx.go | 5 +- eth/rpc/backend/call_tx_test.go | 10 +- eth/rpc/backend/tx_info.go | 4 +- eth/rpc/rpc.go | 2 +- eth/rpc/rpcapi/eth_api_test.go | 184 +++-- eth/rpc/rpcapi/net_api_test.go | 13 + proto/eth/evm/v1/tx.proto | 64 +- x/common/testutil/testnetwork/network.go | 38 +- x/common/testutil/testnetwork/start_node.go | 174 +++++ x/common/testutil/testnetwork/util.go | 126 ---- .../testutil/testnetwork/validator_node.go | 39 +- x/evm/const.go | 4 + x/evm/evmtest/evmante.go | 4 +- x/evm/evmtest/tx.go | 19 + x/evm/json_tx_args_test.go | 10 +- x/evm/keeper/gas_fees.go | 67 +- x/evm/keeper/gas_fees_test.go | 112 +++ x/evm/keeper/keeper.go | 9 +- x/evm/keeper/msg_server.go | 49 +- x/evm/msg.go | 6 +- x/evm/msg_test.go | 2 +- x/evm/tx.go | 301 +------- x/evm/tx.pb.go | 56 +- x/evm/tx_data.go | 128 +++- ...{access_list.go => tx_data_access_list.go} | 87 +-- ...st_test.go => tx_data_access_list_test.go} | 4 +- x/evm/tx_data_dynamic_fee.go | 312 ++++++++ x/evm/tx_data_dynamic_fee_test.go | 676 ++++++++++++++++++ x/evm/{legacy_tx.go => tx_data_legacy.go} | 83 +-- ...gacy_tx_test.go => tx_data_legacy_test.go} | 103 +-- x/evm/tx_test.go | 630 +--------------- x/evm/vmtracer.go | 13 +- 52 files changed, 2689 insertions(+), 1612 deletions(-) create mode 100644 e2e/evm/test/basic_queries.test.ts create mode 100644 eth/rpc/rpcapi/net_api_test.go create mode 100644 x/common/testutil/testnetwork/start_node.go rename x/evm/{access_list.go => tx_data_access_list.go} (73%) rename x/evm/{access_list_test.go => tx_data_access_list_test.go} (88%) create mode 100644 x/evm/tx_data_dynamic_fee.go create mode 100644 x/evm/tx_data_dynamic_fee_test.go rename x/evm/{legacy_tx.go => tx_data_legacy.go} (68%) rename x/evm/{legacy_tx_test.go => tx_data_legacy_test.go} (74%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a45cd410..a34925447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2008](https://github.com/NibiruChain/nibiru/pull/2008) - refactor(evm): clean up precompile setups - [#2013](https://github.com/NibiruChain/nibiru/pull/2013) - chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile. - [#2014](https://github.com/NibiruChain/nibiru/pull/2014) - feat(evm): Emit block bloom event in EndBlock hook. +- [#2017](https://github.com/NibiruChain/nibiru/pull/2017) - fix(evm): Fix DynamicFeeTx gas cap parameters - [#2019](https://github.com/NibiruChain/nibiru/pull/2019) - chore(evm): enabled debug rpc api on localnet. - [#2020](https://github.com/NibiruChain/nibiru/pull/2020) - test(evm): e2e tests for debug namespace diff --git a/app/evmante/evmante_can_transfer.go b/app/evmante/evmante_can_transfer.go index 86aea2765..6be462e3b 100644 --- a/app/evmante/evmante_can_transfer.go +++ b/app/evmante/evmante_can_transfer.go @@ -6,7 +6,7 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" gethcommon "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" @@ -40,7 +40,7 @@ func (ctd CanTransferDecorator) AnteHandle( msgEthTx, ok := msg.(*evm.MsgEthereumTx) if !ok { return ctx, errors.Wrapf( - errortypes.ErrUnknownRequest, + sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil), ) } @@ -62,7 +62,7 @@ func (ctd CanTransferDecorator) AnteHandle( } if coreMsg.GasFeeCap().Cmp(baseFee) < 0 { return ctx, errors.Wrapf( - errortypes.ErrInsufficientFee, + sdkerrors.ErrInsufficientFee, "max fee per gas less than block base fee (%s < %s)", coreMsg.GasFeeCap(), baseFee, ) @@ -89,7 +89,7 @@ func (ctd CanTransferDecorator) AnteHandle( !evmInstance.Context.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) { balanceWei := stateDB.GetBalance(coreMsg.From()) return ctx, errors.Wrapf( - errortypes.ErrInsufficientFunds, + sdkerrors.ErrInsufficientFunds, "failed to transfer %s wei (balance=%s) from address %s using the EVM block context transfer function", coreMsg.Value(), balanceWei, diff --git a/app/evmante/evmante_emit_event.go b/app/evmante/evmante_emit_event.go index 5c81b6b9e..95b85b648 100644 --- a/app/evmante/evmante_emit_event.go +++ b/app/evmante/evmante_emit_event.go @@ -6,7 +6,7 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/NibiruChain/nibiru/v2/x/evm" ) @@ -36,7 +36,7 @@ func (eeed EthEmitEventDecorator) AnteHandle( msgEthTx, ok := msg.(*evm.MsgEthereumTx) if !ok { return ctx, errorsmod.Wrapf( - errortypes.ErrUnknownRequest, + sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil), ) diff --git a/app/evmante/evmante_gas_consume.go b/app/evmante/evmante_gas_consume.go index 94cf3bbda..3a704a766 100644 --- a/app/evmante/evmante_gas_consume.go +++ b/app/evmante/evmante_gas_consume.go @@ -6,7 +6,7 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/NibiruChain/nibiru/v2/eth" @@ -34,8 +34,7 @@ func NewAnteDecEthGasConsume( // AnteHandle validates that the Ethereum tx message has enough to cover // intrinsic gas (during CheckTx only) and that the sender has enough balance to -// pay for the gas cost. If the balance is not sufficient, it will be attempted -// to withdraw enough staking rewards for the payment. +// pay for the gas cost. // // Intrinsic gas for a transaction is the amount of gas that the transaction uses // before the transaction is executed. The gas is a constant value plus any cost @@ -72,13 +71,13 @@ func (anteDec AnteDecEthGasConsume) AnteHandle( // Use the lowest priority of all the messages as the final one. minPriority := int64(math.MaxInt64) - baseFee := anteDec.evmKeeper.GetBaseFee(ctx) + baseFeeMicronibiPerGas := anteDec.evmKeeper.GetBaseFee(ctx) for _, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evm.MsgEthereumTx) if !ok { return ctx, errors.Wrapf( - errortypes.ErrUnknownRequest, + sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil), ) @@ -101,7 +100,7 @@ func (anteDec AnteDecEthGasConsume) AnteHandle( gasWanted += txData.GetGas() } - fees, err := keeper.VerifyFee(txData, evmDenom, baseFee, ctx.IsCheckTx()) + fees, err := keeper.VerifyFee(txData, evmDenom, baseFeeMicronibiPerGas, ctx.IsCheckTx()) if err != nil { return ctx, errors.Wrapf(err, "failed to verify the fees") } @@ -117,7 +116,7 @@ func (anteDec AnteDecEthGasConsume) AnteHandle( ), ) - priority := evm.GetTxPriority(txData, baseFee) + priority := evm.GetTxPriority(txData, baseFeeMicronibiPerGas) if priority < minPriority { minPriority = priority @@ -135,7 +134,7 @@ func (anteDec AnteDecEthGasConsume) AnteHandle( // EthSetupContextDecorator, so it will never exceed the block gas limit. if gasWanted > blockGasLimit { return ctx, errors.Wrapf( - errortypes.ErrOutOfGas, + sdkerrors.ErrOutOfGas, "tx gas (%d) exceeds block gas limit (%d)", gasWanted, blockGasLimit, @@ -158,7 +157,9 @@ func (anteDec AnteDecEthGasConsume) AnteHandle( // deductFee checks if the fee payer has enough funds to pay for the fees and deducts them. // If the spendable balance is not enough, it tries to claim enough staking rewards to cover the fees. -func (anteDec AnteDecEthGasConsume) deductFee(ctx sdk.Context, fees sdk.Coins, feePayer sdk.AccAddress) error { +func (anteDec AnteDecEthGasConsume) deductFee( + ctx sdk.Context, fees sdk.Coins, feePayer sdk.AccAddress, +) error { if fees.IsZero() { return nil } diff --git a/app/evmante/evmante_increment_sender_seq.go b/app/evmante/evmante_increment_sender_seq.go index 94daa2999..2dfa54429 100644 --- a/app/evmante/evmante_increment_sender_seq.go +++ b/app/evmante/evmante_increment_sender_seq.go @@ -4,7 +4,7 @@ package evmante import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/ante" gethcommon "github.com/ethereum/go-ethereum/common" @@ -38,7 +38,7 @@ func (issd AnteDecEthIncrementSenderSequence) AnteHandle( msgEthTx, ok := msg.(*evm.MsgEthereumTx) if !ok { return ctx, errors.Wrapf( - errortypes.ErrUnknownRequest, + sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil), ) } @@ -52,7 +52,7 @@ func (issd AnteDecEthIncrementSenderSequence) AnteHandle( acc := issd.accountKeeper.GetAccount(ctx, msgEthTx.GetFrom()) if acc == nil { return ctx, errors.Wrapf( - errortypes.ErrUnknownAddress, + sdkerrors.ErrUnknownAddress, "account %s is nil", gethcommon.BytesToAddress(msgEthTx.GetFrom().Bytes()), ) } @@ -62,7 +62,7 @@ func (issd AnteDecEthIncrementSenderSequence) AnteHandle( // with same sender, they'll be accepted. if txData.GetNonce() != nonce { return ctx, errors.Wrapf( - errortypes.ErrInvalidSequence, + sdkerrors.ErrInvalidSequence, "invalid nonce; got %d, expected %d", txData.GetNonce(), nonce, ) } diff --git a/app/evmante/evmante_mempool_fees.go b/app/evmante/evmante_mempool_fees.go index 421215381..ca683ec59 100644 --- a/app/evmante/evmante_mempool_fees.go +++ b/app/evmante/evmante_mempool_fees.go @@ -3,8 +3,9 @@ package evmante import ( "cosmossdk.io/errors" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/NibiruChain/nibiru/v2/x/evm" ) @@ -38,32 +39,38 @@ func (d MempoolGasPriceDecorator) AnteHandle( } minGasPrice := ctx.MinGasPrices().AmountOf(d.evmKeeper.GetParams(ctx).EvmDenom) + baseFeeMicronibi := d.evmKeeper.GetBaseFee(ctx) + baseFeeDec := math.LegacyNewDecFromBigInt(baseFeeMicronibi) + // if MinGasPrices is not set, skip the check if minGasPrice.IsZero() { return next(ctx, tx, simulate) + } else if minGasPrice.LT(baseFeeDec) { + minGasPrice = baseFeeDec } - baseFee := d.evmKeeper.GetBaseFee(ctx) - for _, msg := range tx.GetMsgs() { ethTx, ok := msg.(*evm.MsgEthereumTx) if !ok { return ctx, errors.Wrapf( - errortypes.ErrUnknownRequest, + sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil), ) } - effectiveGasPrice := ethTx.GetEffectiveGasPrice(baseFee) - - if sdk.NewDecFromBigInt(effectiveGasPrice).LT(minGasPrice) { + baseFeeWei := evm.NativeToWei(baseFeeMicronibi) + effectiveGasPriceDec := math.LegacyNewDecFromBigInt( + evm.WeiToNative(ethTx.GetEffectiveGasPrice(baseFeeWei)), + ) + if effectiveGasPriceDec.LT(minGasPrice) { + // if sdk.NewDecFromBigInt(effectiveGasPrice).LT(minGasPrice) { return ctx, errors.Wrapf( - errortypes.ErrInsufficientFee, + sdkerrors.ErrInsufficientFee, "provided gas price < minimum local gas price (%s < %s). "+ "Please increase the priority tip (for EIP-1559 txs) or the gas prices "+ "(for access list or legacy txs)", - effectiveGasPrice.String(), minGasPrice.String(), + effectiveGasPriceDec, minGasPrice, ) } } diff --git a/app/evmante/evmante_setup_ctx.go b/app/evmante/evmante_setup_ctx.go index 6cad962a7..f94eae384 100644 --- a/app/evmante/evmante_setup_ctx.go +++ b/app/evmante/evmante_setup_ctx.go @@ -5,7 +5,7 @@ import ( errorsmod "cosmossdk.io/errors" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" ) @@ -31,7 +31,7 @@ func (esc EthSetupContextDecorator) AnteHandle( _, ok := tx.(authante.GasTx) if !ok { return ctx, errorsmod.Wrapf( - errortypes.ErrInvalidType, + sdkerrors.ErrInvalidType, "invalid transaction type %T, expected GasTx", tx, ) } diff --git a/app/evmante/evmante_sigverify.go b/app/evmante/evmante_sigverify.go index a19c664c8..3735d9e8f 100644 --- a/app/evmante/evmante_sigverify.go +++ b/app/evmante/evmante_sigverify.go @@ -6,7 +6,7 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/NibiruChain/nibiru/v2/x/evm" @@ -43,7 +43,7 @@ func (esvd EthSigVerificationDecorator) AnteHandle( msgEthTx, ok := msg.(*evm.MsgEthereumTx) if !ok { return ctx, errors.Wrapf( - errortypes.ErrUnknownRequest, + sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil), ) } @@ -52,7 +52,7 @@ func (esvd EthSigVerificationDecorator) AnteHandle( ethTx := msgEthTx.AsTransaction() if !allowUnprotectedTxs && !ethTx.Protected() { return ctx, errors.Wrapf( - errortypes.ErrNotSupported, + sdkerrors.ErrNotSupported, "rejected unprotected Ethereum transaction. "+ "Please EIP155 sign your transaction to protect it against replay-attacks", ) @@ -61,7 +61,7 @@ func (esvd EthSigVerificationDecorator) AnteHandle( sender, err := signer.Sender(ethTx) if err != nil { return ctx, errors.Wrapf( - errortypes.ErrorInvalidSigner, + sdkerrors.ErrorInvalidSigner, "couldn't retrieve sender address from the ethereum transaction: %s", err.Error(), ) diff --git a/app/evmante/evmante_validate_basic.go b/app/evmante/evmante_validate_basic.go index 4074efc0c..5666a7bc8 100644 --- a/app/evmante/evmante_validate_basic.go +++ b/app/evmante/evmante_validate_basic.go @@ -7,7 +7,7 @@ import ( errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/NibiruChain/nibiru/v2/x/evm" @@ -34,7 +34,7 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu err := tx.ValidateBasic() // ErrNoSignatures is fine with eth tx - if err != nil && !errors.Is(err, errortypes.ErrNoSignatures) { + if err != nil && !errors.Is(err, sdkerrors.ErrNoSignatures) { return ctx, errorsmod.Wrap(err, "tx basic validation failed") } @@ -43,7 +43,7 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu wrapperTx, ok := tx.(protoTxProvider) if !ok { return ctx, errorsmod.Wrapf( - errortypes.ErrUnknownRequest, + sdkerrors.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx, ) @@ -52,13 +52,13 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu protoTx := wrapperTx.GetProtoTx() body := protoTx.Body if body.Memo != "" || body.TimeoutHeight != uint64(0) || len(body.NonCriticalExtensionOptions) > 0 { - return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty") } if len(body.ExtensionOptions) != 1 { return ctx, errorsmod.Wrap( - errortypes.ErrInvalidRequest, + sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1", ) } @@ -66,14 +66,14 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu authInfo := protoTx.AuthInfo if len(authInfo.SignerInfos) > 0 { return ctx, errorsmod.Wrap( - errortypes.ErrInvalidRequest, + sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty", ) } if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" { return ctx, errorsmod.Wrap( - errortypes.ErrInvalidRequest, + sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty", ) } @@ -81,7 +81,7 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu sigs := protoTx.Signatures if len(sigs) > 0 { return ctx, errorsmod.Wrap( - errortypes.ErrInvalidRequest, + sdkerrors.ErrInvalidRequest, "for eth tx Signatures should be empty", ) } @@ -97,7 +97,7 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu msgEthTx, ok := msg.(*evm.MsgEthereumTx) if !ok { return ctx, errorsmod.Wrapf( - errortypes.ErrUnknownRequest, + sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil), ) } @@ -105,7 +105,7 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu // Validate `From` field if msgEthTx.From != "" { return ctx, errorsmod.Wrapf( - errortypes.ErrInvalidRequest, + sdkerrors.ErrInvalidRequest, "invalid From %s, expect empty string", msgEthTx.From, ) } @@ -134,7 +134,7 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu if !authInfo.Fee.Amount.IsEqual(txFee) { return ctx, errorsmod.Wrapf( - errortypes.ErrInvalidRequest, + sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee, @@ -143,7 +143,7 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu if authInfo.Fee.GasLimit != txGasLimit { return ctx, errorsmod.Wrapf( - errortypes.ErrInvalidRequest, + sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit, diff --git a/app/evmante/evmante_verify_eth_acc.go b/app/evmante/evmante_verify_eth_acc.go index 7eccff3f6..7a1feddff 100644 --- a/app/evmante/evmante_verify_eth_acc.go +++ b/app/evmante/evmante_verify_eth_acc.go @@ -4,7 +4,7 @@ package evmante import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/NibiruChain/nibiru/v2/x/evm" @@ -26,8 +26,10 @@ func NewAnteDecVerifyEthAcc(k EVMKeeper, ak evm.AccountKeeper) AnteDecVerifyEthA } } -// AnteHandle validates checks that the sender balance is greater than the total transaction cost. -// The account will be set to store if it doesn't exist, i.e. cannot be found on store. +// AnteHandle validates checks that the sender balance is greater than the total +// transaction cost. The account will be set to store if it doesn't exist, i.e. +// cannot be found on store. +// // This AnteHandler decorator will fail if: // - any of the msgs is not a MsgEthereumTx // - from address is empty @@ -41,7 +43,7 @@ func (anteDec AnteDecVerifyEthAcc) AnteHandle( for i, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evm.MsgEthereumTx) if !ok { - return ctx, errors.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil)) + return ctx, errors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil)) } txData, err := evm.UnpackTxData(msgEthTx.Data) @@ -52,7 +54,7 @@ func (anteDec AnteDecVerifyEthAcc) AnteHandle( // sender address should be in the tx cache from the previous AnteHandle call from := msgEthTx.GetFrom() if from.Empty() { - return ctx, errors.Wrap(errortypes.ErrInvalidAddress, "from address cannot be empty") + return ctx, errors.Wrap(sdkerrors.ErrInvalidAddress, "from address cannot be empty") } // check whether the sender address is EOA @@ -64,7 +66,7 @@ func (anteDec AnteDecVerifyEthAcc) AnteHandle( anteDec.accountKeeper.SetAccount(ctx, acc) acct = statedb.NewEmptyAccount() } else if acct.IsContract() { - return ctx, errors.Wrapf(errortypes.ErrInvalidType, + return ctx, errors.Wrapf(sdkerrors.ErrInvalidType, "the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash) } diff --git a/app/server/config/server_config.go b/app/server/config/server_config.go index ce5e9caaf..b72638ed2 100644 --- a/app/server/config/server_config.go +++ b/app/server/config/server_config.go @@ -7,6 +7,8 @@ import ( "path" "time" + tracerslogger "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/spf13/viper" "github.com/cometbft/cometbft/libs/strings" @@ -114,7 +116,8 @@ type Config struct { type EVMConfig struct { // Tracer defines vm.Tracer type that the EVM will use if the node is run in // trace mode. Default: 'json'. - Tracer string `mapstructure:"tracer"` + Tracer string `mapstructure:"tracer"` + TracerOpts tracerslogger.Config `mapstucture:"tracer_opts"` // MaxTxGasWanted defines the gas wanted for each eth tx returned in ante handler in check tx mode. MaxTxGasWanted uint64 `mapstructure:"max-tx-gas-wanted"` } @@ -215,7 +218,16 @@ func DefaultConfig() *Config { // DefaultEVMConfig returns the default EVM configuration func DefaultEVMConfig() *EVMConfig { return &EVMConfig{ - Tracer: DefaultEVMTracer, + Tracer: DefaultEVMTracer, + TracerOpts: tracerslogger.Config{ + EnableMemory: false, // disable + DisableStack: false, // enable stack + DisableStorage: false, // enable storage + EnableReturnData: false, // disable + Debug: true, // enable debug + Limit: 0, + Overrides: nil, + }, MaxTxGasWanted: DefaultMaxTxGasWanted, } } @@ -381,6 +393,30 @@ tracer = "{{ .EVM.Tracer }}" # MaxTxGasWanted defines the gas wanted for each eth tx returned in ante handler in check tx mode. max-tx-gas-wanted = {{ .EVM.MaxTxGasWanted }} +[evm.tracer_opts] + +# Enable the capture of EVM memory state at each +# execution step. This can be useful for debugging complex contracts but may +# significantly increase the volume of logged data. +memory = false + +# Enable the capture of contract storage changes. By default, storage +# modifications are logged. Disabling storage capture can significantly reduce +# log size for contracts with many storage operations. +stack = true + +# Enable the capture of contract storage changes. +storage = true + +# enable return-data capture +return-data = false + +# enable debug capture +debug = true + +# Maximum length of the tracer output. Zero means unlimited. +limit = 0 + ############################################################################### ### JSON RPC Configuration ### ############################################################################### diff --git a/app/server/start.go b/app/server/start.go index 680673bb1..f18c0101f 100644 --- a/app/server/start.go +++ b/app/server/start.go @@ -29,6 +29,7 @@ import ( dbm "github.com/cometbft/cometbft-db" abciserver "github.com/cometbft/cometbft/abci/server" tcmd "github.com/cometbft/cometbft/cmd/cometbft/commands" + "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" "github.com/cometbft/cometbft/node" "github.com/cometbft/cometbft/p2p" @@ -44,7 +45,7 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server" + sdkserver "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/api" serverconfig "github.com/cosmos/cosmos-sdk/server/config" servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" @@ -101,7 +102,7 @@ For profiling and benchmarking purposes, CPU profiling can be enabled via the '- which accepts a path for the resulting pprof file. `, PreRunE: func(cmd *cobra.Command, _ []string) error { - serverCtx := server.GetServerContextFromCmd(cmd) + serverCtx := sdkserver.GetServerContextFromCmd(cmd) // Bind flags to the Context's Viper so the app construction can set // options accordingly. @@ -110,11 +111,11 @@ which accepts a path for the resulting pprof file. return err } - _, err = server.GetPruningOptionsFromFlags(serverCtx.Viper) + _, err = sdkserver.GetPruningOptionsFromFlags(serverCtx.Viper) return err }, RunE: func(cmd *cobra.Command, _ []string) error { - serverCtx := server.GetServerContextFromCmd(cmd) + serverCtx := sdkserver.GetServerContextFromCmd(cmd) clientCtx, err := client.GetClientQueryContext(cmd) if err != nil { return err @@ -143,7 +144,7 @@ which accepts a path for the resulting pprof file. // amino is needed here for backwards compatibility of REST routes err = startInProcess(serverCtx, clientCtx, opts) - errCode, ok := err.(server.ErrorCode) + errCode, ok := err.(sdkserver.ErrorCode) if !ok { return err } @@ -158,18 +159,18 @@ which accepts a path for the resulting pprof file. cmd.Flags().String(Address, "tcp://0.0.0.0:26658", "Listen address") cmd.Flags().String(Transport, "socket", "Transport protocol: socket, grpc") cmd.Flags().String(TraceStore, "", "Enable KVStore tracing to an output file") - cmd.Flags().String(server.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 5000unibi)") //nolint:lll - cmd.Flags().IntSlice(server.FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary") - cmd.Flags().Uint64(server.FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node") - cmd.Flags().Uint64(server.FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") - cmd.Flags().Bool(server.FlagInterBlockCache, true, "Enable inter-block caching") + cmd.Flags().String(sdkserver.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 5000unibi)") //nolint:lll + cmd.Flags().IntSlice(sdkserver.FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary") + cmd.Flags().Uint64(sdkserver.FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node") + cmd.Flags().Uint64(sdkserver.FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") + cmd.Flags().Bool(sdkserver.FlagInterBlockCache, true, "Enable inter-block caching") cmd.Flags().String(CPUProfile, "", "Enable CPU profiling and write to the provided file") - cmd.Flags().Bool(server.FlagTrace, false, "Provide full stack traces for errors in ABCI Log") - cmd.Flags().String(server.FlagPruning, pruningtypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)") - cmd.Flags().Uint64(server.FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')") - cmd.Flags().Uint64(server.FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')") //nolint:lll - cmd.Flags().Uint(server.FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks") - cmd.Flags().Uint64(server.FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks") + cmd.Flags().Bool(sdkserver.FlagTrace, false, "Provide full stack traces for errors in ABCI Log") + cmd.Flags().String(sdkserver.FlagPruning, pruningtypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)") + cmd.Flags().Uint64(sdkserver.FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')") + cmd.Flags().Uint64(sdkserver.FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')") //nolint:lll + cmd.Flags().Uint(sdkserver.FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks") + cmd.Flags().Uint64(sdkserver.FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks") cmd.Flags().String(AppDBBackend, "", "The type of database for application and snapshots databases") cmd.Flags().Bool(GRPCOnly, false, "Start the node in gRPC query only mode without Tendermint process") @@ -204,20 +205,20 @@ which accepts a path for the resulting pprof file. cmd.Flags().String(TLSCertPath, "", "the cert.pem file path for the server TLS configuration") cmd.Flags().String(TLSKeyPath, "", "the key.pem file path for the server TLS configuration") - cmd.Flags().Uint64(server.FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval") - cmd.Flags().Uint32(server.FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep") + cmd.Flags().Uint64(sdkserver.FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval") + cmd.Flags().Uint32(sdkserver.FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep") // add support for all Tendermint-specific command line options tcmd.AddNodeFlags(cmd) return cmd } -func startStandAlone(ctx *server.Context, opts StartOptions) error { +func startStandAlone(ctx *sdkserver.Context, opts StartOptions) error { addr := ctx.Viper.GetString(Address) transport := ctx.Viper.GetString(Transport) home := ctx.Viper.GetString(flags.FlagHome) - db, err := openDB(home, server.GetAppDBBackend(ctx.Viper)) + db, err := openDB(home, sdkserver.GetAppDBBackend(ctx.Viper)) if err != nil { return err } @@ -271,16 +272,16 @@ func startStandAlone(ctx *server.Context, opts StartOptions) error { }() // Wait for SIGINT or SIGTERM signal - return server.WaitForQuitSignals() + return sdkserver.WaitForQuitSignals() } // legacyAminoCdc is used for the legacy REST API -func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOptions) (err error) { +func startInProcess(ctx *sdkserver.Context, clientCtx client.Context, opts StartOptions) (err error) { cfg := ctx.Config home := cfg.RootDir logger := ctx.Logger - db, err := openDB(home, server.GetAppDBBackend(ctx.Viper)) + db, err := openDB(home, sdkserver.GetAppDBBackend(ctx.Viper)) if err != nil { logger.Error("failed to open DB", "error", err.Error()) return err @@ -381,16 +382,14 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt ethmetricsexp.Setup(conf.JSONRPC.MetricsAddress) } - var idxer eth.EVMTxIndexer + var evmIdxer eth.EVMTxIndexer if conf.JSONRPC.EnableIndexer { - idxDB, err := OpenIndexerDB(home, server.GetAppDBBackend(ctx.Viper)) + idxer, err := OpenEVMIndexer(ctx, ctx.Logger, clientCtx, home) if err != nil { logger.Error("failed to open evm indexer DB", "error", err.Error()) return err } - - idxLogger := ctx.Logger.With("indexer", "evm") - idxer = indexer.NewKVIndexer(idxDB, idxLogger, clientCtx) + evmIdxer = idxer } if conf.API.Enable || conf.JSONRPC.Enable { @@ -508,7 +507,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt tmEndpoint := "/websocket" tmRPCAddr := cfg.RPC.ListenAddress - httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &conf, idxer) + httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &conf, evmIdxer) if err != nil { return err } @@ -531,7 +530,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt // we do not need to start Rosetta or handle any Tendermint related processes. if gRPCOnly { // wait for signal capture and gracefully return - return server.WaitForQuitSignals() + return sdkserver.WaitForQuitSignals() } var rosettaSrv crgserver.Server @@ -584,7 +583,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt } } // Wait for SIGINT or SIGTERM signal - return server.WaitForQuitSignals() + return sdkserver.WaitForQuitSignals() } // OpenIndexerDB opens the custom eth indexer db, using the same db backend as the main app @@ -593,6 +592,22 @@ func OpenIndexerDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) return dbm.NewDB("evmindexer", backendType, dataDir) } +func OpenEVMIndexer( + ctx *sdkserver.Context, + logger log.Logger, + clientCtx client.Context, + homeDir string, +) (eth.EVMTxIndexer, error) { + idxDB, err := OpenIndexerDB(homeDir, sdkserver.GetAppDBBackend(ctx.Viper)) + if err != nil { + logger.Error("failed to open evm indexer DB", "error", err.Error()) + return nil, err + } + + idxLogger := ctx.Logger.With("indexer", "evm") + return indexer.NewKVIndexer(idxDB, idxLogger, clientCtx), nil +} + func openTraceWriter(traceWriterFile string) (w io.Writer, err error) { if traceWriterFile == "" { return @@ -614,15 +629,15 @@ func startTelemetry(cfg config.Config) (*telemetry.Metrics, error) { } // WaitForQuitSignals waits for SIGINT and SIGTERM and returns. -func WaitForQuitSignals() server.ErrorCode { +func WaitForQuitSignals() sdkserver.ErrorCode { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) sig := <-sigs - return server.ErrorCode{Code: int(sig.(syscall.Signal)) + 128} + return sdkserver.ErrorCode{Code: int(sig.(syscall.Signal)) + 128} } // wrapCPUProfile runs callback in a goroutine, then wait for quit signals. -func wrapCPUProfile(ctx *server.Context, callback func() error) error { +func wrapCPUProfile(ctx *sdkserver.Context, callback func() error) error { if cpuProfile := ctx.Viper.GetString(CPUProfile); cpuProfile != "" { f, err := os.Create(cpuProfile) if err != nil { diff --git a/e2e/evm/bun.lockb b/e2e/evm/bun.lockb index c93ef4901bab83e5c0109dff84f5404d3f259300..b88cb597fc795eb38c2b453f92a1c170f2bf6e68 100755 GIT binary patch literal 216205 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
lsE1GlZxUx9+ zAIS^mdGhc*1mZ)S8%TwDc%zURA|NuYkRT^NR>_IW$lZ`uQSNk(>y+6(u}2BEoIW>c z#RMPL7w8jr%~Y}la!MHMmg_fN?4+U+Ru*01%ublZlAF==WhZY|R+;rJf9=TTm8U2a zn&dQYlsE=ovSO}6(8CCo>GR`w%U0|ygI}?e%qiGnk2&!!DI?V5)QdJeDY$`Z$n(1@ ztC##9CrEIY0n$P9Qz210U4nf_5AJ`$^~VV8$|FSM=$LR8w}{)zeSqv^PD79?hHzA< zBuHDh92&yDJGC8am(o~fhm^1cAMeA?!Z_u=JlE#5C$FRlSt()YIb@l*c?8mOf{<=Q z&5dy}CWZ)rZ#>Yzm%d0K6vAB-bgxZ;%v|t@T@MFiBeajtfapIxS;!S}U(kyFku`uN z%QLU5-Na$$7X-|YY~|USC65G$wpcVK!BM8rAhuC%i!X};f`a9lXR9<8AIVw{IySm5 zEF1f5I)dS2IW#i0FzBbgoF!Yg7K^pE=k z$kE}^>d=nV!l)tjCAWju78EF;9J0jrFkbL|-2SQ9F$@I};X?XKqztc(3oi8atspEf z_jUdxN^;QzTLv~anT3nWD5?V|O8&wk)1lEuA?h@NOUqI+18xf_qbdUz^)jxTFAQ8% zBk(FvpYZa5tk;tUkEya3)uz5@DI%lN*@r^ycD*c!+ChxYaqbAKG6{OtcPv^r{#4T) z2&zD=({83H6Hux&M^$jM+7V4jbqOVcPPOX!=X@opQtd-Ukk&g zzC-U@%7H!0GwToA<;3e~JP&CRv<<)(CLPL2DCSC%K)kDsd=TehV7k77#EF*SIf+3*>M+Spb*+Cmj>fm!*_R<4~Ui z7ML&-A^x$pWLuepB~5|*5drIId=>8G&JRFtd*%^3=-(VB}Nb#y5>J(u zq@4<=C~3)^>>w-4@?}Ly3Z|?SE$=Ey(9}ORJ!WWf1v|>xSZH%a`ASGXr8m;!Nf-mV WeGmlrdO{rOK%|_lLB{{5|Nb9awa1qL delta 31055 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 diff --git a/e2e/evm/test/basic_queries.test.ts b/e2e/evm/test/basic_queries.test.ts new file mode 100644 index 000000000..8caed4032 --- /dev/null +++ b/e2e/evm/test/basic_queries.test.ts @@ -0,0 +1,397 @@ +import { describe, expect, it, jest } from "@jest/globals" +import { + toBigInt, + parseEther, + keccak256, + AbiCoder, + TransactionRequest, + Block, + TransactionResponse, +} from "ethers" +import { account, provider } from "./setup" +import { + INTRINSIC_TX_GAS, + alice, + deployContractTestERC20, + deployContractSendNibi, + hexify, + sendTestNibi, +} from "./utils" + +describe("Basic Queries", () => { + jest.setTimeout(15e3) + + it("Simple transfer, balance check", async () => { + const amountToSend = toBigInt(5e12) * toBigInt(1e6) // unibi + const senderBalanceBefore = await provider.getBalance(account) + const recipientBalanceBefore = await provider.getBalance(alice) + expect(senderBalanceBefore).toBeGreaterThan(0) + expect(recipientBalanceBefore).toEqual(BigInt(0)) + + const tenPow12 = toBigInt(1e12) + + // Execute EVM transfer + const transaction: TransactionRequest = { + gasLimit: toBigInt(100e3), + to: alice, + value: amountToSend, + } + const txResponse = await account.sendTransaction(transaction) + await txResponse.wait(1, 10e3) + expect(txResponse).toHaveProperty("blockHash") + + const senderBalanceAfter = await provider.getBalance(account) + const recipientBalanceAfter = await provider.getBalance(alice) + + // Assert balances with logging + const gasUsed = 50000n // 50k gas for the transaction + const txCostMicronibi = amountToSend / tenPow12 + gasUsed + const txCostWei = txCostMicronibi * tenPow12 + const expectedSenderWei = senderBalanceBefore - txCostWei + console.debug("DEBUG should send via transfer method %o:", { + senderBalanceBefore, + amountToSend, + expectedSenderWei, + senderBalanceAfter, + }) + expect(senderBalanceAfter).toEqual(expectedSenderWei) + expect(recipientBalanceAfter).toEqual(amountToSend) + }) + + it("eth_accounts", async () => { + const accounts = await provider.listAccounts() + expect(accounts).not.toHaveLength(0) + }) + + it("eth_estimateGas", async () => { + const tx = { + from: account.address, + to: alice, + value: parseEther("0.01"), // Sending 0.01 Ether + } + const estimatedGas = await provider.estimateGas(tx) + expect(estimatedGas).toBeGreaterThan(BigInt(0)) + expect(estimatedGas).toEqual(INTRINSIC_TX_GAS) + }) + + it("eth_feeHistory", async () => { + const blockCount = 5 // Number of blocks in the requested history + const newestBlock = "latest" // Can be a block number or 'latest' + const rewardPercentiles = [25, 50, 75] // Example percentiles for priority fees + + const feeHistory = await provider.send("eth_feeHistory", [ + blockCount, + newestBlock, + rewardPercentiles, + ]) + expect(feeHistory).toBeDefined() + expect(feeHistory).toHaveProperty("baseFeePerGas") + expect(feeHistory).toHaveProperty("gasUsedRatio") + expect(feeHistory).toHaveProperty("oldestBlock") + expect(feeHistory).toHaveProperty("reward") + }) + + it("eth_gasPrice", async () => { + const gasPrice = await provider.send("eth_gasPrice", []) + expect(gasPrice).toBeDefined() + expect(gasPrice).toEqual(hexify(1)) + }) + + it("eth_getBalance", async () => { + const balance = await provider.getBalance(account.address) + expect(balance).toBeGreaterThan(0) + }) + + it("eth_getBlockByNumber, eth_getBlockByHash", async () => { + const blockNumber = 1 + const blockByNumber = await provider.send("eth_getBlockByNumber", [ + blockNumber, + false, + ]) + expect(blockByNumber).toBeDefined() + expect(blockByNumber).toHaveProperty("hash") + + const blockByHash = await provider.send("eth_getBlockByHash", [ + blockByNumber.hash, + false, + ]) + expect(blockByHash).toBeDefined() + expect(blockByHash.hash).toEqual(blockByNumber.hash) + expect(blockByHash.number).toEqual(blockByNumber.number) + }) + + it("eth_getBlockTransactionCountByHash", async () => { + const blockNumber = 1 + const block = await provider.send("eth_getBlockByNumber", [ + blockNumber, + false, + ]) + const txCount = await provider.send("eth_getBlockTransactionCountByHash", [ + block.hash, + ]) + expect(parseInt(txCount)).toBeGreaterThanOrEqual(0) + }) + + it("eth_getBlockTransactionCountByNumber", async () => { + const blockNumber = 1 + const txCount = await provider.send( + "eth_getBlockTransactionCountByNumber", + [blockNumber], + ) + expect(parseInt(txCount)).toBeGreaterThanOrEqual(0) + }) + + it("eth_getCode", async () => { + const contract = await deployContractSendNibi() + const contractAddr = await contract.getAddress() + const code = await provider.send("eth_getCode", [contractAddr, "latest"]) + expect(code).toBeDefined() + }) + + it("eth_getFilterChanges", async () => { + // Deploy ERC-20 contract + const contract = await deployContractTestERC20() + const contractAddr = await contract.getAddress() + const filter = { + fromBlock: "latest", + address: contractAddr, + } + // Create the filter for a contract + const filterId = await provider.send("eth_newFilter", [filter]) + expect(filterId).toBeDefined() + + // Execute some contract TX + const tx = await contract.transfer(alice, parseEther("0.01")) + await tx.wait(1, 5e3) + await new Promise((resolve) => setTimeout(resolve, 3000)) + + // Assert logs + const changes = await provider.send("eth_getFilterChanges", [filterId]) + expect(changes.length).toBeGreaterThan(0) + expect(changes[0]).toHaveProperty("address") + expect(changes[0]).toHaveProperty("data") + expect(changes[0]).toHaveProperty("topics") + + const success = await provider.send("eth_uninstallFilter", [filterId]) + expect(success).toBeTruthy() + }) + + // Skipping as the method is not implemented + it.skip("eth_getFilterLogs", async () => { + // Deploy ERC-20 contract + const contract = await deployContractTestERC20() + const contractAddr = await contract.getAddress() + const filter = { + fromBlock: "latest", + address: contractAddr, + } + // Execute some contract TX + const tx = await contract.transfer(alice, parseEther("0.01")) + await tx.wait(1, 5e3) + + // Create the filter for a contract + const filterId = await provider.send("eth_newFilter", [filter]) + expect(filterId).toBeDefined() + + // Assert logs + const changes = await provider.send("eth_getFilterLogs", [filterId]) + expect(changes.length).toBeGreaterThan(0) + expect(changes[0]).toHaveProperty("address") + expect(changes[0]).toHaveProperty("data") + expect(changes[0]).toHaveProperty("topics") + }) + + // Skipping as the method is not implemented + it.skip("eth_getLogs", async () => { + // Deploy ERC-20 contract + const contract = await deployContractTestERC20() + const contractAddr = await contract.getAddress() + const filter = { + fromBlock: "latest", + address: contractAddr, + } + // Execute some contract TX + const tx = await contract.transfer(alice, parseEther("0.01")) + + // Assert logs + const changes = await provider.send("eth_getLogs", [filter]) + expect(changes.length).toBeGreaterThan(0) + expect(changes[0]).toHaveProperty("address") + expect(changes[0]).toHaveProperty("data") + expect(changes[0]).toHaveProperty("topics") + }) + + it("eth_getProof", async () => { + // Deploy ERC-20 contract + const contract = await deployContractTestERC20() + const contractAddr = await contract.getAddress() + + const slot = 1 // Assuming balanceOf is at slot 1 + const storageKey = keccak256( + AbiCoder.defaultAbiCoder().encode( + ["address", "uint256"], + [account.address, slot], + ), + ) + const proof = await provider.send("eth_getProof", [ + contractAddr, + [storageKey], + "latest", + ]) + // Assert proof structure + expect(proof).toHaveProperty("address") + expect(proof).toHaveProperty("balance") + expect(proof).toHaveProperty("codeHash") + expect(proof).toHaveProperty("nonce") + expect(proof).toHaveProperty("storageProof") + + if (proof.storageProof.length > 0) { + expect(proof.storageProof[0]).toHaveProperty("key", storageKey) + expect(proof.storageProof[0]).toHaveProperty("value") + expect(proof.storageProof[0]).toHaveProperty("proof") + } + }) + + // Skipping as the method is not implemented + it.skip("eth_getLogs", async () => { + // Deploy ERC-20 contract + const contract = await deployContractTestERC20() + const contractAddr = await contract.getAddress() + const filter = { + fromBlock: "latest", + address: contractAddr, + } + // Execute some contract TX + const tx = await contract.transfer(alice, parseEther("0.01")) + await tx.wait(1, 5e3) + + // Assert logs + const logs = await provider.send("eth_getLogs", [filter]) + expect(logs.length).toBeGreaterThan(0) + expect(logs[0]).toHaveProperty("address") + expect(logs[0]).toHaveProperty("data") + expect(logs[0]).toHaveProperty("topics") + }) + + it("eth_getProof", async () => { + const contract = await deployContractTestERC20() + const contractAddr = await contract.getAddress() + + const slot = 1 // Assuming balanceOf is at slot 1 + const storageKey = keccak256( + AbiCoder.defaultAbiCoder().encode( + ["address", "uint256"], + [account.address, slot], + ), + ) + const proof = await provider.send("eth_getProof", [ + contractAddr, + [storageKey], + "latest", + ]) + // Assert proof structure + expect(proof).toHaveProperty("address") + expect(proof).toHaveProperty("balance") + expect(proof).toHaveProperty("codeHash") + expect(proof).toHaveProperty("nonce") + expect(proof).toHaveProperty("storageProof") + + if (proof.storageProof.length > 0) { + expect(proof.storageProof[0]).toHaveProperty("key", storageKey) + expect(proof.storageProof[0]).toHaveProperty("value") + expect(proof.storageProof[0]).toHaveProperty("proof") + } + }) + + it("eth_getStorageAt", async () => { + const contract = await deployContractTestERC20() + const contractAddr = await contract.getAddress() + + const value = await provider.getStorage(contractAddr, 1) + expect(value).toBeDefined() + }) + + it("eth_getTransactionByBlockHashAndIndex, eth_getTransactionByBlockNumberAndIndex", async () => { + // Execute EVM transfer + const txResponse: TransactionResponse = await sendTestNibi() + const block: Block = (await txResponse.getBlock()) as Block + expect(block).toBeTruthy() + + const txByBlockHash = await provider.send( + "eth_getTransactionByBlockHashAndIndex", + [block.hash, "0x0"], + ) + expect(txByBlockHash).toBeDefined() + expect(txByBlockHash).toHaveProperty("from") + expect(txByBlockHash).toHaveProperty("to") + expect(txByBlockHash).toHaveProperty("blockHash") + expect(txByBlockHash).toHaveProperty("blockNumber") + expect(txByBlockHash).toHaveProperty("value") + + const txByBlockNumber = await provider.send( + "eth_getTransactionByBlockNumberAndIndex", + [block.number, "0x0"], + ) + + expect(txByBlockNumber).toBeDefined() + expect(txByBlockNumber["from"]).toEqual(txByBlockHash["from"]) + expect(txByBlockNumber["to"]).toEqual(txByBlockHash["to"]) + expect(txByBlockNumber["value"]).toEqual(txByBlockHash["value"]) + }) + + it("eth_getTransactionByHash", async () => { + const txResponse = await sendTestNibi() + const txByHash = await provider.getTransaction(txResponse.hash) + expect(txByHash).toBeDefined() + expect(txByHash.hash).toEqual(txResponse.hash) + }) + + it("eth_getTransactionCount", async () => { + const txCount = await provider.getTransactionCount(account.address) + expect(txCount).toBeGreaterThanOrEqual(0) + }) + + it("eth_getTransactionReceipt", async () => { + const txResponse = await sendTestNibi() + const txReceipt = await provider.getTransactionReceipt(txResponse.hash) + expect(txReceipt).toBeDefined() + expect(txReceipt.hash).toEqual(txResponse.hash) + }) + + it("eth_getUncleCountByBlockHash", async () => { + const latestBlock = await provider.getBlockNumber() + const block = await provider.getBlock(latestBlock) + const uncleCount = await provider.send("eth_getUncleCountByBlockHash", [ + block.hash, + ]) + expect(parseInt(uncleCount)).toBeGreaterThanOrEqual(0) + }) + + it("eth_getUncleCountByBlockNumber", async () => { + const latestBlock = await provider.getBlockNumber() + const uncleCount = await provider.send("eth_getUncleCountByBlockNumber", [ + latestBlock, + ]) + expect(parseInt(uncleCount)).toBeGreaterThanOrEqual(0) + }) + + it("eth_maxPriorityFeePerGas", async () => { + const maxPriorityGas = await provider.send("eth_maxPriorityFeePerGas", []) + expect(parseInt(maxPriorityGas)).toBeGreaterThanOrEqual(0) + }) + + it("eth_newBlockFilter", async () => { + const filterId = await provider.send("eth_newBlockFilter", []) + expect(filterId).toBeDefined() + }) + + it("eth_newPendingTransactionFilter", async () => { + const filterId = await provider.send("eth_newPendingTransactionFilter", []) + expect(filterId).toBeDefined() + }) + + it("eth_syncing", async () => { + const syncing = await provider.send("eth_syncing", []) + expect(syncing).toBeFalsy() + }) +}) diff --git a/e2e/evm/test/contract_infinite_loop_gas.test.ts b/e2e/evm/test/contract_infinite_loop_gas.test.ts index 15ed369e6..0c48a6e93 100644 --- a/e2e/evm/test/contract_infinite_loop_gas.test.ts +++ b/e2e/evm/test/contract_infinite_loop_gas.test.ts @@ -1,13 +1,10 @@ import { describe, expect, it } from "@jest/globals" import { toBigInt } from "ethers" -import { InfiniteLoopGasCompiled__factory } from "../types/ethers-contracts" -import { account } from "./setup" +import { deployContractInfiniteLoopGas } from "./utils" describe("Infinite loop gas contract", () => { it("should fail due to out of gas error", async () => { - const factory = new InfiniteLoopGasCompiled__factory(account) - const contract = await factory.deploy() - await contract.waitForDeployment() + const contract = await deployContractInfiniteLoopGas() expect(contract.counter()).resolves.toBe(toBigInt(0)) diff --git a/e2e/evm/test/contract_send_nibi.test.ts b/e2e/evm/test/contract_send_nibi.test.ts index 6dcf7967e..d820c9cd5 100644 --- a/e2e/evm/test/contract_send_nibi.test.ts +++ b/e2e/evm/test/contract_send_nibi.test.ts @@ -1,96 +1,76 @@ +/** + * @file sendNibi.test.ts + * + * This test suite is designed to validate the functionality of the for sending + * NIBI via various mechanisms like transfer, send, and call. The tests ensure + * that the correct amount of NIBI is transferred and that balances are updated + * accordingly. + * + * The methods tested are from the smart contract, + * "e2e/evm/contracts/SendReceiveNibi.sol". + */ import { describe, expect, it } from "@jest/globals" import { toBigInt, Wallet } from "ethers" import { account, provider } from "./setup" -import { deploySendReceiveNibi } from "./utils" +import { deployContractSendNibi } from "./utils" -describe("Send NIBI via smart contract", () => { - it("should send via transfer method", async () => { - const contract = await deploySendReceiveNibi() - const recipient = Wallet.createRandom() - const weiToSend = toBigInt(5e12) * toBigInt(1e6) // 5 micro NIBI - - const ownerBalanceBefore = await provider.getBalance(account) // NIBI - const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI - expect(recipientBalanceBefore).toEqual(BigInt(0)) - - const tx = await contract.sendViaTransfer(recipient, { - value: weiToSend, - }) - const receipt = await tx.wait(1, 5e3) +async function testSendNibi( + method: "sendViaTransfer" | "sendViaSend" | "sendViaCall", + weiToSend: bigint, +) { + const contract = await deployContractSendNibi() + const recipient = Wallet.createRandom() - // Assert balances with logging - const tenPow12 = toBigInt(1e12) - const txCostMicronibi = weiToSend / tenPow12 + receipt.gasUsed - const txCostWei = txCostMicronibi * tenPow12 - const expectedOwnerWei = ownerBalanceBefore - txCostWei - console.debug("DEBUG should send via transfer method %o:", { - ownerBalanceBefore, - weiToSend, - gasUsed: receipt.gasUsed, - gasPrice: `${receipt.gasPrice.toString()} micronibi`, - expectedOwnerWei, - }) - expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei) - expect(provider.getBalance(recipient)).resolves.toBe(weiToSend) - }, 20e3) + const ownerBalanceBefore = await provider.getBalance(account) + const recipientBalanceBefore = await provider.getBalance(recipient) + expect(recipientBalanceBefore).toEqual(BigInt(0)) - it("should send via send method", async () => { - const contract = await deploySendReceiveNibi() - const recipient = Wallet.createRandom() - const weiToSend = toBigInt(100e12) * toBigInt(1e6) // 100 NIBi + const tx = await contract[method](recipient, { value: weiToSend }) + const receipt = await tx.wait(1, 5e3) - const ownerBalanceBefore = await provider.getBalance(account) // NIBI - const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI - expect(recipientBalanceBefore).toEqual(BigInt(0)) + const tenPow12 = toBigInt(1e12) + const txCostMicronibi = weiToSend / tenPow12 + receipt.gasUsed + const txCostWei = txCostMicronibi * tenPow12 + const expectedOwnerWei = ownerBalanceBefore - txCostWei - const tx = await contract.sendViaSend(recipient, { - value: weiToSend, - }) - const receipt = await tx.wait(1, 5e3) + console.debug(`DEBUG method ${method} %o:`, { + ownerBalanceBefore, + weiToSend, + gasUsed: receipt.gasUsed, + gasPrice: `${receipt.gasPrice.toString()} micronibi`, + expectedOwnerWei, + }) - // Assert balances with logging - const tenPow12 = toBigInt(1e12) - const txCostMicronibi = weiToSend / tenPow12 + receipt.gasUsed - const txCostWei = txCostMicronibi * tenPow12 - const expectedOwnerWei = ownerBalanceBefore - txCostWei - console.debug("DEBUG send via send method %o:", { - ownerBalanceBefore, - weiToSend, - gasUsed: receipt.gasUsed, - gasPrice: `${receipt.gasPrice.toString()} micronibi`, - expectedOwnerWei, - }) - expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei) - expect(provider.getBalance(recipient)).resolves.toBe(weiToSend) - }, 20e3) + await expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei) + await expect(provider.getBalance(recipient)).resolves.toBe(weiToSend) +} - it("should send via transfer method", async () => { - const contract = await deploySendReceiveNibi() - const recipient = Wallet.createRandom() - const weiToSend = toBigInt(100e12) * toBigInt(1e6) // 100 NIBI - - const ownerBalanceBefore = await provider.getBalance(account) // NIBI - const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI - expect(recipientBalanceBefore).toEqual(BigInt(0)) +describe("Send NIBI via smart contract", () => { + const TIMEOUT_MS = 20e3 + it( + "method sendViaTransfer", + async () => { + const weiToSend: bigint = toBigInt(5e12) * toBigInt(1e6) + await testSendNibi("sendViaTransfer", weiToSend) + }, + TIMEOUT_MS, + ) - const tx = await contract.sendViaCall(recipient, { - value: weiToSend, - }) - const receipt = await tx.wait(1, 5e3) + it( + "method sendViaSend", + async () => { + const weiToSend: bigint = toBigInt(100e12) * toBigInt(1e6) + await testSendNibi("sendViaSend", weiToSend) + }, + TIMEOUT_MS, + ) - // Assert balances with logging - const tenPow12 = toBigInt(1e12) - const txCostMicronibi = weiToSend / tenPow12 + receipt.gasUsed - const txCostWei = txCostMicronibi * tenPow12 - const expectedOwnerWei = ownerBalanceBefore - txCostWei - console.debug("DEBUG should send via transfer method %o:", { - ownerBalanceBefore, - weiToSend, - gasUsed: receipt.gasUsed, - gasPrice: `${receipt.gasPrice.toString()} micronibi`, - expectedOwnerWei, - }) - expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei) - expect(provider.getBalance(recipient)).resolves.toBe(weiToSend) - }, 20e3) + it( + "method sendViaCall", + async () => { + const weiToSend: bigint = toBigInt(100e12) * toBigInt(1e6) + await testSendNibi("sendViaCall", weiToSend) + }, + TIMEOUT_MS, + ) }) diff --git a/e2e/evm/test/debug_queries.test.ts b/e2e/evm/test/debug_queries.test.ts index 2d6a4909a..c348d2c3f 100644 --- a/e2e/evm/test/debug_queries.test.ts +++ b/e2e/evm/test/debug_queries.test.ts @@ -1,57 +1,62 @@ import { describe, expect, it, beforeAll } from "@jest/globals" -import { parseEther } from "ethers" +import { TransactionReceipt, parseEther } from "ethers" import { provider } from "./setup" -import { alice, deployERC20 } from "./utils" +import { alice, deployContractTestERC20 } from "./utils" describe("debug queries", () => { - let contractAddress - let txHash - let txIndex - let blockNumber - let blockHash + let contractAddress: string + let txHash: string + let txIndex: number + let blockNumber: number + let blockHash: string beforeAll(async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() contractAddress = await contract.getAddress() // Execute some contract TX const txResponse = await contract.transfer(alice, parseEther("0.01")) await txResponse.wait(1, 5e3) - const receipt = await provider.getTransactionReceipt(txResponse.hash) + const receipt: TransactionReceipt = await provider.getTransactionReceipt( + txResponse.hash, + ) txHash = txResponse.hash txIndex = txResponse.index blockNumber = receipt.blockNumber blockHash = receipt.blockHash }, 20e3) - it("debug_traceBlockByNumber", async () => { + // TODO: impl in EVM: remove skip + it.skip("debug_traceBlockByNumber", async () => { const traceResult = await provider.send("debug_traceBlockByNumber", [ blockNumber, ]) expectTrace(traceResult) }) - it("debug_traceBlockByHash", async () => { + // TODO: impl in EVM: remove skip + it.skip("debug_traceBlockByHash", async () => { const traceResult = await provider.send("debug_traceBlockByHash", [ blockHash, ]) expectTrace(traceResult) }) - it("debug_traceTransaction", async () => { + // TODO: impl in EVM: remove skip + it.skip("debug_traceTransaction", async () => { const traceResult = await provider.send("debug_traceTransaction", [txHash]) expectTrace([{ result: traceResult }]) }) - // TODO: implement that in EVM + // TODO: impl in EVM: remove skip it.skip("debug_getBadBlocks", async () => { const traceResult = await provider.send("debug_getBadBlocks", [txHash]) expect(traceResult).toBeDefined() }) - // TODO: implement that in EVM + // TODO: impl in EVM: remove skip it.skip("debug_storageRangeAt", async () => { const traceResult = await provider.send("debug_storageRangeAt", [ blockNumber, diff --git a/e2e/evm/test/erc20.test.ts b/e2e/evm/test/erc20.test.ts index b5d87c3f7..d30bba208 100644 --- a/e2e/evm/test/erc20.test.ts +++ b/e2e/evm/test/erc20.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from "@jest/globals" import { parseUnits, toBigInt, Wallet } from "ethers" import { account } from "./setup" -import { deployERC20 } from "./utils" +import { deployContractTestERC20 } from "./utils" describe("ERC-20 contract tests", () => { it("should send properly", async () => { - const contract = await deployERC20() + const contract = await deployContractTestERC20() expect(contract.getAddress()).resolves.toBeDefined() const ownerInitialBalance = parseUnits("1000000", 18) diff --git a/e2e/evm/test/eth_queries.test.ts b/e2e/evm/test/eth_queries.test.ts index a06e894ea..14f0c7aa5 100644 --- a/e2e/evm/test/eth_queries.test.ts +++ b/e2e/evm/test/eth_queries.test.ts @@ -1,8 +1,61 @@ -import { describe, expect, it } from "@jest/globals" -import { parseEther, keccak256, AbiCoder } from "ethers" +import { describe, expect, it, jest } from "@jest/globals" +import { + toBigInt, + parseEther, + keccak256, + AbiCoder, + TransactionRequest, +} from "ethers" import { account, provider } from "./setup" -import { SendNibiCompiled__factory } from "../types/ethers-contracts" -import { alice, deployERC20, sendTestNibi } from "./utils" +import { + INTRINSIC_TX_GAS, + alice, + deployContractTestERC20, + deployContractSendNibi, + hexify, + sendTestNibi, +} from "./utils" + +describe("Basic Queries", () => { + jest.setTimeout(15e3) + + it("Simple transfer, balance check", async () => { + const amountToSend = toBigInt(5e12) * toBigInt(1e6) // unibi + const senderBalanceBefore = await provider.getBalance(account) + const recipientBalanceBefore = await provider.getBalance(alice) + expect(senderBalanceBefore).toBeGreaterThan(0) + expect(recipientBalanceBefore).toEqual(BigInt(0)) + + const tenPow12 = toBigInt(1e12) + + // Execute EVM transfer + const transaction: TransactionRequest = { + gasLimit: toBigInt(100e3), + to: alice, + value: amountToSend, + } + const txResponse = await account.sendTransaction(transaction) + await txResponse.wait(1, 10e3) + expect(txResponse).toHaveProperty("blockHash") + + const senderBalanceAfter = await provider.getBalance(account) + const recipientBalanceAfter = await provider.getBalance(alice) + + // Assert balances with logging + const gasUsed = 50000n // 50k gas for the transaction + const txCostMicronibi = amountToSend / tenPow12 + gasUsed + const txCostWei = txCostMicronibi * tenPow12 + const expectedSenderWei = senderBalanceBefore - txCostWei + console.debug("DEBUG should send via transfer method %o:", { + senderBalanceBefore, + amountToSend, + expectedSenderWei, + senderBalanceAfter, + }) + expect(senderBalanceAfter).toEqual(expectedSenderWei) + expect(recipientBalanceAfter).toEqual(amountToSend) + }) +}) describe("eth queries", () => { it("eth_accounts", async () => { @@ -18,6 +71,7 @@ describe("eth queries", () => { } const estimatedGas = await provider.estimateGas(tx) expect(estimatedGas).toBeGreaterThan(BigInt(0)) + expect(estimatedGas).toEqual(INTRINSIC_TX_GAS) }) it("eth_feeHistory", async () => { @@ -40,6 +94,7 @@ describe("eth queries", () => { it("eth_gasPrice", async () => { const gasPrice = await provider.send("eth_gasPrice", []) expect(gasPrice).toBeDefined() + expect(gasPrice).toEqual(hexify(1)) }) it("eth_getBalance", async () => { @@ -87,9 +142,7 @@ describe("eth queries", () => { }) it("eth_getCode", async () => { - const factory = new SendNibiCompiled__factory(account) - const contract = await factory.deploy() - await contract.waitForDeployment() + const contract = await deployContractSendNibi() const contractAddr = await contract.getAddress() const code = await provider.send("eth_getCode", [contractAddr, "latest"]) expect(code).toBeDefined() @@ -97,7 +150,7 @@ describe("eth queries", () => { it("eth_getFilterChanges", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const filter = { fromBlock: "latest", @@ -121,12 +174,12 @@ describe("eth queries", () => { const success = await provider.send("eth_uninstallFilter", [filterId]) expect(success).toBeTruthy() - }, 20e3) + }) // Skipping as the method is not implemented it.skip("eth_getFilterLogs", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const filter = { fromBlock: "latest", @@ -146,19 +199,19 @@ describe("eth queries", () => { expect(changes[0]).toHaveProperty("address") expect(changes[0]).toHaveProperty("data") expect(changes[0]).toHaveProperty("topics") - }, 20e3) + }) // Skipping as the method is not implemented it.skip("eth_getLogs", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const filter = { fromBlock: "latest", address: contractAddr, } // Execute some contract TX - const tx = await contract.transfer(alice, parseEther("0.01")) + const _tx = await contract.transfer(alice, parseEther("0.01")) // Assert logs const changes = await provider.send("eth_getLogs", [filter]) @@ -166,11 +219,11 @@ describe("eth queries", () => { expect(changes[0]).toHaveProperty("address") expect(changes[0]).toHaveProperty("data") expect(changes[0]).toHaveProperty("topics") - }, 20e3) + }) it("eth_getProof", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const slot = 1 // Assuming balanceOf is at slot 1 @@ -197,12 +250,12 @@ describe("eth queries", () => { expect(proof.storageProof[0]).toHaveProperty("value") expect(proof.storageProof[0]).toHaveProperty("proof") } - }, 20e3) + }) // Skipping as the method is not implemented it.skip("eth_getLogs", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const filter = { fromBlock: "latest", @@ -218,10 +271,10 @@ describe("eth queries", () => { expect(logs[0]).toHaveProperty("address") expect(logs[0]).toHaveProperty("data") expect(logs[0]).toHaveProperty("topics") - }, 20e3) + }) it("eth_getProof", async () => { - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const slot = 1 // Assuming balanceOf is at slot 1 @@ -248,15 +301,15 @@ describe("eth queries", () => { expect(proof.storageProof[0]).toHaveProperty("value") expect(proof.storageProof[0]).toHaveProperty("proof") } - }, 20e3) + }) it("eth_getStorageAt", async () => { - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const value = await provider.getStorage(contractAddr, 1) expect(value).toBeDefined() - }, 20e3) + }) it("eth_getTransactionByBlockHashAndIndex, eth_getTransactionByBlockNumberAndIndex", async () => { // Execute EVM transfer @@ -283,16 +336,16 @@ describe("eth queries", () => { expect(txByBlockNumber["from"]).toEqual(txByBlockHash["from"]) expect(txByBlockNumber["to"]).toEqual(txByBlockHash["to"]) expect(txByBlockNumber["value"]).toEqual(txByBlockHash["value"]) - }, 20e3) + }) it("eth_getTransactionByHash", async () => { const txResponse = await sendTestNibi() const txByHash = await provider.getTransaction(txResponse.hash) expect(txByHash).toBeDefined() expect(txByHash.hash).toEqual(txResponse.hash) - }, 20e3) + }) - it("eth_getTransactionByHash", async () => { + it("eth_getTransactionCount", async () => { const txCount = await provider.getTransactionCount(account.address) expect(txCount).toBeGreaterThanOrEqual(0) }) @@ -302,7 +355,7 @@ describe("eth queries", () => { const txReceipt = await provider.getTransactionReceipt(txResponse.hash) expect(txReceipt).toBeDefined() expect(txReceipt.hash).toEqual(txResponse.hash) - }, 20e3) + }) it("eth_getUncleCountByBlockHash", async () => { const latestBlock = await provider.getBlockNumber() diff --git a/e2e/evm/test/utils.ts b/e2e/evm/test/utils.ts index 6029685b3..e034fb489 100644 --- a/e2e/evm/test/utils.ts +++ b/e2e/evm/test/utils.ts @@ -1,34 +1,51 @@ import { + InfiniteLoopGasCompiled__factory, SendNibiCompiled__factory, TestERC20Compiled__factory, } from "../types/ethers-contracts" import { account } from "./setup" -import { parseEther, toBigInt, Wallet } from "ethers" +import { parseEther, toBigInt, TransactionRequest, Wallet } from "ethers" -const alice = Wallet.createRandom() +export const alice = Wallet.createRandom() -const deployERC20 = async () => { +export const hexify = (x: number): string => { + return "0x" + x.toString(16) +} + +/** 10 to the power of 12 */ +export const TENPOW12 = toBigInt(1e12) + +export const INTRINSIC_TX_GAS: bigint = 21000n + +export const deployContractTestERC20 = async () => { const factory = new TestERC20Compiled__factory(account) - const contract = await factory.deploy() + const contract = await factory.deploy({ maxFeePerGas: TENPOW12 }) await contract.waitForDeployment() return contract } -const deploySendReceiveNibi = async () => { + +export const deployContractSendNibi = async () => { const factory = new SendNibiCompiled__factory(account) - const contract = await factory.deploy() + const contract = await factory.deploy({ maxFeePerGas: TENPOW12 }) await contract.waitForDeployment() return contract } -const sendTestNibi = async () => { - const transaction = { +export const deployContractInfiniteLoopGas = async () => { + const factory = new InfiniteLoopGasCompiled__factory(account) + const contract = await factory.deploy({ maxFeePerGas: TENPOW12 }) + await contract.waitForDeployment() + return contract +} + +export const sendTestNibi = async () => { + const transaction: TransactionRequest = { gasLimit: toBigInt(100e3), to: alice, value: parseEther("0.01"), + maxFeePerGas: TENPOW12, } const txResponse = await account.sendTransaction(transaction) await txResponse.wait(1, 10e3) return txResponse } - -export { alice, deployERC20, deploySendReceiveNibi, sendTestNibi } diff --git a/eth/rpc/backend/call_tx.go b/eth/rpc/backend/call_tx.go index 7f61bf880..d7cff6eb4 100644 --- a/eth/rpc/backend/call_tx.go +++ b/eth/rpc/backend/call_tx.go @@ -148,6 +148,7 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { } txHash := ethereumTx.AsTransaction().Hash() + b.logger.Debug("eth_sendRawTransaction", "txHash", txHash.Hex()) syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync) rsp, err := syncCtx.BroadcastTx(txBytes) @@ -291,7 +292,9 @@ func (b *Backend) SetTxDefaults(args evm.JsonTxArgs) (evm.JsonTxArgs, error) { } // EstimateGas returns an estimate of gas usage for the given smart contract call. -func (b *Backend) EstimateGas(args evm.JsonTxArgs, blockNrOptional *rpc.BlockNumber) (hexutil.Uint64, error) { +func (b *Backend) EstimateGas( + args evm.JsonTxArgs, blockNrOptional *rpc.BlockNumber, +) (hexutil.Uint64, error) { blockNr := rpc.EthPendingBlockNumber if blockNrOptional != nil { blockNr = *blockNrOptional diff --git a/eth/rpc/backend/call_tx_test.go b/eth/rpc/backend/call_tx_test.go index c10039f8a..879438d51 100644 --- a/eth/rpc/backend/call_tx_test.go +++ b/eth/rpc/backend/call_tx_test.go @@ -305,21 +305,21 @@ func (s *BackendSuite) TestSendRawTransaction() { expPass bool }{ { - "fail - empty bytes", + "sad - empty bytes", func() {}, []byte{}, common.Hash{}, false, }, { - "fail - no RLP encoded bytes", + "sad - no RLP encoded bytes", func() {}, bz, common.Hash{}, false, }, { - "fail - unprotected transactions", + "sad - unprotected transactions", func() { queryClient := s.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) s.backend.allowUnprotectedTxs = false @@ -330,7 +330,7 @@ func (s *BackendSuite) TestSendRawTransaction() { false, }, { - "fail - failed to get evm params", + "sad - failed to get evm params", func() { queryClient := s.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) s.backend.allowUnprotectedTxs = true @@ -341,7 +341,7 @@ func (s *BackendSuite) TestSendRawTransaction() { false, }, { - "fail - failed to broadcast transaction", + "sad - failed to broadcast transaction", func() { client := s.backend.clientCtx.Client.(*mocks.Client) queryClient := s.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) diff --git a/eth/rpc/backend/tx_info.go b/eth/rpc/backend/tx_info.go index bd6d3474a..48bbbf2bf 100644 --- a/eth/rpc/backend/tx_info.go +++ b/eth/rpc/backend/tx_info.go @@ -213,6 +213,8 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ return nil, errors.New("can't find index of ethereum tx") } + // TODO: refactor(evm-rpc-backend): Replace interface with gethcore.Receipt + // in eth_getTransactionReceipt receipt := map[string]interface{}{ // Consensus fields: These fields are defined by the Yellow Paper "status": status, @@ -253,7 +255,7 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ // tolerate the error for pruned node. b.logger.Error("fetch basefee failed, node is pruned?", "height", res.Height, "error", err) } else { - receipt["effectiveGasPrice"] = hexutil.Big(*dynamicTx.EffectiveGasPrice(baseFee)) + receipt["effectiveGasPrice"] = hexutil.Big(*dynamicTx.EffectiveGasPriceWei(baseFee)) } } diff --git a/eth/rpc/rpc.go b/eth/rpc/rpc.go index 4ca1d8393..8bd080c61 100644 --- a/eth/rpc/rpc.go +++ b/eth/rpc/rpc.go @@ -168,7 +168,7 @@ func NewRPCTxFromMsg( return NewRPCTxFromEthTx(tx, blockHash, blockNumber, index, baseFee, chainID) } -// NewTransactionFromData returns a transaction that will serialize to the RPC +// NewRPCTxFromEthTx returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func NewRPCTxFromEthTx( tx *gethcore.Transaction, diff --git a/eth/rpc/rpcapi/eth_api_test.go b/eth/rpc/rpcapi/eth_api_test.go index cac42bea1..39e995e87 100644 --- a/eth/rpc/rpcapi/eth_api_test.go +++ b/eth/rpc/rpcapi/eth_api_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" bank "github.com/cosmos/cosmos-sdk/x/bank/types" geth "github.com/ethereum/go-ethereum" @@ -19,9 +20,9 @@ import ( "github.com/NibiruChain/nibiru/v2/app/appconst" "github.com/NibiruChain/nibiru/v2/eth" + "github.com/NibiruChain/nibiru/v2/eth/rpc/rpcapi" "github.com/NibiruChain/nibiru/v2/gosdk" - nibirucommon "github.com/NibiruChain/nibiru/v2/x/common" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" @@ -43,8 +44,10 @@ type TestSuite struct { suite.Suite cfg testnetwork.Config network *testnetwork.Network + val *testnetwork.Validator ethClient *ethclient.Client + ethAPI *rpcapi.EthAPI fundedAccPrivateKey *ecdsa.PrivateKey fundedAccEthAddr gethcommon.Address @@ -70,7 +73,9 @@ func (s *TestSuite) SetupSuite() { s.Require().NoError(err) s.network = network - s.ethClient = network.Validators[0].JSONRPCClient + s.val = network.Validators[0] + s.ethClient = s.val.JSONRPCClient + s.ethAPI = s.val.EthRPC_ETH s.contractData = embeds.SmartContract_TestERC20 testAccPrivateKey, _ := crypto.GenerateKey() @@ -78,10 +83,8 @@ func (s *TestSuite) SetupSuite() { s.fundedAccEthAddr = crypto.PubkeyToAddress(testAccPrivateKey.PublicKey) s.fundedAccNibiAddr = eth.EthAddrToNibiruAddr(s.fundedAccEthAddr) - val := s.network.Validators[0] - funds := sdk.NewCoins(sdk.NewInt64Coin(eth.EthBaseDenom, 100_000_000)) // 10 NIBI - s.NoError(testnetwork.FillWalletFromValidator(s.fundedAccNibiAddr, funds, val, eth.EthBaseDenom)) + s.NoError(testnetwork.FillWalletFromValidator(s.fundedAccNibiAddr, funds, s.val, eth.EthBaseDenom)) s.NoError(s.network.WaitForNextBlock()) } @@ -109,9 +112,7 @@ func (s *TestSuite) Test_BlockByNumber() { ethBlock, err := s.ethClient.BlockByNumber(context.Background(), big.NewInt(networkBlockNumber)) s.NoError(err) - - // TODO: add more checks about the eth block - s.NotNil(ethBlock) + s.NoError(ethBlock.SanityCheck()) } // Test_BalanceAt EVM method: eth_getBalance @@ -240,7 +241,7 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { s.T().Logf("Sending %d wei to %s", weiToSend, recipientAddr.Hex()) signer := gethcore.LatestSignerForChainID(chainID) - gasPrice := big.NewInt(1) + gasPrice := evm.NativeToWei(big.NewInt(1)) tx, err := gethcore.SignNewTx( s.fundedAccPrivateKey, signer, @@ -249,7 +250,7 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { To: &recipientAddr, Value: weiToSend, Gas: params.TxGas, - GasPrice: gasPrice, + GasPrice: gasPrice, // 1 micronibi per gas }) s.NoError(err) err = s.ethClient.SendTransaction(context.Background(), tx) @@ -261,7 +262,7 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { costOfTx := new(big.Int).Add( weiToSend, - new(big.Int).Mul(evm.NativeToWei(new(big.Int).SetUint64(params.TxGas)), gasPrice), + new(big.Int).Mul((new(big.Int).SetUint64(params.TxGas)), gasPrice), ) wantSenderBalWei := new(big.Int).Sub(senderBalanceBeforeWei, costOfTx) s.Equal(wantSenderBalWei.String(), senderAmountAfterWei.String(), "surpising sender balance") @@ -271,6 +272,8 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { s.Equal(weiToSend.String(), recipientBalanceAfter.String()) } +var blankCtx = context.Background() + // Test_SmartContract includes contract deployment, query, execution func (s *TestSuite) Test_SmartContract() { chainID, err := s.ethClient.ChainID(context.Background()) @@ -278,84 +281,115 @@ func (s *TestSuite) Test_SmartContract() { nonce, err := s.ethClient.NonceAt(context.Background(), s.fundedAccEthAddr, nil) s.NoError(err) - // Deploying contract - signer := gethcore.LatestSignerForChainID(chainID) - txData := s.contractData.Bytecode - tx, err := gethcore.SignNewTx( - s.fundedAccPrivateKey, - signer, - &gethcore.LegacyTx{ - Nonce: nonce, - Gas: 1_500_000, - GasPrice: big.NewInt(1), - Data: txData, - }) - s.NoError(err) - err = s.ethClient.SendTransaction(context.Background(), tx) - s.NoError(err) + s.T().Log("Make sure the account has funds.") + + funds := sdk.NewCoins(sdk.NewInt64Coin(eth.EthBaseDenom, 1_000_000_000)) + s.Require().NoError(testnetwork.FillWalletFromValidator( + s.fundedAccNibiAddr, funds, s.network.Validators[0], eth.EthBaseDenom), + ) s.NoError(s.network.WaitForNextBlock()) - hash := tx.Hash() - receipt, err := s.ethClient.TransactionReceipt(context.Background(), hash) - s.NoError(err) - contractAddress := receipt.ContractAddress - // Querying contract: owner's balance should be 1000_000 tokens - ownerInitialBalance := (&big.Int{}).Mul(big.NewInt(1000_000), nibirucommon.TO_ATTO) - s.assertERC20Balance(contractAddress, s.fundedAccEthAddr, ownerInitialBalance) + grpcUrl := s.network.Validators[0].AppConfig.GRPC.Address + grpcConn, err := gosdk.GetGRPCConnection(grpcUrl, true, 5) + s.NoError(err) - // Querying contract: recipient balance should be 0 - recipientAddr := gethcommon.BytesToAddress(testnetwork.NewAccount(s.network, "contract_recipient")) - s.assertERC20Balance(contractAddress, recipientAddr, big.NewInt(0)) + querier := bank.NewQueryClient(grpcConn) + resp, err := querier.Balance(context.Background(), &bank.QueryBalanceRequest{ + Address: s.fundedAccNibiAddr.String(), + Denom: eth.EthBaseDenom, + }) + s.Require().NoError(err) + // Expect 1.005 billion because of the setup function before this test. + s.True(resp.Balance.Amount.GT(math.NewInt(1_004_900_000)), "unexpectedly low balance ", resp.Balance.Amount.String()) - // Execute contract: send 1000 anibi to recipient - sendAmount := (&big.Int{}).Mul(big.NewInt(1000), nibirucommon.TO_ATTO) - input, err := s.contractData.ABI.Pack("transfer", recipientAddr, sendAmount) - s.NoError(err) - nonce, err = s.ethClient.NonceAt(context.Background(), s.fundedAccEthAddr, nil) - s.NoError(err) - tx, err = gethcore.SignNewTx( + s.T().Log("Deploy contract") + signer := gethcore.LatestSignerForChainID(chainID) + txData := s.contractData.Bytecode + tx, err := gethcore.SignNewTx( s.fundedAccPrivateKey, signer, &gethcore.LegacyTx{ - Nonce: nonce, - To: &contractAddress, - Gas: 1_500_000, - GasPrice: big.NewInt(1), - Data: input, + Nonce: nonce, + Gas: 100_500_000 + params.TxGasContractCreation, + GasPrice: evm.NativeToWei(new(big.Int).Add( + evm.BASE_FEE_MICRONIBI, big.NewInt(0), + )), + Data: txData, }) + s.Require().NoError(err) + + txBz, err := tx.MarshalBinary() s.NoError(err) - err = s.ethClient.SendTransaction(context.Background(), tx) - s.NoError(err) - s.NoError(s.network.WaitForNextBlock()) + txHash, err := s.ethAPI.SendRawTransaction(txBz) + s.Require().NoError(err) - // Querying contract: owner's balance should be 999_000 tokens - ownerBalance := (&big.Int{}).Mul(big.NewInt(999_000), nibirucommon.TO_ATTO) - s.assertERC20Balance(contractAddress, s.fundedAccEthAddr, ownerBalance) + s.T().Log("Assert: tx IS pending just after execution") + pendingTxs, err := s.ethAPI.GetPendingTransactions() + s.NoError(err) + s.Require().Len(pendingTxs, 1) + _ = s.network.WaitForNextBlock() + + s.T().Log("Assert: tx NOT pending") + { + wantCount := 0 + pending, err := s.ethClient.PendingTransactionCount(blankCtx) + s.NoError(err) + s.Require().EqualValues(uint(wantCount), pending) + + pendingTxs, err := s.ethAPI.GetPendingTransactions() + s.NoError(err) + s.Require().Len(pendingTxs, wantCount) + + // This query will succeed only if a receipt is found + _, err = s.ethClient.TransactionReceipt(blankCtx, txHash) + s.Require().Errorf(err, "receipt for txHash: %s", txHash.Hex()) + + // This query succeeds if no receipt is found + _, err = s.ethAPI.GetTransactionReceipt(txHash) + s.Require().NoError(err) + } - // Querying contract: recipient balance should be 1000 tokens - recipientBalance := (&big.Int{}).Mul(big.NewInt(1000), nibirucommon.TO_ATTO) - s.assertERC20Balance(contractAddress, recipientAddr, recipientBalance) + { + weiToSend := evm.NativeToWei(big.NewInt(1)) // 1 unibi + s.T().Logf("Sending %d wei (sanity check)", weiToSend) + accResp, err := s.val.EthRpcQueryClient.QueryClient.EthAccount(blankCtx, + &evm.QueryEthAccountRequest{ + Address: s.fundedAccEthAddr.Hex(), + }) + s.NoError(err) + nonce := accResp.Nonce + recipientAddr := gethcommon.BytesToAddress(testnetwork.NewAccount(s.network, "recipient")) + + signer := gethcore.LatestSignerForChainID(chainID) + gasPrice := evm.NativeToWei(big.NewInt(1)) + tx, err := gethcore.SignNewTx( + s.fundedAccPrivateKey, + signer, + &gethcore.LegacyTx{ + Nonce: nonce, + To: &recipientAddr, + Value: weiToSend, + Gas: params.TxGas, + GasPrice: gasPrice, // 1 micronibi per gas + }) + s.Require().NoError(err) + txBz, err := tx.MarshalBinary() + s.NoError(err) + txHash, err := s.ethAPI.SendRawTransaction(txBz) + // err = s.ethClient.SendTransaction(blankCtx, tx) + s.Require().NoError(err) + _ = s.network.WaitForNextBlock() + + txReceipt, err := s.ethClient.TransactionReceipt(blankCtx, txHash) + s.Require().NoError(err) + s.NotNil(txReceipt) + + _, err = s.ethAPI.GetTransactionLogs(txHash) + s.NoError(err) + } } func (s *TestSuite) TearDownSuite() { s.T().Log("tearing down integration test suite") s.network.Cleanup() } - -func (s *TestSuite) assertERC20Balance( - contractAddress gethcommon.Address, - userAddress gethcommon.Address, - expectedBalance *big.Int, -) { - input, err := s.contractData.ABI.Pack("balanceOf", userAddress) - s.NoError(err) - msg := geth.CallMsg{ - From: s.fundedAccEthAddr, - To: &contractAddress, - Data: input, - } - recipientBalanceBeforeBytes, err := s.ethClient.CallContract(context.Background(), msg, nil) - s.NoError(err) - balance := new(big.Int).SetBytes(recipientBalanceBeforeBytes) - s.Equal(expectedBalance.String(), balance.String()) -} diff --git a/eth/rpc/rpcapi/net_api_test.go b/eth/rpc/rpcapi/net_api_test.go new file mode 100644 index 000000000..2803a7e15 --- /dev/null +++ b/eth/rpc/rpcapi/net_api_test.go @@ -0,0 +1,13 @@ +package rpcapi_test + +import ( + "github.com/NibiruChain/nibiru/v2/app/appconst" +) + +func (s *TestSuite) TestNetNamespace() { + api := s.val.EthRpc_NET + s.Require().True(api.Listening()) + s.EqualValues( + appconst.GetEthChainID(s.val.ClientCtx.ChainID).String(), api.Version()) + s.Equal(0, api.PeerCount()) +} diff --git a/proto/eth/evm/v1/tx.proto b/proto/eth/evm/v1/tx.proto index e97a81e5c..dd336183f 100644 --- a/proto/eth/evm/v1/tx.proto +++ b/proto/eth/evm/v1/tx.proto @@ -51,8 +51,12 @@ message MsgEthereumTx { } // LegacyTx is the transaction data of regular Ethereum transactions. -// NOTE: All non-protected transactions (i.e non EIP155 signed) will fail if the -// AllowUnprotectedTxs parameter is disabled. +// +// Note that setting "evm.Params.AllowUnprotectedTxs" to false will cause all +// non-EIP155 signed transactions to fail, as they'll lack replay protection. +// +// LegacyTx is a custom implementation of "LegacyTx" from +// "github.com/ethereum/go-ethereum/core/types". message LegacyTx { option (gogoproto.goproto_getters) = false; option (cosmos_proto.implements_interface) = "TxData"; @@ -69,15 +73,28 @@ message LegacyTx { string value = 5 [(gogoproto.customtype) = "cosmossdk.io/math.Int", (gogoproto.customname) = "Amount"]; // data is the data payload bytes of the transaction. bytes data = 6; - // v defines the signature value + + // v defines the recovery id as the "v" signature value from the elliptic curve + // digital signatute algorithm (ECDSA). It indicates which of two possible + // solutions should be used to reconstruct the public key from the signature. + // In Ethereum, "v" takes the value 27 or 28 for transactions that are not + // relay-protected. bytes v = 7; - // r defines the signature value + + // r defines the x-coordinate of a point on the elliptic curve in the elliptic curve + // digital signatute algorithm (ECDSA). It's crucial in ensuring uniqueness of + // the signature. bytes r = 8; - // s define the signature value + + // s define the signature value derived from the private key, message hash, and + // the value of "r". It ensures that the signature is tied to both the message + // and the private key of the sender. bytes s = 9; } // AccessListTx is the data of EIP-2930 access list transactions. +// It is a custom implementation of "AccessListTx" from +// "github.com/ethereum/go-ethereum/core/types". message AccessListTx { option (gogoproto.goproto_getters) = false; option (cosmos_proto.implements_interface) = "TxData"; @@ -103,15 +120,28 @@ message AccessListTx { // accesses is an array of access tuples repeated AccessTuple accesses = 8 [(gogoproto.castrepeated) = "AccessList", (gogoproto.jsontag) = "accessList", (gogoproto.nullable) = false]; - // v defines the signature value + + // v defines the recovery id and "v" signature value from the elliptic curve + // digital signatute algorithm (ECDSA). It indicates which of two possible + // solutions should be used to reconstruct the public key from the signature. + // In Ethereum, "v" takes the value 27 or 28 for transactions that are not + // relay-protected. bytes v = 9; - // r defines the signature value + + // r defines the x-coordinate of a point on the elliptic curve in the elliptic curve + // digital signatute algorithm (ECDSA). It's crucial in ensuring uniqueness of + // the signature. bytes r = 10; - // s define the signature value + + // s define the signature value derived from the private key, message hash, and + // the value of "r". It ensures that the signature is tied to both the message + // and the private key of the sender. bytes s = 11; } -// DynamicFeeTx is the data of EIP-1559 dinamic fee transactions. +// DynamicFeeTx is the data of EIP-1559 dynamic fee transactions. It is a custom +// implementation of "DynamicFeeTx" from +// "github.com/ethereum/go-ethereum/core/types". message DynamicFeeTx { option (gogoproto.goproto_getters) = false; option (cosmos_proto.implements_interface) = "TxData"; @@ -139,11 +169,21 @@ message DynamicFeeTx { // accesses is an array of access tuples repeated AccessTuple accesses = 9 [(gogoproto.castrepeated) = "AccessList", (gogoproto.jsontag) = "accessList", (gogoproto.nullable) = false]; - // v defines the signature value + // v defines the recovery id and "v" signature value from the elliptic curve + // digital signatute algorithm (ECDSA). It indicates which of two possible + // solutions should be used to reconstruct the public key from the signature. + // In Ethereum, "v" takes the value 27 or 28 for transactions that are not + // relay-protected. bytes v = 10; - // r defines the signature value + + // r defines the x-coordinate of a point on the elliptic curve in the elliptic curve + // digital signatute algorithm (ECDSA). It's crucial in ensuring uniqueness of + // the signature. bytes r = 11; - // s define the signature value + + // s define the signature value derived from the private key, message hash, and + // the value of "r". It ensures that the signature is tied to both the message + // and the private key of the sender. bytes s = 12; } diff --git a/x/common/testutil/testnetwork/network.go b/x/common/testutil/testnetwork/network.go index 774824f9a..53509c4cf 100644 --- a/x/common/testutil/testnetwork/network.go +++ b/x/common/testutil/testnetwork/network.go @@ -98,30 +98,31 @@ func BuildNetworkConfig(appGenesis app.GenesisState) Config { chainID := "chain-" + tmrand.NewRand().Str(6) return Config{ - Codec: encCfg.Codec, - TxConfig: encCfg.TxConfig, - LegacyAmino: encCfg.Amino, - InterfaceRegistry: encCfg.InterfaceRegistry, AccountRetriever: authtypes.AccountRetriever{}, + AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction), AppConstructor: NewAppConstructor(encCfg, chainID), - GenesisState: appGenesis, - TimeoutCommit: time.Second / 2, - ChainID: chainID, - NumValidators: 1, BondDenom: denoms.NIBI, + BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction), + ChainID: chainID, + CleanupDir: true, + Codec: encCfg.Codec, + EnableTMLogging: false, // super noisy + GenesisState: appGenesis, + InterfaceRegistry: encCfg.InterfaceRegistry, + KeyringOptions: []keyring.Option{}, + LegacyAmino: encCfg.Amino, MinGasPrices: fmt.Sprintf("0.000006%s", denoms.NIBI), - AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction), + NumValidators: 1, + PruningStrategy: types.PruningOptionNothing, + SigningAlgo: string(hd.Secp256k1Type), StakingTokens: sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction), - BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction), StartingTokens: sdk.NewCoins( sdk.NewCoin(denoms.NUSD, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), sdk.NewCoin(denoms.NIBI, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), sdk.NewCoin(denoms.USDC, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), ), - PruningStrategy: types.PruningOptionNothing, - CleanupDir: true, - SigningAlgo: string(hd.Secp256k1Type), - KeyringOptions: []keyring.Option{}, + TimeoutCommit: time.Second / 2, + TxConfig: encCfg.TxConfig, } } @@ -262,12 +263,11 @@ func New(logger Logger, baseDir string, cfg Config) (network *Network, err error appCfg.JSONRPC.API = serverconfig.GetAPINamespaces() } - loggerNoOp := log.NewNopLogger() + serverCtxLogger := log.NewNopLogger() if cfg.EnableTMLogging { - loggerNoOp = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + serverCtxLogger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) } - - ctx.Logger = loggerNoOp + ctx.Logger = serverCtxLogger nodeDirName := fmt.Sprintf("node%d", valIdx) nodeDir := filepath.Join(network.BaseDir, nodeDirName, "simd") @@ -454,7 +454,7 @@ func New(logger Logger, baseDir string, cfg Config) (network *Network, err error logger.Log("starting test network...") for idx, v := range network.Validators { - err := startInProcess(cfg, v) + err := startNodeAndServers(cfg, v) if err != nil { return nil, fmt.Errorf("failed to start node: %w", err) } diff --git a/x/common/testutil/testnetwork/start_node.go b/x/common/testutil/testnetwork/start_node.go new file mode 100644 index 000000000..6753ffdc5 --- /dev/null +++ b/x/common/testutil/testnetwork/start_node.go @@ -0,0 +1,174 @@ +package testnetwork + +import ( + "fmt" + "os" + "time" + + "cosmossdk.io/errors" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/NibiruChain/nibiru/v2/app/server" + ethrpc "github.com/NibiruChain/nibiru/v2/eth/rpc" + "github.com/NibiruChain/nibiru/v2/eth/rpc/backend" + "github.com/NibiruChain/nibiru/v2/eth/rpc/rpcapi" + + "github.com/cosmos/cosmos-sdk/server/api" + servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" + srvtypes "github.com/cosmos/cosmos-sdk/server/types" + + "github.com/cometbft/cometbft/libs/log" + "github.com/cometbft/cometbft/node" + "github.com/cometbft/cometbft/p2p" + pvm "github.com/cometbft/cometbft/privval" + "github.com/cometbft/cometbft/proxy" + "github.com/cometbft/cometbft/rpc/client/local" +) + +func startNodeAndServers(cfg Config, val *Validator) error { + logger := val.Ctx.Logger + evmServerCtxLogger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + tmCfg := val.Ctx.Config + tmCfg.Instrumentation.Prometheus = false + + if err := val.AppConfig.ValidateBasic(); err != nil { + return err + } + + nodeKey, err := p2p.LoadOrGenNodeKey(tmCfg.NodeKeyFile()) + if err != nil { + return err + } + + app := cfg.AppConstructor(*val) + + genDocProvider := node.DefaultGenesisDocProviderFunc(tmCfg) + tmNode, err := node.NewNode( + tmCfg, + pvm.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()), + nodeKey, + proxy.NewLocalClientCreator(app), + genDocProvider, + node.DefaultDBProvider, + node.DefaultMetricsProvider(tmCfg.Instrumentation), + logger.With("module", val.Moniker), + ) + if err != nil { + return fmt.Errorf("failed to construct Node: %w", err) + } + + if err := tmNode.Start(); err != nil { + return fmt.Errorf("failed Node.Start(): %w", err) + } + + val.tmNode = tmNode + val.tmNode.Logger = logger + + if val.RPCAddress != "" { + val.RPCClient = local.New(tmNode) + } + + // We'll need a RPC client if the validator exposes a gRPC or REST endpoint. + if val.APIAddress != "" || val.AppConfig.GRPC.Enable { + val.ClientCtx = val.ClientCtx. + WithClient(val.RPCClient) + + // Add the tx service in the gRPC router. + app.RegisterTxService(val.ClientCtx) + + // Add the tendermint queries service in the gRPC router. + app.RegisterTendermintService(val.ClientCtx) + + val.EthRpc_NET = rpcapi.NewImplNetAPI(val.ClientCtx) + } + + if val.APIAddress != "" { + apiSrv := api.New(val.ClientCtx, logger.With("module", "api-server")) + app.RegisterAPIRoutes(apiSrv, val.AppConfig.API) + + errCh := make(chan error) + + go func() { + if err := apiSrv.Start(val.AppConfig.Config); err != nil { + errCh <- err + } + }() + + select { + case err := <-errCh: + return err + case <-time.After(srvtypes.ServerStartTime): // assume server started successfully + } + + val.api = apiSrv + } + + if val.AppConfig.GRPC.Enable { + grpcSrv, err := servergrpc.StartGRPCServer(val.ClientCtx, app, val.AppConfig.GRPC) + if err != nil { + return err + } + + val.grpc = grpcSrv + + if val.AppConfig.GRPCWeb.Enable { + val.grpcWeb, err = servergrpc.StartGRPCWeb(grpcSrv, val.AppConfig.Config) + if err != nil { + return err + } + } + } + + val.Ctx.Logger = evmServerCtxLogger + + useEthJsonRPC := val.AppConfig.JSONRPC.Enable && val.AppConfig.JSONRPC.Address != "" + if useEthJsonRPC { + if val.Ctx == nil || val.Ctx.Viper == nil { + return fmt.Errorf("validator %s context is nil", val.Moniker) + } + + tmEndpoint := "/websocket" + tmRPCAddr := fmt.Sprintf("tcp://%s", val.AppConfig.GRPC.Address) + + val.Logger.Log("Set EVM indexer") + + homeDir := val.Ctx.Config.RootDir + evmTxIndexer, err := server.OpenEVMIndexer( + val.Ctx, evmServerCtxLogger, val.ClientCtx, homeDir, + ) + if err != nil { + return err + } + val.EthTxIndexer = evmTxIndexer + + val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig, nil) + if err != nil { + return errors.Wrap(err, "failed to start JSON-RPC server") + } + + address := fmt.Sprintf("http://%s", val.AppConfig.JSONRPC.Address) + + val.JSONRPCClient, err = ethclient.Dial(address) + if err != nil { + return fmt.Errorf("failed to dial JSON-RPC at address %s: %w", val.AppConfig.JSONRPC.Address, err) + } + + val.Logger.Log("Set up Ethereum JSON-RPC client objects") + val.EthRpcQueryClient = ethrpc.NewQueryClient(val.ClientCtx) + val.EthRpcBackend = backend.NewBackend( + val.Ctx, + val.Ctx.Logger, + val.ClientCtx, + val.AppConfig.JSONRPC.AllowUnprotectedTxs, + val.EthTxIndexer, + ) + + val.Logger.Log("Expose typed methods for each namespace") + val.EthRPC_ETH = rpcapi.NewImplEthAPI(val.Ctx.Logger, val.EthRpcBackend) + val.EthRpc_WEB3 = rpcapi.NewImplWeb3API() + + val.Ctx.Logger = logger // set back to normal setting + } + + return nil +} diff --git a/x/common/testutil/testnetwork/util.go b/x/common/testutil/testnetwork/util.go index 0e48c3188..4ba1e082d 100644 --- a/x/common/testutil/testnetwork/util.go +++ b/x/common/testutil/testnetwork/util.go @@ -6,12 +6,8 @@ import ( "os" "path/filepath" "testing" - "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - - "github.com/NibiruChain/nibiru/v2/app/server" tmtypes "github.com/cometbft/cometbft/abci/types" sdkcodec "github.com/cosmos/cosmos-sdk/codec" @@ -21,19 +17,11 @@ import ( "github.com/NibiruChain/nibiru/v2/app/codec" "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server/api" - servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" - srvtypes "github.com/cosmos/cosmos-sdk/server/types" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" tmos "github.com/cometbft/cometbft/libs/os" - "github.com/cometbft/cometbft/node" - "github.com/cometbft/cometbft/p2p" - pvm "github.com/cometbft/cometbft/privval" - "github.com/cometbft/cometbft/proxy" - "github.com/cometbft/cometbft/rpc/client/local" "github.com/cometbft/cometbft/types" tmtime "github.com/cometbft/cometbft/types/time" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -41,120 +29,6 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" ) -func startInProcess(cfg Config, val *Validator) error { - logger := val.Ctx.Logger - tmCfg := val.Ctx.Config - tmCfg.Instrumentation.Prometheus = false - - if err := val.AppConfig.ValidateBasic(); err != nil { - return err - } - - nodeKey, err := p2p.LoadOrGenNodeKey(tmCfg.NodeKeyFile()) - if err != nil { - return err - } - - app := cfg.AppConstructor(*val) - - genDocProvider := node.DefaultGenesisDocProviderFunc(tmCfg) - tmNode, err := node.NewNode( - tmCfg, - pvm.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()), - nodeKey, - proxy.NewLocalClientCreator(app), - genDocProvider, - node.DefaultDBProvider, - node.DefaultMetricsProvider(tmCfg.Instrumentation), - logger.With("module", val.Moniker), - ) - if err != nil { - return fmt.Errorf("failed to construct Node: %w", err) - } - - if err := tmNode.Start(); err != nil { - return fmt.Errorf("failed Node.Start(): %w", err) - } - - val.tmNode = tmNode - val.tmNode.Logger = logger - - if val.RPCAddress != "" { - val.RPCClient = local.New(tmNode) - } - - // We'll need a RPC client if the validator exposes a gRPC or REST endpoint. - if val.APIAddress != "" || val.AppConfig.GRPC.Enable { - val.ClientCtx = val.ClientCtx. - WithClient(val.RPCClient) - - // Add the tx service in the gRPC router. - app.RegisterTxService(val.ClientCtx) - - // Add the tendermint queries service in the gRPC router. - app.RegisterTendermintService(val.ClientCtx) - } - - if val.APIAddress != "" { - apiSrv := api.New(val.ClientCtx, logger.With("module", "api-server")) - app.RegisterAPIRoutes(apiSrv, val.AppConfig.API) - - errCh := make(chan error) - - go func() { - if err := apiSrv.Start(val.AppConfig.Config); err != nil { - errCh <- err - } - }() - - select { - case err := <-errCh: - return err - case <-time.After(srvtypes.ServerStartTime): // assume server started successfully - } - - val.api = apiSrv - } - - if val.AppConfig.GRPC.Enable { - grpcSrv, err := servergrpc.StartGRPCServer(val.ClientCtx, app, val.AppConfig.GRPC) - if err != nil { - return err - } - - val.grpc = grpcSrv - - if val.AppConfig.GRPCWeb.Enable { - val.grpcWeb, err = servergrpc.StartGRPCWeb(grpcSrv, val.AppConfig.Config) - if err != nil { - return err - } - } - } - if val.AppConfig.JSONRPC.Enable && val.AppConfig.JSONRPC.Address != "" { - if val.Ctx == nil || val.Ctx.Viper == nil { - return fmt.Errorf("validator %s context is nil", val.Moniker) - } - - tmEndpoint := "/websocket" - tmRPCAddr := fmt.Sprintf("tcp://%s", val.AppConfig.GRPC.Address) - - val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig, nil) - if err != nil { - return err - } - - address := fmt.Sprintf("http://%s", val.AppConfig.JSONRPC.Address) - - val.JSONRPCClient, err = ethclient.Dial(address) - if err != nil { - return fmt.Errorf("failed to dial JSON-RPC at %s: %w", val.AppConfig.JSONRPC.Address, err) - } - } - - return nil -} - func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error { genTime := tmtime.Now() diff --git a/x/common/testutil/testnetwork/validator_node.go b/x/common/testutil/testnetwork/validator_node.go index 6d851f4ce..bbdb53395 100644 --- a/x/common/testutil/testnetwork/validator_node.go +++ b/x/common/testutil/testnetwork/validator_node.go @@ -3,14 +3,20 @@ package testnetwork import ( "context" "fmt" + "math/big" "net/http" "strings" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/suite" serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config" + "github.com/NibiruChain/nibiru/v2/eth" + ethrpc "github.com/NibiruChain/nibiru/v2/eth/rpc" + "github.com/NibiruChain/nibiru/v2/eth/rpc/backend" + "github.com/NibiruChain/nibiru/v2/eth/rpc/rpcapi" "github.com/cometbft/cometbft/node" tmclient "github.com/cometbft/cometbft/rpc/client" @@ -20,6 +26,11 @@ import ( serverapi "github.com/cosmos/cosmos-sdk/server/api" sdk "github.com/cosmos/cosmos-sdk/types" "google.golang.org/grpc" + + geth "github.com/ethereum/go-ethereum" + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/v2/x/evm/embeds" ) // Validator defines an in-process Tendermint validator node. Through this @@ -73,7 +84,14 @@ type Validator struct { // - rpc.Local RPCClient tmclient.Client - JSONRPCClient *ethclient.Client + JSONRPCClient *ethclient.Client + EthRpcQueryClient *ethrpc.QueryClient + EthRpcBackend *backend.Backend + EthTxIndexer eth.EVMTxIndexer + + EthRPC_ETH *rpcapi.EthAPI + EthRpc_WEB3 *rpcapi.APIWeb3 + EthRpc_NET *rpcapi.NetAPI Logger Logger @@ -228,3 +246,22 @@ func centerText(text string, width int) string { return fmt.Sprintf("%s%s%s", leftBuffer, text, rightBuffer) } + +func (val *Validator) AssertERC20Balance( + contract gethcommon.Address, + accAddr gethcommon.Address, + expectedBalance *big.Int, + s *suite.Suite, +) { + input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("balanceOf", accAddr) + s.NoError(err) + msg := geth.CallMsg{ + From: accAddr, + To: &contract, + Data: input, + } + recipientBalanceBeforeBytes, err := val.JSONRPCClient.CallContract(context.Background(), msg, nil) + s.NoError(err) + balance := new(big.Int).SetBytes(recipientBalanceBeforeBytes) + s.Equal(expectedBalance.String(), balance.String()) +} diff --git a/x/evm/const.go b/x/evm/const.go index b568eff06..b8302100e 100644 --- a/x/evm/const.go +++ b/x/evm/const.go @@ -10,6 +10,10 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" ) +// BASE_FEE_MICRONIBI is the global base fee value for the network. It has a +// constant value of 1 unibi (micronibi) == 10^12 wei. +var BASE_FEE_MICRONIBI = big.NewInt(1) + const ( // ModuleName string name of module ModuleName = "evm" diff --git a/x/evm/evmtest/evmante.go b/x/evm/evmtest/evmante.go index b25a72f37..54788d1a8 100644 --- a/x/evm/evmtest/evmante.go +++ b/x/evm/evmtest/evmante.go @@ -25,7 +25,7 @@ func HappyTransferTx(deps *TestDeps, nonce uint64) *evm.MsgEthereumTx { Nonce: nonce, Amount: big.NewInt(10), GasLimit: GasLimitCreateContract().Uint64(), - GasPrice: big.NewInt(1), + GasPrice: evm.NativeToWei(big.NewInt(1)), To: &to, } tx := evm.NewTx(ethContractCreationTxParams) @@ -72,7 +72,7 @@ func HappyCreateContractTx(deps *TestDeps) *evm.MsgEthereumTx { Nonce: 1, Amount: big.NewInt(10), GasLimit: GasLimitCreateContract().Uint64(), - GasPrice: big.NewInt(1), + GasPrice: evm.NativeToWei(big.NewInt(1)), } tx := evm.NewTx(ethContractCreationTxParams) tx.From = deps.Sender.EthAddr.Hex() diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index ea6055454..d75efc860 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -7,6 +7,8 @@ import ( "math/big" "testing" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" gethcommon "github.com/ethereum/go-ethereum/common" @@ -273,3 +275,20 @@ func TransferWei( } return err } + +// ValidLegacyTx: Useful initial condition for tests +// Exported only for use in tests. +func ValidLegacyTx() *evm.LegacyTx { + sdkInt := sdkmath.NewIntFromBigInt(evm.NativeToWei(big.NewInt(420))) + return &evm.LegacyTx{ + Nonce: 24, + GasLimit: 50_000, + To: gethcommon.HexToAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed").Hex(), + GasPrice: &sdkInt, + Amount: &sdkInt, + Data: []byte{}, + V: []byte{}, + R: []byte{}, + S: []byte{}, + } +} diff --git a/x/evm/json_tx_args_test.go b/x/evm/json_tx_args_test.go index fb38cc6ca..026fec314 100644 --- a/x/evm/json_tx_args_test.go +++ b/x/evm/json_tx_args_test.go @@ -11,7 +11,7 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm" ) -func (suite *TxDataTestSuite) TestTxArgsString() { +func (suite *Suite) TestTxArgsString() { testCases := []struct { name string txArgs evm.JsonTxArgs @@ -49,7 +49,7 @@ func (suite *TxDataTestSuite) TestTxArgsString() { } } -func (suite *TxDataTestSuite) TestConvertTxArgsEthTx() { +func (suite *Suite) TestConvertTxArgsEthTx() { testCases := []struct { name string txArgs evm.JsonTxArgs @@ -99,7 +99,7 @@ func (suite *TxDataTestSuite) TestConvertTxArgsEthTx() { } } -func (suite *TxDataTestSuite) TestToMessageEVM() { +func (suite *Suite) TestToMessageEVM() { testCases := []struct { name string txArgs evm.JsonTxArgs @@ -227,7 +227,7 @@ func (suite *TxDataTestSuite) TestToMessageEVM() { } } -func (suite *TxDataTestSuite) TestGetFrom() { +func (suite *Suite) TestGetFrom() { testCases := []struct { name string txArgs evm.JsonTxArgs @@ -252,7 +252,7 @@ func (suite *TxDataTestSuite) TestGetFrom() { } } -func (suite *TxDataTestSuite) TestGetData() { +func (suite *Suite) TestGetData() { testCases := []struct { name string txArgs evm.JsonTxArgs diff --git a/x/evm/keeper/gas_fees.go b/x/evm/keeper/gas_fees.go index 97b0e3678..e33fc7f09 100644 --- a/x/evm/keeper/gas_fees.go +++ b/x/evm/keeper/gas_fees.go @@ -22,18 +22,28 @@ import ( ) // GetEthIntrinsicGas returns the intrinsic gas cost for the transaction -func (k *Keeper) GetEthIntrinsicGas(ctx sdk.Context, msg core.Message, cfg *params.ChainConfig, isContractCreation bool) (uint64, error) { +func (k *Keeper) GetEthIntrinsicGas( + ctx sdk.Context, + msg core.Message, + cfg *params.ChainConfig, + isContractCreation bool, +) (uint64, error) { return core.IntrinsicGas( msg.Data(), msg.AccessList(), isContractCreation, true, true, ) } -// RefundGas transfers the leftover gas to the sender of the message, caped to half of the total gas -// consumed in the transaction. Additionally, the function sets the total gas consumed to the value -// returned by the EVM execution, thus ignoring the previous intrinsic gas consumed during in the -// AnteHandler. -func (k *Keeper) RefundGas(ctx sdk.Context, msg core.Message, leftoverGas uint64, denom string) error { +// RefundGas transfers the leftover gas to the sender of the message, caped to +// half of the total gas consumed in the transaction. Additionally, the function +// sets the total gas consumed to the value returned by the EVM execution, thus +// ignoring the previous intrinsic gas consumed during in the AnteHandler. +func (k *Keeper) RefundGas( + ctx sdk.Context, + msg core.Message, + leftoverGas uint64, + denom string, +) error { // Return EVM tokens for remaining gas, exchanged at the original rate. remaining := new(big.Int).Mul(new(big.Int).SetUint64(leftoverGas), msg.GasPrice()) @@ -59,17 +69,17 @@ func (k *Keeper) RefundGas(ctx sdk.Context, msg core.Message, leftoverGas uint64 return nil } -// ResetGasMeterAndConsumeGas reset first the gas meter consumed value to zero and set it back to the new value -// 'gasUsed' +// ResetGasMeterAndConsumeGas reset first the gas meter consumed value to zero +// and set it back to the new value 'gasUsed'. func (k *Keeper) ResetGasMeterAndConsumeGas(ctx sdk.Context, gasUsed uint64) { // reset the gas count ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "reset the gas count") ctx.GasMeter().ConsumeGas(gasUsed, "apply evm transaction") } -// GasToRefund calculates the amount of gas the state machine should refund to the sender. It is -// capped by the refund quotient value. -// Note: do not pass 0 to refundQuotient +// GasToRefund calculates the amount of gas the state machine should refund to +// the sender. It is capped by the refund quotient value. Note that passing a +// jrefundQuotient of 0 will cause problems. func GasToRefund(availableRefund, gasConsumed, refundQuotient uint64) uint64 { // Apply refund counter refund := gasConsumed / refundQuotient @@ -125,13 +135,14 @@ func (k *Keeper) DeductTxCostsFromUserBalance( return nil } -// VerifyFee is used to return the fee for the given transaction data in sdk.Coins. It checks that the -// gas limit is not reached, the gas limit is higher than the intrinsic gas and that the -// base fee is lower than the gas fee cap. +// VerifyFee is used to return the fee for the given transaction data in +// sdk.Coins. It checks that the gas limit is not reached, the gas limit is +// higher than the intrinsic gas and that the base fee is lower than the gas fee +// cap. func VerifyFee( txData evm.TxData, denom string, - baseFee *big.Int, + baseFeeMicronibi *big.Int, isCheckTx bool, ) (sdk.Coins, error) { isContractCreation := txData.GetTo() == nil @@ -160,18 +171,26 @@ func VerifyFee( ) } - if baseFee != nil && txData.GetGasFeeCap().Cmp(baseFee) < 0 { - return nil, errors.Wrapf(errortypes.ErrInsufficientFee, - "the tx gasfeecap is lower than the tx baseFee: %s (gasfeecap), %s (basefee) ", - txData.GetGasFeeCap(), - baseFee) + if baseFeeMicronibi == nil { + baseFeeMicronibi = evm.BASE_FEE_MICRONIBI } - feeAmt := txData.EffectiveFee(baseFee) - if feeAmt.Sign() == 0 { + // gasFeeCapMicronibi := evm.WeiToNative(txData.GetGasFeeCapWei()) + // if baseFeeMicronibi != nil && gasFeeCapMicronibi.Cmp(baseFeeMicronibi) < 0 { + // baseFeeWei := evm.NativeToWei(baseFeeMicronibi) + // return nil, errors.Wrapf(errortypes.ErrInsufficientFee, + // "the tx gasfeecap is lower than the tx baseFee: %s (gasfeecap), %s (basefee) wei per gas", + // txData.GetGasFeeCapWei(), + // baseFeeWei, + // ) + // } + + baseFeeWei := evm.NativeToWei(baseFeeMicronibi) + feeAmtMicronibi := evm.WeiToNative(txData.EffectiveFeeWei(baseFeeWei)) + if feeAmtMicronibi.Sign() == 0 { // zero fee, no need to deduct - return sdk.Coins{}, nil + return sdk.Coins{{Denom: denom, Amount: sdkmath.ZeroInt()}}, nil } - return sdk.Coins{{Denom: denom, Amount: sdkmath.NewIntFromBigInt(feeAmt)}}, nil + return sdk.Coins{{Denom: denom, Amount: sdkmath.NewIntFromBigInt(feeAmtMicronibi)}}, nil } diff --git a/x/evm/keeper/gas_fees_test.go b/x/evm/keeper/gas_fees_test.go index d9ba98a66..1840d9e1b 100644 --- a/x/evm/keeper/gas_fees_test.go +++ b/x/evm/keeper/gas_fees_test.go @@ -1,2 +1,114 @@ // Copyright (c) 2023-2024 Nibi, Inc. package keeper_test + +import ( + "math/big" + + gethparams "github.com/ethereum/go-ethereum/params" + + sdkmath "cosmossdk.io/math" + + "github.com/NibiruChain/nibiru/v2/x/evm" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" + evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper" +) + +// TestVerifyFee asserts that the result of VerifyFee is the effective fee +// in units of micronibi per gas. +func (s *Suite) TestVerifyFee() { + baseFeeMicronibi := evm.BASE_FEE_MICRONIBI + + type testCase struct { + name string + txData evm.TxData + baseFeeMicronibi *big.Int + wantCoinAmt string + wantErr string + } + + for _, getTestCase := range []func() testCase{ + func() testCase { + txData := evmtest.ValidLegacyTx() + effectiveFeeMicronibi := evm.WeiToNative(txData.EffectiveFeeWei(nil)) + return testCase{ + name: "happy: legacy tx", + txData: txData, + baseFeeMicronibi: baseFeeMicronibi, + wantCoinAmt: effectiveFeeMicronibi.String(), + wantErr: "", + } + }, + func() testCase { + txData := evmtest.ValidLegacyTx() + txData.GasLimit = gethparams.TxGas - 1 + effectiveFeeMicronibi := evm.WeiToNative(txData.EffectiveFeeWei(nil)) + return testCase{ + name: "sad: gas limit lower than global tx gas cost", + txData: txData, + baseFeeMicronibi: baseFeeMicronibi, + wantCoinAmt: effectiveFeeMicronibi.String(), + wantErr: "gas limit too low", + } + }, + func() testCase { + txData := evmtest.ValidLegacyTx() + + // Set a gas price that would make the gas fee cap "too low", i.e. + // lower than the base fee + baseFeeWei := evm.NativeToWei(baseFeeMicronibi) + lowGasPrice := sdkmath.NewIntFromBigInt( + new(big.Int).Sub(baseFeeWei, big.NewInt(1)), + ) + txData.GasPrice = &lowGasPrice + + effectiveFeeMicronibi := evm.WeiToNative(txData.EffectiveFeeWei(baseFeeWei)) + + return testCase{ + name: "happy: gas fee cap lower than base fee", + txData: txData, + baseFeeMicronibi: baseFeeMicronibi, + wantCoinAmt: effectiveFeeMicronibi.String(), + wantErr: "", + } + }, + func() testCase { + txData := evmtest.ValidLegacyTx() + + // Set the base fee per gas and user-configured fee per gas to 0. + gasPrice := sdkmath.ZeroInt() + txData.GasLimit = gethparams.TxGas // needed for intrinsic gas + txData.GasPrice = &gasPrice + baseFeeMicronibi := big.NewInt(0) + + // Expect a cost to be 0 + wantCoinAmt := "0" + effectiveFeeMicronibi := evm.WeiToNative(txData.EffectiveFeeWei(nil)) + s.Require().Equal(wantCoinAmt, effectiveFeeMicronibi.String()) + + return testCase{ + // This is impossible because base fee is 1 unibi, however this + // case is technically valid. + name: "happy: the impossible zero case", + txData: txData, + baseFeeMicronibi: baseFeeMicronibi, + wantCoinAmt: "0", + wantErr: "", + } + }, + } { + feeDenom := evm.DefaultEVMDenom + isCheckTx := true + tc := getTestCase() + s.Run(tc.name, func() { + gotCoins, err := evmkeeper.VerifyFee( + tc.txData, feeDenom, tc.baseFeeMicronibi, isCheckTx, + ) + if tc.wantErr != "" { + s.Require().ErrorContains(err, tc.wantErr) + return + } + s.Require().NoError(err) + s.Equal(tc.wantCoinAmt, gotCoins.AmountOf(feeDenom).String()) + }) + } +} diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index d98b12436..4728adec5 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -120,9 +120,12 @@ func (k Keeper) GetMinGasMultiplier(ctx sdk.Context) math.LegacyDec { return math.LegacyNewDecWithPrec(50, 2) // 50% } -func (k Keeper) GetBaseFee(ctx sdk.Context) *big.Int { - // TODO: plug in fee market keeper - return big.NewInt(1) +// GetBaseFee returns the gas base fee in units of the EVM denom. Note that this +// function is currently constant/stateless. +func (k Keeper) GetBaseFee(_ sdk.Context) *big.Int { + // TODO: (someday maybe): Consider making base fee dynamic based on + // congestion in the previous block. + return evm.BASE_FEE_MICRONIBI } // Logger returns a module-specific logger. diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 275735d0a..f2cb75e95 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -255,9 +255,10 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { switch { case ctx.BlockHeight() == h: - // Case 1: The requested height matches the one from the context, so we can retrieve the header - // hash directly from the context. - // Note: The headerHash is only set at begin block, it will be nil in case of a query context + // Case 1: The requested height matches the one from the context, so + // we can retrieve the header hash directly from the context. Note: + // The headerHash is only set at begin block, it will be nil in case + // of a query context headerHash := ctx.HeaderHash() if len(headerHash) != 0 { return gethcommon.BytesToHash(headerHash) @@ -275,8 +276,10 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { return gethcommon.BytesToHash(headerHash) case ctx.BlockHeight() > h: - // Case 2: if the chain is not the current height we need to retrieve the hash from the store for the - // current chain epoch. This only applies if the current height is greater than the requested height. + // Case 2: if the chain is not the current height we need to retrieve + // the hash from the store for the current chain epoch. This only + // applies if the current height is greater than the requested + // height. histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, h) if !found { k.Logger(ctx).Debug("historical info not found", "height", h) @@ -370,12 +373,21 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, return nil, errors.Wrap(err, "intrinsic gas failed") } - // Should check again even if it is checked on Ante Handler, because eth_call don't go through Ante Handler. + // Check if the provided gas in the message is enough to cover the intrinsic + // gas, the base gas cost before execution occurs (gethparams.TxGas, contract + // creation, and cost per byte of the data payload). + // + // Should check again even if it is checked on Ante Handler, because eth_call + // don't go through Ante Handler. if leftoverGas < intrinsicGas { // eth_estimateGas will check for this exact error - return nil, errors.Wrap(core.ErrIntrinsicGas, "apply message") + return nil, errors.Wrapf( + core.ErrIntrinsicGas, + "apply message msg.Gas = %d, intrinsic gas = %d.", + leftoverGas, intrinsicGas, + ) } - leftoverGas -= intrinsicGas + leftoverGas = leftoverGas - intrinsicGas // access list preparation is moved from ante handler to here, because it's // needed when `ApplyMessage` is called under contexts where ante handlers @@ -390,7 +402,6 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, msgWei, err := ParseWeiAsMultipleOfMicronibi(msg.Value()) if err != nil { return nil, err - // return nil, fmt.Errorf("cannot use \"value\" in wei that can't be converted to unibi. %s is not divisible by 10^12", msg.Value()) } if contractCreation { @@ -398,10 +409,21 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, // - reset sender's nonce to msg.Nonce() before calling evm. // - increase sender's nonce by one no matter the result. stateDB.SetNonce(sender.Address(), msg.Nonce()) - ret, _, leftoverGas, vmErr = evmObj.Create(sender, msg.Data(), leftoverGas, msgWei) + ret, _, leftoverGas, vmErr = evmObj.Create( + sender, + msg.Data(), + leftoverGas, + msgWei, + ) stateDB.SetNonce(sender.Address(), msg.Nonce()+1) } else { - ret, leftoverGas, vmErr = evmObj.Call(sender, *msg.To(), msg.Data(), leftoverGas, msgWei) + ret, leftoverGas, vmErr = evmObj.Call( + sender, + *msg.To(), + msg.Data(), + leftoverGas, + msgWei, + ) } // After EIP-3529: refunds are capped to gasUsed / 5 @@ -445,7 +467,10 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, } gasUsed := math.LegacyMaxDec(minimumGasUsed, math.LegacyNewDec(int64(temporaryGasUsed))).TruncateInt().Uint64() - // reset leftoverGas, to be used by the tracer + + // This resulting "leftoverGas" is used by the tracer. This happens as a + // result of the defer statement near the beginning of the function with + // "vm.Tracer". leftoverGas = msg.Gas() - gasUsed return &evm.MsgEthereumTxResponse{ diff --git a/x/evm/msg.go b/x/evm/msg.go index d7d190aca..dc15727c5 100644 --- a/x/evm/msg.go +++ b/x/evm/msg.go @@ -280,16 +280,16 @@ func (msg MsgEthereumTx) GetEffectiveFee(baseFee *big.Int) *big.Int { if err != nil { return nil } - return txData.EffectiveFee(baseFee) + return txData.EffectiveFeeWei(baseFee) } // GetEffectiveFee returns the fee for dynamic fee tx -func (msg MsgEthereumTx) GetEffectiveGasPrice(baseFee *big.Int) *big.Int { +func (msg MsgEthereumTx) GetEffectiveGasPrice(baseFeeWei *big.Int) *big.Int { txData, err := UnpackTxData(msg.Data) if err != nil { return nil } - return txData.EffectiveGasPrice(baseFee) + return txData.EffectiveGasPriceWei(baseFeeWei) } // GetFrom loads the ethereum sender address from the sigcache and returns an diff --git a/x/evm/msg_test.go b/x/evm/msg_test.go index c673f6453..98beaafdc 100644 --- a/x/evm/msg_test.go +++ b/x/evm/msg_test.go @@ -261,7 +261,7 @@ func (s *MsgsSuite) TestMsgEthereumTx_ValidateBasic() { gasTipCap: nil, chainID: validChainID, expectPass: false, - errMsg: "gas price cannot be nil", + errMsg: "cannot be nil: invalid gas price", }, { msg: "negative gas price - Legacy Tx", diff --git a/x/evm/tx.go b/x/evm/tx.go index 64ab143c9..f710def67 100644 --- a/x/evm/tx.go +++ b/x/evm/tx.go @@ -6,22 +6,15 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - gethmath "github.com/ethereum/go-ethereum/common/math" gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - - "github.com/NibiruChain/nibiru/v2/eth" ) // EvmTxArgs encapsulates all possible params to create all EVM txs types. // This includes LegacyTx, DynamicFeeTx and AccessListTx - -type EvmTxArgs struct { //revive:disable-line:exported +type EvmTxArgs struct { Nonce uint64 GasLimit uint64 Input []byte @@ -46,17 +39,20 @@ var DefaultPriorityReduction = sdk.DefaultPowerReduction // tx_priority = tip_price / priority_reduction func GetTxPriority(txData TxData, baseFee *big.Int) (priority int64) { // calculate priority based on effective gas price - tipPrice := txData.EffectiveGasPrice(baseFee) + tipPrice := txData.EffectiveGasPriceWei(baseFee) + // Return the min of the max possible priorty and the derived priority priority = math.MaxInt64 - priorityBig := new(big.Int).Quo(tipPrice, DefaultPriorityReduction.BigInt()) + derivedPriority := new(big.Int).Quo(tipPrice, DefaultPriorityReduction.BigInt()) - // safety check - if priorityBig.IsInt64() { - priority = priorityBig.Int64() + // Overflow safety check + var priorityBigI64 int64 + if derivedPriority.IsInt64() { + priorityBigI64 = derivedPriority.Int64() + } else { + priorityBigI64 = priority } - - return priority + return min(priority, priorityBigI64) } // Failed returns if the contract execution failed in vm errors @@ -81,278 +77,3 @@ func (m *MsgEthereumTxResponse) Revert() []byte { } return common.CopyBytes(m.Ret) } - -func NewDynamicFeeTx(tx *gethcore.Transaction) (*DynamicFeeTx, error) { - txData := &DynamicFeeTx{ - Nonce: tx.Nonce(), - Data: tx.Data(), - GasLimit: tx.Gas(), - } - - v, r, s := tx.RawSignatureValues() - if to := tx.To(); to != nil { - txData.To = to.Hex() - } - - if tx.Value() != nil { - amountInt, err := eth.SafeNewIntFromBigInt(tx.Value()) - if err != nil { - return nil, err - } - txData.Amount = &amountInt - } - - if tx.GasFeeCap() != nil { - gasFeeCapInt, err := eth.SafeNewIntFromBigInt(tx.GasFeeCap()) - if err != nil { - return nil, err - } - txData.GasFeeCap = &gasFeeCapInt - } - - if tx.GasTipCap() != nil { - gasTipCapInt, err := eth.SafeNewIntFromBigInt(tx.GasTipCap()) - if err != nil { - return nil, err - } - txData.GasTipCap = &gasTipCapInt - } - - if tx.AccessList() != nil { - al := tx.AccessList() - txData.Accesses = NewAccessList(&al) - } - - txData.SetSignatureValues(tx.ChainId(), v, r, s) - return txData, nil -} - -// TxType returns the tx type -func (tx *DynamicFeeTx) TxType() uint8 { - return gethcore.DynamicFeeTxType -} - -// Copy returns an instance with the same field values -func (tx *DynamicFeeTx) Copy() TxData { - return &DynamicFeeTx{ - ChainID: tx.ChainID, - Nonce: tx.Nonce, - GasTipCap: tx.GasTipCap, - GasFeeCap: tx.GasFeeCap, - GasLimit: tx.GasLimit, - To: tx.To, - Amount: tx.Amount, - Data: common.CopyBytes(tx.Data), - Accesses: tx.Accesses, - V: common.CopyBytes(tx.V), - R: common.CopyBytes(tx.R), - S: common.CopyBytes(tx.S), - } -} - -// GetChainID returns the chain id field from the DynamicFeeTx -func (tx *DynamicFeeTx) GetChainID() *big.Int { - if tx.ChainID == nil { - return nil - } - - return tx.ChainID.BigInt() -} - -// GetAccessList returns the AccessList field. -func (tx *DynamicFeeTx) GetAccessList() gethcore.AccessList { - if tx.Accesses == nil { - return nil - } - return *tx.Accesses.ToEthAccessList() -} - -// GetData returns a copy of the input data bytes. -func (tx *DynamicFeeTx) GetData() []byte { - return common.CopyBytes(tx.Data) -} - -// GetGas returns the gas limit. -func (tx *DynamicFeeTx) GetGas() uint64 { - return tx.GasLimit -} - -// GetGasPrice returns the gas fee cap field. -func (tx *DynamicFeeTx) GetGasPrice() *big.Int { - return tx.GetGasFeeCap() -} - -// GetGasTipCap returns the gas tip cap field. -func (tx *DynamicFeeTx) GetGasTipCap() *big.Int { - if tx.GasTipCap == nil { - return nil - } - return tx.GasTipCap.BigInt() -} - -// GetGasFeeCap returns the gas fee cap field. -func (tx *DynamicFeeTx) GetGasFeeCap() *big.Int { - if tx.GasFeeCap == nil { - return nil - } - return tx.GasFeeCap.BigInt() -} - -// GetValue returns the tx amount. -func (tx *DynamicFeeTx) GetValue() *big.Int { - if tx.Amount == nil { - return nil - } - - return tx.Amount.BigInt() -} - -// GetNonce returns the account sequence for the transaction. -func (tx *DynamicFeeTx) GetNonce() uint64 { return tx.Nonce } - -// GetTo returns the pointer to the recipient address. -func (tx *DynamicFeeTx) GetTo() *common.Address { - if tx.To == "" { - return nil - } - to := common.HexToAddress(tx.To) - return &to -} - -// AsEthereumData returns an DynamicFeeTx transaction tx from the proto-formatted -// TxData defined on the Cosmos EVM. -func (tx *DynamicFeeTx) AsEthereumData() gethcore.TxData { - v, r, s := tx.GetRawSignatureValues() - return &gethcore.DynamicFeeTx{ - ChainID: tx.GetChainID(), - Nonce: tx.GetNonce(), - GasTipCap: tx.GetGasTipCap(), - GasFeeCap: tx.GetGasFeeCap(), - Gas: tx.GetGas(), - To: tx.GetTo(), - Value: tx.GetValue(), - Data: tx.GetData(), - AccessList: tx.GetAccessList(), - V: v, - R: r, - S: s, - } -} - -// GetRawSignatureValues returns the V, R, S signature values of the transaction. -// The return values should not be modified by the caller. -func (tx *DynamicFeeTx) GetRawSignatureValues() (v, r, s *big.Int) { - return rawSignatureValues(tx.V, tx.R, tx.S) -} - -// SetSignatureValues sets the signature values to the transaction. -func (tx *DynamicFeeTx) SetSignatureValues(chainID, v, r, s *big.Int) { - if v != nil { - tx.V = v.Bytes() - } - if r != nil { - tx.R = r.Bytes() - } - if s != nil { - tx.S = s.Bytes() - } - if chainID != nil { - chainIDInt := sdkmath.NewIntFromBigInt(chainID) - tx.ChainID = &chainIDInt - } -} - -// Validate performs a stateless validation of the tx fields. -func (tx DynamicFeeTx) Validate() error { - if tx.GasTipCap == nil { - return errorsmod.Wrap(ErrInvalidGasCap, "gas tip cap cannot nil") - } - - if tx.GasFeeCap == nil { - return errorsmod.Wrap(ErrInvalidGasCap, "gas fee cap cannot nil") - } - - if tx.GasTipCap.IsNegative() { - return errorsmod.Wrapf(ErrInvalidGasCap, "gas tip cap cannot be negative %s", tx.GasTipCap) - } - - if tx.GasFeeCap.IsNegative() { - return errorsmod.Wrapf(ErrInvalidGasCap, "gas fee cap cannot be negative %s", tx.GasFeeCap) - } - - if !eth.IsValidInt256(tx.GetGasTipCap()) { - return errorsmod.Wrap(ErrInvalidGasCap, "out of bound") - } - - if !eth.IsValidInt256(tx.GetGasFeeCap()) { - return errorsmod.Wrap(ErrInvalidGasCap, "out of bound") - } - - if tx.GasFeeCap.LT(*tx.GasTipCap) { - return errorsmod.Wrapf( - ErrInvalidGasCap, "max priority fee per gas higher than max fee per gas (%s > %s)", - tx.GasTipCap, tx.GasFeeCap, - ) - } - - if !eth.IsValidInt256(tx.Fee()) { - return errorsmod.Wrap(ErrInvalidGasFee, "out of bound") - } - - amount := tx.GetValue() - // Amount can be 0 - if amount != nil && amount.Sign() == -1 { - return errorsmod.Wrapf(ErrInvalidAmount, "amount cannot be negative %s", amount) - } - if !eth.IsValidInt256(amount) { - return errorsmod.Wrap(ErrInvalidAmount, "out of bound") - } - - if tx.To != "" { - if err := eth.ValidateAddress(tx.To); err != nil { - return errorsmod.Wrap(err, "invalid to address") - } - } - - chainID := tx.GetChainID() - - if chainID == nil { - return errorsmod.Wrap( - errortypes.ErrInvalidChainID, - "chain ID must be present on AccessList txs", - ) - } - - return nil -} - -// Fee returns gasprice * gaslimit. -func (tx DynamicFeeTx) Fee() *big.Int { - return fee(tx.GetGasFeeCap(), tx.GasLimit) -} - -// Cost returns amount + gasprice * gaslimit. -func (tx DynamicFeeTx) Cost() *big.Int { - return cost(tx.Fee(), tx.GetValue()) -} - -// EffectiveGasPrice computes the effective gas price based on eip-1559 rules -// `effectiveGasPrice = min(baseFee + tipCap, feeCap)` -func EffectiveGasPrice(baseFee, feeCap, tipCap *big.Int) *big.Int { - return gethmath.BigMin(new(big.Int).Add(tipCap, baseFee), feeCap) -} - -// EffectiveGasPrice returns the effective gas price -func (tx *DynamicFeeTx) EffectiveGasPrice(baseFee *big.Int) *big.Int { - return EffectiveGasPrice(baseFee, tx.GasFeeCap.BigInt(), tx.GasTipCap.BigInt()) -} - -// EffectiveFee returns effective_gasprice * gaslimit. -func (tx DynamicFeeTx) EffectiveFee(baseFee *big.Int) *big.Int { - return fee(tx.EffectiveGasPrice(baseFee), tx.GasLimit) -} - -// EffectiveCost returns amount + effective_gasprice * gaslimit. -func (tx DynamicFeeTx) EffectiveCost(baseFee *big.Int) *big.Int { - return cost(tx.EffectiveFee(baseFee), tx.GetValue()) -} diff --git a/x/evm/tx.pb.go b/x/evm/tx.pb.go index 6c61d6357..f2082703b 100644 --- a/x/evm/tx.pb.go +++ b/x/evm/tx.pb.go @@ -84,8 +84,12 @@ func (m *MsgEthereumTx) XXX_DiscardUnknown() { var xxx_messageInfo_MsgEthereumTx proto.InternalMessageInfo // LegacyTx is the transaction data of regular Ethereum transactions. -// NOTE: All non-protected transactions (i.e non EIP155 signed) will fail if the -// AllowUnprotectedTxs parameter is disabled. +// +// Note that setting "evm.Params.AllowUnprotectedTxs" to false will cause all +// non-EIP155 signed transactions to fail, as they'll lack replay protection. +// +// LegacyTx is a custom implementation of "LegacyTx" from +// "github.com/ethereum/go-ethereum/core/types". type LegacyTx struct { // nonce corresponds to the account nonce (transaction sequence). Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"` @@ -99,11 +103,19 @@ type LegacyTx struct { Amount *cosmossdk_io_math.Int `protobuf:"bytes,5,opt,name=value,proto3,customtype=cosmossdk.io/math.Int" json:"value,omitempty"` // data is the data payload bytes of the transaction. Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"` - // v defines the signature value + // v defines the recovery id as the "v" signature value from the elliptic curve + // digital signatute algorithm (ECDSA). It indicates which of two possible + // solutions should be used to reconstruct the public key from the signature. + // In Ethereum, "v" takes the value 27 or 28 for transactions that are not + // relay-protected. V []byte `protobuf:"bytes,7,opt,name=v,proto3" json:"v,omitempty"` - // r defines the signature value + // r defines the x-coordinate of a point on the elliptic curve in the elliptic curve + // digital signatute algorithm (ECDSA). It's crucial in ensuring uniqueness of + // the signature. R []byte `protobuf:"bytes,8,opt,name=r,proto3" json:"r,omitempty"` - // s define the signature value + // s define the signature value derived from the private key, message hash, and + // the value of "r". It ensures that the signature is tied to both the message + // and the private key of the sender. S []byte `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"` } @@ -141,6 +153,8 @@ func (m *LegacyTx) XXX_DiscardUnknown() { var xxx_messageInfo_LegacyTx proto.InternalMessageInfo // AccessListTx is the data of EIP-2930 access list transactions. +// It is a custom implementation of "AccessListTx" from +// "github.com/ethereum/go-ethereum/core/types". type AccessListTx struct { // chain_id of the destination EVM chain ChainID *cosmossdk_io_math.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=cosmossdk.io/math.Int" json:"chainID"` @@ -158,11 +172,19 @@ type AccessListTx struct { Data []byte `protobuf:"bytes,7,opt,name=data,proto3" json:"data,omitempty"` // accesses is an array of access tuples Accesses AccessList `protobuf:"bytes,8,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"` - // v defines the signature value + // v defines the recovery id and "v" signature value from the elliptic curve + // digital signatute algorithm (ECDSA). It indicates which of two possible + // solutions should be used to reconstruct the public key from the signature. + // In Ethereum, "v" takes the value 27 or 28 for transactions that are not + // relay-protected. V []byte `protobuf:"bytes,9,opt,name=v,proto3" json:"v,omitempty"` - // r defines the signature value + // r defines the x-coordinate of a point on the elliptic curve in the elliptic curve + // digital signatute algorithm (ECDSA). It's crucial in ensuring uniqueness of + // the signature. R []byte `protobuf:"bytes,10,opt,name=r,proto3" json:"r,omitempty"` - // s define the signature value + // s define the signature value derived from the private key, message hash, and + // the value of "r". It ensures that the signature is tied to both the message + // and the private key of the sender. S []byte `protobuf:"bytes,11,opt,name=s,proto3" json:"s,omitempty"` } @@ -199,7 +221,9 @@ func (m *AccessListTx) XXX_DiscardUnknown() { var xxx_messageInfo_AccessListTx proto.InternalMessageInfo -// DynamicFeeTx is the data of EIP-1559 dinamic fee transactions. +// DynamicFeeTx is the data of EIP-1559 dynamic fee transactions. It is a custom +// implementation of "DynamicFeeTx" from +// "github.com/ethereum/go-ethereum/core/types". type DynamicFeeTx struct { // chain_id of the destination EVM chain ChainID *cosmossdk_io_math.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=cosmossdk.io/math.Int" json:"chainID"` @@ -219,11 +243,19 @@ type DynamicFeeTx struct { Data []byte `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty"` // accesses is an array of access tuples Accesses AccessList `protobuf:"bytes,9,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"` - // v defines the signature value + // v defines the recovery id and "v" signature value from the elliptic curve + // digital signatute algorithm (ECDSA). It indicates which of two possible + // solutions should be used to reconstruct the public key from the signature. + // In Ethereum, "v" takes the value 27 or 28 for transactions that are not + // relay-protected. V []byte `protobuf:"bytes,10,opt,name=v,proto3" json:"v,omitempty"` - // r defines the signature value + // r defines the x-coordinate of a point on the elliptic curve in the elliptic curve + // digital signatute algorithm (ECDSA). It's crucial in ensuring uniqueness of + // the signature. R []byte `protobuf:"bytes,11,opt,name=r,proto3" json:"r,omitempty"` - // s define the signature value + // s define the signature value derived from the private key, message hash, and + // the value of "r". It ensures that the signature is tied to both the message + // and the private key of the sender. S []byte `protobuf:"bytes,12,opt,name=s,proto3" json:"s,omitempty"` } diff --git a/x/evm/tx_data.go b/x/evm/tx_data.go index 375eaf63d..979dcd72f 100644 --- a/x/evm/tx_data.go +++ b/x/evm/tx_data.go @@ -4,8 +4,12 @@ package evm import ( "math/big" + errorsmod "cosmossdk.io/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" + + "github.com/NibiruChain/nibiru/v2/eth" ) var ( @@ -14,23 +18,66 @@ var ( _ TxData = &DynamicFeeTx{} ) -// TxData implements the Ethereum transaction tx structure. It is used -// solely as intended in Ethereum abiding by the protocol. +// TxData is the underlying data of a transaction. Its counterpart with private +// fields, "gethcore.TxData" is implemented by DynamicFeeTx, LegacyTx and +// AccessListTx from the same package. Each trnsaction type is implemented here +// for protobuf marshaling. +// +// According to https://github.com/ethereum/go-ethereum/issues/23154: +// TxData exists for the sole purpose of making it easier to construct a +// "gethcore.Transaction" more conviently in Go code. The methods of TxData are +// an internal implementation detail and will never have a stable API. +// +// Because the fields are private in the go-ethereum code, it is impossible to +// provide custom implementations for these methods without creating a new TxData +// data structure. Thus, the current interface exists. type TxData interface { - // TODO: embed ethtypes.TxData. See https://github.com/ethereum/go-ethereum/issues/23154 - TxType() byte Copy() TxData GetChainID() *big.Int GetAccessList() gethcore.AccessList GetData() []byte + GetNonce() uint64 + + // GetGas returns the gas limit in gas units. Note that this is not a "fee" + // in wei or micronibi or a price. GetGas() uint64 + + // Gas price as wei spent per unit gas. GetGasPrice() *big.Int - GetGasTipCap() *big.Int - GetGasFeeCap() *big.Int - GetValue() *big.Int + + // GetGasTipCapWei returns a cap on the gas tip in units of wei. + // + // Also called "maxPriorityFeePerGas" in Alchemy and Ethers. + // See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]. + // Base fees are determined by the network, not the end user that broadcasts + // the transaction. Adding a tip increases one's "priority" in the block. + // + // The terminology "fee per gas" essentially means "wei per unit gas". + // See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas] for more info. + // + // [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]: https://docs.alchemy.com/docs/maxpriorityfeepergas-vs-maxfeepergas. + GetGasTipCapWei() *big.Int + + // GetGasFeeCapWei returns a cap on the gas fees paid in units of wei, where: + // feesWithoutCap := effective gas price (wei per gas) * gas units + // fees -> min(feesWithoutCap, gasFeeCap) + // Also called "maxFeePerGas" in Alchemy and Ethers. + // + // maxFeePerGas := baseFeePerGas + maxPriorityFeePerGas + // + // The terminology "fee per gas" essentially means "wei per unit gas". + // See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas] for more info. + // + // [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]: https://docs.alchemy.com/docs/maxpriorityfeepergas-vs-maxfeepergas. + GetGasFeeCapWei() *big.Int + + // GetValueWei: amount of ether (wei units) sent in the transaction. + GetValueWei() *big.Int + GetTo() *common.Address + GetToRaw() string GetRawSignatureValues() (v, r, s *big.Int) SetSignatureValues(chainID, v, r, s *big.Int) @@ -44,9 +91,9 @@ type TxData interface { Cost() *big.Int // effective gasPrice/fee/cost according to current base fee - EffectiveGasPrice(baseFee *big.Int) *big.Int - EffectiveFee(baseFee *big.Int) *big.Int - EffectiveCost(baseFee *big.Int) *big.Int + EffectiveGasPriceWei(baseFeeWei *big.Int) *big.Int + EffectiveFeeWei(baseFeeWei *big.Int) *big.Int + EffectiveCost(baseFeeWei *big.Int) *big.Int } // NOTE: All non-protected transactions (i.e. non EIP155 signed) will fail if the @@ -112,9 +159,13 @@ func rawSignatureValues(vBz, rBz, sBz []byte) (v, r, s *big.Int) { return v, r, s } -func fee(gasPrice *big.Int, gas uint64) *big.Int { +// Returns the fee in wei corresponding to the given gas price and gas amount. +// Args: +// - weiPerGas: Wei per unit gas (gas price). +// - gas: gas units +func priceTimesGas(weiPerGas *big.Int, gas uint64) *big.Int { gasLimit := new(big.Int).SetUint64(gas) - return new(big.Int).Mul(gasPrice, gasLimit) + return new(big.Int).Mul(weiPerGas, gasLimit) } func cost(fee, value *big.Int) *big.Int { @@ -123,3 +174,56 @@ func cost(fee, value *big.Int) *big.Int { } return fee } + +func (tx *DynamicFeeTx) GetToRaw() string { return tx.To } +func (tx *LegacyTx) GetToRaw() string { return tx.To } +func (tx *AccessListTx) GetToRaw() string { return tx.To } + +func ValidateTxDataAmount(txData TxData) error { + amount := txData.GetValueWei() + // Amount can be 0 + if amount != nil && amount.Sign() == -1 { + return errorsmod.Wrapf(ErrInvalidAmount, "amount cannot be negative %s", amount) + } + if !eth.IsValidInt256(amount) { + return errorsmod.Wrap(ErrInvalidAmount, "out of bound") + } + return nil +} + +func ValidateTxDataTo(txData TxData) error { + to := txData.GetToRaw() + if to != "" { + if err := eth.ValidateAddress(to); err != nil { + return errorsmod.Wrap(err, "invalid to address") + } + } + return nil +} + +func ValidateTxDataGasPrice(txData TxData) error { + gasPrice := txData.GetGasPrice() + if gasPrice == nil { + return errorsmod.Wrap(ErrInvalidGasPrice, "cannot be nil") + } + if !eth.IsValidInt256(gasPrice) { + return errorsmod.Wrap(ErrInvalidGasPrice, "out of bound") + } + + if gasPrice.Sign() == -1 { + return errorsmod.Wrapf(ErrInvalidGasPrice, "gas price cannot be negative %s", gasPrice) + } + return nil +} + +func ValidateTxDataChainID(txData TxData) error { + chainID := txData.GetChainID() + + if chainID == nil { + return errorsmod.Wrap( + sdkerrors.ErrInvalidChainID, + "chain ID must be derived from TxData txs", + ) + } + return nil +} diff --git a/x/evm/access_list.go b/x/evm/tx_data_access_list.go similarity index 73% rename from x/evm/access_list.go rename to x/evm/tx_data_access_list.go index ac66aec1f..19ccaa488 100644 --- a/x/evm/access_list.go +++ b/x/evm/tx_data_access_list.go @@ -150,7 +150,7 @@ func (tx *AccessListTx) GetGas() uint64 { return tx.GasLimit } -// GetGasPrice returns the gas price field. +// Gas price as wei spent per unit gas. func (tx *AccessListTx) GetGasPrice() *big.Int { if tx.GasPrice == nil { return nil @@ -158,18 +158,35 @@ func (tx *AccessListTx) GetGasPrice() *big.Int { return tx.GasPrice.BigInt() } -// GetGasTipCap returns the gas price field. -func (tx *AccessListTx) GetGasTipCap() *big.Int { +// GetGasTipCapWei returns a cap on the gas tip in units of wei. +// For an [AccessListTx], this is taken to be the gas price. +// +// Also called "maxPriorityFeePerGas" in Alchemy and Ethers. +// See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]. +// Base fees are determined by the network, not the end user that broadcasts +// the transaction. Adding a tip increases one's "priority" in the block. +// +// The terminology "fee per gas" essentially means "wei per unit gas". +// See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas] for more info. +// +// [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]: https://docs.alchemy.com/docs/maxpriorityfeepergas-vs-maxfeepergas. +func (tx *AccessListTx) GetGasTipCapWei() *big.Int { return tx.GetGasPrice() } -// GetGasFeeCap returns the gas price field. -func (tx *AccessListTx) GetGasFeeCap() *big.Int { +// GetGasFeeCapWei returns a cap on the gas fees paid in units of wei: +// For an [AccessListTx], this is taken to be the gas price. +// +// The terminology "fee per gas" essentially means "wei per unit gas". +// See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas] for more info. +// +// [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]: https://docs.alchemy.com/docs/maxpriorityfeepergas-vs-maxfeepergas. +func (tx *AccessListTx) GetGasFeeCapWei() *big.Int { return tx.GetGasPrice() } -// GetValue returns the tx amount. -func (tx *AccessListTx) GetValue() *big.Int { +// GetValueWei returns the tx amount. +func (tx *AccessListTx) GetValueWei() *big.Int { if tx.Amount == nil { return nil } @@ -199,7 +216,7 @@ func (tx *AccessListTx) AsEthereumData() gethcore.TxData { GasPrice: tx.GetGasPrice(), Gas: tx.GetGas(), To: tx.GetTo(), - Value: tx.GetValue(), + Value: tx.GetValueWei(), Data: tx.GetData(), AccessList: tx.GetAccessList(), V: v, @@ -233,37 +250,20 @@ func (tx *AccessListTx) SetSignatureValues(chainID, v, r, s *big.Int) { // Validate performs a stateless validation of the tx fields. func (tx AccessListTx) Validate() error { - gasPrice := tx.GetGasPrice() - if gasPrice == nil { - return errorsmod.Wrap(ErrInvalidGasPrice, "cannot be nil") - } - if !eth.IsValidInt256(gasPrice) { - return errorsmod.Wrap(ErrInvalidGasPrice, "out of bound") - } - - if gasPrice.Sign() == -1 { - return errorsmod.Wrapf(ErrInvalidGasPrice, "gas price cannot be negative %s", gasPrice) - } - - amount := tx.GetValue() - // Amount can be 0 - if amount != nil && amount.Sign() == -1 { - return errorsmod.Wrapf(ErrInvalidAmount, "amount cannot be negative %s", amount) - } - if !eth.IsValidInt256(amount) { - return errorsmod.Wrap(ErrInvalidAmount, "out of bound") + for _, err := range []error{ + ValidateTxDataAmount(&tx), + ValidateTxDataTo(&tx), + ValidateTxDataGasPrice(&tx), + } { + if err != nil { + return err + } } if !eth.IsValidInt256(tx.Fee()) { return errorsmod.Wrap(ErrInvalidGasFee, "out of bound") } - if tx.To != "" { - if err := eth.ValidateAddress(tx.To); err != nil { - return errorsmod.Wrap(err, "invalid to address") - } - } - chainID := tx.GetChainID() if chainID == nil { @@ -278,25 +278,26 @@ func (tx AccessListTx) Validate() error { // Fee returns gasprice * gaslimit. func (tx AccessListTx) Fee() *big.Int { - return fee(tx.GetGasPrice(), tx.GetGas()) + return priceTimesGas(tx.GetGasPrice(), tx.GetGas()) } // Cost returns amount + gasprice * gaslimit. func (tx AccessListTx) Cost() *big.Int { - return cost(tx.Fee(), tx.GetValue()) + return cost(tx.Fee(), tx.GetValueWei()) } -// EffectiveGasPrice is the same as GasPrice for AccessListTx -func (tx AccessListTx) EffectiveGasPrice(_ *big.Int) *big.Int { - return tx.GetGasPrice() +// EffectiveGasPriceWei is the same as GasPrice for AccessListTx +func (tx AccessListTx) EffectiveGasPriceWei(baseFeeWei *big.Int) *big.Int { + return BigIntMax(tx.GetGasPrice(), baseFeeWei) } -// EffectiveFee is the same as Fee for AccessListTx -func (tx AccessListTx) EffectiveFee(_ *big.Int) *big.Int { - return tx.Fee() +// EffectiveFeeWei is the same as Fee for AccessListTx +func (tx AccessListTx) EffectiveFeeWei(baseFeeWei *big.Int) *big.Int { + return priceTimesGas(tx.EffectiveGasPriceWei(baseFeeWei), tx.GetGas()) } // EffectiveCost is the same as Cost for AccessListTx -func (tx AccessListTx) EffectiveCost(_ *big.Int) *big.Int { - return tx.Cost() +func (tx AccessListTx) EffectiveCost(baseFeeWei *big.Int) *big.Int { + txFee := tx.EffectiveFeeWei(baseFeeWei) + return cost(txFee, tx.GetValueWei()) } diff --git a/x/evm/access_list_test.go b/x/evm/tx_data_access_list_test.go similarity index 88% rename from x/evm/access_list_test.go rename to x/evm/tx_data_access_list_test.go index 07f7d144b..54b0760dd 100644 --- a/x/evm/access_list_test.go +++ b/x/evm/tx_data_access_list_test.go @@ -8,7 +8,7 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm" ) -func (suite *TxDataTestSuite) TestTestNewAccessList() { +func (suite *Suite) TestTestNewAccessList() { testCases := []struct { name string ethAccessList *gethcore.AccessList @@ -32,7 +32,7 @@ func (suite *TxDataTestSuite) TestTestNewAccessList() { } } -func (suite *TxDataTestSuite) TestAccessListToEthAccessList() { +func (suite *Suite) TestAccessListToEthAccessList() { ethAccessList := gethcore.AccessList{{Address: suite.addr, StorageKeys: []common.Hash{{0}}}} al := evm.NewAccessList(ðAccessList) actual := al.ToEthAccessList() diff --git a/x/evm/tx_data_dynamic_fee.go b/x/evm/tx_data_dynamic_fee.go new file mode 100644 index 000000000..81797617b --- /dev/null +++ b/x/evm/tx_data_dynamic_fee.go @@ -0,0 +1,312 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package evm + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + gethmath "github.com/ethereum/go-ethereum/common/math" + gethcore "github.com/ethereum/go-ethereum/core/types" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + "github.com/NibiruChain/nibiru/v2/eth" +) + +// BigIntMax returns max(x,y). +func BigIntMax(x, y *big.Int) *big.Int { + if x == nil && y != nil { + return y + } else if x != nil && y == nil { + return x + } else if x == nil && y == nil { + return nil + } + + if x.Cmp(y) > 0 { + return x + } + return y +} + +func NewDynamicFeeTx(tx *gethcore.Transaction) (*DynamicFeeTx, error) { + txData := &DynamicFeeTx{ + Nonce: tx.Nonce(), + Data: tx.Data(), + GasLimit: tx.Gas(), + } + + v, r, s := tx.RawSignatureValues() + if to := tx.To(); to != nil { + txData.To = to.Hex() + } + + if tx.Value() != nil { + amountInt, err := eth.SafeNewIntFromBigInt(tx.Value()) + if err != nil { + return nil, err + } + txData.Amount = &amountInt + } + + if tx.GasFeeCap() != nil { + gasFeeCapInt, err := eth.SafeNewIntFromBigInt(tx.GasFeeCap()) + if err != nil { + return nil, err + } + txData.GasFeeCap = &gasFeeCapInt + } + + if tx.GasTipCap() != nil { + gasTipCapInt, err := eth.SafeNewIntFromBigInt(tx.GasTipCap()) + if err != nil { + return nil, err + } + txData.GasTipCap = &gasTipCapInt + } + + if tx.AccessList() != nil { + al := tx.AccessList() + txData.Accesses = NewAccessList(&al) + } + + txData.SetSignatureValues(tx.ChainId(), v, r, s) + return txData, nil +} + +// TxType returns the tx type +func (tx *DynamicFeeTx) TxType() uint8 { + return gethcore.DynamicFeeTxType +} + +// Copy returns an instance with the same field values +func (tx *DynamicFeeTx) Copy() TxData { + return &DynamicFeeTx{ + ChainID: tx.ChainID, + Nonce: tx.Nonce, + GasTipCap: tx.GasTipCap, + GasFeeCap: tx.GasFeeCap, + GasLimit: tx.GasLimit, + To: tx.To, + Amount: tx.Amount, + Data: common.CopyBytes(tx.Data), + Accesses: tx.Accesses, + V: common.CopyBytes(tx.V), + R: common.CopyBytes(tx.R), + S: common.CopyBytes(tx.S), + } +} + +// GetChainID returns the chain id field from the DynamicFeeTx +func (tx *DynamicFeeTx) GetChainID() *big.Int { + if tx.ChainID == nil { + return nil + } + + return tx.ChainID.BigInt() +} + +// GetAccessList returns the AccessList field. +func (tx *DynamicFeeTx) GetAccessList() gethcore.AccessList { + if tx.Accesses == nil { + return nil + } + return *tx.Accesses.ToEthAccessList() +} + +// GetData returns a copy of the input data bytes. +func (tx *DynamicFeeTx) GetData() []byte { + return common.CopyBytes(tx.Data) +} + +// GetGas returns the gas limit. +func (tx *DynamicFeeTx) GetGas() uint64 { + return tx.GasLimit +} + +// Gas price as wei spent per unit gas. +func (tx *DynamicFeeTx) GetGasPrice() *big.Int { + return tx.GetGasFeeCapWei() +} + +// GetGasTipCapWei returns a cap on the gas tip in units of wei. +// +// Also called "maxPriorityFeePerGas" in Alchemy and Ethers. +// See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]. +// Base fees are determined by the network, not the end user that broadcasts +// the transaction. Adding a tip increases one's "priority" in the block. +// +// The terminology "fee per gas" essentially means "wei per unit gas". +// See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas] for more info. +// +// [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]: https://docs.alchemy.com/docs/maxpriorityfeepergas-vs-maxfeepergas. +func (tx *DynamicFeeTx) GetGasTipCapWei() *big.Int { + if tx.GasTipCap == nil { + return nil + } + return tx.GasTipCap.BigInt() +} + +// GetGasFeeCapWei returns a cap on the gas fees paid in units of wei, where: +// +// feesWithoutCap := effective gas price (wei per gas) * gas units +// gas fee cap -> min(feesWithoutCap, gasFeeCap) +// +// Also called "maxFeePerGas" in Alchemy and Ethers. +// maxFeePerGas := baseFeePerGas + maxPriorityFeePerGas +// +// The terminology "fee per gas" essentially means "wei per unit gas". +// See [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas] for more info. +// +// [Alchemy Docs - maxPriorityFeePerGas vs maxFeePerGas]: https://docs.alchemy.com/docs/maxpriorityfeepergas-vs-maxfeepergas. +func (tx *DynamicFeeTx) GetGasFeeCapWei() *big.Int { + if tx.GasFeeCap == nil { + return nil + } + return tx.GasFeeCap.BigInt() +} + +// GetValueWei returns the tx amount. +func (tx *DynamicFeeTx) GetValueWei() *big.Int { + if tx.Amount == nil { + return nil + } + + return tx.Amount.BigInt() +} + +// GetNonce returns the account sequence for the transaction. +func (tx *DynamicFeeTx) GetNonce() uint64 { return tx.Nonce } + +// GetTo returns the pointer to the recipient address. +func (tx *DynamicFeeTx) GetTo() *common.Address { + if tx.To == "" { + return nil + } + to := common.HexToAddress(tx.To) + return &to +} + +// AsEthereumData returns an DynamicFeeTx transaction tx from the proto-formatted +// TxData defined on the Cosmos EVM. +func (tx *DynamicFeeTx) AsEthereumData() gethcore.TxData { + v, r, s := tx.GetRawSignatureValues() + return &gethcore.DynamicFeeTx{ + ChainID: tx.GetChainID(), + Nonce: tx.GetNonce(), + GasTipCap: tx.GetGasTipCapWei(), + GasFeeCap: tx.GetGasFeeCapWei(), + Gas: tx.GetGas(), + To: tx.GetTo(), + Value: tx.GetValueWei(), + Data: tx.GetData(), + AccessList: tx.GetAccessList(), + V: v, + R: r, + S: s, + } +} + +// GetRawSignatureValues returns the V, R, S signature values of the transaction. +// The return values should not be modified by the caller. +func (tx *DynamicFeeTx) GetRawSignatureValues() (v, r, s *big.Int) { + return rawSignatureValues(tx.V, tx.R, tx.S) +} + +// SetSignatureValues sets the signature values to the transaction. +func (tx *DynamicFeeTx) SetSignatureValues(chainID, v, r, s *big.Int) { + if v != nil { + tx.V = v.Bytes() + } + if r != nil { + tx.R = r.Bytes() + } + if s != nil { + tx.S = s.Bytes() + } + if chainID != nil { + chainIDInt := sdkmath.NewIntFromBigInt(chainID) + tx.ChainID = &chainIDInt + } +} + +// Validate performs a stateless validation of the tx fields. +func (tx DynamicFeeTx) Validate() error { + if tx.GasTipCap == nil { + return errorsmod.Wrap(ErrInvalidGasCap, "gas tip cap cannot nil") + } + + if tx.GasFeeCap == nil { + return errorsmod.Wrap(ErrInvalidGasCap, "gas fee cap cannot nil") + } + + if tx.GasTipCap.IsNegative() { + return errorsmod.Wrapf(ErrInvalidGasCap, "gas tip cap cannot be negative %s", tx.GasTipCap) + } + + if tx.GasFeeCap.IsNegative() { + return errorsmod.Wrapf(ErrInvalidGasCap, "gas fee cap cannot be negative %s", tx.GasFeeCap) + } + + if !eth.IsValidInt256(tx.GetGasTipCapWei()) { + return errorsmod.Wrap(ErrInvalidGasCap, "out of bound") + } + + if !eth.IsValidInt256(tx.GetGasFeeCapWei()) { + return errorsmod.Wrap(ErrInvalidGasCap, "out of bound") + } + + if tx.GasFeeCap.LT(*tx.GasTipCap) { + return errorsmod.Wrapf( + ErrInvalidGasCap, "max priority fee per gas higher than max fee per gas (%s > %s)", + tx.GasTipCap, tx.GasFeeCap, + ) + } + + if !eth.IsValidInt256(tx.Fee()) { + return errorsmod.Wrap(ErrInvalidGasFee, "out of bound") + } + + for _, err := range []error{ + ValidateTxDataAmount(&tx), + ValidateTxDataTo(&tx), + ValidateTxDataChainID(&tx), + } { + if err != nil { + return err + } + } + + return nil +} + +// Fee returns gasprice * gaslimit. +func (tx DynamicFeeTx) Fee() *big.Int { + return priceTimesGas(tx.GetGasFeeCapWei(), tx.GasLimit) +} + +// Cost returns amount + gasprice * gaslimit. +func (tx DynamicFeeTx) Cost() *big.Int { + return cost(tx.Fee(), tx.GetValueWei()) +} + +// EffectiveGasPriceWei returns the effective gas price based on EIP-1559 rules. +// `effectiveGasPrice = min(baseFee + tipCap, feeCap)` +func (tx *DynamicFeeTx) EffectiveGasPriceWei(baseFeeWei *big.Int) *big.Int { + feeWithSpecifiedTip := new(big.Int).Add(tx.GasTipCap.BigInt(), baseFeeWei) + + // Enforce base fee as the minimum [EffectiveGasPriceWei]: + rawEffectiveGasPrice := gethmath.BigMin(feeWithSpecifiedTip, tx.GasFeeCap.BigInt()) + return BigIntMax(baseFeeWei, rawEffectiveGasPrice) +} + +// EffectiveFeeWei returns effective_gasprice * gaslimit. +func (tx DynamicFeeTx) EffectiveFeeWei(baseFeeWei *big.Int) *big.Int { + return priceTimesGas(tx.EffectiveGasPriceWei(baseFeeWei), tx.GasLimit) +} + +// EffectiveCost returns amount + effective_gasprice * gaslimit. +func (tx DynamicFeeTx) EffectiveCost(baseFeeWei *big.Int) *big.Int { + return cost(tx.EffectiveFeeWei(baseFeeWei), tx.GetValueWei()) +} diff --git a/x/evm/tx_data_dynamic_fee_test.go b/x/evm/tx_data_dynamic_fee_test.go new file mode 100644 index 000000000..ec2b5d579 --- /dev/null +++ b/x/evm/tx_data_dynamic_fee_test.go @@ -0,0 +1,676 @@ +package evm_test + +import ( + "math/big" + "strings" + + "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/common" + gethcore "github.com/ethereum/go-ethereum/core/types" + + "github.com/NibiruChain/nibiru/v2/x/evm" +) + +func (suite *Suite) TestNewDynamicFeeTx() { + testCases := []struct { + name string + expError bool + tx *gethcore.Transaction + }{ + { + "non-empty tx", + false, + gethcore.NewTx(&gethcore.DynamicFeeTx{ + Nonce: 1, + Data: []byte("data"), + Gas: 100, + Value: big.NewInt(1), + AccessList: gethcore.AccessList{}, + To: &suite.addr, + V: suite.bigInt, + R: suite.bigInt, + S: suite.bigInt, + }), + }, + { + "value out of bounds tx", + true, + gethcore.NewTx(&gethcore.DynamicFeeTx{ + Nonce: 1, + Data: []byte("data"), + Gas: 100, + Value: suite.overflowBigInt, + AccessList: gethcore.AccessList{}, + To: &suite.addr, + V: suite.bigInt, + R: suite.bigInt, + S: suite.bigInt, + }), + }, + { + "gas fee cap out of bounds tx", + true, + gethcore.NewTx(&gethcore.DynamicFeeTx{ + Nonce: 1, + Data: []byte("data"), + Gas: 100, + GasFeeCap: suite.overflowBigInt, + Value: big.NewInt(1), + AccessList: gethcore.AccessList{}, + To: &suite.addr, + V: suite.bigInt, + R: suite.bigInt, + S: suite.bigInt, + }), + }, + { + "gas tip cap out of bounds tx", + true, + gethcore.NewTx(&gethcore.DynamicFeeTx{ + Nonce: 1, + Data: []byte("data"), + Gas: 100, + GasTipCap: suite.overflowBigInt, + Value: big.NewInt(1), + AccessList: gethcore.AccessList{}, + To: &suite.addr, + V: suite.bigInt, + R: suite.bigInt, + S: suite.bigInt, + }), + }, + } + for _, tc := range testCases { + tx, err := evm.NewDynamicFeeTx(tc.tx) + + if tc.expError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().NotEmpty(tx) + suite.Require().Equal(uint8(2), tx.TxType()) + } + } +} + +func (suite *Suite) TestDynamicFeeTxAsEthereumData() { + feeConfig := &gethcore.DynamicFeeTx{ + Nonce: 1, + Data: []byte("data"), + Gas: 100, + Value: big.NewInt(1), + AccessList: gethcore.AccessList{}, + To: &suite.addr, + V: suite.bigInt, + R: suite.bigInt, + S: suite.bigInt, + } + + tx := gethcore.NewTx(feeConfig) + + dynamicFeeTx, err := evm.NewDynamicFeeTx(tx) + suite.Require().NoError(err) + + res := dynamicFeeTx.AsEthereumData() + resTx := gethcore.NewTx(res) + + suite.Require().Equal(feeConfig.Nonce, resTx.Nonce()) + suite.Require().Equal(feeConfig.Data, resTx.Data()) + suite.Require().Equal(feeConfig.Gas, resTx.Gas()) + suite.Require().Equal(feeConfig.Value, resTx.Value()) + suite.Require().Equal(feeConfig.AccessList, resTx.AccessList()) + suite.Require().Equal(feeConfig.To, resTx.To()) +} + +func (suite *Suite) TestDynamicFeeTxCopy() { + tx := &evm.DynamicFeeTx{} + txCopy := tx.Copy() + + suite.Require().Equal(&evm.DynamicFeeTx{}, txCopy) + // TODO: Test for different pointers +} + +func (suite *Suite) TestDynamicFeeTxGetChainID() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp *big.Int + }{ + { + "empty chainID", + evm.DynamicFeeTx{ + ChainID: nil, + }, + nil, + }, + { + "non-empty chainID", + evm.DynamicFeeTx{ + ChainID: &suite.sdkInt, + }, + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetChainID() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetAccessList() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp gethcore.AccessList + }{ + { + "empty accesses", + evm.DynamicFeeTx{ + Accesses: nil, + }, + nil, + }, + { + "nil", + evm.DynamicFeeTx{ + Accesses: evm.NewAccessList(nil), + }, + nil, + }, + { + "non-empty accesses", + evm.DynamicFeeTx{ + Accesses: evm.AccessList{ + { + Address: suite.hexAddr, + StorageKeys: []string{}, + }, + }, + }, + gethcore.AccessList{ + { + Address: suite.addr, + StorageKeys: []common.Hash{}, + }, + }, + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetAccessList() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetData() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + }{ + { + "non-empty transaction", + evm.DynamicFeeTx{ + Data: nil, + }, + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetData() + + suite.Require().Equal(tc.tx.Data, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetGas() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp uint64 + }{ + { + "non-empty gas", + evm.DynamicFeeTx{ + GasLimit: suite.uint64, + }, + suite.uint64, + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetGas() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetGasPrice() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp *big.Int + }{ + { + "non-empty gasFeeCap", + evm.DynamicFeeTx{ + GasFeeCap: &suite.sdkInt, + }, + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetGasPrice() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetGasTipCap() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp *big.Int + }{ + { + "empty gasTipCap", + evm.DynamicFeeTx{ + GasTipCap: nil, + }, + nil, + }, + { + "non-empty gasTipCap", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + }, + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetGasTipCapWei() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetGasFeeCap() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp *big.Int + }{ + { + "empty gasFeeCap", + evm.DynamicFeeTx{ + GasFeeCap: nil, + }, + nil, + }, + { + "non-empty gasFeeCap", + evm.DynamicFeeTx{ + GasFeeCap: &suite.sdkInt, + }, + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetGasFeeCapWei() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetValue() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp *big.Int + }{ + { + "empty amount", + evm.DynamicFeeTx{ + Amount: nil, + }, + nil, + }, + { + "non-empty amount", + evm.DynamicFeeTx{ + Amount: &suite.sdkInt, + }, + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetValueWei() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetNonce() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp uint64 + }{ + { + "non-empty nonce", + evm.DynamicFeeTx{ + Nonce: suite.uint64, + }, + suite.uint64, + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetNonce() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxGetTo() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + exp *common.Address + }{ + { + "empty suite.address", + evm.DynamicFeeTx{ + To: "", + }, + nil, + }, + { + "non-empty suite.address", + evm.DynamicFeeTx{ + To: suite.hexAddr, + }, + &suite.addr, + }, + } + + for _, tc := range testCases { + actual := tc.tx.GetTo() + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxSetSignatureValues() { + testCases := []struct { + name string + chainID *big.Int + r *big.Int + v *big.Int + s *big.Int + }{ + { + "empty values", + nil, + nil, + nil, + nil, + }, + { + "non-empty values", + suite.bigInt, + suite.bigInt, + suite.bigInt, + suite.bigInt, + }, + } + + for _, tc := range testCases { + tx := &evm.DynamicFeeTx{} + tx.SetSignatureValues(tc.chainID, tc.v, tc.r, tc.s) + + v, r, s := tx.GetRawSignatureValues() + chainID := tx.GetChainID() + + suite.Require().Equal(tc.v, v, tc.name) + suite.Require().Equal(tc.r, r, tc.name) + suite.Require().Equal(tc.s, s, tc.name) + suite.Require().Equal(tc.chainID, chainID, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxValidate() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + expError bool + }{ + { + "empty", + evm.DynamicFeeTx{}, + true, + }, + { + "gas tip cap is nil", + evm.DynamicFeeTx{ + GasTipCap: nil, + }, + true, + }, + { + "gas fee cap is nil", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkZeroInt, + }, + true, + }, + { + "gas tip cap is negative", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkMinusOneInt, + GasFeeCap: &suite.sdkZeroInt, + }, + true, + }, + { + "gas tip cap is negative", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkZeroInt, + GasFeeCap: &suite.sdkMinusOneInt, + }, + true, + }, + { + "gas fee cap < gas tip cap", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkZeroInt, + }, + true, + }, + { + "amount is negative", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + Amount: &suite.sdkMinusOneInt, + }, + true, + }, + { + "to suite.address is invalid", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + Amount: &suite.sdkInt, + To: suite.invalidAddr, + }, + true, + }, + { + "chain ID not present on AccessList txs", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + Amount: &suite.sdkInt, + To: suite.hexAddr, + ChainID: nil, + }, + true, + }, + { + "no errors", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + Amount: &suite.sdkInt, + To: suite.hexAddr, + ChainID: &suite.sdkInt, + }, + false, + }, + } + + for _, tc := range testCases { + err := tc.tx.Validate() + + if tc.expError { + suite.Require().Error(err, tc.name) + continue + } + + suite.Require().NoError(err, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxEffectiveGasPrice() { + testCases := []struct { + name string + tx func() evm.DynamicFeeTx + baseFeeWei *big.Int + exp *big.Int + }{ + { + name: "all equal to base fee", + tx: func() evm.DynamicFeeTx { + return evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + } + }, + baseFeeWei: (&suite.sdkInt).BigInt(), + exp: (&suite.sdkInt).BigInt(), + }, + { + name: "baseFee < tip < feeCap", + tx: func() evm.DynamicFeeTx { + gasTipCap, _ := math.NewIntFromString("5" + strings.Repeat("0", 12)) + gasFeeCap, _ := math.NewIntFromString("10" + strings.Repeat("0", 12)) + return evm.DynamicFeeTx{ + GasTipCap: &gasTipCap, + GasFeeCap: &gasFeeCap, + } + }, + baseFeeWei: evm.NativeToWei(evm.BASE_FEE_MICRONIBI), + exp: evm.NativeToWei(big.NewInt(6)), + }, + { + name: "baseFee < feeCap < tip", + tx: func() evm.DynamicFeeTx { + gasTipCap, _ := math.NewIntFromString("10" + strings.Repeat("0", 12)) + gasFeeCap, _ := math.NewIntFromString("2" + strings.Repeat("0", 12)) + return evm.DynamicFeeTx{ + GasTipCap: &gasTipCap, + GasFeeCap: &gasFeeCap, + } + }, + baseFeeWei: evm.NativeToWei(evm.BASE_FEE_MICRONIBI), + exp: evm.NativeToWei(big.NewInt(2)), + }, + { + name: "below baseFee", + tx: func() evm.DynamicFeeTx { + gasTipCap, _ := math.NewIntFromString("0" + strings.Repeat("0", 12)) + gasFeeCap, _ := math.NewIntFromString("0" + strings.Repeat("0", 12)) + return evm.DynamicFeeTx{ + GasTipCap: &gasTipCap, + GasFeeCap: &gasFeeCap, + } + }, + baseFeeWei: evm.NativeToWei(evm.BASE_FEE_MICRONIBI), + exp: evm.NativeToWei(big.NewInt(1)), + }, + } + + for _, tc := range testCases { + txData := tc.tx() + actual := txData.EffectiveGasPriceWei(tc.baseFeeWei) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxEffectiveFee() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + baseFee *big.Int + exp *big.Int + }{ + { + "non-empty dynamic fee tx", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + GasLimit: uint64(1), + }, + (&suite.sdkInt).BigInt(), + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveFeeWei(tc.baseFee) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxEffectiveCost() { + testCases := []struct { + name string + tx evm.DynamicFeeTx + baseFee *big.Int + exp *big.Int + }{ + { + "non-empty dynamic fee tx", + evm.DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + GasLimit: uint64(1), + Amount: &suite.sdkZeroInt, + }, + (&suite.sdkInt).BigInt(), + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveCost(tc.baseFee) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *Suite) TestDynamicFeeTxFeeCost() { + tx := &evm.DynamicFeeTx{} + suite.Require().Panics(func() { tx.Fee() }, "should panic") + suite.Require().Panics(func() { tx.Cost() }, "should panic") +} diff --git a/x/evm/legacy_tx.go b/x/evm/tx_data_legacy.go similarity index 68% rename from x/evm/legacy_tx.go rename to x/evm/tx_data_legacy.go index e77b03f92..6a1baea30 100644 --- a/x/evm/legacy_tx.go +++ b/x/evm/tx_data_legacy.go @@ -4,8 +4,6 @@ package evm import ( "math/big" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - errorsmod "cosmossdk.io/errors" "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" @@ -86,7 +84,7 @@ func (tx *LegacyTx) GetGas() uint64 { return tx.GasLimit } -// GetGasPrice returns the gas price field. +// GetGasPrice is equivalent to wei per unit gas. func (tx *LegacyTx) GetGasPrice() *big.Int { if tx.GasPrice == nil { return nil @@ -94,18 +92,20 @@ func (tx *LegacyTx) GetGasPrice() *big.Int { return tx.GasPrice.BigInt() } -// GetGasTipCap returns the gas price field. -func (tx *LegacyTx) GetGasTipCap() *big.Int { +// GetGasTipCapWei returns a cap on the gas tip in units of wei. +// For a [LegacyTx], this is taken to be the gas price. +func (tx *LegacyTx) GetGasTipCapWei() *big.Int { return tx.GetGasPrice() } -// GetGasFeeCap returns the gas price field. -func (tx *LegacyTx) GetGasFeeCap() *big.Int { +// GetGasFeeCapWei returns a cap on the gas fees paid in units of wei. +// For a [LegacyTx], this is taken to be the gas price. +func (tx *LegacyTx) GetGasFeeCapWei() *big.Int { return tx.GetGasPrice() } -// GetValue returns the tx amount. -func (tx *LegacyTx) GetValue() *big.Int { +// GetValueWei returns the tx amount. +func (tx *LegacyTx) GetValueWei() *big.Int { if tx.Amount == nil { return nil } @@ -133,7 +133,7 @@ func (tx *LegacyTx) AsEthereumData() gethcore.TxData { GasPrice: tx.GetGasPrice(), Gas: tx.GetGas(), To: tx.GetTo(), - Value: tx.GetValue(), + Value: tx.GetValueWei(), Data: tx.GetData(), V: v, R: r, @@ -162,69 +162,46 @@ func (tx *LegacyTx) SetSignatureValues(_, v, r, s *big.Int) { // Validate performs a stateless validation of the tx fields. func (tx LegacyTx) Validate() error { - gasPrice := tx.GetGasPrice() - if gasPrice == nil { - return errorsmod.Wrap(ErrInvalidGasPrice, "gas price cannot be nil") + for _, err := range []error{ + ValidateTxDataAmount(&tx), + ValidateTxDataTo(&tx), + ValidateTxDataGasPrice(&tx), + ValidateTxDataChainID(&tx), + } { + if err != nil { + return err + } } - if gasPrice.Sign() == -1 { - return errorsmod.Wrapf(ErrInvalidGasPrice, "gas price cannot be negative %s", gasPrice) - } - if !eth.IsValidInt256(gasPrice) { - return errorsmod.Wrap(ErrInvalidGasPrice, "out of bound") - } if !eth.IsValidInt256(tx.Fee()) { return errorsmod.Wrap(ErrInvalidGasFee, "out of bound") } - amount := tx.GetValue() - // Amount can be 0 - if amount != nil && amount.Sign() == -1 { - return errorsmod.Wrapf(ErrInvalidAmount, "amount cannot be negative %s", amount) - } - if !eth.IsValidInt256(amount) { - return errorsmod.Wrap(ErrInvalidAmount, "out of bound") - } - - if tx.To != "" { - if err := eth.ValidateAddress(tx.To); err != nil { - return errorsmod.Wrap(err, "invalid to address") - } - } - - chainID := tx.GetChainID() - - if chainID == nil { - return errorsmod.Wrap( - errortypes.ErrInvalidChainID, - "chain ID must be derived from LegacyTx txs", - ) - } - return nil } // Fee returns gasprice * gaslimit. func (tx LegacyTx) Fee() *big.Int { - return fee(tx.GetGasPrice(), tx.GetGas()) + return priceTimesGas(tx.GetGasPrice(), tx.GetGas()) } // Cost returns amount + gasprice * gaslimit. func (tx LegacyTx) Cost() *big.Int { - return cost(tx.Fee(), tx.GetValue()) + return cost(tx.Fee(), tx.GetValueWei()) } -// EffectiveGasPrice is the same as GasPrice for LegacyTx -func (tx LegacyTx) EffectiveGasPrice(_ *big.Int) *big.Int { - return tx.GetGasPrice() +// EffectiveGasPriceWei is the same as GasPrice for LegacyTx +func (tx LegacyTx) EffectiveGasPriceWei(baseFeeWei *big.Int) *big.Int { + return BigIntMax(tx.GetGasPrice(), baseFeeWei) } -// EffectiveFee is the same as Fee for LegacyTx -func (tx LegacyTx) EffectiveFee(_ *big.Int) *big.Int { - return tx.Fee() +// EffectiveFeeWei is the same as Fee for LegacyTx +func (tx LegacyTx) EffectiveFeeWei(baseFeeWei *big.Int) *big.Int { + return priceTimesGas(tx.EffectiveGasPriceWei(baseFeeWei), tx.GetGas()) } // EffectiveCost is the same as Cost for LegacyTx -func (tx LegacyTx) EffectiveCost(_ *big.Int) *big.Int { - return tx.Cost() +func (tx LegacyTx) EffectiveCost(baseFeeWei *big.Int) *big.Int { + txFee := tx.EffectiveFeeWei(baseFeeWei) + return cost(txFee, tx.GetValueWei()) } diff --git a/x/evm/legacy_tx_test.go b/x/evm/tx_data_legacy_test.go similarity index 74% rename from x/evm/legacy_tx_test.go rename to x/evm/tx_data_legacy_test.go index 56cf6fc96..6b81152cf 100644 --- a/x/evm/legacy_tx_test.go +++ b/x/evm/tx_data_legacy_test.go @@ -7,9 +7,10 @@ import ( gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/NibiruChain/nibiru/v2/x/evm" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" ) -func (suite *TxDataTestSuite) TestNewLegacyTx() { +func (suite *Suite) TestNewLegacyTx() { testCases := []struct { name string tx *gethcore.Transaction @@ -39,14 +40,14 @@ func (suite *TxDataTestSuite) TestNewLegacyTx() { } } -func (suite *TxDataTestSuite) TestLegacyTxTxType() { +func (suite *Suite) TestLegacyTxTxType() { tx := evm.LegacyTx{} actual := tx.TxType() suite.Require().Equal(uint8(0), actual) } -func (suite *TxDataTestSuite) TestLegacyTxCopy() { +func (suite *Suite) TestLegacyTxCopy() { tx := &evm.LegacyTx{} txData := tx.Copy() @@ -54,21 +55,21 @@ func (suite *TxDataTestSuite) TestLegacyTxCopy() { // TODO: Test for different pointers } -func (suite *TxDataTestSuite) TestLegacyTxGetChainID() { +func (suite *Suite) TestLegacyTxGetChainID() { tx := evm.LegacyTx{} actual := tx.GetChainID() suite.Require().Nil(actual) } -func (suite *TxDataTestSuite) TestLegacyTxGetAccessList() { +func (suite *Suite) TestLegacyTxGetAccessList() { tx := evm.LegacyTx{} actual := tx.GetAccessList() suite.Require().Nil(actual) } -func (suite *TxDataTestSuite) TestLegacyTxGetData() { +func (suite *Suite) TestLegacyTxGetData() { testCases := []struct { name string tx evm.LegacyTx @@ -88,7 +89,7 @@ func (suite *TxDataTestSuite) TestLegacyTxGetData() { } } -func (suite *TxDataTestSuite) TestLegacyTxGetGas() { +func (suite *Suite) TestLegacyTxGetGas() { testCases := []struct { name string tx evm.LegacyTx @@ -110,7 +111,7 @@ func (suite *TxDataTestSuite) TestLegacyTxGetGas() { } } -func (suite *TxDataTestSuite) TestLegacyTxGetGasPrice() { +func (suite *Suite) TestLegacyTxGetGasPrice() { testCases := []struct { name string tx evm.LegacyTx @@ -133,13 +134,13 @@ func (suite *TxDataTestSuite) TestLegacyTxGetGasPrice() { } for _, tc := range testCases { - actual := tc.tx.GetGasFeeCap() + actual := tc.tx.GetGasFeeCapWei() suite.Require().Equal(tc.exp, actual, tc.name) } } -func (suite *TxDataTestSuite) TestLegacyTxGetGasTipCap() { +func (suite *Suite) TestLegacyTxGetGasTipCap() { testCases := []struct { name string tx evm.LegacyTx @@ -155,13 +156,13 @@ func (suite *TxDataTestSuite) TestLegacyTxGetGasTipCap() { } for _, tc := range testCases { - actual := tc.tx.GetGasTipCap() + actual := tc.tx.GetGasTipCapWei() suite.Require().Equal(tc.exp, actual, tc.name) } } -func (suite *TxDataTestSuite) TestLegacyTxGetGasFeeCap() { +func (suite *Suite) TestLegacyTxGetGasFeeCap() { testCases := []struct { name string tx evm.LegacyTx @@ -177,13 +178,13 @@ func (suite *TxDataTestSuite) TestLegacyTxGetGasFeeCap() { } for _, tc := range testCases { - actual := tc.tx.GetGasFeeCap() + actual := tc.tx.GetGasFeeCapWei() suite.Require().Equal(tc.exp, actual, tc.name) } } -func (suite *TxDataTestSuite) TestLegacyTxGetValue() { +func (suite *Suite) TestLegacyTxGetValue() { testCases := []struct { name string tx evm.LegacyTx @@ -206,13 +207,13 @@ func (suite *TxDataTestSuite) TestLegacyTxGetValue() { } for _, tc := range testCases { - actual := tc.tx.GetValue() + actual := tc.tx.GetValueWei() suite.Require().Equal(tc.exp, actual, tc.name) } } -func (suite *TxDataTestSuite) TestLegacyTxGetNonce() { +func (suite *Suite) TestLegacyTxGetNonce() { testCases := []struct { name string tx evm.LegacyTx @@ -233,7 +234,7 @@ func (suite *TxDataTestSuite) TestLegacyTxGetNonce() { } } -func (suite *TxDataTestSuite) TestLegacyTxGetTo() { +func (suite *Suite) TestLegacyTxGetTo() { testCases := []struct { name string tx evm.LegacyTx @@ -262,14 +263,14 @@ func (suite *TxDataTestSuite) TestLegacyTxGetTo() { } } -func (suite *TxDataTestSuite) TestLegacyTxAsEthereumData() { +func (suite *Suite) TestLegacyTxAsEthereumData() { tx := &evm.LegacyTx{} txData := tx.AsEthereumData() suite.Require().Equal(&gethcore.LegacyTx{}, txData) } -func (suite *TxDataTestSuite) TestLegacyTxSetSignatureValues() { +func (suite *Suite) TestLegacyTxSetSignatureValues() { testCases := []struct { name string v *big.Int @@ -295,52 +296,54 @@ func (suite *TxDataTestSuite) TestLegacyTxSetSignatureValues() { } } -func (suite *TxDataTestSuite) TestLegacyTxValidate() { +func (suite *Suite) TestLegacyTxValidate() { testCases := []struct { name string - tx evm.LegacyTx + tx func(tx *evm.LegacyTx) *evm.LegacyTx expError bool }{ { - "empty", - evm.LegacyTx{}, - true, + name: "empty", + tx: func(_ *evm.LegacyTx) *evm.LegacyTx { return new(evm.LegacyTx) }, + expError: true, }, { - "gas price is nil", - evm.LegacyTx{ - GasPrice: nil, + name: "gas price is nil", + tx: func(tx *evm.LegacyTx) *evm.LegacyTx { + tx.GasPrice = nil + return tx }, - true, + expError: true, }, { - "gas price is negative", - evm.LegacyTx{ - GasPrice: &suite.sdkMinusOneInt, + name: "gas price is negative", + tx: func(tx *evm.LegacyTx) *evm.LegacyTx { + tx.GasPrice = &suite.sdkMinusOneInt + return tx }, - true, + expError: true, }, { - "amount is negative", - evm.LegacyTx{ - GasPrice: &suite.sdkInt, - Amount: &suite.sdkMinusOneInt, + name: "amount is negative", + tx: func(tx *evm.LegacyTx) *evm.LegacyTx { + tx.Amount = &suite.sdkMinusOneInt + return tx }, - true, + expError: true, }, { - "to address is invalid", - evm.LegacyTx{ - GasPrice: &suite.sdkInt, - Amount: &suite.sdkInt, - To: suite.invalidAddr, + name: "to address is invalid", + tx: func(tx *evm.LegacyTx) *evm.LegacyTx { + tx.To = suite.invalidAddr + return tx }, - true, + expError: true, }, } for _, tc := range testCases { - err := tc.tx.Validate() + got := tc.tx(evmtest.ValidLegacyTx()) + err := got.Validate() if tc.expError { suite.Require().Error(err, tc.name) @@ -351,7 +354,7 @@ func (suite *TxDataTestSuite) TestLegacyTxValidate() { } } -func (suite *TxDataTestSuite) TestLegacyTxEffectiveGasPrice() { +func (suite *Suite) TestLegacyTxEffectiveGasPrice() { testCases := []struct { name string tx evm.LegacyTx @@ -369,13 +372,13 @@ func (suite *TxDataTestSuite) TestLegacyTxEffectiveGasPrice() { } for _, tc := range testCases { - actual := tc.tx.EffectiveGasPrice(tc.baseFee) + actual := tc.tx.EffectiveGasPriceWei(tc.baseFee) suite.Require().Equal(tc.exp, actual, tc.name) } } -func (suite *TxDataTestSuite) TestLegacyTxEffectiveFee() { +func (suite *Suite) TestLegacyTxEffectiveFee() { testCases := []struct { name string tx evm.LegacyTx @@ -394,13 +397,13 @@ func (suite *TxDataTestSuite) TestLegacyTxEffectiveFee() { } for _, tc := range testCases { - actual := tc.tx.EffectiveFee(tc.baseFee) + actual := tc.tx.EffectiveFeeWei(tc.baseFee) suite.Require().Equal(tc.exp, actual, tc.name) } } -func (suite *TxDataTestSuite) TestLegacyTxEffectiveCost() { +func (suite *Suite) TestLegacyTxEffectiveCost() { testCases := []struct { name string tx evm.LegacyTx @@ -426,7 +429,7 @@ func (suite *TxDataTestSuite) TestLegacyTxEffectiveCost() { } } -func (suite *TxDataTestSuite) TestLegacyTxFeeCost() { +func (suite *Suite) TestLegacyTxFeeCost() { tx := &evm.LegacyTx{} suite.Require().Panics(func() { tx.Fee() }, "should panic") diff --git a/x/evm/tx_test.go b/x/evm/tx_test.go index 2a8a2acd2..d7c884c8b 100644 --- a/x/evm/tx_test.go +++ b/x/evm/tx_test.go @@ -8,15 +8,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - gethcore "github.com/ethereum/go-ethereum/core/types" - "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" "github.com/stretchr/testify/suite" ) -type TxDataTestSuite struct { +type Suite struct { suite.Suite sdkInt sdkmath.Int @@ -34,7 +32,7 @@ type TxDataTestSuite struct { hexInputBytes hexutil.Bytes } -func (suite *TxDataTestSuite) SetupTest() { +func (suite *Suite) SetupTest() { suite.sdkInt = sdkmath.NewInt(9001) suite.uint64 = suite.sdkInt.Uint64() suite.hexUint64 = hexutil.Uint64(100) @@ -53,627 +51,5 @@ func (suite *TxDataTestSuite) SetupTest() { } func TestTxDataTestSuite(t *testing.T) { - suite.Run(t, new(TxDataTestSuite)) -} - -func (suite *TxDataTestSuite) TestNewDynamicFeeTx() { - testCases := []struct { - name string - expError bool - tx *gethcore.Transaction - }{ - { - "non-empty tx", - false, - gethcore.NewTx(&gethcore.DynamicFeeTx{ - Nonce: 1, - Data: []byte("data"), - Gas: 100, - Value: big.NewInt(1), - AccessList: gethcore.AccessList{}, - To: &suite.addr, - V: suite.bigInt, - R: suite.bigInt, - S: suite.bigInt, - }), - }, - { - "value out of bounds tx", - true, - gethcore.NewTx(&gethcore.DynamicFeeTx{ - Nonce: 1, - Data: []byte("data"), - Gas: 100, - Value: suite.overflowBigInt, - AccessList: gethcore.AccessList{}, - To: &suite.addr, - V: suite.bigInt, - R: suite.bigInt, - S: suite.bigInt, - }), - }, - { - "gas fee cap out of bounds tx", - true, - gethcore.NewTx(&gethcore.DynamicFeeTx{ - Nonce: 1, - Data: []byte("data"), - Gas: 100, - GasFeeCap: suite.overflowBigInt, - Value: big.NewInt(1), - AccessList: gethcore.AccessList{}, - To: &suite.addr, - V: suite.bigInt, - R: suite.bigInt, - S: suite.bigInt, - }), - }, - { - "gas tip cap out of bounds tx", - true, - gethcore.NewTx(&gethcore.DynamicFeeTx{ - Nonce: 1, - Data: []byte("data"), - Gas: 100, - GasTipCap: suite.overflowBigInt, - Value: big.NewInt(1), - AccessList: gethcore.AccessList{}, - To: &suite.addr, - V: suite.bigInt, - R: suite.bigInt, - S: suite.bigInt, - }), - }, - } - for _, tc := range testCases { - tx, err := evm.NewDynamicFeeTx(tc.tx) - - if tc.expError { - suite.Require().Error(err) - } else { - suite.Require().NoError(err) - suite.Require().NotEmpty(tx) - suite.Require().Equal(uint8(2), tx.TxType()) - } - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxAsEthereumData() { - feeConfig := &gethcore.DynamicFeeTx{ - Nonce: 1, - Data: []byte("data"), - Gas: 100, - Value: big.NewInt(1), - AccessList: gethcore.AccessList{}, - To: &suite.addr, - V: suite.bigInt, - R: suite.bigInt, - S: suite.bigInt, - } - - tx := gethcore.NewTx(feeConfig) - - dynamicFeeTx, err := evm.NewDynamicFeeTx(tx) - suite.Require().NoError(err) - - res := dynamicFeeTx.AsEthereumData() - resTx := gethcore.NewTx(res) - - suite.Require().Equal(feeConfig.Nonce, resTx.Nonce()) - suite.Require().Equal(feeConfig.Data, resTx.Data()) - suite.Require().Equal(feeConfig.Gas, resTx.Gas()) - suite.Require().Equal(feeConfig.Value, resTx.Value()) - suite.Require().Equal(feeConfig.AccessList, resTx.AccessList()) - suite.Require().Equal(feeConfig.To, resTx.To()) -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxCopy() { - tx := &evm.DynamicFeeTx{} - txCopy := tx.Copy() - - suite.Require().Equal(&evm.DynamicFeeTx{}, txCopy) - // TODO: Test for different pointers -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetChainID() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp *big.Int - }{ - { - "empty chainID", - evm.DynamicFeeTx{ - ChainID: nil, - }, - nil, - }, - { - "non-empty chainID", - evm.DynamicFeeTx{ - ChainID: &suite.sdkInt, - }, - (&suite.sdkInt).BigInt(), - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetChainID() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetAccessList() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp gethcore.AccessList - }{ - { - "empty accesses", - evm.DynamicFeeTx{ - Accesses: nil, - }, - nil, - }, - { - "nil", - evm.DynamicFeeTx{ - Accesses: evm.NewAccessList(nil), - }, - nil, - }, - { - "non-empty accesses", - evm.DynamicFeeTx{ - Accesses: evm.AccessList{ - { - Address: suite.hexAddr, - StorageKeys: []string{}, - }, - }, - }, - gethcore.AccessList{ - { - Address: suite.addr, - StorageKeys: []common.Hash{}, - }, - }, - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetAccessList() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetData() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - }{ - { - "non-empty transaction", - evm.DynamicFeeTx{ - Data: nil, - }, - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetData() - - suite.Require().Equal(tc.tx.Data, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetGas() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp uint64 - }{ - { - "non-empty gas", - evm.DynamicFeeTx{ - GasLimit: suite.uint64, - }, - suite.uint64, - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetGas() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetGasPrice() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp *big.Int - }{ - { - "non-empty gasFeeCap", - evm.DynamicFeeTx{ - GasFeeCap: &suite.sdkInt, - }, - (&suite.sdkInt).BigInt(), - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetGasPrice() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetGasTipCap() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp *big.Int - }{ - { - "empty gasTipCap", - evm.DynamicFeeTx{ - GasTipCap: nil, - }, - nil, - }, - { - "non-empty gasTipCap", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - }, - (&suite.sdkInt).BigInt(), - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetGasTipCap() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetGasFeeCap() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp *big.Int - }{ - { - "empty gasFeeCap", - evm.DynamicFeeTx{ - GasFeeCap: nil, - }, - nil, - }, - { - "non-empty gasFeeCap", - evm.DynamicFeeTx{ - GasFeeCap: &suite.sdkInt, - }, - (&suite.sdkInt).BigInt(), - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetGasFeeCap() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetValue() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp *big.Int - }{ - { - "empty amount", - evm.DynamicFeeTx{ - Amount: nil, - }, - nil, - }, - { - "non-empty amount", - evm.DynamicFeeTx{ - Amount: &suite.sdkInt, - }, - (&suite.sdkInt).BigInt(), - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetValue() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetNonce() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp uint64 - }{ - { - "non-empty nonce", - evm.DynamicFeeTx{ - Nonce: suite.uint64, - }, - suite.uint64, - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetNonce() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxGetTo() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - exp *common.Address - }{ - { - "empty suite.address", - evm.DynamicFeeTx{ - To: "", - }, - nil, - }, - { - "non-empty suite.address", - evm.DynamicFeeTx{ - To: suite.hexAddr, - }, - &suite.addr, - }, - } - - for _, tc := range testCases { - actual := tc.tx.GetTo() - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxSetSignatureValues() { - testCases := []struct { - name string - chainID *big.Int - r *big.Int - v *big.Int - s *big.Int - }{ - { - "empty values", - nil, - nil, - nil, - nil, - }, - { - "non-empty values", - suite.bigInt, - suite.bigInt, - suite.bigInt, - suite.bigInt, - }, - } - - for _, tc := range testCases { - tx := &evm.DynamicFeeTx{} - tx.SetSignatureValues(tc.chainID, tc.v, tc.r, tc.s) - - v, r, s := tx.GetRawSignatureValues() - chainID := tx.GetChainID() - - suite.Require().Equal(tc.v, v, tc.name) - suite.Require().Equal(tc.r, r, tc.name) - suite.Require().Equal(tc.s, s, tc.name) - suite.Require().Equal(tc.chainID, chainID, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxValidate() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - expError bool - }{ - { - "empty", - evm.DynamicFeeTx{}, - true, - }, - { - "gas tip cap is nil", - evm.DynamicFeeTx{ - GasTipCap: nil, - }, - true, - }, - { - "gas fee cap is nil", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkZeroInt, - }, - true, - }, - { - "gas tip cap is negative", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkMinusOneInt, - GasFeeCap: &suite.sdkZeroInt, - }, - true, - }, - { - "gas tip cap is negative", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkZeroInt, - GasFeeCap: &suite.sdkMinusOneInt, - }, - true, - }, - { - "gas fee cap < gas tip cap", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - GasFeeCap: &suite.sdkZeroInt, - }, - true, - }, - { - "amount is negative", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - GasFeeCap: &suite.sdkInt, - Amount: &suite.sdkMinusOneInt, - }, - true, - }, - { - "to suite.address is invalid", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - GasFeeCap: &suite.sdkInt, - Amount: &suite.sdkInt, - To: suite.invalidAddr, - }, - true, - }, - { - "chain ID not present on AccessList txs", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - GasFeeCap: &suite.sdkInt, - Amount: &suite.sdkInt, - To: suite.hexAddr, - ChainID: nil, - }, - true, - }, - { - "no errors", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - GasFeeCap: &suite.sdkInt, - Amount: &suite.sdkInt, - To: suite.hexAddr, - ChainID: &suite.sdkInt, - }, - false, - }, - } - - for _, tc := range testCases { - err := tc.tx.Validate() - - if tc.expError { - suite.Require().Error(err, tc.name) - continue - } - - suite.Require().NoError(err, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxEffectiveGasPrice() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - baseFee *big.Int - exp *big.Int - }{ - { - "non-empty dynamic fee tx", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - GasFeeCap: &suite.sdkInt, - }, - (&suite.sdkInt).BigInt(), - (&suite.sdkInt).BigInt(), - }, - } - - for _, tc := range testCases { - actual := tc.tx.EffectiveGasPrice(tc.baseFee) - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxEffectiveFee() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - baseFee *big.Int - exp *big.Int - }{ - { - "non-empty dynamic fee tx", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - GasFeeCap: &suite.sdkInt, - GasLimit: uint64(1), - }, - (&suite.sdkInt).BigInt(), - (&suite.sdkInt).BigInt(), - }, - } - - for _, tc := range testCases { - actual := tc.tx.EffectiveFee(tc.baseFee) - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxEffectiveCost() { - testCases := []struct { - name string - tx evm.DynamicFeeTx - baseFee *big.Int - exp *big.Int - }{ - { - "non-empty dynamic fee tx", - evm.DynamicFeeTx{ - GasTipCap: &suite.sdkInt, - GasFeeCap: &suite.sdkInt, - GasLimit: uint64(1), - Amount: &suite.sdkZeroInt, - }, - (&suite.sdkInt).BigInt(), - (&suite.sdkInt).BigInt(), - }, - } - - for _, tc := range testCases { - actual := tc.tx.EffectiveCost(tc.baseFee) - - suite.Require().Equal(tc.exp, actual, tc.name) - } -} - -func (suite *TxDataTestSuite) TestDynamicFeeTxFeeCost() { - tx := &evm.DynamicFeeTx{} - suite.Require().Panics(func() { tx.Fee() }, "should panic") - suite.Require().Panics(func() { tx.Cost() }, "should panic") + suite.Run(t, new(Suite)) } diff --git a/x/evm/vmtracer.go b/x/evm/vmtracer.go index 5438d778d..2a20f2b01 100644 --- a/x/evm/vmtracer.go +++ b/x/evm/vmtracer.go @@ -28,13 +28,18 @@ func NewTracer(tracer string, msg core.Message, cfg *params.ChainConfig, height Debug: true, } - // FIXME: inconsistent logging between stdout and stderr switch tracer { case TracerAccessList: - preCompiles := vm.DefaultActivePrecompiles(cfg.Rules(big.NewInt(height), cfg.MergeNetsplitBlock != nil)) - return logger.NewAccessListTracer(msg.AccessList(), msg.From(), *msg.To(), preCompiles) + rules := cfg.Rules(big.NewInt(height), cfg.MergeNetsplitBlock != nil) + precompileAddrs := vm.DefaultActivePrecompiles(rules) + return logger.NewAccessListTracer( + msg.AccessList(), + msg.From(), + *msg.To(), + precompileAddrs, + ) case TracerJSON: - return logger.NewJSONLogger(logCfg, os.Stderr) + return logger.NewJSONLogger(logCfg, os.Stdout) case TracerMarkdown: return logger.NewMarkdownLogger(logCfg, os.Stdout) case TracerStruct: