diff --git a/.github/workflows/revive-bench.yml b/.github/workflows/revive-bench.yml new file mode 100644 index 000000000..89fdb9d5e --- /dev/null +++ b/.github/workflows/revive-bench.yml @@ -0,0 +1,67 @@ +name: pallet-revive Benchmark + +# Dry-runs the critical dotNS paths on pallet-revive against live Paseo Asset Hub Next and Polkadot +# Asset Hub state, under the limit each one runs with in production. Read-only: nothing is signed +# or submitted, and no secret is used. `deployed` measures the contracts live on the network, +# `build` this checkout's implementations upgraded into the same state. +on: + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + inputs: + fail-at: + description: Fail a path above this share of its limit, in percent + default: "95" + +permissions: + contents: read + +concurrency: + group: revive-bench-${{ github.ref }} + cancel-in-progress: true + +jobs: + bench: + name: ${{ matrix.network }} (${{ matrix.target }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + network: [paseo, polkadot] + target: [deployed, build] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: ./.github/actions/setup-foundry + if: matrix.target == 'build' + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.2.6" + no-cache: true + + # The postinstall (`setup.bash`) fetches `lib/`, which only the build needs. + - run: bun install ${{ matrix.target == 'deployed' && '--ignore-scripts' || '' }} + + - run: forge build + if: matrix.target == 'build' + + - name: Run benchmark + env: + FAIL_AT: ${{ inputs.fail-at || '95' }} + run: | + bun scripts/bench/revive-bench.mjs \ + --network ${{ matrix.network }} \ + ${{ matrix.target == 'build' && '--build' || '' }} \ + --fail-at "$FAIL_AT" \ + --json revive-bench-${{ matrix.network }}-${{ matrix.target }}.json + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: revive-bench-${{ matrix.network }}-${{ matrix.target }} + path: revive-bench-${{ matrix.network }}-${{ matrix.target }}.json + if-no-files-found: ignore diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8aafe7900..688e3a418 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -271,6 +271,17 @@ While a fork test is in flight, skip it with: forge test --no-match-path 'test/fork/**' ``` +### pallet-revive benchmark + +Foundry tests run on its in-process EVM, which does not meter weight and proof size the way pallet-revive does. `scripts/bench/revive-bench.mjs` measures the critical paths on pallet-revive itself: gateway issuance, reservations and claims, settlement, `expireReservation`, and the largest governance batches. It dry-runs each one as a Root batch against live Asset Hub state and compares it with the limit it runs under: the gateway's `MaxContractCallWeight`, the extrinsic limit for a user's own call, or the scheduler's limit for a referendum. Nothing is signed or submitted. + +```bash +bun scripts/bench/revive-bench.mjs --network paseo # contracts live on the network +forge build && bun scripts/bench/revive-bench.mjs --network paseo --build # this checkout +``` + +`--build` upgrades every proxy and store beacon to the implementations in `out/` inside the same dry run, so a change can be measured before it ships. A path fails above `--fail-at` percent of its limit (95 by default). Paths expected to fail for now are listed in the script's `KNOWN_FAILURES`, each with what clears it: they are reported as known, and the run fails on any other failure, or when a listed path passes, so the list stays current. The `pallet-revive Benchmark` workflow runs both modes on Paseo Asset Hub Next and Polkadot Asset Hub every Monday, and on demand. + ### Cleanup checklist before merging an upgrade PR 1. Delete the upgrade script under `scripts/deploy/`. diff --git a/README.md b/README.md index 323d24642..3acb178e6 100644 --- a/README.md +++ b/README.md @@ -382,7 +382,7 @@ Addresses are recorded per network in `deployments//.json`, a ### Build and test -Builds and tests are run with Foundry. Fork tests use the local Paseo Asset Hub adapter described in [DEPLOYMENTS.md](./DEPLOYMENTS.md); ordinary unit, fuzz, and invariant tests run against Foundry's in-process EVM. +Builds and tests are run with Foundry. Fork tests use the local Paseo Asset Hub adapter described in [DEPLOYMENTS.md](./DEPLOYMENTS.md); ordinary unit, fuzz, and invariant tests run against Foundry's in-process EVM. A weekly benchmark measures the critical paths on pallet-revive against live Asset Hub state; see [CONTRIBUTING.md](./CONTRIBUTING.md#pallet-revive-benchmark). ## Security diff --git a/bun.lock b/bun.lock index 807410fde..950e42342 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "@openzeppelin/upgrades-core": "1.44.2", }, "devDependencies": { + "@polkadot/api": "16.5.6", "cross-env": "^10.1.0", "dotenv": "^16.4.5", "prettier": "^2.6.2", @@ -95,6 +96,80 @@ "@openzeppelin/upgrades-core": ["@openzeppelin/upgrades-core@1.44.2", "", { "dependencies": { "@nomicfoundation/slang": "^0.18.3", "bignumber.js": "^9.1.2", "cbor": "^10.0.0", "chalk": "^4.1.0", "compare-versions": "^6.0.0", "debug": "^4.1.1", "ethereumjs-util": "^7.0.3", "minimatch": "^9.0.5", "minimist": "^1.2.7", "proper-lockfile": "^4.1.1", "solidity-ast": "^0.4.60" }, "bin": { "openzeppelin-upgrades-core": "dist/cli/cli.js" } }, "sha512-m6iorjyhPK9ow5/trNs7qsBC/SOzJCO51pvvAF2W9nOiZ1t0RtCd+rlRmRmlWTv4M33V0wzIUeamJ2BPbzgUXA=="], + "@polkadot-api/json-rpc-provider": ["@polkadot-api/json-rpc-provider@0.0.1", "", {}, "sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA=="], + + "@polkadot-api/json-rpc-provider-proxy": ["@polkadot-api/json-rpc-provider-proxy@0.1.0", "", {}, "sha512-8GSFE5+EF73MCuLQm8tjrbCqlgclcHBSRaswvXziJ0ZW7iw3UEMsKkkKvELayWyBuOPa2T5i1nj6gFOeIsqvrg=="], + + "@polkadot-api/metadata-builders": ["@polkadot-api/metadata-builders@0.3.2", "", { "dependencies": { "@polkadot-api/substrate-bindings": "0.6.0", "@polkadot-api/utils": "0.1.0" } }, "sha512-TKpfoT6vTb+513KDzMBTfCb/ORdgRnsS3TDFpOhAhZ08ikvK+hjHMt5plPiAX/OWkm1Wc9I3+K6W0hX5Ab7MVg=="], + + "@polkadot-api/observable-client": ["@polkadot-api/observable-client@0.3.2", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.3.2", "@polkadot-api/substrate-bindings": "0.6.0", "@polkadot-api/utils": "0.1.0" }, "peerDependencies": { "@polkadot-api/substrate-client": "0.1.4", "rxjs": ">=7.8.0" } }, "sha512-HGgqWgEutVyOBXoGOPp4+IAq6CNdK/3MfQJmhCJb8YaJiaK4W6aRGrdQuQSTPHfERHCARt9BrOmEvTXAT257Ug=="], + + "@polkadot-api/substrate-bindings": ["@polkadot-api/substrate-bindings@0.6.0", "", { "dependencies": { "@noble/hashes": "^1.3.1", "@polkadot-api/utils": "0.1.0", "@scure/base": "^1.1.1", "scale-ts": "^1.6.0" } }, "sha512-lGuhE74NA1/PqdN7fKFdE5C1gNYX357j1tWzdlPXI0kQ7h3kN0zfxNOpPUN7dIrPcOFZ6C0tRRVrBylXkI6xPw=="], + + "@polkadot-api/substrate-client": ["@polkadot-api/substrate-client@0.1.4", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.1", "@polkadot-api/utils": "0.1.0" } }, "sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A=="], + + "@polkadot-api/utils": ["@polkadot-api/utils@0.1.0", "", {}, "sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA=="], + + "@polkadot/api": ["@polkadot/api@16.5.6", "", { "dependencies": { "@polkadot/api-augment": "16.5.6", "@polkadot/api-base": "16.5.6", "@polkadot/api-derive": "16.5.6", "@polkadot/keyring": "^14.0.3", "@polkadot/rpc-augment": "16.5.6", "@polkadot/rpc-core": "16.5.6", "@polkadot/rpc-provider": "16.5.6", "@polkadot/types": "16.5.6", "@polkadot/types-augment": "16.5.6", "@polkadot/types-codec": "16.5.6", "@polkadot/types-create": "16.5.6", "@polkadot/types-known": "16.5.6", "@polkadot/util": "^14.0.3", "@polkadot/util-crypto": "^14.0.3", "eventemitter3": "^5.0.1", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-5h/X3pY8WpqGk4XTaiIUjKD6Pnk8k4bJ6EIwPKLP8/kfFWKSOenpN6ggZxANr+Qj+RgXrp4TxJVcuhXSiBh9Sg=="], + + "@polkadot/api-augment": ["@polkadot/api-augment@16.5.6", "", { "dependencies": { "@polkadot/api-base": "16.5.6", "@polkadot/rpc-augment": "16.5.6", "@polkadot/types": "16.5.6", "@polkadot/types-augment": "16.5.6", "@polkadot/types-codec": "16.5.6", "@polkadot/util": "^14.0.3", "tslib": "^2.8.1" } }, "sha512-bunJF1c3nIuDtU6iwa+reTt9U47Y8iOC8Gw7PfANlZmLJmO/XVXnWc3JJLM+g9ESDn2raHJELeWBFVOXQrbtUw=="], + + "@polkadot/api-base": ["@polkadot/api-base@16.5.6", "", { "dependencies": { "@polkadot/rpc-core": "16.5.6", "@polkadot/types": "16.5.6", "@polkadot/util": "^14.0.3", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-eBLIv86ZZY4t5OrobVoGC+QXbErOGlBpI2rJI5OMvTNPoVvtEoI++u+wwRScjkOZaUhXyQikd+0Uv71qr3xnsA=="], + + "@polkadot/api-derive": ["@polkadot/api-derive@16.5.6", "", { "dependencies": { "@polkadot/api": "16.5.6", "@polkadot/api-augment": "16.5.6", "@polkadot/api-base": "16.5.6", "@polkadot/rpc-core": "16.5.6", "@polkadot/types": "16.5.6", "@polkadot/types-codec": "16.5.6", "@polkadot/util": "^14.0.3", "@polkadot/util-crypto": "^14.0.3", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-cHdvPvhYFch18uPTcuOZJ8VceOfercod2fi4xCnHJAmattzlgj9qCgnOoxdmBS9GZ403ZyRHOjBuUwZy/IsUWQ=="], + + "@polkadot/keyring": ["@polkadot/keyring@14.0.3", "", { "dependencies": { "@polkadot/util": "14.0.3", "@polkadot/util-crypto": "14.0.3", "tslib": "^2.8.0" } }, "sha512-ozp1dQwaHCjgX/fpTTORmHjxdUNQnyiTVJszpzUaUpvtH/IGZhSU/mSHXMqNETS/g57vQa7NatIDcWfyR9abyA=="], + + "@polkadot/networks": ["@polkadot/networks@14.0.3", "", { "dependencies": { "@polkadot/util": "14.0.3", "@substrate/ss58-registry": "^1.51.0", "tslib": "^2.8.0" } }, "sha512-/VqTLUDn+Wm8S2L/yaGFddo3oW4vRYav0Rg4pLg/semMZLaN8PJ6h927ucn9JyWdH82QfZfyiIPORt0ZF3isyw=="], + + "@polkadot/rpc-augment": ["@polkadot/rpc-augment@16.5.6", "", { "dependencies": { "@polkadot/rpc-core": "16.5.6", "@polkadot/types": "16.5.6", "@polkadot/types-codec": "16.5.6", "@polkadot/util": "^14.0.3", "tslib": "^2.8.1" } }, "sha512-vlrNvl2VtU09jZV/AvH7jBb/cNUO+dWu8Xj9pId5ctSUnZHm8o8wRk9ekyieKP57OUoKMd8+VScwMKd624SxTw=="], + + "@polkadot/rpc-core": ["@polkadot/rpc-core@16.5.6", "", { "dependencies": { "@polkadot/rpc-augment": "16.5.6", "@polkadot/rpc-provider": "16.5.6", "@polkadot/types": "16.5.6", "@polkadot/util": "^14.0.3", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-l6od++WlvKH4mw5mtsIh2AhiBs3H+TtdOoUHVLCx/R9il7+gl+arltzZ8vBuffyh/O+uQ36lI8yUoD1g4gi1tA=="], + + "@polkadot/rpc-provider": ["@polkadot/rpc-provider@16.5.6", "", { "dependencies": { "@polkadot/keyring": "^14.0.3", "@polkadot/types": "16.5.6", "@polkadot/types-support": "16.5.6", "@polkadot/util": "^14.0.3", "@polkadot/util-crypto": "^14.0.3", "@polkadot/x-fetch": "^14.0.3", "@polkadot/x-global": "^14.0.3", "@polkadot/x-ws": "^14.0.3", "eventemitter3": "^5.0.1", "mock-socket": "^9.3.1", "nock": "^13.5.5", "tslib": "^2.8.1" }, "optionalDependencies": { "@substrate/connect": "0.8.11" } }, "sha512-46sHIjKYr4aSzBCfbyqtCwuP8MMJ3jOp0xx9eggOGbKyP8Z0j0Cp+1nNkZUYzehcdGjjrmCxCbQp17wc6cj4zA=="], + + "@polkadot/types": ["@polkadot/types@16.5.6", "", { "dependencies": { "@polkadot/keyring": "^14.0.3", "@polkadot/types-augment": "16.5.6", "@polkadot/types-codec": "16.5.6", "@polkadot/types-create": "16.5.6", "@polkadot/util": "^14.0.3", "@polkadot/util-crypto": "^14.0.3", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-X/sfMHJS4RkRhnsc4CQqzUy7BM/s2y71TrBFHPYAjs2q/rbZ/BwvBk70SrUiSa0+iRRn3RewbBZm+AB8CbkdKw=="], + + "@polkadot/types-augment": ["@polkadot/types-augment@16.5.6", "", { "dependencies": { "@polkadot/types": "16.5.6", "@polkadot/types-codec": "16.5.6", "@polkadot/util": "^14.0.3", "tslib": "^2.8.1" } }, "sha512-QN5UrluUZCVgknUDW0gps/FRQ13Qgm24w53pCd2HgD0nmTtXDt9D4psjWwx5JkGTkUAvpzFWwN41bkxAeCiV6g=="], + + "@polkadot/types-codec": ["@polkadot/types-codec@16.5.6", "", { "dependencies": { "@polkadot/util": "^14.0.3", "@polkadot/x-bigint": "^14.0.3", "tslib": "^2.8.1" } }, "sha512-3tzUv1LZOL97IlQmko4dqbfRC0cg9IQ2QAHRVoDIWsXrVovp1V3kPdP0o6e3I8T2XB9IlbabK91v+ZiIxhGMZw=="], + + "@polkadot/types-create": ["@polkadot/types-create@16.5.6", "", { "dependencies": { "@polkadot/types-codec": "16.5.6", "@polkadot/util": "^14.0.3", "tslib": "^2.8.1" } }, "sha512-g7g3hrjpz4KgqQqei9PU0JY9fsFHBmThWALZk5pWB32vyDyDcXZiyhH3agDhqfmzQiolTW2FuvcNJxgS634J1w=="], + + "@polkadot/types-known": ["@polkadot/types-known@16.5.6", "", { "dependencies": { "@polkadot/networks": "^14.0.3", "@polkadot/types": "16.5.6", "@polkadot/types-codec": "16.5.6", "@polkadot/types-create": "16.5.6", "@polkadot/util": "^14.0.3", "tslib": "^2.8.1" } }, "sha512-c78NcVO3LIvi4xzxB39WewE+80I4jOYUtPBaB4AzSMespEwIr92VTeX3KzFWuutxDXLSPqeVfXhaAhBB0NssiQ=="], + + "@polkadot/types-support": ["@polkadot/types-support@16.5.6", "", { "dependencies": { "@polkadot/util": "^14.0.3", "tslib": "^2.8.1" } }, "sha512-Hqpa/hCvXZXUTUiJMAE55UXpzAeCVLaFlzzXQXLkne0vhmv3/JkWcBnX755a/b9+C4b3MKEz2i0tSKLsa3DldA=="], + + "@polkadot/util": ["@polkadot/util@14.0.3", "", { "dependencies": { "@polkadot/x-bigint": "14.0.3", "@polkadot/x-global": "14.0.3", "@polkadot/x-textdecoder": "14.0.3", "@polkadot/x-textencoder": "14.0.3", "@types/bn.js": "^5.1.6", "bn.js": "^5.2.1", "tslib": "^2.8.0" } }, "sha512-mg1NR7ixHlNiz2zbvdcdy1OXZmca2tVA4DpewGpY/qFkW/gq9HdDrHLu7g0k90QnunDcFW4emb7NB60sGJQ0bw=="], + + "@polkadot/util-crypto": ["@polkadot/util-crypto@14.0.3", "", { "dependencies": { "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@polkadot/networks": "14.0.3", "@polkadot/util": "14.0.3", "@polkadot/wasm-crypto": "^7.5.3", "@polkadot/wasm-util": "^7.5.3", "@polkadot/x-bigint": "14.0.3", "@polkadot/x-randomvalues": "14.0.3", "@scure/base": "^1.1.7", "@scure/sr25519": "^0.2.0", "tslib": "^2.8.0" } }, "sha512-V00BI6XnZLCkrAmV8uN0eSB6fy48CkxdDZT29cgSMSwHPtY6oKUNgd1ST07PGCL5x8XflwjoA7CTlhdbp1Y9gw=="], + + "@polkadot/wasm-bridge": ["@polkadot/wasm-bridge@7.5.4", "", { "dependencies": { "@polkadot/wasm-util": "7.5.4", "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, "sha512-6xaJVvoZbnbgpQYXNw9OHVNWjXmtcoPcWh7hlwx3NpfiLkkjljj99YS+XGZQlq7ks2fVCg7FbfknkNb8PldDaA=="], + + "@polkadot/wasm-crypto": ["@polkadot/wasm-crypto@7.5.4", "", { "dependencies": { "@polkadot/wasm-bridge": "7.5.4", "@polkadot/wasm-crypto-asmjs": "7.5.4", "@polkadot/wasm-crypto-init": "7.5.4", "@polkadot/wasm-crypto-wasm": "7.5.4", "@polkadot/wasm-util": "7.5.4", "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, "sha512-1seyClxa7Jd7kQjfnCzTTTfYhTa/KUTDUaD3DMHBk5Q4ZUN1D1unJgX+v1aUeXSPxmzocdZETPJJRZjhVOqg9g=="], + + "@polkadot/wasm-crypto-asmjs": ["@polkadot/wasm-crypto-asmjs@7.5.4", "", { "dependencies": { "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*" } }, "sha512-ZYwxQHAJ8pPt6kYk9XFmyuFuSS+yirJLonvP+DYbxOrARRUHfN4nzp4zcZNXUuaFhpbDobDSFn6gYzye6BUotA=="], + + "@polkadot/wasm-crypto-init": ["@polkadot/wasm-crypto-init@7.5.4", "", { "dependencies": { "@polkadot/wasm-bridge": "7.5.4", "@polkadot/wasm-crypto-asmjs": "7.5.4", "@polkadot/wasm-crypto-wasm": "7.5.4", "@polkadot/wasm-util": "7.5.4", "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, "sha512-U6s4Eo2rHs2n1iR01vTz/sOQ7eOnRPjaCsGWhPV+ZC/20hkVzwPAhiizu/IqMEol4tO2yiSheD4D6bn0KxUJhg=="], + + "@polkadot/wasm-crypto-wasm": ["@polkadot/wasm-crypto-wasm@7.5.4", "", { "dependencies": { "@polkadot/wasm-util": "7.5.4", "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*" } }, "sha512-PsHgLsVTu43eprwSvUGnxybtOEuHPES6AbApcs7y5ZbM2PiDMzYbAjNul098xJK/CPtrxZ0ePDFnaQBmIJyTFw=="], + + "@polkadot/wasm-util": ["@polkadot/wasm-util@7.5.4", "", { "dependencies": { "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*" } }, "sha512-hqPpfhCpRAqCIn/CYbBluhh0TXmwkJnDRjxrU9Bnqtw9nMNa97D8JuOjdd2pi0rxm+eeLQ/f1rQMp71RMM9t4w=="], + + "@polkadot/x-bigint": ["@polkadot/x-bigint@14.0.3", "", { "dependencies": { "@polkadot/x-global": "14.0.3", "tslib": "^2.8.0" } }, "sha512-U0al6BKgldFrEbmSObRAlzv9VDs5SMa/rbvZKvvkVec0sWTzYPWQZU1ZC/biXLYdjdKML89BeuCKmXZtCcGhUQ=="], + + "@polkadot/x-fetch": ["@polkadot/x-fetch@14.0.3", "", { "dependencies": { "@polkadot/x-global": "14.0.3", "node-fetch": "^3.3.2", "tslib": "^2.8.0" } }, "sha512-695c5aPBPtYcnn2zM+u0mXgyNHINlO0qGlGcJq3/0t5NVRZv5KZhk7NNm6antOay9uUjGG40F/r+LPzDT3QamA=="], + + "@polkadot/x-global": ["@polkadot/x-global@14.0.3", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-MzMEynJ7HMTy/plLmdyP8rv14RS/6s29HZodUG9aCOscBnEiEDxVEax/ztRJqxhhQuHeYdx0LYDwVbdQDTkqNw=="], + + "@polkadot/x-randomvalues": ["@polkadot/x-randomvalues@14.0.3", "", { "dependencies": { "@polkadot/x-global": "14.0.3", "tslib": "^2.8.0" }, "peerDependencies": { "@polkadot/util": "14.0.3", "@polkadot/wasm-util": "*" } }, "sha512-qTPcrk0nIHL2tIu5e0cLj3puQvjCK7onehnqO2fvlmWeIlvDel66fwWs06Ipsib+CwLJdmE6WgNy+8Jv74r6YA=="], + + "@polkadot/x-textdecoder": ["@polkadot/x-textdecoder@14.0.3", "", { "dependencies": { "@polkadot/x-global": "14.0.3", "tslib": "^2.8.0" } }, "sha512-4RJYDG00iUzQ7YAuS/yvkWRZlkjYU8PUNdJHRfqtJ+SjrSPB7LYYxFhLgw43TZUtHmIueNTsml2Ukv3xXTr2kA=="], + + "@polkadot/x-textencoder": ["@polkadot/x-textencoder@14.0.3", "", { "dependencies": { "@polkadot/x-global": "14.0.3", "tslib": "^2.8.0" } }, "sha512-9HH6o2L+r99wEfXhPb5g+Xwn7qouqD32PsMux7B0dFGR2KNqP4KwO19Hu+gdij6wsEhy7delhZwzHenrWwDfhQ=="], + + "@polkadot/x-ws": ["@polkadot/x-ws@14.0.3", "", { "dependencies": { "@polkadot/x-global": "14.0.3", "tslib": "^2.8.0", "ws": "^8.18.0" } }, "sha512-tOPdkMye3iuXnuFtdNg5+iSu7Cz9LRL8z5psMuZpUpThMYChGsS2pDFtNvXOKU8ohhO+frY9VdJ9VBg1WL9Iug=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.2", "", { "os": "android", "cpu": "arm" }, "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.2", "", { "os": "android", "cpu": "arm64" }, "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg=="], @@ -151,8 +226,20 @@ "@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + "@scure/sr25519": ["@scure/sr25519@0.2.0", "", { "dependencies": { "@noble/curves": "~1.9.2", "@noble/hashes": "~1.8.0" } }, "sha512-uUuLP7Z126XdSizKtrCGqYyR3b3hYtJ6Fg/XFUXmc2//k2aXHDLqZwFeXxL97gg4XydPROPVnuaHGF2+xriSKg=="], + "@solidity-parser/parser": ["@solidity-parser/parser@0.20.2", "", {}, "sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA=="], + "@substrate/connect": ["@substrate/connect@0.8.11", "", { "dependencies": { "@substrate/connect-extension-protocol": "^2.0.0", "@substrate/connect-known-chains": "^1.1.5", "@substrate/light-client-extension-helpers": "^1.0.0", "smoldot": "2.0.26" } }, "sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw=="], + + "@substrate/connect-extension-protocol": ["@substrate/connect-extension-protocol@2.2.2", "", {}, "sha512-t66jwrXA0s5Goq82ZtjagLNd7DPGCNjHeehRlE/gcJmJ+G56C0W+2plqOMRicJ8XGR1/YFnUSEqUFiSNbjGrAA=="], + + "@substrate/connect-known-chains": ["@substrate/connect-known-chains@1.10.3", "", {}, "sha512-OJEZO1Pagtb6bNE3wCikc2wrmvEU5x7GxFFLqqbz1AJYYxSlrPCGu4N2og5YTExo4IcloNMQYFRkBGue0BKZ4w=="], + + "@substrate/light-client-extension-helpers": ["@substrate/light-client-extension-helpers@1.0.0", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "^0.0.1", "@polkadot-api/json-rpc-provider-proxy": "^0.1.0", "@polkadot-api/observable-client": "^0.3.0", "@polkadot-api/substrate-client": "^0.1.2", "@substrate/connect-extension-protocol": "^2.0.0", "@substrate/connect-known-chains": "^1.1.5", "rxjs": "^7.8.1" }, "peerDependencies": { "smoldot": "2.x" } }, "sha512-TdKlni1mBBZptOaeVrKnusMg/UBpWUORNDv5fdCaJklP4RJiFOzBCrzC+CyVI5kQzsXBisZ+2pXm+rIjS38kHg=="], + + "@substrate/ss58-registry": ["@substrate/ss58-registry@1.51.0", "", {}, "sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ=="], + "@tsconfig/node10": ["@tsconfig/node10@1.0.12", "", {}, "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ=="], "@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="], @@ -261,6 +348,8 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], @@ -299,8 +388,12 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -343,6 +436,8 @@ "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "keccak": ["keccak@3.0.4", "", { "dependencies": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0", "readable-stream": "^3.6.0" } }, "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q=="], "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], @@ -363,12 +458,20 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "mock-socket": ["mock-socket@9.3.1", "", {}, "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "nock": ["nock@13.5.6", "", { "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", "propagate": "^2.0.0" } }, "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ=="], + "node-addon-api": ["node-addon-api@2.0.2", "", {}, "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], "nofilter": ["nofilter@3.1.0", "", {}, "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g=="], @@ -397,6 +500,8 @@ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "propagate": ["propagate@2.0.1", "", {}, "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], @@ -411,8 +516,12 @@ "rollup": ["rollup@4.60.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.2", "@rollup/rollup-android-arm64": "4.60.2", "@rollup/rollup-darwin-arm64": "4.60.2", "@rollup/rollup-darwin-x64": "4.60.2", "@rollup/rollup-freebsd-arm64": "4.60.2", "@rollup/rollup-freebsd-x64": "4.60.2", "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", "@rollup/rollup-linux-arm-musleabihf": "4.60.2", "@rollup/rollup-linux-arm64-gnu": "4.60.2", "@rollup/rollup-linux-arm64-musl": "4.60.2", "@rollup/rollup-linux-loong64-gnu": "4.60.2", "@rollup/rollup-linux-loong64-musl": "4.60.2", "@rollup/rollup-linux-ppc64-gnu": "4.60.2", "@rollup/rollup-linux-ppc64-musl": "4.60.2", "@rollup/rollup-linux-riscv64-gnu": "4.60.2", "@rollup/rollup-linux-riscv64-musl": "4.60.2", "@rollup/rollup-linux-s390x-gnu": "4.60.2", "@rollup/rollup-linux-x64-gnu": "4.60.2", "@rollup/rollup-linux-x64-musl": "4.60.2", "@rollup/rollup-openbsd-x64": "4.60.2", "@rollup/rollup-openharmony-arm64": "4.60.2", "@rollup/rollup-win32-arm64-msvc": "4.60.2", "@rollup/rollup-win32-ia32-msvc": "4.60.2", "@rollup/rollup-win32-x64-gnu": "4.60.2", "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ=="], + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "scale-ts": ["scale-ts@1.6.1", "", {}, "sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g=="], + "scrypt-js": ["scrypt-js@3.0.1", "", {}, "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="], "secp256k1": ["secp256k1@4.0.4", "", { "dependencies": { "elliptic": "^6.5.7", "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" } }, "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw=="], @@ -433,6 +542,8 @@ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "smoldot": ["smoldot@2.0.26", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig=="], + "solidity-ast": ["solidity-ast@0.4.62", "", {}, "sha512-jSC7msQCkJXIzM8LlDjRZ5cif5w40g6THlXHFk3zchbL5dm3YLoBETvqPGo5KndYkftjhcs5kz1fnTu4d34lVQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -463,6 +574,8 @@ "ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-script": "dist/bin-script-deprecated.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], @@ -481,6 +594,8 @@ "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], diff --git a/contracts/utils/DotnsConstants.sol b/contracts/utils/DotnsConstants.sol index 0ebccf589..07a73bcb4 100644 --- a/contracts/utils/DotnsConstants.sol +++ b/contracts/utils/DotnsConstants.sol @@ -69,26 +69,30 @@ library DotnsConstants { /// @notice Default per-name live-claim cap the name whitelist starts with. /// @dev Governance retunes it on the whitelist within `WHITELIST_MAX_CLAIMANTS_LIMIT`. - uint16 internal constant WHITELIST_DEFAULT_MAX_CLAIMANTS = 64; + uint16 internal constant WHITELIST_DEFAULT_MAX_CLAIMANTS = 24; /// @notice Default claim-reason byte cap the name whitelist starts with. /// @dev Governance retunes it on the whitelist within `WHITELIST_MAX_REASON_LIMIT`. uint256 internal constant WHITELIST_DEFAULT_MAX_REASON_BYTES = 256; - /// @notice Upper bound on the whitelist live-claim cap. Caps the claim clear-loop below the - /// block gas limit. - uint16 internal constant WHITELIST_MAX_CLAIMANTS_LIMIT = 128; + /// @notice Upper bound on the whitelist live-claim cap. + /// @dev A grant clears every claim on the name in one call, so at this bound a referendum's + /// `grantName` still fits the weight one governance call can execute, with each claim + /// carrying the longest reason. The pallet-revive benchmark checks it. + uint16 internal constant WHITELIST_MAX_CLAIMANTS_LIMIT = 30; /// @notice Upper bound on the whitelist reason byte cap. uint256 internal constant WHITELIST_MAX_REASON_LIMIT = 256; /// @notice Default cap on labels granted in one `grantNames` call. /// @dev Governance retunes it on the whitelist within `WHITELIST_MAX_GRANT_BATCH_LIMIT`. - uint16 internal constant WHITELIST_DEFAULT_MAX_GRANT_BATCH = 100; + uint16 internal constant WHITELIST_DEFAULT_MAX_GRANT_BATCH = 32; - /// @notice Upper bound on the `grantNames` batch cap. Bounds one call below the block gas - /// limit. - uint16 internal constant WHITELIST_MAX_GRANT_BATCH_LIMIT = 256; + /// @notice Upper bound on the `grantNames` batch cap. + /// @dev At this bound a referendum's `grantNames` still fits the weight one governance call + /// can execute, with every label at the longest DNS label length, since the whitelist + /// stores each label it grants. The pallet-revive benchmark checks it. + uint16 internal constant WHITELIST_MAX_GRANT_BATCH_LIMIT = 36; /// @notice Well-known key the protocol registry is registered under, itself. /// @dev Not for discovery: the registry is the bootstrap contract every consumer already diff --git a/contracts/whitelist/IDotnsNameWhitelist.sol b/contracts/whitelist/IDotnsNameWhitelist.sol index 1c28ba732..781f2e0a9 100644 --- a/contracts/whitelist/IDotnsNameWhitelist.sol +++ b/contracts/whitelist/IDotnsNameWhitelist.sol @@ -266,6 +266,9 @@ interface IDotnsNameWhitelist { /// @notice Sets the live-claim cap per name. /// @dev Restricted to a substrate Root dispatch. The cap is bounded by /// `DotnsConstants.WHITELIST_MAX_CLAIMANTS_LIMIT`, which bounds the resolution clear-loop. + /// The cap only stops new requests: a name that collected more claims under a higher cap + /// keeps them, and governance rejects some with @custom:function reject before a + /// @custom:function grantName on it fits one call. /// @custom:reverts MaxClaimantsOutOfRange when `newMax` is zero or above the ceiling. /// @custom:emits MaxClaimantsSet. /// @param newMax New per-name claim cap. diff --git a/package.json b/package.json index 569a11aab..1d5142f5e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "bun": ">=1.2.6" }, "devDependencies": { + "@polkadot/api": "16.5.6", "cross-env": "^10.1.0", "dotenv": "^16.4.5", "prettier": "^2.6.2", diff --git a/scripts/bench/revive-bench.mjs b/scripts/bench/revive-bench.mjs new file mode 100644 index 000000000..ea8c29ddd --- /dev/null +++ b/scripts/bench/revive-bench.mjs @@ -0,0 +1,577 @@ +#!/usr/bin/env bun +// Measures the weight and proof size of the critical dotNS paths on pallet-revive, against live +// Asset Hub state, and checks each against the limit it runs under in production: +// +// gateway DotnsGateway calls DotnsPopController as Root, under MaxContractCallWeight +// signed a user's own transaction, under the normal-class extrinsic limit +// governance a Root call enacted by a referendum, under the scheduler's maximum weight +// +// Each path runs in a Root `Utility.batch_all` passed to `DryRunApi_dry_run_call` at the finalized +// head, after the setup calls it needs. Nothing is signed or submitted. With --build, the batch +// first upgrades every proxy and store beacon to the implementations in this checkout's `out/`, so the +// figures are for this checkout's code on live state. +// +// Usage: bun scripts/bench/revive-bench.mjs --network [--build] +// [--fail-at ] [--json ] +// +// Exits 1 when a path does not fit its limit, uses more than --fail-at percent of it, or fails, +// unless KNOWN_FAILURES lists it. A listed path that passes also exits 1, so the list is kept +// current. + +import { ApiPromise, WsProvider } from "@polkadot/api"; +import fs from "node:fs"; +import path from "node:path"; +import { parseArgs } from "node:util"; +import { + concat, + decodeFunctionResult, + encodeFunctionData, + getAddress, + keccak256, + parseAbi, + toHex, +} from "viem"; + +const NETWORKS = { + paseo: { + url: "wss://paseo-asset-hub-next-rpc.polkadot.io", + manifest: "deployments/paseo-assethub/420420417.json", + }, + polkadot: { + url: "wss://polkadot-asset-hub-rpc.polkadot.io", + manifest: "deployments/polkadot/420420419.json", + }, +}; + +// UUPS proxies, and the StoreFactory call that moves each store beacon to a new implementation +// (the factory owns the beacons). +const PROXIES = [ + "DotnsProtocolRegistry", + "DotnsRegistry", + "DotnsRegistrar", + "DotnsRegistrarController", + "DotnsNameEscrow", + "DotnsResolver", + "DotnsContentResolver", + "DotnsReverseResolver", + "DotnsPopResolver", + "DotnsPopController", + "PopRules", + "DotnsNameWhitelist", + "StoreFactory", +]; +const STORES = { + LabelStore: "upgradeLabelStoreImplementation", + UserStore: "upgradeUserStoreImplementation", +}; + +// Far above any real limit, so a path that does not fit still gets a figure. A dry run does not +// check the block limits. +const MEASURE_LIMIT = { refTime: 20_000_000_000_000n, proofSize: 128n << 20n }; +// Binary-search resolution for signed calls. +const RESOLUTION = { refTime: 50_000_000n, proofSize: 4096n }; +// Used when a deployed controller predates the constant. +const DEFAULT_QUEUE_STEPS = 4n; +const MAX_DEPOSIT = (1n << 128n) - 1n; +const CHAT_KEY = "0x04" + "11".repeat(64); +// Longest DNS label, `StringUtils.MAX_DNS_LABEL_OCTETS`. The whitelist stores each label it +// grants, so its rows use labels of this length. +const MAX_LABEL_OCTETS = 63; +const ANY_ACCOUNT = "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"; + +// Paths expected to fail for now, by network, target and name prefix, each with what clears it. +// They are reported as known and do not fail the run. +const KNOWN_FAILURES = [ + { + network: "polkadot", + target: "deployed", + paths: [ + "Device name + reservation, first in container", + "Device name + reservation, user has a store", + "Personhood claim", + ], + reason: "Polkadot Asset Hub runs 1.0.0, whose issuance paths exceed the threshold; cleared by the 1.1.0 upgrade", + }, + { + network: "polkadot", + target: "build", + paths: ["Device name + reservation, first in container"], + reason: "device containers are not prepared on Polkadot Asset Hub; the row is skipped once every container exists", + }, + { + network: "*", + target: "deployed", + paths: ["grantNames,", "grantName clearing"], + reason: "the live whitelist caps are above the defaults; cleared when governance lowers them", + }, +]; + +const abi = parseAbi([ + "function issueDeviceNameWithReservation(((string label, address user, bytes chatKey) issuance, string reservedLabel) params)", + "function issueDeviceName((string label, address user, bytes chatKey) params)", + "function reservePersonhoodName((address user, string label) params)", + "function issuePersonhoodName((string label, address user, (uint8 kind, string deviceLabel, bytes chatKey) link) params)", + "function expireReservation(string label)", + "function claimLabelStore() returns (bool)", + "function MAX_QUEUE_STEPS_PER_CALL() view returns (uint256)", + "function MAX_RESERVATION_QUEUE() view returns (uint16)", + "function version() view returns (string)", + "function tldNode() view returns (bytes32)", + "function recordExists(bytes32 node) view returns (bool)", + "function grantName(string label, address user)", + "function grantNames(string[] labels, address user)", + "function requestName(string label, string reason, address user)", + "function setWindow(uint64 startsIn, uint64 duration)", + "function maxGrantBatch() view returns (uint16)", + "function maxClaimants() view returns (uint16)", + "function maxReasonBytes() view returns (uint256)", + "function setMaxGrantBatch(uint16 newMax)", + "function setMaxClaimants(uint16 newMax)", + "function setMaxReasonBytes(uint256 newMax)", + "function owner() view returns (address)", + "function upgradeToAndCall(address newImplementation, bytes data)", + "function upgradeLabelStoreImplementation(address newImplementation)", + "function upgradeUserStoreImplementation(address newImplementation)", +]); + +const { values: args } = parseArgs({ + options: { + network: { type: "string" }, + build: { type: "boolean", default: false }, + "fail-at": { type: "string", default: "95" }, + json: { type: "string" }, + }, +}); +const net = NETWORKS[args.network]; +if (!net) throw new Error(`--network must be one of ${Object.keys(NETWORKS).join(", ")}`); +const failAt = Number(args["fail-at"]); +if (!(failAt > 0)) throw new Error(`--fail-at must be a positive percentage, got ${args["fail-at"]}`); + +const repo = path.resolve(import.meta.dirname, "../.."); +const manifest = JSON.parse(fs.readFileSync(path.join(repo, net.manifest), "utf8")); +const CONTROLLER = manifest.DotnsPopController; +const WHITELIST = manifest.DotnsNameWhitelist; + +const api = await ApiPromise.create({ + provider: new WsProvider(net.url, 1000, {}, 600_000), + noInitWarn: true, +}); +const head = await api.rpc.chain.getFinalizedHead(); +const at = await api.at(head); +const blockNumber = (await api.rpc.chain.getHeader(head)).number.toNumber(); + +const weightOf = (w) => ({ refTime: BigInt(w.refTime), proofSize: BigInt(w.proofSize) }); +const LIMITS = { + gateway: weightOf(api.consts.dotnsGateway.maxContractCallWeight.toJSON()), + signed: weightOf(api.consts.system.blockWeights.toJSON().perClass.normal.maxExtrinsic), + governance: weightOf(api.consts.scheduler.maximumWeight.toJSON()), +}; + +// Chain reads and call builders + +async function read(to, functionName, fnArgs = []) { + const data = encodeFunctionData({ abi, functionName, args: fnArgs }); + const r = (await at.call.reviveApi.call(ANY_ACCOUNT, to, 0, null, null, data)).toJSON(); + const out = r.result?.ok; + // Bit 0 of the flags is REVERT. + if (!out || (out.flags.bits & 1) !== 0) throw new Error(`${functionName} reverted on ${to}`); + return decodeFunctionResult({ abi, functionName, data: out.data }); +} + +async function readOr(fallback, ...readArgs) { + try { + return await read(...readArgs); + } catch { + return fallback; + } +} + +const accountOf = async (h160) => (await at.call.reviveApi.accountId(h160)).toString(); + +const contractCall = (to, functionName, fnArgs, weight = MEASURE_LIMIT) => + api.tx.revive.call(to, 0, weight, MAX_DEPOSIT, encodeFunctionData({ abi, functionName, args: fnArgs })); + +// Fails the batch when the inner call fails, unlike `dispatch_as`. +const asSigned = (account, call) => + api.tx.utility.dispatchAsFallible({ system: { Signed: account } }, call); + +// Funds an account so it can pay the fees and storage deposits of its calls. +const fund = (account) => api.tx.balances.forceSetBalance(account, 10n ** 22n); + +// --build: upgrade every proxy and store beacon to this checkout's implementations + +async function buildUpgrades() { + const calls = []; + const upgrade = async (target, artifact, functionName, fnArgs) => { + const owner = getAddress(await read(target, "owner")); + const ownerAccount = await accountOf(owner); + const file = path.join(repo, "out", `${artifact}.sol`, `${artifact}.json`); + const initcode = JSON.parse(fs.readFileSync(file, "utf8")).bytecode.object; + const salt = keccak256(toHex(`dotns-bench-${artifact}`)); + const impl = getAddress( + "0x" + keccak256(concat(["0xff", owner, salt, keccak256(initcode)])).slice(26), + ); + calls.push( + fund(ownerAccount), + asSigned(ownerAccount, api.tx.revive.instantiateWithCode(0, MEASURE_LIMIT, MAX_DEPOSIT, initcode, "0x", salt)), + asSigned(ownerAccount, contractCall(target, functionName, fnArgs(impl))), + ); + }; + for (const name of PROXIES) { + await upgrade(manifest[name], name, "upgradeToAndCall", (impl) => [impl, "0x"]); + } + for (const [store, functionName] of Object.entries(STORES)) { + await upgrade(manifest.StoreFactory, store, functionName, (impl) => [impl]); + } + return calls; +} + +// Dry runs + +async function dryRun(calls) { + const tx = api.tx.utility.batchAll(calls); + const r = (await at.call.dryRunApi.dryRunCall({ system: "Root" }, tx, 5)).toJSON(); + const exec = r.ok?.executionResult; + if (!exec) throw new Error(`dry run rejected: ${JSON.stringify(r).slice(0, 300)}`); + const info = exec.ok ?? exec.err.postInfo; + return { ok: !!exec.ok, error: exec.err?.error, weight: weightOf(info.actualWeight) }; +} + +const errorName = (e) => { + if (!e?.module) return JSON.stringify(e); + const meta = api.registry.findMetaError({ + index: api.createType("u8", e.module.index), + error: api.createType("[u8;4]", e.module.error), + }); + return `${meta.section}.${meta.name}`; +}; + +// A Root call: its cost is the batch's weight with the call minus the batch's weight without it. +async function measureRoot(calls, measure) { + const base = await dryRun([...calls, api.tx.system.remark("0x")]); + if (!base.ok) return { error: `setup failed: ${errorName(base.error)}` }; + const roomy = await dryRun([...calls, measure(MEASURE_LIMIT)]); + if (!roomy.ok) return { error: `fails: ${errorName(roomy.error)}` }; + return { + refTime: roomy.weight.refTime - base.weight.refTime, + proofSize: roomy.weight.proofSize - base.weight.proofSize, + }; +} + +// A signed call: a dispatch on behalf of an account reports its declared weight, so the cost is +// the smallest limit, in each dimension, under which the call still succeeds. +async function measureSigned(calls, measure) { + const runs = async (w) => (await dryRun([...calls, measure(w)])).ok; + const roomy = await dryRun([...calls, measure(MEASURE_LIMIT)]); + if (!roomy.ok) return { error: `fails: ${errorName(roomy.error)}` }; + const search = async (dim) => { + let lo = 0n; + let hi = MEASURE_LIMIT[dim]; + while (hi - lo > RESOLUTION[dim]) { + const mid = (lo + hi) / 2n; + if (await runs({ ...MEASURE_LIMIT, [dim]: mid })) hi = mid; + else lo = mid; + } + return hi; + }; + return { refTime: await search("refTime"), proofSize: await search("proofSize") }; +} + +async function bench(prefix, s) { + const calls = [...prefix, ...s.setup]; + const cost = await (s.signed ? measureSigned : measureRoot)(calls, s.measure); + if (cost.error) return cost; + const limit = LIMITS[s.limit]; + const fits = (await dryRun([...calls, s.measure(limit)])).ok; + const share = (v, max) => Number((v * 10000n) / max) / 100; + return { + ...cost, + refShare: share(cost.refTime, limit.refTime), + proofShare: share(cost.proofSize, limit.proofSize), + fits, + }; +} + +// Scenarios + +// Fresh users and labels on every run, so nothing collides with live names. +const seed = BigInt(keccak256(toHex(`${blockNumber}-${Date.now()}`))) & ((1n << 64n) - 1n); +let counter = 0n; +const user = () => + getAddress("0xbe9c" + seed.toString(16).padStart(16, "0") + (++counter).toString(16).padStart(20, "0")); +const label = (length = 12) => { + let out = "zq"; + while (out.length < length) { + let n = BigInt(keccak256(toHex(`${seed}-${++counter}`))); + for (let i = 0; i < 40 && out.length < length; i++, n /= 26n) { + out += String.fromCharCode(97 + Number(n % 26n)); + } + } + return out; +}; +const longLabel = () => label(MAX_LABEL_OCTETS); + +const reserve = (u, base, w) => + contractCall(CONTROLLER, "reservePersonhoodName", [{ user: u, label: base }], w); +const issueDevice = (u, deviceLabel, base, w) => { + const issuance = { label: deviceLabel, user: u, chatKey: CHAT_KEY }; + return base + ? contractCall(CONTROLLER, "issueDeviceNameWithReservation", [{ issuance, reservedLabel: base }], w) + : contractCall(CONTROLLER, "issueDeviceName", [issuance], w); +}; +const claim = (u, base, deviceLabel) => (w) => + contractCall( + CONTROLLER, + "issuePersonhoodName", + [{ label: base, user: u, link: { kind: 1, deviceLabel, chatKey: "0x" } }], + w, + ); + +// `count` users join the queue for `base`, then each moves to a queue of its own. Their entries +// in `base` are left empty, for the next call on `base` to clear. +function leftEntries(base, count) { + const users = Array.from({ length: count }, user); + return { + join: users.map((u) => reserve(u, base)), + leave: users.map((u) => reserve(u, label())), + }; +} + +// The first numeric container that does not exist yet, and the first one that does. +async function findContainers() { + const tld = await read(manifest.DotnsProtocolRegistry, "tldNode"); + let free; + let used; + for (let i = 0; i < 100 && !(free && used); i++) { + const suffix = String(i).padStart(2, "0"); + const node = keccak256(concat([tld, keccak256(toHex(suffix))])); + if (await read(manifest.DotnsRegistry, "recordExists", [node])) used ??= suffix; + else free ??= suffix; + } + return { free, used: used ?? "00" }; +} + +// The whitelist bounds in this checkout's `DotnsConstants`. The constants are internal, so they +// have no getter and are read from the source. +function buildWhitelistBounds() { + const source = fs.readFileSync(path.join(repo, "contracts/utils/DotnsConstants.sol"), "utf8"); + const bound = (name) => { + const m = source.match(new RegExp(`constant ${name} = (\\d+);`)); + if (!m) throw new Error(`${name} not found in DotnsConstants.sol`); + return Number(m[1]); + }; + return { + grantBatch: bound("WHITELIST_MAX_GRANT_BATCH_LIMIT"), + claimants: bound("WHITELIST_MAX_CLAIMANTS_LIMIT"), + reasonBytes: bound("WHITELIST_MAX_REASON_LIMIT"), + }; +} + +// The whitelist caps the rows run at, and the calls that set them. The deployed contracts run +// at their live caps, which is what a referendum can hit today. The build runs at the bounds +// governance could raise the caps to, which is what the bounds promise. +async function whitelistCaps() { + if (!args.build) { + return { + grantBatch: Number(await read(WHITELIST, "maxGrantBatch")), + claimants: Number(await read(WHITELIST, "maxClaimants")), + reasonBytes: Number(await read(WHITELIST, "maxReasonBytes")), + setup: [], + }; + } + const caps = buildWhitelistBounds(); + return { + ...caps, + setup: [ + contractCall(WHITELIST, "setMaxGrantBatch", [caps.grantBatch]), + contractCall(WHITELIST, "setMaxClaimants", [caps.claimants]), + contractCall(WHITELIST, "setMaxReasonBytes", [BigInt(caps.reasonBytes)]), + ], + }; +} + +async function scenarios() { + const queueSteps = Number(await readOr(DEFAULT_QUEUE_STEPS, CONTROLLER, "MAX_QUEUE_STEPS_PER_CALL")); + const queueMax = Number(await read(CONTROLLER, "MAX_RESERVATION_QUEUE")); + const caps = await whitelistCaps(); + const { free, used } = await findContainers(); + const deviceIn = (container) => `${label()}.${container}`; + // Makes sure `used` exists, for a container that is not there yet on a fresh chain. + const warm = () => [issueDevice(user(), deviceIn(used))]; + // Gives `u` a LabelStore, holding the names issued to them in `issued`, so the next gateway + // call writes into the store instead of queueing a pending claim. + const withStore = async (u, issued) => { + const account = await accountOf(u); + return [...warm(), ...issued, fund(account), + asSigned(account, contractCall(CONTROLLER, "claimLabelStore", []))]; + }; + + const list = []; + const add = (name, limit, setup, measure, signed = false) => + list.push({ name, limit, setup, measure, signed }); + + if (free) { + const u = user(); + const d = deviceIn(free); + const base = label(); + add(`Device name + reservation, first in container ${free}`, "gateway", [], + (w) => issueDevice(u, d, base, w)); + } + { + const u = user(); + const d = deviceIn(used); + const base = label(); + add("Device name + reservation", "gateway", warm(), (w) => issueDevice(u, d, base, w)); + } + { + const u = user(); + const d = deviceIn(used); + const base = label(); + add("Device name + reservation, user has a store", "gateway", + await withStore(u, [issueDevice(u, deviceIn(used))]), (w) => issueDevice(u, d, base, w)); + } + { + const u = user(); + const d = deviceIn(used); + add("Device name only", "gateway", warm(), (w) => issueDevice(u, d, undefined, w)); + } + { + const u = user(); + const base = label(); + add("Reservation only", "gateway", [], (w) => reserve(u, base, w)); + } + { + const base = label(); + const { join, leave } = leftEntries(base, queueSteps); + const u = user(); + add(`Reservation behind ${queueSteps} left entries`, "gateway", [...join, ...leave], + (w) => reserve(u, base, w)); + } + { + // The user is at the front of another queue, with a user waiting behind them. + const u = user(); + const held = label(); + const base = label(); + add("Reservation leaving the front of another queue", "gateway", + [reserve(u, held), reserve(user(), held)], (w) => reserve(u, base, w)); + } + { + const u = user(); + const base = label(); + const d = deviceIn(used); + add("Personhood claim, linked to the device name", "gateway", + [...warm(), issueDevice(u, d, base)], claim(u, base, d)); + } + { + const u = user(); + const base = label(); + const d = deviceIn(used); + const { join, leave } = leftEntries(base, queueSteps); + add(`Personhood claim behind ${queueSteps} left entries`, "gateway", + [...warm(), ...join, issueDevice(u, d, base), ...leave], claim(u, base, d)); + } + { + const u = user(); + const base = label(); + const d = deviceIn(used); + add("Personhood claim, user has a store", "gateway", + await withStore(u, [issueDevice(u, d, base)]), claim(u, base, d)); + } + { + const u = user(); + const account = await accountOf(u); + add("claimLabelStore, first settlement", "signed", + [...warm(), issueDevice(u, deviceIn(used)), fund(account)], + (w) => asSigned(account, contractCall(CONTROLLER, "claimLabelStore", [], w)), true); + } + { + // Permissionless, so it runs as Root: the cost does not depend on the caller. + const base = label(); + const { join, leave } = leftEntries(base, queueMax); + add(`expireReservation on ${queueMax} left entries`, "signed", [...join, ...leave], + (w) => contractCall(CONTROLLER, "expireReservation", [base], w)); + } + { + const labels = Array.from({ length: caps.grantBatch }, longLabel); + const u = user(); + add(`grantNames, ${caps.grantBatch} names`, "governance", caps.setup, + (w) => contractCall(WHITELIST, "grantNames", [labels, u], w)); + } + { + // Claims come from users: Root cannot request a name. + const name = longLabel(); + const reason = "r".repeat(caps.reasonBytes); + const submitter = await accountOf(user()); + const requests = Array.from({ length: caps.claimants }, () => + asSigned(submitter, contractCall(WHITELIST, "requestName", [name, reason, user()]))); + const winner = user(); + add(`grantName clearing ${caps.claimants} claims`, "governance", + [...caps.setup, contractCall(WHITELIST, "setWindow", [0n, 86400n]), fund(submitter), ...requests], + (w) => contractCall(WHITELIST, "grantName", [name, winner], w)); + } + return list; +} + +// Run and report + +const gas = (v) => `${(Number(v) / 1e9).toFixed(1)}B`; +const kib = (v) => `${(Number(v) / 1024).toFixed(0)} KiB`; +const pct = (v) => `${v.toFixed(1)}%`; + +const version = await readOr("unknown", CONTROLLER, "version"); +const prefix = args.build ? await buildUpgrades() : []; +const target = args.build ? "this checkout" : `deployed ${version}`; +console.log(`${args.network} at block ${blockNumber}, ${target}`); + +const targetKind = args.build ? "build" : "deployed"; +const knownFailure = (name) => + KNOWN_FAILURES.find( + (k) => + (k.network === "*" || k.network === args.network) && + k.target === targetKind && + k.paths.some((p) => name.startsWith(p)), + ); + +const rows = []; +for (const s of await scenarios()) { + const r = await bench(prefix, s); + const share = r.error ? undefined : Math.max(r.refShare, r.proofShare); + const outcome = r.error ?? (!r.fits ? "does not fit" : share > failAt ? `over ${failAt}%` : "ok"); + const known = knownFailure(s.name); + const passed = outcome === "ok"; + const result = !known + ? outcome + : passed + ? "ok, but listed in KNOWN_FAILURES: remove it" + : `known: ${outcome}`; + rows.push({ name: s.name, limit: s.limit, ...r, share, result, known: known?.reason, failed: known ? passed : !passed }); + const figure = r.error ? "" : `${pct(share).padStart(7)} of ${s.limit} limit `; + console.log(`${s.name.padEnd(52)} ${figure}${result}`); +} +await api.disconnect(); + +const limits = Object.entries(LIMITS) + .map(([k, l]) => `${k} ${gas(l.refTime)} / ${kib(l.proofSize)}`) + .join(", "); +const summary = [ + `### ${args.network}: ${target}`, + "", + `Block ${blockNumber}. Limits: ${limits}. A path fails above ${failAt}% of its limit.`, + "", + ...[...new Set(rows.filter((r) => r.known).map((r) => r.known))].map((reason) => `- Known: ${reason}.`), + "", + "| Path | Limit | ref_time | Proof size | Result |", + "|---|---|---|---|---|", + ...rows.map((r) => + r.error + ? `| ${r.name} | ${r.limit} | | | ${r.result} |` + : `| ${r.name} | ${r.limit} | ${gas(r.refTime)} (${pct(r.refShare)}) | ${kib(r.proofSize)} (${pct(r.proofShare)}) | ${r.result} |`, + ), + "", +].join("\n"); +if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary + "\n"); +if (args.json) { + const json = { network: args.network, target, blockNumber, failAt, limits: LIMITS, rows }; + fs.writeFileSync(args.json, JSON.stringify(json, (_, v) => (typeof v === "bigint" ? v.toString() : v), 2)); +} +process.exit(rows.some((r) => r.failed) ? 1 : 0);