diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..4cef3c944 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# Patterns are path-anchored under packages/ rather than `**//` because +# BuildKit treats `/` as matching files too, so `**/build/` would also +# hide the per-package `scripts/build` entry scripts. + +.git +.github +**/node_modules +.pnpm-store + +# Build outputs (regenerated by `pnpm build`). +packages/*/artifacts +packages/*/cache +packages/*/cache_forge +packages/*/forge-artifacts +packages/*/out +packages/*/dist +packages/*/build +packages/*/typechain-types +packages/*/coverage +packages/*/types + +# Forge crash dumps (also gitignored). Match only the per-package `core` file at the +# package root — NOT `**/core`, which would also exclude `packages/toolshed/src/core/` +# and other directories named `core` deeper in the tree. +packages/*/core + +# Editor / OS noise +.vscode +.idea +.DS_Store + +# Local env / addresses (must not leak into a published image) +.env +**/.env +**/addresses-local*.json +**/localNetwork.json +**/.keystore + +# Docs and audit PDFs aren't needed at runtime +docs diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml new file mode 100644 index 000000000..9304aa390 --- /dev/null +++ b/.github/workflows/publish-image.yml @@ -0,0 +1,148 @@ +# Builds the workspace image multi-arch (linux/amd64 + linux/arm64) and pushes +# to ghcr.io/graphprotocol/contracts. Consumed by local-network's +# graph-contracts wrapper via CONTRACTS_VERSION (pin a `:sha-` for +# reproducibility). +# +# Native runner per platform (no QEMU), per-platform digest push, manifest +# merge in a separate job. Runs independently of build-test.yml — they share +# `pnpm install + pnpm build` but stay decoupled so test feedback isn't tied +# to release packaging. + +name: Publish container image + +permissions: + contents: read + +on: + workflow_dispatch: + push: + branches: + - main + - 'deployment/**' + tags: + - 'v*' + +env: + IMAGE: ghcr.io/graphprotocol/contracts + +jobs: + build: + name: Build (${{ matrix.platform }}) + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + attestations: write + id-token: write + steps: + - name: Prepare platform pair + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + submodules: recursive + + - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker labels + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.IMAGE }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + file: Dockerfile + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + pull: true + cache-from: type=gha,scope=${{ env.PLATFORM_PAIR }} + cache-to: type=gha,mode=max,scope=${{ env.PLATFORM_PAIR }} + outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} + + - name: Export digest + if: github.event_name != 'pull_request' + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: github.event_name != 'pull_request' + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: Merge into multi-arch manifest + needs: build + # Avoids orphan per-platform digest blobs if a `need` is skipped or fails. + if: | + !cancelled() + && needs.build.result == 'success' + && github.event_name != 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker tags + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.IMAGE }} + tags: | + type=ref,event=tag + # Moving per-branch tag for casual use; pin :sha- for reproducibility. + type=ref,event=branch + # Ensures workflow_dispatch from any branch yields a usable tag. + type=sha,enable=true + + # Glob `*` expands to the digest-named files from the build job. + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.IMAGE }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..2d3ce01df --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Workspace base image: deps + `pnpm install` + `pnpm build`, no entrypoint. +# Consumers (e.g. local-network's graph-contracts wrapper) layer their own +# deploy script on top, or `docker run` to invoke pnpm/hardhat directly. + +FROM node:24-bookworm-slim + +# libudev-dev / libusb-1.0-0-dev: native deps of hardhat-secure-accounts. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl git jq python3 make g++ libudev-dev libusb-1.0-0-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=ghcr.io/foundry-rs/foundry:stable \ + /usr/local/bin/forge /usr/local/bin/cast /usr/local/bin/ + +ENV COREPACK_ENABLE_STRICT=1 +RUN corepack enable + +# Husky's postinstall needs .git and is pointless in an image build. +ENV HUSKY=0 + +WORKDIR /opt/contracts + +COPY . . + +RUN pnpm install --frozen-lockfile \ + && pnpm build diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..267d0e258 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +# Local build of the workspace image. Pair with local-network's +# CONTRACTS_VERSION=local to consume this build from there. +# +# `docker compose build` (or `just build-image`) produces +# `ghcr.io/graphprotocol/contracts:${CONTRACTS_TAG:-local}`. Override +# CONTRACTS_TAG to distinguish multiple in-flight checkouts. + +services: + contracts: + image: ghcr.io/graphprotocol/contracts:${CONTRACTS_TAG:-local} + build: + context: . + dockerfile: Dockerfile diff --git a/docs/RewardsBehaviourChanges.md b/docs/RewardsBehaviourChanges.md new file mode 100644 index 000000000..63c17c4c2 --- /dev/null +++ b/docs/RewardsBehaviourChanges.md @@ -0,0 +1,175 @@ +# Rewards Behaviour Changes + +Functional summary of how reward behaviour changed between the Horizon mainnet baseline and the current issuance upgrade. + +## Activation Overview + +Changes fall into two categories: + +- **Automatic on upgrade:** New logic that activates immediately when the upgraded contracts are deployed behind their proxies. No governance action required. These include: zero-signal detection, zero-allocated-tokens reclaim, POI presentation paths (claim/reclaim/defer), allocation resize staleness check, allocation close reclaim, and the `POIPresented` event. + +- **Governance-gated:** Features that require explicit governance transactions after upgrade. Until configured, the system preserves legacy behaviour (rewards are dropped, not reclaimed). These include: setting the issuance allocator, configuring reclaim addresses (per-condition and default), setting the eligibility oracle, and changing the minimum subgraph signal threshold. + +This two-phase approach allows a safe upgrade with the new infrastructure in place, while governance coordinates separate activation steps for each optional feature. + +## Issuance Rate + +**Before:** A single `issuancePerBlock` storage variable, set by governance via `setIssuancePerBlock()`, determined all reward issuance. + +**After:** An optional `issuanceAllocator` contract can be set by governance. When set, the effective issuance rate comes from the allocator (which can distribute issuance across multiple targets). When unset, the legacy `issuancePerBlock` value is used as a fallback. The allocator calls `beforeIssuanceAllocationChange()` on the RewardsManager before changing rates, ensuring accumulators are snapshotted first. + +**Activates:** Governance-gated — requires `setIssuanceAllocator()`. Until called, the legacy `issuancePerBlock` value continues to apply. + +## Reward Conditions + +A new `RewardsCondition` library defines typed `bytes32` identifiers for every situation where rewards cannot be distributed normally: + +| Condition | Trigger | +| ---------------------- | ---------------------------------------------------- | +| `NO_SIGNAL` | Zero total curation signal globally | +| `SUBGRAPH_DENIED` | Subgraph is on the denylist | +| `BELOW_MINIMUM_SIGNAL` | Subgraph signal below `minimumSubgraphSignal` | +| `NO_ALLOCATED_TOKENS` | Subgraph has signal but zero allocated tokens | +| `INDEXER_INELIGIBLE` | Indexer fails eligibility oracle check at claim time | +| `STALE_POI` | POI presented after staleness deadline | +| `ZERO_POI` | POI is `bytes32(0)` | +| `ALLOCATION_TOO_YOUNG` | Allocation created in the current epoch | +| `CLOSE_ALLOCATION` | Allocation being closed with uncollected rewards | + +**Activates:** Automatic on upgrade — the library and all condition checks are available immediately once the upgraded contracts are deployed. + +## Reclaim System + +**Before:** When rewards could not be distributed (denied subgraph, below-signal subgraph, stale POI, etc.), the tokens were silently lost -- never minted to anyone. + +**After:** Undistributable rewards are _reclaimed_ by minting them to a configurable address. Governance can set a per-condition address via `setReclaimAddress(condition, address)` and a catch-all fallback via `setDefaultReclaimAddress(address)`. If neither is configured for a given condition, rewards are still not minted (preserving the old drop behaviour). Every reclaim emits a `RewardsReclaimed` event with the condition, amount, indexer, allocation, and subgraph. + +**Activates:** Governance-gated — requires `setReclaimAddress()` and/or `setDefaultReclaimAddress()` for each condition. Until configured, rewards are dropped (preserving legacy behaviour). + +## Zero Global Signal + +**Before:** Issuance during periods with zero total curation signal was silently lost. + +**After:** Detected in `updateAccRewardsPerSignal()` and reclaimed as `NO_SIGNAL`. + +**Activates:** Automatic on upgrade — detection is built into the accumulator update. Reclaim requires a configured address for `NO_SIGNAL`. + +## Subgraph-Level Denial + +**Before:** Denial was a binary gate checked only at `takeRewards()` time. When a subgraph was denied, `takeRewards()` returned 0 and emitted `RewardsDenied`. The calling AllocationManager still advanced the allocation's reward snapshot, permanently dropping those rewards. + +**After:** Denial is handled at two levels: + +- **RewardsManager (accumulator level):** When `onSubgraphSignalUpdate` or `onSubgraphAllocationUpdate` is called for a denied subgraph, `accRewardsForSubgraph` and `accRewardsPerAllocatedToken` freeze (stop increasing). New rewards accruing during the denial period are reclaimed immediately rather than accumulated. `setDenied()` now snapshots accumulators before changing denial state so the boundary is clean. + +- **AllocationManager (claim level):** POI presentation for a denied subgraph is _deferred_ -- returns 0 **without advancing the allocation's snapshot**. This preserves uncollected pre-denial rewards. When the subgraph is later un-denied, those preserved rewards become claimable again. + +**Activates:** Automatic on upgrade — the accumulator-level freeze and claim-level deferral apply immediately. Denial state itself is set via `setDenied()` (Governor or SubgraphAvailabilityOracle). + +## Below-Minimum Signal + +**Before:** `getAccRewardsForSubgraph()` silently excluded rewards for subgraphs below `minimumSubgraphSignal`. Those rewards were lost. + +**After:** The same exclusion occurs, but excluded rewards are reclaimed to the `BELOW_MINIMUM_SIGNAL` address instead of being lost. Changes to `minimumSubgraphSignal` apply retroactively to all pending rewards at the next accumulator update, so governance should call `onSubgraphSignalUpdate()` on affected subgraphs before changing the threshold. + +**Activates:** Automatic on upgrade for the reclaim path. Threshold changes via `setMinimumSubgraphSignal()` are retroactive — governance should call `onSubgraphSignalUpdate()` on affected subgraphs before changing the threshold. + +## Zero Allocated Tokens + +**Before:** When a subgraph had signal but no allocations, `getAccRewardsPerAllocatedToken()` returned 0 for per-token rewards. The subgraph-level accumulator still grew, but the rewards were stranded -- distributable to no one. + +**After:** Detected as `NO_ALLOCATED_TOKENS` and reclaimed. When allocations resume, `accRewardsPerAllocatedToken` resumes from its stored value rather than resetting to zero. + +**Activates:** Automatic on upgrade — detection is built into the accumulator update. + +## Indexer Eligibility + +**Before:** No per-indexer eligibility checks existed. + +**After:** An optional `rewardsEligibilityOracle` can be set by governance. When set, `takeRewards()` checks `isEligible(indexer)` at claim time. If the indexer is ineligible, rewards are denied (emitting `RewardsDeniedDueToEligibility`) and reclaimed to the `INDEXER_INELIGIBLE` address. Subgraph denial takes precedence: if a subgraph is denied, eligibility is not checked. + +**Activates:** Governance-gated — requires `setRewardsEligibilityOracle()`. Until called, no eligibility checks are performed. + +## POI Presentation (AllocationManager) + +**Before:** A single conditional expression decided whether `takeRewards()` was called. If any condition failed (stale, zero POI, too young, altruistic), rewards were set to 0. The allocation's reward snapshot always advanced and pending rewards were always cleared, permanently dropping any undistributable rewards. + +**After:** Three distinct paths based on the determined condition: + +1. **Claim** (`NONE`): `takeRewards()` mints tokens, distributed to indexer and delegators. Snapshot advances. +2. **Reclaim** (`STALE_POI`, `ZERO_POI`): `reclaimRewards()` mints tokens to the reclaim address. Snapshot advances and pending rewards are cleared. +3. **Defer** (`ALLOCATION_TOO_YOUNG`, `SUBGRAPH_DENIED`): Returns 0 **without advancing the snapshot or clearing pending rewards**. Rewards are preserved for later collection. Accumulators are still updated via `onSubgraphAllocationUpdate()` to keep reclaim tracking current. + +The POI presentation timestamp is now recorded immediately on entry (before condition evaluation), so the staleness clock resets regardless of reward outcome. Over-delegation force-close is skipped on the deferred path to avoid closing allocations with preserved uncollected rewards. + +**Activates:** Automatic on upgrade — the three-path logic applies to all POI presentations immediately. + +## Allocation Resize + +**Before:** Resizing always accumulated pending rewards for the delta period, regardless of allocation staleness. + +**After:** If the allocation is stale at resize time, pending rewards are reclaimed as `STALE_POI` and cleared. This prevents stale allocations from silently accumulating pending rewards through repeated resizes. + +**Activates:** Automatic on upgrade — applies to all resize operations immediately. + +## Allocation Close + +**Before:** Closing an allocation advanced the snapshot and closed it. Any uncollected rewards were permanently lost. + +**After:** Before closing, `reclaimRewards(CLOSE_ALLOCATION, allocationId)` is called to mint uncollected rewards to the reclaim address. + +**Activates:** Automatic on upgrade — applies to all close operations immediately. + +## Observability + +A new `POIPresented` event is emitted on every POI presentation, including the determined `condition` as a `bytes32` field. This provides off-chain visibility into why a given presentation did or did not result in rewards, which was previously invisible. + +**Activates:** Automatic on upgrade — emitted on every POI presentation immediately. + +## View Functions + +Several view functions were added or changed to expose the new reward state. + +### Accumulator Views Freeze for Non-Claimable Subgraphs + +The existing accumulator view functions now exclude rewards for subgraphs that are not claimable (denied, below minimum signal, or with zero allocated tokens). Previously these accumulators always grew; callers reading them as continuously-increasing counters need to account for the new freeze behaviour. + +**`getAccRewardsForSubgraph()`** — Previously always returned a growing value regardless of subgraph state. Now returns a frozen value when the subgraph is not claimable: the internal helper `_getSubgraphRewardsState()` determines a `RewardsCondition`, and when the condition is anything other than `NONE`, new rewards are excluded from the returned total. The accumulator resumes growing when the subgraph becomes claimable again. + +**`getAccRewardsPerAllocatedToken()`** — Derives from `getAccRewardsForSubgraph()`, so it inherits the freeze. When the subgraph is not claimable, new per-token rewards are zero because the subgraph-level delta is zero. At snapshot points the implementation zeroes `undistributedRewards` and reclaims them instead of adding them to `accRewardsPerAllocatedToken`. + +**`getRewards()`** — Returns the claimable reward estimate for an allocation. Because it reads `getAccRewardsPerAllocatedToken()`, it now returns a frozen value for allocations on non-claimable subgraphs. Pre-existing `accRewardsPending` from prior resizes is still included. Note: indexer eligibility is _not_ checked here (only at `takeRewards()` time), so the view does not reflect eligibility-based denial. + +**`getNewRewardsPerSignal()`** — No visible change in return value. Internally it now separates claimable from unclaimable issuance (zero-signal periods), but the public view still returns only the claimable portion. The unclaimable portion is reclaimed as `NO_SIGNAL` at the next `updateAccRewardsPerSignal()` call. + +### New Getters on IRewardsManager + +| Function | Returns | Purpose | +| ----------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `getIssuanceAllocator()` | `IIssuanceAllocationDistribution` | Current allocator contract (zero if unset) | +| `getReclaimAddress(bytes32 reason)` | `address` | Per-condition reclaim address (zero if unconfigured) | +| `getDefaultReclaimAddress()` | `address` | Fallback reclaim address | +| `getRewardsEligibilityOracle()` | `IRewardsEligibility` | Current eligibility oracle (zero if unset) | +| `getAllocatedIssuancePerBlock()` | `uint256` | Effective issuance rate — returns the allocator rate when set, otherwise falls back to storage. Replaces the legacy `getRewardsIssuancePerBlock()` for callers that need the protocol rate | +| `getRawIssuancePerBlock()` | `uint256` | Raw storage value, ignoring the allocator. Useful for debugging allocator configuration | + +### Changed Return Semantics + +**`getAllocationData()`** (IRewardsIssuer, implemented by SubgraphService) now returns a sixth value, `accRewardsPending`, representing accumulated rewards from allocation resizing that have not yet been claimed. Callers that destructure the return tuple need updating. + +**`IAllocation.State`** struct adds two fields: `accRewardsPending` (pending rewards from resize) and `createdAtEpoch` (epoch when the allocation was created). Both affect the return value of `getAllocation()`. + +## Provenance + +Merge commits into `main` that introduced the changes described above, in chronological order. + +| Date | Merge | PR | Scope | +| ---------- | ----------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2025-12-16 | `ff2f00a62` | #1265 | Eligibility oracle audit doc fixes (TRST-L-1, TRST-L-2) | +| 2025-12-16 | `48be37a20` | #1267 | Issuance allocator audit fix — default allocation, `setReclaimAddress` | +| 2025-12-31 | `89f1321c4` | #1272 | Issuance allocator audit fix v3 — forced reclaim, PPM-to-absolute migration | +| 2026-01-08 | `3d274a4f1` | #1255 | Issuance baseline — RewardsManager extensions, eligibility interface, test suites | +| 2026-01-08 | `363924149` | #1256 | Rewards Eligibility Oracle — full oracle implementation | +| 2026-01-08 | `cdef9b5fd` | #1257 | Issuance Allocator — full allocator, RewardsReclaim library, allocation close reclaim | +| 2026-02-17 | `ada315500` | #1279 | Rewards reclaiming (audited) — RewardsCondition rename, `setDefaultReclaimAddress`, subgraph denial accumulator handling, zero-signal reclaim, POI three-path logic, `POIPresented` event | +| 2026-02-19 | `127b7ef6f` | #1280 | Issuance umbrella merge — all prior work plus stale-allocation-resize reclaim (TRST-R-1) | diff --git a/justfile b/justfile new file mode 100644 index 000000000..8c3362811 --- /dev/null +++ b/justfile @@ -0,0 +1,8 @@ +default: + @just --list + +# Build the workspace image locally as `ghcr.io/graphprotocol/contracts:local`. +# Override the tag with `CONTRACTS_TAG=foo just build-image`. Consumed by +# local-network's graph-contracts wrapper when CONTRACTS_VERSION=local. +build-image: + docker compose build diff --git a/package.json b/package.json index 5808128ba..c66f0334f 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lint:sol": "pnpm -r run lint:sol; prettier -w --cache --log-level warn 'packages/**/*.sol'; pnpm todo", "lint:forge": "pnpm -r run lint:forge", "lint:md": "markdownlint --fix --ignore-path .gitignore 'packages/**/*.md' 'docs/**/*.md' '*.md'; prettier -w --cache --log-level warn 'packages/**/*.md' 'docs/**/*.md' '*.md'", - "lint:json": "prettier -w --cache --log-level warn 'packages/**/*.json' '.changeset/**/*.json' '*.json'", + "lint:json": "prettier -w --cache --log-level warn 'packages/**/*.{json,json5}' '.changeset/**/*.json' '*.{json,json5}'", "lint:yaml": "npx yaml-lint .github/**/*.{yml,yaml} packages/contracts/task/config/*.yml; prettier -w --cache --log-level warn 'packages/**/*.{yml,yaml}' '.github/**/*.{yml,yaml}'", "test": "pnpm build && pnpm -r run test:self", "test:prod": "FOUNDRY_PROFILE=prod pnpm test", @@ -56,8 +56,17 @@ "overrides": { "@types/node": "^20.17.50" }, + "packageExtensions": { + "@nomiclabs/hardhat-waffle@*": { + "dependencies": { + "@ethereum-waffle/chai": "*", + "@ethereum-waffle/provider": "*" + } + } + }, "patchedDependencies": { - "typechain@8.3.2": "patches/typechain@8.3.2.patch" + "typechain@8.3.2": "patches/typechain@8.3.2.patch", + "rocketh@0.17.13": "patches/rocketh@0.17.13.patch" } }, "lint-staged": { @@ -73,7 +82,7 @@ "markdownlint --fix", "prettier -w --cache --log-level warn" ], - "*.json": "prettier -w --cache --log-level warn", + "*.{json,json5}": "prettier -w --cache --log-level warn", "*.{yml,yaml}": [ "npx yamllint", "prettier -w --cache --log-level warn" diff --git a/packages/address-book/CHANGELOG.md b/packages/address-book/CHANGELOG.md index 11e71dae2..1427d84c2 100644 --- a/packages/address-book/CHANGELOG.md +++ b/packages/address-book/CHANGELOG.md @@ -1,5 +1,11 @@ # @graphprotocol/address-book +## 1.2.0 + +### Minor Changes + +- Upgraded Rewards Manager and Subgraph Service with Rewards Eligibility Oracle and rewards reclaiming. + ## 1.1.0 ### Minor Changes diff --git a/packages/address-book/docs/PublishingGuide.md b/packages/address-book/docs/PublishingGuide.md new file mode 100644 index 000000000..d4b021783 --- /dev/null +++ b/packages/address-book/docs/PublishingGuide.md @@ -0,0 +1,108 @@ +# Publishing @graphprotocol/address-book + +Step-by-step guide for releasing a new version of the address-book package and deploying it to the network monitor. + +## Prerequisites + +- npm publish access for the `@graphprotocol` scope +- Write access to the [network-monitor](https://github.com/edgeandnode/network-monitor) repo +- Ability to trigger GitHub Actions workflows in both repos + +## Step 1: Update Address Files + +Update the source address files in the contracts monorepo. These live in: + +- `packages/horizon/addresses.json` +- `packages/subgraph-service/addresses.json` +- `packages/issuance/addresses.json` + +The address-book package symlinks to these files during development, so changes here are automatically reflected locally. + +## Step 2: Create a Changeset + +From the monorepo root: + +```bash +pnpm changeset +``` + +- Select `@graphprotocol/address-book` +- Choose the bump type (patch/minor/major) +- Describe what changed (e.g., "update arbitrumSepolia addresses after deployment") + +## Step 3: Version the Package + +```bash +pnpm changeset version +``` + +This consumes the changeset, bumps the version in `packages/address-book/package.json`, and updates `CHANGELOG.md`. + +## Step 4: Commit and Push + +```bash +git add . +git commit -m "chore: release @graphprotocol/address-book vX.Y.Z" +git push +``` + +## Step 5: Publish to npm + +1. Go to the contracts monorepo → Actions → "Publish package to NPM" +2. Select `address-book` as the package +3. Set tag to `latest` (or a pre-release tag) +4. Run workflow + +The workflow automatically: + +- Publishes to npm (symlinks are converted to real files via `prepublishOnly`) +- Creates and pushes a git tag (`@graphprotocol/address-book@X.Y.Z`) + +## Step 6: Verify on npm + +```bash +npm view @graphprotocol/address-book version +``` + +Confirm the new version is live. + +## Step 7: Update the Network Monitor + +In the [network-monitor](https://github.com/edgeandnode/network-monitor) repo: + +1. Update `package.json` to reference the new version: + + ```json + "@graphprotocol/address-book": "X.Y.Z", + ``` + +2. Run `yarn` to update the lockfile +3. Commit and push + +The network monitor imports addresses from: + +- `@graphprotocol/address-book/horizon/addresses.json` (in `src/env.ts`) +- `@graphprotocol/address-book/subgraph-service/addresses.json` (in `src/env.ts`, `src/tests/contracts.ts`) + +## Step 8: Deploy the Network Monitor + +1. Go to the network-monitor repo → Actions → "Deployment" +2. Choose the target cluster: + - **`network`** → production (mainnet) + - **`testnet`** → testnet +3. Run workflow + +This builds a Docker image, pushes it to `ghcr.io/edgeandnode/network-monitor`, and restarts the StatefulSet on GKE. + +## Quick Reference + +| Step | Action | Where | +| ---- | ------------------------------- | ----------------------------- | +| 1 | Update address files | contracts monorepo | +| 2 | `pnpm changeset` | contracts monorepo | +| 3 | `pnpm changeset version` | contracts monorepo | +| 4 | Commit + push | contracts monorepo | +| 5 | Publish to npm (auto-tags) | contracts monorepo GH Actions | +| 6 | Verify on npm | npmjs.com | +| 7 | Bump version in network-monitor | network-monitor repo | +| 8 | Deploy network monitor | network-monitor GH Actions | diff --git a/packages/address-book/package.json b/packages/address-book/package.json index 86de6244d..f0e85dbd0 100644 --- a/packages/address-book/package.json +++ b/packages/address-book/package.json @@ -1,6 +1,6 @@ { "name": "@graphprotocol/address-book", - "version": "1.1.0", + "version": "1.2.1", "publishConfig": { "access": "public" }, @@ -13,9 +13,7 @@ "directory": "packages/address-book" }, "exports": { - "./horizon/addresses.json": "./src/horizon/addresses.json", - "./issuance/addresses.json": "./src/issuance/addresses.json", - "./subgraph-service/addresses.json": "./src/subgraph-service/addresses.json" + "./*/addresses.json": "./src/*/addresses.json" }, "files": [ "src/**/*.json", diff --git a/packages/address-book/scripts/copy-addresses-for-publish.js b/packages/address-book/scripts/copy-addresses-for-publish.js index 6335f7dc5..0665359d4 100755 --- a/packages/address-book/scripts/copy-addresses-for-publish.js +++ b/packages/address-book/scripts/copy-addresses-for-publish.js @@ -3,67 +3,52 @@ /** * Copy Addresses for Publishing * - * This script copies the actual addresses.json files from horizon and subgraph-service - * packages to replace the symlinks before npm publish. - * - * Why we need this: - * - Development uses symlinks (committed to git) for convenience - * - npm publish doesn't include symlinks in the published package - * - We need actual files in the published package for consumers - * - * The postpublish script will restore the symlinks after publishing. + * Replaces the dev-time symlinks under src//addresses.json with real + * file copies before npm publish — npm does not include symlinks in the + * published tarball. restore-symlinks.js puts the symlinks back afterwards. */ const fs = require('fs') const path = require('path') +const SOURCES = require('./sources') -const FILES_TO_COPY = [ - { - source: '../../../horizon/addresses.json', - target: 'src/horizon/addresses.json', - }, - { - source: '../../../issuance/addresses.json', - target: 'src/issuance/addresses.json', - }, - { - source: '../../../subgraph-service/addresses.json', - target: 'src/subgraph-service/addresses.json', - }, -] +const ROOT = path.resolve(__dirname, '..') +const SRC = path.join(ROOT, 'src') -function copyFileForPublish(source, target) { - const targetPath = path.resolve(__dirname, '..', target) - const sourcePath = path.resolve(path.dirname(targetPath), source) +function copyOne(name) { + const sourcePath = path.resolve(ROOT, '..', name, 'addresses.json') + const targetDir = path.join(SRC, name) + const targetPath = path.join(targetDir, 'addresses.json') - // Ensure source exists if (!fs.existsSync(sourcePath)) { console.error(`❌ Source file ${sourcePath} does not exist`) process.exit(1) } - // Remove existing symlink - if (fs.existsSync(targetPath)) { - fs.unlinkSync(targetPath) - } + fs.mkdirSync(targetDir, { recursive: true }) + fs.rmSync(targetPath, { force: true }) + fs.copyFileSync(sourcePath, targetPath) + console.log(`✅ Copied for publish: src/${name}/addresses.json`) +} - // Copy actual file - try { - fs.copyFileSync(sourcePath, targetPath) - console.log(`✅ Copied for publish: ${target} <- ${source}`) - } catch (error) { - console.error(`❌ Failed to copy ${source} to ${target}:`, error.message) +function checkDrift() { + const dirs = fs + .readdirSync(SRC) + .filter((d) => fs.statSync(path.join(SRC, d)).isDirectory()) + .sort() + const expected = [...SOURCES].sort() + if (JSON.stringify(dirs) !== JSON.stringify(expected)) { + console.error(`❌ Drift between SOURCES and src/`) + console.error(` SOURCES: [${expected.join(', ')}]`) + console.error(` src/ : [${dirs.join(', ')}]`) process.exit(1) } } function main() { console.log('📦 Copying address files for npm publish...') - - for (const { source, target } of FILES_TO_COPY) { - copyFileForPublish(source, target) - } - + for (const name of SOURCES) copyOne(name) + checkDrift() console.log('✅ Address files copied for publish!') } diff --git a/packages/address-book/scripts/restore-symlinks.js b/packages/address-book/scripts/restore-symlinks.js index 2f3a871f2..eb2f1f5df 100755 --- a/packages/address-book/scripts/restore-symlinks.js +++ b/packages/address-book/scripts/restore-symlinks.js @@ -3,54 +3,32 @@ /** * Restore Symlinks After Publishing * - * This script restores the symlinks after npm publish completes. - * The prepublishOnly script replaces symlinks with actual files for publishing, - * and this script puts the symlinks back for development. + * Restores the dev-time symlinks under src//addresses.json after + * npm publish. copy-addresses-for-publish.js replaces them with real files + * for the publish step; this puts them back. */ const fs = require('fs') const path = require('path') +const SOURCES = require('./sources') -const SYMLINKS_TO_RESTORE = [ - { - target: '../../../horizon/addresses.json', - link: 'src/horizon/addresses.json', - }, - { - target: '../../../issuance/addresses.json', - link: 'src/issuance/addresses.json', - }, - { - target: '../../../subgraph-service/addresses.json', - link: 'src/subgraph-service/addresses.json', - }, -] +const ROOT = path.resolve(__dirname, '..') +const SRC = path.join(ROOT, 'src') -function restoreSymlink(target, link) { - const linkPath = path.resolve(__dirname, '..', link) +function restoreOne(name) { + const linkTarget = `../../../${name}/addresses.json` + const linkDir = path.join(SRC, name) + const linkPath = path.join(linkDir, 'addresses.json') - // Remove the copied file - if (fs.existsSync(linkPath)) { - fs.unlinkSync(linkPath) - } - - // Restore symlink - try { - fs.symlinkSync(target, linkPath) - console.log(`✅ Restored symlink: ${link} -> ${target}`) - } catch (error) { - console.error(`❌ Failed to restore symlink ${link}:`, error.message) - process.exit(1) - } + fs.mkdirSync(linkDir, { recursive: true }) + fs.rmSync(linkPath, { force: true }) + fs.symlinkSync(linkTarget, linkPath) + console.log(`✅ Restored symlink: src/${name}/addresses.json -> ${linkTarget}`) } function main() { console.log('🔗 Restoring symlinks after publish...') - - for (const { target, link } of SYMLINKS_TO_RESTORE) { - restoreSymlink(target, link) - } - + for (const name of SOURCES) restoreOne(name) console.log('✅ Symlinks restored!') } diff --git a/packages/address-book/scripts/sources.js b/packages/address-book/scripts/sources.js new file mode 100644 index 000000000..6885c1a2b --- /dev/null +++ b/packages/address-book/scripts/sources.js @@ -0,0 +1,4 @@ +// Source packages exported from @graphprotocol/address-book. +// Each name corresponds to packages//addresses.json (source of truth) +// and packages/address-book/src//addresses.json (publish symlink). +module.exports = ['horizon', 'issuance', 'subgraph-service'] diff --git a/packages/address-book/src/issuance/addresses.json b/packages/address-book/src/issuance/addresses.json new file mode 120000 index 000000000..b73ad34ff --- /dev/null +++ b/packages/address-book/src/issuance/addresses.json @@ -0,0 +1 @@ +../../../issuance/addresses.json \ No newline at end of file diff --git a/packages/contracts/.solhint.json b/packages/contracts/.solhint.json index d30847305..780d82f39 100644 --- a/packages/contracts/.solhint.json +++ b/packages/contracts/.solhint.json @@ -1,3 +1,3 @@ { - "extends": ["solhint:recommended", "./../../.solhint.json"] + "extends": "./../../.solhint.json" } diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 86b77d5c5..ba90039ca 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -60,7 +60,8 @@ const config: HardhatUserConfig = { etherscan: { // Use ARBISCAN_API_KEY for Arbitrum networks // For mainnet Ethereum, use ETHERSCAN_API_KEY - apiKey: vars.has('ARBISCAN_API_KEY') ? vars.get('ARBISCAN_API_KEY') : '', + // Check both keystore (vars) and environment variable + apiKey: vars.has('ARBISCAN_API_KEY') ? vars.get('ARBISCAN_API_KEY') : (process.env.ARBISCAN_API_KEY ?? ''), }, sourcify: { enabled: false, diff --git a/packages/data-edge/.solhint.json b/packages/data-edge/.solhint.json index d30847305..780d82f39 100644 --- a/packages/data-edge/.solhint.json +++ b/packages/data-edge/.solhint.json @@ -1,3 +1,3 @@ { - "extends": ["solhint:recommended", "./../../.solhint.json"] + "extends": "./../../.solhint.json" } diff --git a/packages/data-edge/hardhat.config.ts b/packages/data-edge/hardhat.config.ts index a3b1ccff4..6dee140d0 100644 --- a/packages/data-edge/hardhat.config.ts +++ b/packages/data-edge/hardhat.config.ts @@ -1,15 +1,12 @@ import '@typechain/hardhat' -// Plugins -import '@nomiclabs/hardhat-ethers' -import '@nomiclabs/hardhat-etherscan' -import '@nomiclabs/hardhat-waffle' +import '@nomicfoundation/hardhat-ethers' +import '@nomicfoundation/hardhat-chai-matchers' +import '@nomicfoundation/hardhat-verify' import 'hardhat-abi-exporter' import 'hardhat-gas-reporter' import 'hardhat-contract-sizer' -import '@openzeppelin/hardhat-upgrades' import 'solidity-coverage' -import '@tenderly/hardhat-tenderly' -import 'hardhat-secure-accounts' // for graph config +import 'hardhat-secure-accounts' // Tasks import './tasks/craft-calldata' import './tasks/post-calldata' @@ -29,20 +26,12 @@ interface NetworkConfig { const networkConfigs: NetworkConfig[] = [ { network: 'mainnet', chainId: 1 }, - { network: 'ropsten', chainId: 3 }, - { network: 'rinkeby', chainId: 4 }, - { network: 'kovan', chainId: 42 }, { network: 'sepolia', chainId: 11155111 }, { network: 'arbitrum-one', chainId: 42161, url: 'https://arb1.arbitrum.io/rpc', }, - { - network: 'arbitrum-goerli', - chainId: 421613, - url: 'https://goerli-rollup.arbitrum.io/rpc', - }, { network: 'arbitrum-sepolia', chainId: 421614, @@ -89,10 +78,6 @@ task('accounts', 'Prints the list of accounts', async (_, bre) => { // Config const config: HardhatUserConfig = { - graph: { - addressBook: process.env.ADDRESS_BOOK || 'addresses.json', - disableSecureAccounts: true, - }, paths: { sources: './contracts', tests: './test', @@ -140,10 +125,8 @@ const config: HardhatUserConfig = { etherscan: { apiKey: { mainnet: process.env.ETHERSCAN_API_KEY, - goerli: process.env.ETHERSCAN_API_KEY, sepolia: process.env.ETHERSCAN_API_KEY, arbitrumOne: process.env.ARBISCAN_API_KEY, - arbitrumGoerli: process.env.ARBISCAN_API_KEY, arbitrumSepolia: process.env.ARBISCAN_API_KEY, }, }, @@ -155,17 +138,13 @@ const config: HardhatUserConfig = { }, typechain: { outDir: 'build/types', - target: 'ethers-v5', + target: 'ethers-v6', }, abiExporter: { path: './build/abis', clear: false, flat: true, }, - tenderly: { - project: process.env.TENDERLY_PROJECT, - username: process.env.TENDERLY_USERNAME, - }, contractSizer: { alphaSort: true, runOnCompile: false, diff --git a/packages/data-edge/package.json b/packages/data-edge/package.json index c97514031..15b97d050 100644 --- a/packages/data-edge/package.json +++ b/packages/data-edge/package.json @@ -7,8 +7,6 @@ "license": "GPL-2.0-or-later", "main": "index.js", "scripts": { - "prepare": "cd ../.. && husky install packages/contracts/.husky", - "prepublishOnly": "scripts/prepublish", "build": "pnpm build:self", "build:self": "scripts/build", "clean": "rm -rf build/ cache/ dist/ reports/ artifacts/", @@ -35,43 +33,30 @@ "LICENSE" ], "devDependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/providers": "^5.7.0", - "@nomiclabs/hardhat-ethers": "^2.0.2", - "@nomiclabs/hardhat-etherscan": "^3.1.2", - "@nomiclabs/hardhat-waffle": "^2.0.1", - "@openzeppelin/contracts": "^4.5.0", - "@openzeppelin/hardhat-upgrades": "^1.8.2", - "@tenderly/api-client": "^1.0.13", - "@tenderly/hardhat-tenderly": "^1.0.13", - "@typechain/ethers-v5": "^10.2.1", - "@typechain/hardhat": "^6.1.6", + "@nomicfoundation/hardhat-chai-matchers": "catalog:", + "@nomicfoundation/hardhat-ethers": "catalog:", + "@nomicfoundation/hardhat-verify": "catalog:", + "@typechain/ethers-v6": "^0.5.0", + "@typechain/hardhat": "catalog:", "@types/mocha": "^9.0.0", - "@types/node": "^20.17.50", + "@types/node": "catalog:", "@types/sinon-chai": "^3.2.12", - "chai": "^4.2.0", - "dotenv": "^16.0.0", + "chai": "catalog:", + "dotenv": "catalog:", "eslint": "catalog:", - "ethereum-waffle": "^3.0.2", - "ethers": "^5.7.2", - "ethlint": "^1.2.5", + "ethers": "catalog:", "hardhat": "catalog:", "hardhat-abi-exporter": "^2.2.0", - "hardhat-contract-sizer": "^2.0.3", - "hardhat-gas-reporter": "^1.0.4", - "hardhat-secure-accounts": "0.0.6", - "husky": "^7.0.4", - "lint-staged": "^12.3.5", - "lodash": "^4.17.21", - "markdownlint-cli": "0.45.0", + "hardhat-contract-sizer": "catalog:", + "hardhat-gas-reporter": "catalog:", + "hardhat-secure-accounts": "catalog:", + "markdownlint-cli": "catalog:", "prettier": "catalog:", "prettier-plugin-solidity": "catalog:", "solhint": "catalog:", "solidity-coverage": "^0.8.16", - "truffle-flattener": "^1.4.4", - "ts-node": ">=8.0.0", - "typechain": "^8.3.0", + "ts-node": "catalog:", + "typechain": "catalog:", "typescript": "catalog:" } } diff --git a/packages/data-edge/tasks/craft-calldata.ts b/packages/data-edge/tasks/craft-calldata.ts index 8e285886c..855478f68 100644 --- a/packages/data-edge/tasks/craft-calldata.ts +++ b/packages/data-edge/tasks/craft-calldata.ts @@ -1,5 +1,3 @@ -import '@nomiclabs/hardhat-ethers' - import { Contract } from 'ethers' import { task } from 'hardhat/config' @@ -35,15 +33,13 @@ task('data:craft', 'Build calldata') .addParam('selector', 'Selector name') .addParam('data', 'Call data to post') .setAction(async (taskArgs, hre) => { - // parse input const edgeAddress = taskArgs.edge const calldata = taskArgs.data const selector = taskArgs.selector - // build data const abi = getAbiForSelector(selector) const contract = getContract(edgeAddress, abi, hre.ethers.provider) - const tx = await contract.populateTransaction[selector](calldata) + const tx = await contract[selector].populateTransaction(calldata) const txData = tx.data console.log(txData) }) diff --git a/packages/data-edge/tasks/deploy.ts b/packages/data-edge/tasks/deploy.ts index 0ad97d194..ca142b1e2 100644 --- a/packages/data-edge/tasks/deploy.ts +++ b/packages/data-edge/tasks/deploy.ts @@ -1,5 +1,3 @@ -import '@nomiclabs/hardhat-ethers' - import { promises as fs } from 'fs' import { task } from 'hardhat/config' @@ -31,25 +29,25 @@ task('data-edge:deploy', 'Deploy a DataEdge contract') console.log(`Deploying contract...`) const contract = await factory.deploy() - const tx = contract.deployTransaction + const tx = contract.deploymentTransaction()! - // The address the Contract WILL have once mined - console.log(`> deployer: ${await contract.signer.getAddress()}`) - console.log(`> contract: ${contract.address}`) + const contractAddress = await contract.getAddress() + const [signer] = await hre.ethers.getSigners() + console.log(`> deployer: ${await signer.getAddress()}`) + console.log(`> contract: ${contractAddress}`) console.log( - `> tx: ${tx.hash} nonce:${tx.nonce} limit: ${tx.gasLimit.toString()} gas: ${tx.gasPrice.toNumber() / 1e9} (gwei)`, + `> tx: ${tx.hash} nonce:${tx.nonce} limit: ${tx.gasLimit.toString()} gas: ${Number(tx.gasPrice) / 1e9} (gwei)`, ) - // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() console.log(`Done!`) // Update addresses.json - const chainId = hre.network.config.chainId.toString() + const chainId = hre.network.config.chainId!.toString() if (!addresses[chainId]) { addresses[chainId] = {} } const deployName = `${taskArgs.deployName}${taskArgs.contract}` - addresses[chainId][deployName] = contract.address + addresses[chainId][deployName] = contractAddress return fs.writeFile('addresses.json', JSON.stringify(addresses, null, 2) + '\n') }) diff --git a/packages/data-edge/tasks/post-calldata.ts b/packages/data-edge/tasks/post-calldata.ts index fbededfbc..edd455511 100644 --- a/packages/data-edge/tasks/post-calldata.ts +++ b/packages/data-edge/tasks/post-calldata.ts @@ -1,30 +1,28 @@ -import '@nomiclabs/hardhat-ethers' - import { task } from 'hardhat/config' task('data:post', 'Post calldata') .addParam('edge', 'Address of the data edge contract') .addParam('data', 'Call data to post') .setAction(async (taskArgs, hre) => { - // prepare data const edgeAddress = taskArgs.edge const txData = taskArgs.data + const [signer] = await hre.ethers.getSigners() const contract = await hre.ethers.getContractAt('DataEdge', edgeAddress) + const contractAddress = await contract.getAddress() const txRequest = { data: txData, - to: contract.address, + to: contractAddress, } - // send transaction console.log(`Sending data...`) - console.log(`> edge: ${contract.address}`) - console.log(`> sender: ${await contract.signer.getAddress()}`) + console.log(`> edge: ${contractAddress}`) + console.log(`> sender: ${await signer.getAddress()}`) console.log(`> payload: ${txData}`) - const tx = await contract.signer.sendTransaction(txRequest) + const tx = await signer.sendTransaction(txRequest) console.log( - `> tx: ${tx.hash} nonce:${tx.nonce} limit: ${tx.gasLimit.toString()} gas: ${tx.gasPrice.toNumber() / 1e9} (gwei)`, + `> tx: ${tx.hash} nonce:${tx.nonce} limit: ${tx.gasLimit.toString()} gas: ${Number(tx.gasPrice) / 1e9} (gwei)`, ) const rx = await tx.wait() - console.log('> rx: ', rx.status == 1 ? 'success' : 'failed') + console.log('> rx: ', rx!.status == 1 ? 'success' : 'failed') console.log(`Done!`) }) diff --git a/packages/data-edge/test/dataedge.test.ts b/packages/data-edge/test/dataedge.test.ts index 479758881..b96257786 100644 --- a/packages/data-edge/test/dataedge.test.ts +++ b/packages/data-edge/test/dataedge.test.ts @@ -1,57 +1,43 @@ -import '@nomiclabs/hardhat-ethers' - -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ethers } from 'hardhat' -import { DataEdge, DataEdge__factory } from '../build/types' - -const { getContractFactory, getSigners } = ethers -const { id, hexConcat, randomBytes, hexlify, defaultAbiCoder } = ethers.utils +import { DataEdge } from '../build/types' describe('DataEdge', () => { let edge: DataEdge - let me: SignerWithAddress + let me: Awaited>[0] beforeEach(async () => { - ;[me] = await getSigners() + ;[me] = await ethers.getSigners() - const factory = (await getContractFactory('DataEdge', me)) as DataEdge__factory + const factory = await ethers.getContractFactory('DataEdge', me) edge = await factory.deploy() - await edge.deployed() + await edge.waitForDeployment() }) describe('submit data', () => { it('post any arbitrary data as selector', async () => { - // virtual function call const txRequest = { data: '0x123123', - to: edge.address, + to: await edge.getAddress(), } - // send transaction const tx = await me.sendTransaction(txRequest) const rx = await tx.wait() - // transaction must work - it just stores data - expect(rx.status).eq(1) + expect(rx!.status).eq(1) }) it('post long calldata', async () => { - // virtual function call - const selector = id('setEpochBlocksPayload(bytes)').slice(0, 10) - // calldata payload - const messageBlocks = hexlify(randomBytes(1000)) - const txCalldata = defaultAbiCoder.encode(['bytes'], [messageBlocks]) // we abi encode to allow the subgraph to decode it properly - const txData = hexConcat([selector, txCalldata]) - // craft full transaction + const selector = ethers.id('setEpochBlocksPayload(bytes)').slice(0, 10) + const messageBlocks = ethers.hexlify(ethers.randomBytes(1000)) + const txCalldata = ethers.AbiCoder.defaultAbiCoder().encode(['bytes'], [messageBlocks]) + const txData = ethers.concat([selector, txCalldata]) const txRequest = { data: txData, - to: edge.address, + to: await edge.getAddress(), } - // send transaction const tx = await me.sendTransaction(txRequest) const rx = await tx.wait() - // transaction must work - it just stores data - expect(rx.status).eq(1) + expect(rx!.status).eq(1) }) }) }) diff --git a/packages/data-edge/test/eventful-dataedge.test.ts b/packages/data-edge/test/eventful-dataedge.test.ts index 8bdf86a2e..974dde5dc 100644 --- a/packages/data-edge/test/eventful-dataedge.test.ts +++ b/packages/data-edge/test/eventful-dataedge.test.ts @@ -1,63 +1,47 @@ -import '@nomiclabs/hardhat-ethers' - -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ethers } from 'hardhat' -import { EventfulDataEdge, EventfulDataEdge__factory } from '../build/types' - -const { getContractFactory, getSigners } = ethers -const { id, hexConcat, randomBytes, hexlify, defaultAbiCoder } = ethers.utils +import { EventfulDataEdge } from '../build/types' describe('EventfulDataEdge', () => { let edge: EventfulDataEdge - let me: SignerWithAddress + let me: Awaited>[0] beforeEach(async () => { - ;[me] = await getSigners() + ;[me] = await ethers.getSigners() - const factory = (await getContractFactory('EventfulDataEdge', me)) as EventfulDataEdge__factory + const factory = await ethers.getContractFactory('EventfulDataEdge', me) edge = await factory.deploy() - await edge.deployed() + await edge.waitForDeployment() }) describe('submit data', () => { it('post any arbitrary data as selector', async () => { - // virtual function call const txRequest = { data: '0x123123', - to: edge.address, + to: await edge.getAddress(), } - // send transaction const tx = await me.sendTransaction(txRequest) const rx = await tx.wait() - // transaction must work - it just stores data - expect(rx.status).eq(1) - // emit log event - const event = edge.interface.parseLog(rx.logs[0]).args - expect(event.data).eq(txRequest.data) + expect(rx!.status).eq(1) + const event = edge.interface.parseLog({ topics: rx!.logs[0].topics as string[], data: rx!.logs[0].data }) + expect(event!.args.data).eq(txRequest.data) }) it('post long calldata', async () => { - // virtual function call - const selector = id('setEpochBlocksPayload(bytes)').slice(0, 10) - // calldata payload - const messageBlocks = hexlify(randomBytes(1000)) - const txCalldata = defaultAbiCoder.encode(['bytes'], [messageBlocks]) // we abi encode to allow the subgraph to decode it properly - const txData = hexConcat([selector, txCalldata]) - // craft full transaction + const selector = ethers.id('setEpochBlocksPayload(bytes)').slice(0, 10) + const messageBlocks = ethers.hexlify(ethers.randomBytes(1000)) + const txCalldata = ethers.AbiCoder.defaultAbiCoder().encode(['bytes'], [messageBlocks]) + const txData = ethers.concat([selector, txCalldata]) const txRequest = { data: txData, - to: edge.address, + to: await edge.getAddress(), } - // send transaction const tx = await me.sendTransaction(txRequest) const rx = await tx.wait() - // transaction must work - it just stores data - expect(rx.status).eq(1) - // emit log event - const event = edge.interface.parseLog(rx.logs[0]).args - expect(event.data).eq(txRequest.data) + expect(rx!.status).eq(1) + const event = edge.interface.parseLog({ topics: rx!.logs[0].topics as string[], data: rx!.logs[0].data }) + expect(event!.args.data).eq(txRequest.data) }) }) }) diff --git a/packages/deployment/.gitignore b/packages/deployment/.gitignore index 1c6b1095e..d48c62c73 100644 --- a/packages/deployment/.gitignore +++ b/packages/deployment/.gitignore @@ -1,3 +1,4 @@ deployments/ fork/ txs/ +lib/generated/ diff --git a/packages/deployment/CLAUDE.md b/packages/deployment/CLAUDE.md index 89458a18c..598c3baf4 100644 --- a/packages/deployment/CLAUDE.md +++ b/packages/deployment/CLAUDE.md @@ -10,8 +10,9 @@ Before modifying any deployment scripts in `deploy/`, read: ## Key Rules (from principles) -- **`process.exit(1)` after generating governance TXs** - never return, always exit +- **`saveGovernanceTx` returns** - governance TX generation returns (not exit), downstream scripts check their own preconditions - **Idempotent scripts** - check on-chain state, skip if already done +- **Shared precondition checks** - use `lib/preconditions.ts` for configure/transfer checks, not inline copies - **Package imports** - use `@graphprotocol/deployment/...` not relative paths - **Contract registry** - use `Contracts.X` not string literals - **Standard numbering** - `01_deploy`, `02_upgrade`, ..., `09_end` diff --git a/packages/deployment/README.md b/packages/deployment/README.md index bf0968669..cce3d1c89 100644 --- a/packages/deployment/README.md +++ b/packages/deployment/README.md @@ -7,41 +7,54 @@ Unified deployment package for Graph Protocol contracts. ```bash cd packages/deployment -# Deploy and upgrade specific contracts -npx hardhat deploy --tags rewards-manager --network arbitrumSepolia -npx hardhat deploy --tags subgraph-service --network arbitrumSepolia - -# Deploy issuance contracts (full lifecycle with verification) -npx hardhat deploy --tags issuance-allocation --network arbitrumSepolia - -# Check status +# Read-only status (no --tags = no mutations) npx hardhat deploy:status --network arbitrumSepolia +npx hardhat deploy --tags GIP-0088 --network arbitrumSepolia + +# Component lifecycle (single contract) +npx hardhat deploy --tags IssuanceAllocator,deploy --network arbitrumSepolia +npx hardhat deploy --tags IssuanceAllocator,configure --network arbitrumSepolia +npx hardhat deploy --tags IssuanceAllocator,transfer --network arbitrumSepolia + +# Goal-driven (full GIP-0088 deployment) +npx hardhat deploy --tags GIP-0088:upgrade,deploy --network arbitrumSepolia +npx hardhat deploy --tags GIP-0088:upgrade,configure --network arbitrumSepolia +npx hardhat deploy --tags GIP-0088:upgrade,transfer --network arbitrumSepolia +npx hardhat deploy --tags GIP-0088:upgrade,upgrade --network arbitrumSepolia ``` +See [docs/Gip0088.md](./docs/Gip0088.md) for the full GIP-0088 workflow. + ## Deployment Flow +Each script is idempotent and goal-seeking: it checks on-chain state and either does what's needed or returns. Scripts that need governance authority build a TX batch and either execute it directly (deployer has permission) or save it for the Safe (`saveGovernanceTx` returns — does not exit). + ``` -sync → deploy → upgrade - │ │ │ - │ │ └─► Generate TX, try execute, sync if success - │ └─► Deploy impl if bytecode changed, store pending - └─► Check executed pendings, import from address books +sync → deploy → configure → transfer → upgrade (governance batch) + │ │ │ │ │ + │ │ │ │ └─► Bundle proxy upgrades + deferred config + │ │ │ └─► Revoke deployer role + transfer ProxyAdmin + │ │ └─► Deployer-only role grants and params + │ └─► Deploy impl + proxy if needed; store pendingImplementation + └─► Import on-chain state into address books ``` -**Stops at governance boundary** - if deployer lacks permission, stops with TX file path for Safe upload. - ## Structure ``` packages/deployment/ -├── deploy/ # hardhat-deploy scripts -│ ├── common/ # 00_sync.ts -│ ├── contracts/ # RewardsManager -│ ├── subgraph-service/ # SubgraphService -│ └── issuance/ # Issuance contracts -├── tasks/ # Hardhat tasks (deploy:*) -├── governance/ # Safe TX builders -└── test/ # Integration tests +├── deploy/ # rocketh deploy scripts (numbered per component) +│ ├── common/ # 00_sync.ts +│ ├── horizon/ # RM, HS, PE, L2Curation, RC +│ ├── service/ # SubgraphService, DisputeManager +│ ├── allocate/ # IssuanceAllocator, DefaultAllocation, DirectAllocation +│ ├── agreement/ # RecurringAgreementManager +│ ├── rewards/ # RewardsEligibilityOracle, Reclaim +│ └── gip/0088/ # GIP-0088 goal orchestration +├── lib/ # Shared utilities (preconditions, registry, tags, ABIs) +├── tasks/ # Hardhat tasks (deploy:*) +├── docs/ # Documentation +└── test/ # Unit tests ``` ## Available Tasks @@ -64,7 +77,8 @@ FORK_NETWORK=arbitrumSepolia ARBITRUM_SEPOLIA_RPC= pnpm test ## See Also -- [docs/DeploymentDesignPrinciples.md](./docs/DeploymentDesignPrinciples.md) - Core design principles and patterns +- [docs/deploy/ImplementationPrinciples.md](./docs/deploy/ImplementationPrinciples.md) - Core design principles and patterns - [docs/Architecture.md](./docs/Architecture.md) - Package structure and tags - [docs/GovernanceWorkflow.md](./docs/GovernanceWorkflow.md) - Detailed governance workflow -- [Design.md](./docs/Design.md) - Technical design documentation +- [docs/Design.md](./docs/Design.md) - Technical design documentation +- [docs/LocalForkTesting.md](./docs/LocalForkTesting.md) - Fork-based and local network testing diff --git a/packages/deployment/config/arbitrumOne.json5 b/packages/deployment/config/arbitrumOne.json5 new file mode 100644 index 000000000..85baff79a --- /dev/null +++ b/packages/deployment/config/arbitrumOne.json5 @@ -0,0 +1,12 @@ +{ + // Deployment configuration for Arbitrum One (mainnet) + // Values here are committed for reference and reproducibility. + + IssuanceAllocator: { + // RAM allocation: how much issuance flows to RecurringAgreementManager + // ramAllocatorMintingGrtPerBlock: GRT per block minted by IA and sent to RAM + // ramSelfMintingGrtPerBlock: 0 (RAM does not self-mint) + ramAllocatorMintingGrtPerBlock: '6', + ramSelfMintingGrtPerBlock: '0', + }, +} diff --git a/packages/deployment/config/arbitrumSepolia.json5 b/packages/deployment/config/arbitrumSepolia.json5 new file mode 100644 index 000000000..3944d469d --- /dev/null +++ b/packages/deployment/config/arbitrumSepolia.json5 @@ -0,0 +1,12 @@ +{ + // Deployment configuration for Arbitrum Sepolia (testnet) + // Values here are committed for reference and reproducibility. + + IssuanceAllocator: { + // RAM allocation: how much issuance flows to RecurringAgreementManager + // ramAllocatorMintingGrtPerBlock: GRT per block minted by IA and sent to RAM + // ramSelfMintingGrtPerBlock: GRT per block (0 = RAM does not self-mint) + ramAllocatorMintingGrtPerBlock: '0.5', + ramSelfMintingGrtPerBlock: '0', + }, +} diff --git a/packages/deployment/config/localNetwork.json5 b/packages/deployment/config/localNetwork.json5 new file mode 100644 index 000000000..09d9340ee --- /dev/null +++ b/packages/deployment/config/localNetwork.json5 @@ -0,0 +1,11 @@ +{ + // Deployment configuration for local-network (docker-compose dev stack) + // Local network uses generous rates for fast iteration and testing. + + IssuanceAllocator: { + // RAM allocation: how much issuance flows to RecurringAgreementManager + // Local network uses a high rate so agreements accumulate meaningful rewards quickly + ramAllocatorMintingGrtPerBlock: '6', + ramSelfMintingGrtPerBlock: '0', + }, +} diff --git a/packages/deployment/deploy/agreement/manager/01_deploy.ts b/packages/deployment/deploy/agreement/manager/01_deploy.ts new file mode 100644 index 000000000..dabd71cfb --- /dev/null +++ b/packages/deployment/deploy/agreement/manager/01_deploy.ts @@ -0,0 +1,16 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { requireDeployer, requireGraphToken } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createProxyDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createProxyDeployModule( + Contracts.issuance.RecurringAgreementManager, + (env) => { + const paymentsEscrow = env.getOrNull('PaymentsEscrow') + if (!paymentsEscrow) throw new Error('Missing PaymentsEscrow deployment after sync.') + return { + constructorArgs: [requireGraphToken(env).address, paymentsEscrow.address], + initializeArgs: [requireDeployer(env)], + } + }, + { prerequisites: [Contracts.horizon.L2GraphToken, Contracts.horizon.PaymentsEscrow] }, +) diff --git a/packages/deployment/deploy/agreement/manager/02_upgrade.ts b/packages/deployment/deploy/agreement/manager/02_upgrade.ts new file mode 100644 index 000000000..70b140182 --- /dev/null +++ b/packages/deployment/deploy/agreement/manager/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts.issuance.RecurringAgreementManager) diff --git a/packages/deployment/deploy/agreement/manager/04_configure.ts b/packages/deployment/deploy/agreement/manager/04_configure.ts new file mode 100644 index 000000000..0d0d7b1a2 --- /dev/null +++ b/packages/deployment/deploy/agreement/manager/04_configure.ts @@ -0,0 +1,225 @@ +import { ACCESS_CONTROL_ENUMERABLE_ABI, ISSUANCE_TARGET_ABI } from '@graphprotocol/deployment/lib/abis.js' +import { supportsInterface } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { getGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' +import { ComponentTags, DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContract, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { checkRAMConfigured } from '@graphprotocol/deployment/lib/preconditions.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph, tx } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' +import { encodeFunctionData, keccak256, toHex } from 'viem' + +/** + * Configure RecurringAgreementManager + * + * Grants: + * - COLLECTOR_ROLE to RecurringCollector + * - DATA_SERVICE_ROLE to SubgraphService + * - GOVERNOR_ROLE to protocol governor + * - PAUSE_ROLE to pause guardian + * + * Sets: + * - IssuanceAllocator as RAM's issuance source + * + * Idempotent: checks on-chain state, skips if already configured. + * + * Usage: + * pnpm hardhat deploy --tags RecurringAgreementManager:configure --network + */ +export default createActionModule( + Contracts.issuance.RecurringAgreementManager, + DeploymentActions.CONFIGURE, + async (env) => { + const client = graph.getPublicClient(env) as PublicClient + const governor = await getGovernor(env) + const pauseGuardian = await getPauseGuardian(env) + + const ram = requireContract(env, Contracts.issuance.RecurringAgreementManager) + const rc = requireContract(env, Contracts.horizon.RecurringCollector) + const ss = requireContract(env, Contracts['subgraph-service'].SubgraphService) + const ia = requireContract(env, Contracts.issuance.IssuanceAllocator) + + env.showMessage(`\n========== Configure ${Contracts.issuance.RecurringAgreementManager.name} ==========`) + env.showMessage(`RAM: ${ram.address}`) + env.showMessage(`RC: ${rc.address}`) + env.showMessage(`SS: ${ss.address}`) + env.showMessage(`IA: ${ia.address}`) + + // Check if already configured (shared precondition check) + const precondition = await checkRAMConfigured( + client, + ram.address, + rc.address, + ss.address, + ia.address, + governor, + pauseGuardian, + ) + if (precondition.done) { + env.showMessage(`\n✅ ${Contracts.issuance.RecurringAgreementManager.name} already configured\n`) + return + } + + // Role constants + const COLLECTOR_ROLE = keccak256(toHex('COLLECTOR_ROLE')) + const DATA_SERVICE_ROLE = keccak256(toHex('DATA_SERVICE_ROLE')) + const GOVERNOR_ROLE = keccak256(toHex('GOVERNOR_ROLE')) + const PAUSE_ROLE = keccak256(toHex('PAUSE_ROLE')) + + // Check what still needs configuring + env.showMessage('\n📋 Checking current configuration...\n') + + const rcHasCollectorRole = (await client.readContract({ + address: ram.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [COLLECTOR_ROLE, rc.address as `0x${string}`], + })) as boolean + env.showMessage(` RC COLLECTOR_ROLE: ${rcHasCollectorRole ? '✓' : '✗'}`) + + const ssHasDataServiceRole = (await client.readContract({ + address: ram.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [DATA_SERVICE_ROLE, ss.address as `0x${string}`], + })) as boolean + env.showMessage(` SS DATA_SERVICE_ROLE: ${ssHasDataServiceRole ? '✓' : '✗'}`) + + // Check role grants + const governorHasRole = (await client.readContract({ + address: ram.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [GOVERNOR_ROLE, governor as `0x${string}`], + })) as boolean + env.showMessage(` Governor GOVERNOR_ROLE: ${governorHasRole ? '✓' : '✗'}`) + + const pauseGuardianHasRole = (await client.readContract({ + address: ram.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [PAUSE_ROLE, pauseGuardian as `0x${string}`], + })) as boolean + env.showMessage(` PauseGuardian PAUSE_ROLE: ${pauseGuardianHasRole ? '✓' : '✗'}`) + + // Determine executor: deployer (fresh) or governor (prod) + const deployer = requireDeployer(env) + const deployerIsGovernor = (await client.readContract({ + address: ram.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [GOVERNOR_ROLE, deployer as `0x${string}`], + })) as boolean + + if (!deployerIsGovernor) { + env.showMessage(`\n ○ Deployer does not have GOVERNOR_ROLE — skipping (governance TX in upgrade step)\n`) + return + } + + // Build TX list for missing configuration + const txs: Array<{ to: string; data: `0x${string}`; label: string }> = [] + + if (!rcHasCollectorRole) { + txs.push({ + to: ram.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [COLLECTOR_ROLE, rc.address as `0x${string}`], + }), + label: `grantRole(COLLECTOR_ROLE, ${rc.address})`, + }) + } + + if (!ssHasDataServiceRole) { + txs.push({ + to: ram.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [DATA_SERVICE_ROLE, ss.address as `0x${string}`], + }), + label: `grantRole(DATA_SERVICE_ROLE, ${ss.address})`, + }) + } + + if (!governorHasRole) { + txs.push({ + to: ram.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [GOVERNOR_ROLE, governor as `0x${string}`], + }), + label: `grantRole(GOVERNOR_ROLE, ${governor})`, + }) + } + + if (!pauseGuardianHasRole) { + txs.push({ + to: ram.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [PAUSE_ROLE, pauseGuardian as `0x${string}`], + }), + label: `grantRole(PAUSE_ROLE, ${pauseGuardian})`, + }) + } + + // Check issuance allocator — skip if IA doesn't support the interface yet (pending upgrade) + let iaConfigured = false + try { + const currentIA = (await client.readContract({ + address: ram.address as `0x${string}`, + abi: ISSUANCE_TARGET_ABI, + functionName: 'getIssuanceAllocator', + })) as string + iaConfigured = currentIA.toLowerCase() === ia.address.toLowerCase() + env.showMessage(` IssuanceAllocator: ${iaConfigured ? '✓' : '✗'} (current: ${currentIA})`) + } catch { + env.showMessage(` IssuanceAllocator: ✗ (getter not available)`) + } + + if (!iaConfigured) { + const IISSUANCE_ALLOCATION_DISTRIBUTION_ID = '0x79da37fc' // type(IIssuanceAllocationDistribution).interfaceId + const iaSupported = await supportsInterface(client, ia.address, IISSUANCE_ALLOCATION_DISTRIBUTION_ID) + if (iaSupported) { + txs.push({ + to: ram.address, + data: encodeFunctionData({ + abi: ISSUANCE_TARGET_ABI, + functionName: 'setIssuanceAllocator', + args: [ia.address as `0x${string}`], + }), + label: `setIssuanceAllocator(${ia.address})`, + }) + } else { + env.showMessage(` ○ IA does not yet support IIssuanceAllocationDistribution — skipping setIssuanceAllocator`) + } + } + + if (txs.length === 0) return + + env.showMessage('\n🔨 Executing configuration as deployer...\n') + const txFn = tx(env) + for (const t of txs) { + await txFn({ account: deployer, to: t.to as `0x${string}`, data: t.data }) + env.showMessage(` ✓ ${t.label}`) + } + env.showMessage(`\n✅ ${Contracts.issuance.RecurringAgreementManager.name} configuration complete!\n`) + }, + { + extraDependencies: [ + ComponentTags.RECURRING_COLLECTOR, + ComponentTags.SUBGRAPH_SERVICE, + ComponentTags.ISSUANCE_ALLOCATOR, + ], + prerequisites: [ + Contracts.horizon.RecurringCollector, + Contracts['subgraph-service'].SubgraphService, + Contracts.issuance.IssuanceAllocator, + ], + }, +) diff --git a/packages/deployment/deploy/agreement/manager/05_transfer_governance.ts b/packages/deployment/deploy/agreement/manager/05_transfer_governance.ts new file mode 100644 index 000000000..50d3f7582 --- /dev/null +++ b/packages/deployment/deploy/agreement/manager/05_transfer_governance.ts @@ -0,0 +1,60 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + requireContract, + requireDeployer, + transferProxyAdminOwnership, +} from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { checkDeployerRevoked } from '@graphprotocol/deployment/lib/preconditions.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { execute, graph, read } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Transfer RecurringAgreementManager governance from deployer + * + * - Revoke GOVERNOR_ROLE from deployment account + * - Transfer ProxyAdmin ownership to governor + * + * Role grants (GOVERNOR_ROLE, PAUSE_ROLE, COLLECTOR_ROLE, DATA_SERVICE_ROLE) + * happen in 04_configure.ts. This script only revokes deployer access. + * + * Idempotent: checks on-chain state, skips if already transferred. + * + * Usage: + * pnpm hardhat deploy --tags RecurringAgreementManager,transfer --network + */ +export default createActionModule( + Contracts.issuance.RecurringAgreementManager, + DeploymentActions.TRANSFER, + async (env) => { + const readFn = read(env) + const executeFn = execute(env) + const client = graph.getPublicClient(env) as PublicClient + const deployer = requireDeployer(env) + const ram = requireContract(env, Contracts.issuance.RecurringAgreementManager) + + env.showMessage(`\n========== Transfer ${Contracts.issuance.RecurringAgreementManager.name} ==========`) + + // Check if deployer GOVERNOR_ROLE already revoked (shared precondition check) + const precondition = await checkDeployerRevoked(client, ram.address, deployer) + if (precondition.done) { + env.showMessage(`✓ Deployer GOVERNOR_ROLE already revoked`) + } else { + const GOVERNOR_ROLE = (await readFn(ram, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` + + env.showMessage(`🔨 Revoking deployer GOVERNOR_ROLE...`) + await executeFn(ram, { + account: deployer, + functionName: 'revokeRole', + args: [GOVERNOR_ROLE, deployer], + }) + env.showMessage(` ✓ revokeRole(GOVERNOR_ROLE) executed`) + } + + // Transfer ProxyAdmin ownership to governor + await transferProxyAdminOwnership(env, Contracts.issuance.RecurringAgreementManager) + + env.showMessage(`\n✅ ${Contracts.issuance.RecurringAgreementManager.name} governance transferred!\n`) + }, +) diff --git a/packages/deployment/deploy/agreement/manager/09_end.ts b/packages/deployment/deploy/agreement/manager/09_end.ts new file mode 100644 index 000000000..c68c1db6a --- /dev/null +++ b/packages/deployment/deploy/agreement/manager/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.issuance.RecurringAgreementManager) diff --git a/packages/deployment/deploy/agreement/manager/10_status.ts b/packages/deployment/deploy/agreement/manager/10_status.ts new file mode 100644 index 000000000..d7e3f98bc --- /dev/null +++ b/packages/deployment/deploy/agreement/manager/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.issuance.RecurringAgreementManager) diff --git a/packages/deployment/deploy/allocate/allocator/01_deploy.ts b/packages/deployment/deploy/allocate/allocator/01_deploy.ts index 0db712c63..58bd3ca30 100644 --- a/packages/deployment/deploy/allocate/allocator/01_deploy.ts +++ b/packages/deployment/deploy/allocate/allocator/01_deploy.ts @@ -1,49 +1,12 @@ import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { SpecialTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { deployProxyContract, requireContract } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * Deploy IssuanceAllocator - Token allocation contract with transparent proxy - * - * This deploys IssuanceAllocator as an upgradeable contract using OpenZeppelin v5's - * TransparentUpgradeableProxy pattern. The contract is initialized atomically - * during proxy deployment to prevent front-running attacks. - * - * Architecture: - * - Implementation: IssuanceAllocator contract with GRT token constructor arg - * - Proxy: OZ v5 TransparentUpgradeableProxy with atomic initialization - * - Admin: Per-proxy ProxyAdmin (created by OZ v5 proxy, owned by governor) - * - * Initial Setup (IssuanceAllocator.md Step 1): - * - Governor receives initial GOVERNOR_ROLE for configuration - * - Per-proxy ProxyAdmin owned by governor (controls upgrades) - * - Default target set to address(0) (no minting until configured) - * - Governance transfer happens in separate script - * - * Deployment strategy: - * - First run: Deploy implementation + proxy (creates per-proxy ProxyAdmin) - * - Subsequent runs: - * - If implementation unchanged: No-op (reuse existing) - * - If implementation changed: Deploy new implementation, store as pending - * - Upgrades must be done via governance - * - * Usage: - * pnpm hardhat deploy --tags issuance-allocator-deploy --network - */ - -const func: DeployScriptModule = async (env) => { - const graphToken = requireContract(env, Contracts.horizon.L2GraphToken).address - - env.showMessage(`\n📦 Deploying ${Contracts.issuance.IssuanceAllocator.name} with GraphToken: ${graphToken}`) - - await deployProxyContract(env, { - contract: Contracts.issuance.IssuanceAllocator, - constructorArgs: [graphToken], - }) -} - -func.tags = Tags.issuanceAllocatorDeploy -func.dependencies = [SpecialTags.SYNC] - -export default func +import { requireContract, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createProxyDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createProxyDeployModule( + Contracts.issuance.IssuanceAllocator, + (env) => ({ + constructorArgs: [requireContract(env, Contracts.horizon.L2GraphToken).address], + initializeArgs: [requireDeployer(env)], + }), + { prerequisites: [Contracts.horizon.L2GraphToken] }, +) diff --git a/packages/deployment/deploy/allocate/allocator/02_upgrade.ts b/packages/deployment/deploy/allocate/allocator/02_upgrade.ts index 66cab6a8d..8f012a025 100644 --- a/packages/deployment/deploy/allocate/allocator/02_upgrade.ts +++ b/packages/deployment/deploy/allocate/allocator/02_upgrade.ts @@ -1,26 +1,4 @@ import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { upgradeImplementation } from '@graphprotocol/deployment/lib/upgrade-implementation.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' -// IssuanceAllocator Upgrade -// -// Generates governance TX batch and executes upgrade via per-proxy ProxyAdmin. -// -// Workflow: -// 1. Check for pending implementation in address book -// 2. Generate governance TX (upgradeAndCall to per-proxy ProxyAdmin) -// 3. Fork mode: execute via governor impersonation -// 4. Production: output TX file for Safe execution -// -// Usage: -// FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags issuance-allocator-upgrade --network localhost - -const func: DeployScriptModule = async (env) => { - await upgradeImplementation(env, Contracts.issuance.IssuanceAllocator) -} - -func.tags = Tags.issuanceAllocatorUpgrade -func.dependencies = [actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.DEPLOY)] - -export default func +export default createUpgradeModule(Contracts.issuance.IssuanceAllocator) diff --git a/packages/deployment/deploy/allocate/allocator/03_deploy.ts b/packages/deployment/deploy/allocate/allocator/03_deploy.ts deleted file mode 100644 index a3a1c6cb9..000000000 --- a/packages/deployment/deploy/allocate/allocator/03_deploy.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireUpgradeExecuted } from '@graphprotocol/deployment/lib/execute-governance.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * IssuanceAllocator end state - deployed, upgraded, configured, and governance transferred - * - * Full lifecycle (steps 1-6 from IssuanceAllocator.md): - * 1. Deploy and initialize with deployer as GOVERNOR_ROLE - * 2-3. Configure issuance rate and RewardsManager allocation - * 4-5. (Optional upgrade steps) - * 6. Transfer governance to protocol governance multisig - * - * Usage: - * pnpm hardhat deploy --tags issuance-allocator --network - */ -const func: DeployScriptModule = async (env) => { - requireUpgradeExecuted(env, 'IssuanceAllocator') - env.showMessage(`\n✓ IssuanceAllocator ready (governance transferred)`) -} - -func.tags = Tags.issuanceAllocator -func.dependencies = [ - actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.DEPLOY), - actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.UPGRADE), - actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.CONFIGURE), - actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.TRANSFER), -] - -export default func diff --git a/packages/deployment/deploy/allocate/allocator/04_configure.ts b/packages/deployment/deploy/allocate/allocator/04_configure.ts index 32076684f..d46243e74 100644 --- a/packages/deployment/deploy/allocate/allocator/04_configure.ts +++ b/packages/deployment/deploy/allocate/allocator/04_configure.ts @@ -1,157 +1,168 @@ -import { REWARDS_MANAGER_DEPRECATED_ABI, SET_TARGET_ALLOCATION_ABI } from '@graphprotocol/deployment/lib/abis.js' -import { requireRewardsManagerUpgraded } from '@graphprotocol/deployment/lib/contract-checks.js' +import { ACCESS_CONTROL_ENUMERABLE_ABI, REWARDS_MANAGER_DEPRECATED_ABI } from '@graphprotocol/deployment/lib/abis.js' import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { getGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' +import { ComponentTags, DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { execute, graph, read, tx } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { checkIAConfigured } from '@graphprotocol/deployment/lib/preconditions.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph, read, tx } from '@graphprotocol/deployment/rocketh/deploy.js' import type { PublicClient } from 'viem' import { encodeFunctionData } from 'viem' /** - * Configure ${Contracts.issuance.IssuanceAllocator.name} initial state (deployer account) + * Configure IssuanceAllocator * - * Configuration steps (IssuanceAllocator.md steps 2-3): - * 2. Set issuance rate to match RewardsManager - * 3. Configure RM as 100% self-minting target + * - Sets issuance rate to match RewardsManager + * - Configures RM as 100% self-minting target + * - Grants GOVERNOR_ROLE to protocol governor + * - Grants PAUSE_ROLE to pause guardian + * + * If deployer has GOVERNOR_ROLE (fresh deploy), executes directly. + * If governance transferred, generates governance TX or executes via governor. * - * Requires deployer to have GOVERNOR_ROLE (granted during initialization in step 1). - * PAUSE_ROLE will be granted in step 6 (transfer governance script). * Idempotent: checks on-chain state, skips if already configured. * * Usage: - * pnpm hardhat deploy --tags issuance-allocator-configure --network + * pnpm hardhat deploy --tags IssuanceAllocator,configure --network */ -const func: DeployScriptModule = async (env) => { - const readFn = read(env) - const executeFn = execute(env) - - const deployer = requireDeployer(env) - - const [issuanceAllocator, rewardsManager] = requireContracts(env, [ - Contracts.issuance.IssuanceAllocator, - Contracts.horizon.RewardsManager, - ]) - - // Create viem client for direct contract calls - const client = graph.getPublicClient(env) - - // Check if RewardsManager supports IIssuanceTarget (has been upgraded) - // Throws error if not upgraded - await requireRewardsManagerUpgraded(client as PublicClient, rewardsManager.address, env) - - env.showMessage(`\n========== Configure ${Contracts.issuance.IssuanceAllocator.name} ==========`) - env.showMessage(`${Contracts.issuance.IssuanceAllocator.name}: ${issuanceAllocator.address}`) - env.showMessage(`${Contracts.horizon.RewardsManager.name}: ${rewardsManager.address}`) - env.showMessage(`Deployer: ${deployer}\n`) - - // Get role constants - const GOVERNOR_ROLE = (await readFn(issuanceAllocator, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` - - // Check current state - env.showMessage('📋 Checking current configuration...\n') - - const checks = { - issuanceRate: false, - rmAllocation: false, - } - - // Check issuance rate - // Note: Use viem directly for RM because synced deployment has empty ABI - const rmIssuanceRate = (await client.readContract({ - address: rewardsManager.address as `0x${string}`, - abi: REWARDS_MANAGER_DEPRECATED_ABI, - functionName: 'issuancePerBlock', - })) as bigint - const iaIssuanceRate = (await readFn(issuanceAllocator, { functionName: 'getIssuancePerBlock' })) as bigint - checks.issuanceRate = iaIssuanceRate === rmIssuanceRate && iaIssuanceRate > 0n - env.showMessage(` Issuance rate: ${checks.issuanceRate ? '✓' : '✗'} (IA: ${iaIssuanceRate}, RM: ${rmIssuanceRate})`) - - // Check RM allocation (should be 100% self-minting) - try { - const rmAllocation = (await readFn(issuanceAllocator, { - functionName: 'getTargetAllocation', - args: [rewardsManager.address], - })) as { totalAllocationRate: bigint; allocatorMintingRate: bigint; selfMintingRate: bigint } - const expectedSelfMinting = iaIssuanceRate > 0n ? iaIssuanceRate : rmIssuanceRate - checks.rmAllocation = - rmAllocation.allocatorMintingRate === 0n && rmAllocation.selfMintingRate === expectedSelfMinting - env.showMessage( - ` RM allocation: ${checks.rmAllocation ? '✓' : '✗'} (allocator: ${rmAllocation.allocatorMintingRate}, self: ${rmAllocation.selfMintingRate})`, +export default createActionModule( + Contracts.issuance.IssuanceAllocator, + DeploymentActions.CONFIGURE, + async (env) => { + const readFn = read(env) + const deployer = requireDeployer(env) + const governor = await getGovernor(env) + const pauseGuardian = await getPauseGuardian(env) + + const [issuanceAllocator, rewardsManager] = requireContracts(env, [ + Contracts.issuance.IssuanceAllocator, + Contracts.horizon.RewardsManager, + ]) + + const client = graph.getPublicClient(env) as PublicClient + + env.showMessage(`\n========== Configure ${Contracts.issuance.IssuanceAllocator.name} ==========`) + env.showMessage(`${Contracts.issuance.IssuanceAllocator.name}: ${issuanceAllocator.address}`) + env.showMessage(`${Contracts.horizon.RewardsManager.name}: ${rewardsManager.address}`) + + // Check if already configured (shared precondition check) + const precondition = await checkIAConfigured( + client, + issuanceAllocator.address, + rewardsManager.address, + governor, + pauseGuardian, ) - } catch (error) { - env.showMessage(` RM allocation: ✗ (error reading: ${error})`) - } - - // Check deployer role (informational - determines who can execute missing config) - const deployerHasGovernorRole = (await readFn(issuanceAllocator, { - functionName: 'hasRole', - args: [GOVERNOR_ROLE, deployer], - })) as boolean - env.showMessage(` Deployer GOVERNOR_ROLE: ${deployerHasGovernorRole ? '✓' : '✗'} (${deployer})`) - - // Note: PAUSE_ROLE will be granted in step 6 (transfer governance) - - // Configuration complete? - const configurationComplete = Object.values(checks).every(Boolean) - if (configurationComplete) { - env.showMessage(`\n✅ ${Contracts.issuance.IssuanceAllocator.name} already configured\n`) - return - } - - // Check if deployer has permission to execute missing configuration - // If governance has been transferred, configuration must be done via governance TX - if (!deployerHasGovernorRole) { - env.showMessage('\n❌ Configuration incomplete but deployer does not have GOVERNOR_ROLE') - env.showMessage(' Governance has been transferred - this configuration must be done via governance TX') - env.showMessage(` Missing configuration:`) - if (!checks.issuanceRate) { - env.showMessage(` - Issuance rate (currently: ${iaIssuanceRate})`) + if (precondition.done) { + env.showMessage(`\n✅ ${Contracts.issuance.IssuanceAllocator.name} already configured\n`) + return + } + + // Get RM issuance rate (target for IA) + const rmIssuanceRate = (await client.readContract({ + address: rewardsManager.address as `0x${string}`, + abi: REWARDS_MANAGER_DEPRECATED_ABI, + functionName: 'issuancePerBlock', + })) as bigint + + if (rmIssuanceRate === 0n) { + env.showMessage(`\n ○ RM.issuancePerBlock is 0 — skipping IA configure\n`) + return + } + + // Determine what still needs configuring + env.showMessage('\n📋 Checking current configuration...\n') + + const iaIssuanceRate = (await readFn(issuanceAllocator, { functionName: 'getIssuancePerBlock' })) as bigint + const rateOk = iaIssuanceRate === rmIssuanceRate && iaIssuanceRate > 0n + env.showMessage(` Issuance rate: ${rateOk ? '✓' : '✗'} (IA: ${iaIssuanceRate}, RM: ${rmIssuanceRate})`) + + // Check role grants + const GOVERNOR_ROLE = (await readFn(issuanceAllocator, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` + const PAUSE_ROLE = (await readFn(issuanceAllocator, { functionName: 'PAUSE_ROLE' })) as `0x${string}` + + const governorHasRole = (await readFn(issuanceAllocator, { + functionName: 'hasRole', + args: [GOVERNOR_ROLE, governor], + })) as boolean + env.showMessage(` Governor GOVERNOR_ROLE: ${governorHasRole ? '✓' : '✗'}`) + + const pauseGuardianHasRole = (await readFn(issuanceAllocator, { + functionName: 'hasRole', + args: [PAUSE_ROLE, pauseGuardian], + })) as boolean + env.showMessage(` PauseGuardian PAUSE_ROLE: ${pauseGuardianHasRole ? '✓' : '✗'}`) + + // Determine executor: deployer if has GOVERNOR_ROLE, else protocol governor + const deployerHasRole = (await readFn(issuanceAllocator, { + functionName: 'hasRole', + args: [GOVERNOR_ROLE, deployer], + })) as boolean + + // Build TX data for missing configuration + const txs: Array<{ to: string; data: `0x${string}`; label: string }> = [] + + if (!rateOk) { + txs.push({ + to: issuanceAllocator.address, + data: encodeFunctionData({ + abi: [ + { + inputs: [{ type: 'uint256' }], + name: 'setIssuancePerBlock', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], + functionName: 'setIssuancePerBlock', + args: [rmIssuanceRate], + }), + label: `setIssuancePerBlock(${rmIssuanceRate})`, + }) + } + + if (!governorHasRole) { + txs.push({ + to: issuanceAllocator.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [GOVERNOR_ROLE, governor as `0x${string}`], + }), + label: `grantRole(GOVERNOR_ROLE, ${governor})`, + }) + } + + if (!pauseGuardianHasRole) { + txs.push({ + to: issuanceAllocator.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [PAUSE_ROLE, pauseGuardian as `0x${string}`], + }), + label: `grantRole(PAUSE_ROLE, ${pauseGuardian})`, + }) } - if (!checks.rmAllocation) { - env.showMessage(` - RM allocation (not configured)`) + + if (!deployerHasRole) { + env.showMessage(`\n ○ Deployer does not have GOVERNOR_ROLE — skipping (governance TX in upgrade step)\n`) + return } - env.showMessage(`\n This should not happen in normal deployment flow.`) - env.showMessage(` Configuration (step 5) should complete before governance transfer (step 6).\n`) - process.exit(1) - } - - // Execute configuration as deployer - env.showMessage('\n🔨 Executing configuration...\n') - - // Step 2: Set issuance rate - if (!checks.issuanceRate) { - env.showMessage(` Setting issuance rate to ${rmIssuanceRate}...`) - await executeFn(issuanceAllocator, { - account: deployer, - functionName: 'setIssuancePerBlock', - args: [rmIssuanceRate], - }) - env.showMessage(' ✓ setIssuancePerBlock executed') - } - - // Step 3: Configure RM allocation (3-arg version: target, allocatorMintingRate, selfMintingRate) - // Note: Use tx() with encoded data to select the 3-arg overload (rocketh picks wrong one) - if (!checks.rmAllocation) { + + if (txs.length === 0) return + + env.showMessage('\n🔨 Executing configuration as deployer...\n') const txFn = tx(env) - const rate = iaIssuanceRate > 0n ? iaIssuanceRate : rmIssuanceRate - env.showMessage(` Setting RM allocation (0, ${rate})...`) - const data = encodeFunctionData({ - abi: SET_TARGET_ALLOCATION_ABI, - functionName: 'setTargetAllocation', - args: [rewardsManager.address as `0x${string}`, 0n, rate], - }) - await txFn({ account: deployer, to: issuanceAllocator.address, data }) - env.showMessage(' ✓ setTargetAllocation executed') - } - - env.showMessage(`\n✅ ${Contracts.issuance.IssuanceAllocator.name} configuration complete!\n`) -} - -func.tags = Tags.issuanceAllocatorConfigure -func.dependencies = [ - actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.DEPLOY), - ComponentTags.REWARDS_MANAGER_UPGRADE, -] - -export default func + for (const t of txs) { + await txFn({ account: deployer, to: t.to as `0x${string}`, data: t.data }) + env.showMessage(` ✓ ${t.label}`) + } + env.showMessage(`\n✅ ${Contracts.issuance.IssuanceAllocator.name} configuration complete!\n`) + }, + { + extraDependencies: [ComponentTags.REWARDS_MANAGER], + prerequisites: [Contracts.horizon.RewardsManager], + }, +) diff --git a/packages/deployment/deploy/allocate/allocator/05_verify_governance.ts b/packages/deployment/deploy/allocate/allocator/05_verify_governance.ts deleted file mode 100644 index 3674ffdd7..000000000 --- a/packages/deployment/deploy/allocate/allocator/05_verify_governance.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { getGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { getProxyAdminAddress, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { graph, read } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * Verify governance and configuration for all issuance contracts - * - * This implements Step 7 from IssuanceAllocator.md: - * - Bytecode verification (deployment bytecode matches expected contract) - * - Access control: - * - Governor has GOVERNOR_ROLE on all contracts - * - Deployment account does NOT have GOVERNOR_ROLE - * - Pause guardian has PAUSE_ROLE on pausable contracts - * - Off-chain: Review all RoleGranted events since deployment - * - Pause state: Verify contract is not paused - * - Issuance rate: Verify matches RewardsManager rate exactly - * - Target configuration: Verify only expected targets exist - * - Proxy configuration: Verify ProxyAdmin controls proxy and is owned by governance - * - * The issuance contracts use role-based access control (OpenZeppelin AccessControl) - * rather than ownership patterns. - * - * This script is idempotent and runs after governance transfer (step 6) to ensure - * proper access control configuration before activation (steps 8-10). - * - * Usage: - * pnpm hardhat deploy --tags verify-governance --network - * - * Or as part of full deployment: - * pnpm hardhat deploy --tags issuance-allocation --network - */ -const func: DeployScriptModule = async (env) => { - const readFn = read(env) - - const deployer = requireDeployer(env) - - // Get protocol governor and pause guardian from Controller - const governor = await getGovernor(env) - const pauseGuardian = await getPauseGuardian(env) - - const contracts = [ - Contracts.issuance.IssuanceAllocator.name, - Contracts.issuance.PilotAllocation.name, - Contracts.issuance.RewardsEligibilityOracle.name, - ] - - env.showMessage('\n========== Governance and Configuration Verification ==========\n') - - // 1. Verify GOVERNOR_ROLE (governor has, deployer does not) - env.showMessage('1. Verifying GOVERNOR_ROLE assignment...') - for (const contractName of contracts) { - const deployment = env.getOrNull(contractName) - if (!deployment) { - env.showMessage(` Skipping ${contractName} - not deployed`) - continue - } - - try { - const governorRole = (await readFn(deployment, { functionName: 'GOVERNOR_ROLE' })) as string - - // Check governor has role - const governorHasRole = (await readFn(deployment, { - functionName: 'hasRole', - args: [governorRole, governor], - })) as boolean - - // Check deployer does NOT have role - const deployerHasRole = (await readFn(deployment, { - functionName: 'hasRole', - args: [governorRole, deployer], - })) as boolean - - if (governorHasRole && !deployerHasRole) { - env.showMessage(` ✓ ${contractName}: Governor has GOVERNOR_ROLE, deployer revoked`) - } else if (governorHasRole && deployerHasRole) { - env.showMessage(` ⚠ ${contractName}: Governor has GOVERNOR_ROLE but deployer NOT revoked`) - } else if (!governorHasRole && deployerHasRole) { - env.showMessage(` ⚠ ${contractName}: Deployer has GOVERNOR_ROLE but governance NOT transferred`) - } else { - env.showMessage(` ✗ ${contractName}: WARNING - Neither governor nor deployer has GOVERNOR_ROLE`) - } - } catch (error) { - env.showMessage(` ✗ ${contractName}: Error verifying governance: ${error}`) - } - } - - // 2. Verify PAUSE_ROLE - env.showMessage('\n2. Verifying PAUSE_ROLE assignment...') - const pausableContracts = [ - Contracts.issuance.IssuanceAllocator.name, - Contracts.issuance.PilotAllocation.name, - Contracts.issuance.RewardsEligibilityOracle.name, - ] - for (const contractName of pausableContracts) { - const deployment = env.getOrNull(contractName) - if (!deployment) continue - - try { - const pauseRole = (await readFn(deployment, { functionName: 'PAUSE_ROLE' })) as string - const hasPauseRole = (await readFn(deployment, { - functionName: 'hasRole', - args: [pauseRole, pauseGuardian], - })) as boolean - - if (hasPauseRole) { - env.showMessage(` ✓ ${contractName}: Pause guardian has PAUSE_ROLE`) - } else { - env.showMessage( - ` ⚠ ${contractName}: Pause guardian does NOT have PAUSE_ROLE (will be granted in 06_transfer_governance)`, - ) - } - } catch (error) { - env.showMessage(` ⚠ ${contractName}: Cannot verify PAUSE_ROLE: ${error}`) - } - } - - // 3. Verify IssuanceAllocator configuration - env.showMessage('\n3. Verifying IssuanceAllocator configuration...') - const iaDeployment = env.getOrNull(Contracts.issuance.IssuanceAllocator.name) - if (iaDeployment) { - try { - const issuanceRate = (await readFn(iaDeployment, { functionName: 'getIssuancePerBlock' })) as bigint - const isPaused = (await readFn(iaDeployment, { functionName: 'paused' })) as boolean - - env.showMessage(` Issuance rate: ${issuanceRate} tokens/block`) - env.showMessage(` Paused: ${isPaused}`) - - if (issuanceRate === 0n) { - env.showMessage(` ⚠ Issuance rate is 0 (will be configured in step 5)`) - } else { - env.showMessage(` ✓ Issuance rate configured`) - } - - if (isPaused) { - env.showMessage(` ✗ WARNING: Contract is PAUSED`) - } else { - env.showMessage(` ✓ Contract is not paused`) - } - } catch (error) { - env.showMessage(` ✗ Error verifying IssuanceAllocator configuration: ${error}`) - } - } - - // 4. Verify per-proxy ProxyAdmin ownership (OZ v5 pattern) - env.showMessage('\n4. Verifying per-proxy ProxyAdmin ownership...') - const client = graph.getPublicClient(env) - const proxiedContracts = [ - Contracts.issuance.IssuanceAllocator.name, - Contracts.issuance.PilotAllocation.name, - Contracts.issuance.RewardsEligibilityOracle.name, - ] - for (const contractName of proxiedContracts) { - const proxyDeployment = env.getOrNull(`${contractName}_Proxy`) - if (!proxyDeployment) { - env.showMessage(` Skipping ${contractName} - proxy not deployed`) - continue - } - - try { - // Read per-proxy ProxyAdmin address from ERC1967 slot - const proxyAdminAddress = await getProxyAdminAddress(client, proxyDeployment.address) - - // Read owner from ProxyAdmin - const owner = (await client.readContract({ - address: proxyAdminAddress as `0x${string}`, - abi: [{ name: 'owner', type: 'function', inputs: [], outputs: [{ type: 'address' }] }], - functionName: 'owner', - })) as string - - if (owner.toLowerCase() === governor.toLowerCase()) { - env.showMessage(` ✓ ${contractName}: ProxyAdmin (${proxyAdminAddress}) owned by governor`) - } else { - env.showMessage(` ✗ ${contractName}: ProxyAdmin owned by ${owner}, expected ${governor}`) - } - } catch (error) { - env.showMessage(` ✗ ${contractName}: Error verifying ProxyAdmin ownership: ${error}`) - } - } - - env.showMessage('\n========== Verification Complete ==========\n') -} - -func.tags = Tags.verifyGovernance -func.dependencies = [actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.TRANSFER)] // Run after governance transfer (step 6) - -export default func diff --git a/packages/deployment/deploy/allocate/allocator/06_transfer_governance.ts b/packages/deployment/deploy/allocate/allocator/06_transfer_governance.ts index eba857f27..b960839b7 100644 --- a/packages/deployment/deploy/allocate/allocator/06_transfer_governance.ts +++ b/packages/deployment/deploy/allocate/allocator/06_transfer_governance.ts @@ -1,132 +1,61 @@ import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { getGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { execute, read } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { getGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + requireContracts, + requireDeployer, + transferProxyAdminOwnership, +} from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { checkDeployerRevoked } from '@graphprotocol/deployment/lib/preconditions.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { execute, graph, read } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' /** - * Transfer governance of ${Contracts.issuance.IssuanceAllocator.name} from deployer to protocol governor (deployer account) + * Transfer IssuanceAllocator governance from deployer to protocol governor * - * Step 6 from IssuanceAllocator.md: - * - Grant PAUSE_ROLE to pause guardian (from Controller) - * - Grant GOVERNOR_ROLE to protocol governor (from Controller.getGovernor()) - * - Revoke GOVERNOR_ROLE from deployment account (MUST grant to governance first, then revoke) + * - Revoke GOVERNOR_ROLE from deployment account + * - Transfer ProxyAdmin ownership to governor * - * This is a critical security step that transfers control from the deployment account - * to the protocol governance multisig. After this step, only governance can modify - * issuance allocations and rates. + * Role grants (GOVERNOR_ROLE to governor, PAUSE_ROLE to pauseGuardian) happen + * in 04_configure.ts. This script only revokes deployer access. * - * Requires deployer to have GOVERNOR_ROLE (granted during initialization in step 1). * Idempotent: checks on-chain state, skips if already transferred. * * Usage: - * pnpm hardhat deploy --tags issuance-transfer-governance --network + * pnpm hardhat deploy --tags IssuanceAllocator,transfer --network */ -const func: DeployScriptModule = async (env) => { +export default createActionModule(Contracts.issuance.IssuanceAllocator, DeploymentActions.TRANSFER, async (env) => { const readFn = read(env) const executeFn = execute(env) + const client = graph.getPublicClient(env) as PublicClient const deployer = requireDeployer(env) - - // Get protocol governor and pause guardian from Controller const governor = await getGovernor(env) - const pauseGuardian = await getPauseGuardian(env) - const [issuanceAllocator] = requireContracts(env, [Contracts.issuance.IssuanceAllocator]) - env.showMessage(`\n========== Transfer Governance of ${Contracts.issuance.IssuanceAllocator.name} ==========`) - env.showMessage(`${Contracts.issuance.IssuanceAllocator.name}: ${issuanceAllocator.address}`) + env.showMessage(`\n========== Transfer ${Contracts.issuance.IssuanceAllocator.name} ==========`) env.showMessage(`Deployer: ${deployer}`) - env.showMessage(`Protocol Governor (from Controller): ${governor}`) - env.showMessage(`Pause Guardian: ${pauseGuardian}\n`) - - // Get role constants - const GOVERNOR_ROLE = (await readFn(issuanceAllocator, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` - const PAUSE_ROLE = (await readFn(issuanceAllocator, { functionName: 'PAUSE_ROLE' })) as `0x${string}` - - // Check current state - env.showMessage('📋 Checking current governance state...\n') - - const checks = { - pauseRole: false, - governorHasRole: false, - deployerRevoked: false, - } - - // Check pause role - checks.pauseRole = (await readFn(issuanceAllocator, { - functionName: 'hasRole', - args: [PAUSE_ROLE, pauseGuardian], - })) as boolean - env.showMessage(` Pause guardian has PAUSE_ROLE: ${checks.pauseRole ? '✓' : '✗'} (${pauseGuardian})`) - - // Check governor has GOVERNOR_ROLE - checks.governorHasRole = (await readFn(issuanceAllocator, { - functionName: 'hasRole', - args: [GOVERNOR_ROLE, governor], - })) as boolean - env.showMessage(` Governor has GOVERNOR_ROLE: ${checks.governorHasRole ? '✓' : '✗'} (${governor})`) - - // Check deployer no longer has GOVERNOR_ROLE - const deployerHasRole = (await readFn(issuanceAllocator, { - functionName: 'hasRole', - args: [GOVERNOR_ROLE, deployer], - })) as boolean - checks.deployerRevoked = !deployerHasRole - env.showMessage(` Deployer GOVERNOR_ROLE revoked: ${checks.deployerRevoked ? '✓' : '✗'} (${deployer})`) - - // All checks passed? - const allPassed = Object.values(checks).every(Boolean) - if (allPassed) { - env.showMessage(`\n✅ Governance already transferred to ${governor}\n`) - return - } + env.showMessage(`Governor: ${governor}\n`) - // Execute governance transfer - // CRITICAL: Must grant to governance BEFORE revoking from deployer - env.showMessage('\n🔨 Executing governance transfer...\n') + // Check if deployer GOVERNOR_ROLE already revoked (shared precondition check) + const precondition = await checkDeployerRevoked(client, issuanceAllocator.address, deployer) + if (precondition.done) { + env.showMessage(`✓ Deployer GOVERNOR_ROLE already revoked`) + } else { + const GOVERNOR_ROLE = (await readFn(issuanceAllocator, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` - // Step 1: Grant PAUSE_ROLE to pause guardian - if (!checks.pauseRole) { - env.showMessage(` Granting PAUSE_ROLE to ${pauseGuardian}...`) - await executeFn(issuanceAllocator, { - account: deployer, - functionName: 'grantRole', - args: [PAUSE_ROLE, pauseGuardian], - }) - env.showMessage(' ✓ grantRole(PAUSE_ROLE) executed') - } - - // Step 2: Grant GOVERNOR_ROLE to governor - if (!checks.governorHasRole) { - env.showMessage(` Granting GOVERNOR_ROLE to ${governor}...`) - await executeFn(issuanceAllocator, { - account: deployer, - functionName: 'grantRole', - args: [GOVERNOR_ROLE, governor], - }) - env.showMessage(' ✓ grantRole(GOVERNOR_ROLE) executed') - } - - // Step 3: Revoke GOVERNOR_ROLE from deployer (ONLY after governance has the role) - if (!checks.deployerRevoked) { - env.showMessage(` Revoking GOVERNOR_ROLE from deployer ${deployer}...`) + env.showMessage(`🔨 Revoking deployer GOVERNOR_ROLE...`) await executeFn(issuanceAllocator, { account: deployer, functionName: 'revokeRole', args: [GOVERNOR_ROLE, deployer], }) - env.showMessage(' ✓ revokeRole(GOVERNOR_ROLE) executed') + env.showMessage(` ✓ revokeRole(GOVERNOR_ROLE) executed`) } - env.showMessage(`\n✅ Governance transferred to ${governor}!\n`) - env.showMessage( - `⚠️ IMPORTANT: Deployer no longer has control. Only governance can modify ${Contracts.issuance.IssuanceAllocator.name}.\n`, - ) -} - -func.tags = Tags.issuanceTransfer -func.dependencies = [actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.CONFIGURE)] + // Transfer ProxyAdmin ownership to governor + await transferProxyAdminOwnership(env, Contracts.issuance.IssuanceAllocator) -export default func + env.showMessage(`\n✅ ${Contracts.issuance.IssuanceAllocator.name} governance transferred!\n`) +}) diff --git a/packages/deployment/deploy/allocate/allocator/07_activate.ts b/packages/deployment/deploy/allocate/allocator/07_activate.ts deleted file mode 100644 index 4d189166e..000000000 --- a/packages/deployment/deploy/allocate/allocator/07_activate.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { GRAPH_TOKEN_ABI, ISSUANCE_TARGET_ABI, REWARDS_MANAGER_ABI } from '@graphprotocol/deployment/lib/abis.js' -import { getTargetChainIdFromEnv } from '@graphprotocol/deployment/lib/address-book-utils.js' -import { requireRewardsManagerUpgraded } from '@graphprotocol/deployment/lib/contract-checks.js' -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { getGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' -import { ComponentTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { createGovernanceTxBuilder, saveGovernanceTxAndExit } from '@graphprotocol/deployment/lib/execute-governance.js' -import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' -import type { PublicClient } from 'viem' -import { encodeFunctionData } from 'viem' - -/** - * Activate ${Contracts.issuance.IssuanceAllocator.name} in the protocol (governance account) - * - * Steps 8-10 from IssuanceAllocator.md: - * - Configure RewardsManager to use IssuanceAllocator - * - Grant minter role to IssuanceAllocator on GraphToken - * - (Optional) Set default target for unallocated issuance - * - * Idempotent: checks on-chain state, skips if already activated. - * Generates Safe TX batch for governance execution. - * Does NOT execute - governance must execute via Safe or deploy:execute-governance. - * - * Usage: - * pnpm hardhat deploy --tags issuance-activation --network - */ -const func: DeployScriptModule = async (env) => { - const deployer = requireDeployer(env) - - // Get protocol governor from Controller - const governor = await getGovernor(env) - - const [issuanceAllocator, rewardsManager, graphToken] = requireContracts(env, [ - Contracts.issuance.IssuanceAllocator, - Contracts.horizon.RewardsManager, - Contracts.horizon.L2GraphToken, - ]) - - const iaAddress = issuanceAllocator.address - const rmAddress = rewardsManager.address - const gtAddress = graphToken.address - - // Create viem client for direct contract calls - const client = graph.getPublicClient(env) as PublicClient - - // Check if RewardsManager supports IIssuanceTarget (has been upgraded) - // Throws error if not upgraded - await requireRewardsManagerUpgraded(client, rmAddress, env) - - const targetChainId = await getTargetChainIdFromEnv(env) - - env.showMessage(`\n========== Activate ${Contracts.issuance.IssuanceAllocator.name} ==========`) - env.showMessage(`Network: ${env.name} (chainId=${targetChainId})`) - env.showMessage(`Deployer: ${deployer}`) - env.showMessage(`Protocol Governor (from Controller): ${governor}`) - env.showMessage(`${Contracts.issuance.IssuanceAllocator.name}: ${iaAddress}`) - env.showMessage(`${Contracts.horizon.RewardsManager.name}: ${rmAddress}`) - env.showMessage(`${Contracts.horizon.L2GraphToken.name}: ${gtAddress}\n`) - - // Check current state - env.showMessage('📋 Checking current activation state...\n') - - const checks = { - iaIntegrated: false, - iaMinter: false, - } - - // Step 8: Check RM.getIssuanceAllocator() == IA - // Note: Use viem directly because synced deployments have empty ABIs - const currentIA = (await client.readContract({ - address: rmAddress as `0x${string}`, - abi: REWARDS_MANAGER_ABI, - functionName: 'getIssuanceAllocator', - })) as string - checks.iaIntegrated = currentIA.toLowerCase() === iaAddress.toLowerCase() - env.showMessage(` IA integrated: ${checks.iaIntegrated ? '✓' : '✗'} (current: ${currentIA})`) - - // Step 9: Check GraphToken.isMinter(IA) - checks.iaMinter = (await client.readContract({ - address: gtAddress as `0x${string}`, - abi: GRAPH_TOKEN_ABI, - functionName: 'isMinter', - args: [iaAddress as `0x${string}`], - })) as boolean - env.showMessage(` IA minter: ${checks.iaMinter ? '✓' : '✗'}`) - - // All checks passed? - const allPassed = Object.values(checks).every(Boolean) - if (allPassed) { - env.showMessage(`\n✅ ${Contracts.issuance.IssuanceAllocator.name} already activated\n`) - return - } - - // Build TX batch for missing activation steps - env.showMessage('\n🔨 Building activation TX batch...\n') - - const builder = await createGovernanceTxBuilder(env, `activate-${Contracts.issuance.IssuanceAllocator.name}`) - - // Step 8: RM.setIssuanceAllocator(IA) - if (!checks.iaIntegrated) { - const data = encodeFunctionData({ - abi: ISSUANCE_TARGET_ABI, - functionName: 'setIssuanceAllocator', - args: [iaAddress as `0x${string}`], - }) - builder.addTx({ to: rmAddress, value: '0', data }) - env.showMessage(` + RewardsManager.setIssuanceAllocator(${iaAddress})`) - } - - // Step 9: GraphToken.addMinter(IA) - if (!checks.iaMinter) { - const data = encodeFunctionData({ - abi: GRAPH_TOKEN_ABI, - functionName: 'addMinter', - args: [iaAddress as `0x${string}`], - }) - builder.addTx({ to: gtAddress, value: '0', data }) - env.showMessage(` + GraphToken.addMinter(${iaAddress})`) - } - - saveGovernanceTxAndExit(env, builder, `${Contracts.issuance.IssuanceAllocator.name} activation`) -} - -func.tags = Tags.issuanceActivation -func.dependencies = [ComponentTags.VERIFY_GOVERNANCE, ComponentTags.REWARDS_MANAGER_DEPLOY] // Run after governance transfer and verification (steps 6-7) - -export default func diff --git a/packages/deployment/deploy/allocate/allocator/08_allocation.ts b/packages/deployment/deploy/allocate/allocator/08_allocation.ts deleted file mode 100644 index 9b18ae5c8..000000000 --- a/packages/deployment/deploy/allocate/allocator/08_allocation.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - checkIssuanceAllocatorActivation, - isRewardsManagerUpgraded, -} from '@graphprotocol/deployment/lib/contract-checks.js' -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { ComponentTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireContracts } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' -import type { PublicClient } from 'viem' - -/** - * Full IssuanceAllocator deployment - deploy, configure, transfer governance, verify, and activate - * - * This is the aggregate tag for complete IssuanceAllocator setup (IssuanceAllocator.md steps 1-10): - * 1. Deploy IssuanceAllocator proxy and implementation (deployer has initial GOVERNOR_ROLE) - * 2-3. Configure: set rate, RM allocation (deployer executes) - * 4-5. (Optional upgrade steps via governance) - * 6. Transfer governance: grant roles to governance, revoke from deployer (deployer executes) - * 7. Verify: bytecode, access control, configuration (automated verification) - * 8-10. Generate governance TX for activation: RM integration, minter role (governance must execute) - * - * Requires: - * - RewardsManager to be upgraded first (supports IIssuanceTarget) - * - Governance to execute activation TX (steps 8-10) via Safe or deploy:execute-governance - * - * Usage: - * pnpm hardhat deploy --tags issuance-allocation --network - */ -const func: DeployScriptModule = async (env) => { - const [issuanceAllocator, rewardsManager, graphToken] = requireContracts(env, [ - Contracts.issuance.IssuanceAllocator, - Contracts.horizon.RewardsManager, - Contracts.horizon.L2GraphToken, - ]) - - // Verify RM has been upgraded (supports IERC165) - const client = graph.getPublicClient(env) as PublicClient - const upgraded = await isRewardsManagerUpgraded(client, rewardsManager.address) - if (!upgraded) { - env.showMessage( - `\n❌ ${Contracts.horizon.RewardsManager.name} not upgraded - run deploy:execute-governance first\n`, - ) - process.exit(1) - } - - // Verify activation state - const activation = await checkIssuanceAllocatorActivation( - client, - issuanceAllocator.address, - rewardsManager.address, - graphToken.address, - ) - - if (!activation.iaIntegrated || !activation.iaMinter) { - env.showMessage(`\n❌ ${Contracts.issuance.IssuanceAllocator.name} not fully activated`) - env.showMessage( - ` IA integrated with ${Contracts.horizon.RewardsManager.name}: ${activation.iaIntegrated ? '✓' : '✗'}`, - ) - env.showMessage(` IA has minter role: ${activation.iaMinter ? '✓' : '✗'}\n`) - process.exit(1) - } - - env.showMessage(`\n✅ ${Contracts.issuance.IssuanceAllocator.name} fully deployed, configured, and activated\n`) -} - -func.tags = Tags.issuanceAllocation -func.dependencies = [ComponentTags.REWARDS_MANAGER, ComponentTags.ISSUANCE_ALLOCATOR, ComponentTags.ISSUANCE_ACTIVATION] - -export default func diff --git a/packages/deployment/deploy/allocate/allocator/09_end.ts b/packages/deployment/deploy/allocate/allocator/09_end.ts new file mode 100644 index 000000000..272c2915e --- /dev/null +++ b/packages/deployment/deploy/allocate/allocator/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.issuance.IssuanceAllocator) diff --git a/packages/deployment/deploy/allocate/allocator/10_status.ts b/packages/deployment/deploy/allocate/allocator/10_status.ts new file mode 100644 index 000000000..23df5d817 --- /dev/null +++ b/packages/deployment/deploy/allocate/allocator/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.issuance.IssuanceAllocator) diff --git a/packages/deployment/deploy/allocate/default/01_deploy.ts b/packages/deployment/deploy/allocate/default/01_deploy.ts new file mode 100644 index 000000000..311c11b1b --- /dev/null +++ b/packages/deployment/deploy/allocate/default/01_deploy.ts @@ -0,0 +1,39 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { ComponentTags, DeploymentActions, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { deployProxyContract, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import type { DeployScriptModule } from '@rocketh/core/types' + +/** + * Deploy DefaultAllocation proxy — IA's default target for unallocated issuance + * + * Uses the shared DirectAllocation_Implementation. + * Initialized with deployer as governor (transferred in transfer step). + * + * Usage: + * pnpm hardhat deploy --tags DefaultAllocation,deploy --network + */ +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.DEPLOY)) return + await syncComponentsFromRegistry(env, [ + Contracts.issuance.DirectAllocation_Implementation, + Contracts.issuance.DefaultAllocation, + ]) + + env.showMessage(`\n📦 Deploying DefaultAllocation proxy...`) + env.showMessage(` Shared implementation: ${Contracts.issuance.DirectAllocation_Implementation.name}`) + + await deployProxyContract(env, { + contract: Contracts.issuance.DefaultAllocation, + sharedImplementation: Contracts.issuance.DirectAllocation_Implementation, + initializeArgs: [requireDeployer(env)], + }) + + env.showMessage('\n✓ DefaultAllocation deployment complete') +} + +func.tags = [ComponentTags.DEFAULT_ALLOCATION] +func.dependencies = [ComponentTags.DIRECT_ALLOCATION_IMPL] +func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) + +export default func diff --git a/packages/deployment/deploy/allocate/default/02_upgrade.ts b/packages/deployment/deploy/allocate/default/02_upgrade.ts new file mode 100644 index 000000000..2bb15a1da --- /dev/null +++ b/packages/deployment/deploy/allocate/default/02_upgrade.ts @@ -0,0 +1,27 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { ComponentTags, DeploymentActions, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { upgradeImplementation } from '@graphprotocol/deployment/lib/upgrade-implementation.js' +import type { DeployScriptModule } from '@rocketh/core/types' + +// DefaultAllocation Upgrade +// +// Upgrades DefaultAllocation proxy to DirectAllocation implementation via per-proxy ProxyAdmin. + +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.UPGRADE)) return + await syncComponentsFromRegistry(env, [ + Contracts.issuance.DirectAllocation_Implementation, + Contracts.issuance.DefaultAllocation, + ]) + await upgradeImplementation(env, Contracts.issuance.DefaultAllocation, { + implementationName: 'DirectAllocation', + }) + await syncComponentsFromRegistry(env, [Contracts.issuance.DefaultAllocation]) +} + +func.tags = [ComponentTags.DEFAULT_ALLOCATION] +func.dependencies = [ComponentTags.DIRECT_ALLOCATION_IMPL] +func.skip = async () => shouldSkipAction(DeploymentActions.UPGRADE) + +export default func diff --git a/packages/deployment/deploy/allocate/default/04_configure.ts b/packages/deployment/deploy/allocate/default/04_configure.ts new file mode 100644 index 000000000..528531ff6 --- /dev/null +++ b/packages/deployment/deploy/allocate/default/04_configure.ts @@ -0,0 +1,119 @@ +import { ACCESS_CONTROL_ENUMERABLE_ABI } from '@graphprotocol/deployment/lib/abis.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { getGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContract, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { checkDefaultAllocationConfigured } from '@graphprotocol/deployment/lib/preconditions.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph, read, tx } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' +import { encodeFunctionData } from 'viem' + +/** + * Configure DefaultAllocation + * + * - Grants GOVERNOR_ROLE to protocol governor + * - Grants PAUSE_ROLE to pause guardian + * + * Note: IA.setDefaultTarget(DA) is an activation step in issuance-connect, + * not a configure step (requires IA to have minter role). + * + * Idempotent: checks on-chain state, skips if already configured. + * + * Usage: + * pnpm hardhat deploy --tags DefaultAllocation,configure --network + */ +export default createActionModule(Contracts.issuance.DefaultAllocation, DeploymentActions.CONFIGURE, async (env) => { + const client = graph.getPublicClient(env) as PublicClient + const readFn = read(env) + const deployer = requireDeployer(env) + const governor = await getGovernor(env) + const pauseGuardian = await getPauseGuardian(env) + + const defaultAllocation = requireContract(env, Contracts.issuance.DefaultAllocation) + + env.showMessage(`\n========== Configure ${Contracts.issuance.DefaultAllocation.name} ==========`) + env.showMessage(`DefaultAllocation: ${defaultAllocation.address}`) + + // Check if already configured (shared precondition check) + const precondition = await checkDefaultAllocationConfigured( + client, + defaultAllocation.address, + governor, + pauseGuardian, + ) + if (precondition.done) { + env.showMessage(`\n✅ ${Contracts.issuance.DefaultAllocation.name} already configured\n`) + return + } + + env.showMessage('\n📋 Checking current configuration...\n') + + const GOVERNOR_ROLE = (await readFn(defaultAllocation, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` + const PAUSE_ROLE = (await readFn(defaultAllocation, { functionName: 'PAUSE_ROLE' })) as `0x${string}` + + const governorHasRole = (await client.readContract({ + address: defaultAllocation.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [GOVERNOR_ROLE, governor as `0x${string}`], + })) as boolean + env.showMessage(` Governor GOVERNOR_ROLE: ${governorHasRole ? '✓' : '✗'}`) + + const pauseGuardianHasRole = (await client.readContract({ + address: defaultAllocation.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [PAUSE_ROLE, pauseGuardian as `0x${string}`], + })) as boolean + env.showMessage(` PauseGuardian PAUSE_ROLE: ${pauseGuardianHasRole ? '✓' : '✗'}`) + + const deployerHasRole = (await client.readContract({ + address: defaultAllocation.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [GOVERNOR_ROLE, deployer as `0x${string}`], + })) as boolean + + const txs: Array<{ to: string; data: `0x${string}`; label: string }> = [] + + if (!governorHasRole) { + txs.push({ + to: defaultAllocation.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [GOVERNOR_ROLE, governor as `0x${string}`], + }), + label: `grantRole(GOVERNOR_ROLE, ${governor})`, + }) + } + + if (!pauseGuardianHasRole) { + txs.push({ + to: defaultAllocation.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [PAUSE_ROLE, pauseGuardian as `0x${string}`], + }), + label: `grantRole(PAUSE_ROLE, ${pauseGuardian})`, + }) + } + + if (!deployerHasRole) { + env.showMessage(`\n ○ Deployer does not have GOVERNOR_ROLE — skipping (governance TX in upgrade step)\n`) + return + } + + if (txs.length === 0) return + + env.showMessage('\n🔨 Executing role grants as deployer...\n') + const txFn = tx(env) + for (const t of txs) { + await txFn({ account: deployer, to: t.to as `0x${string}`, data: t.data }) + env.showMessage(` ✓ ${t.label}`) + } + + env.showMessage(`\n✅ ${Contracts.issuance.DefaultAllocation.name} configuration complete!\n`) +}) diff --git a/packages/deployment/deploy/allocate/default/05_transfer_governance.ts b/packages/deployment/deploy/allocate/default/05_transfer_governance.ts new file mode 100644 index 000000000..af5bcd8e6 --- /dev/null +++ b/packages/deployment/deploy/allocate/default/05_transfer_governance.ts @@ -0,0 +1,51 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + requireContract, + requireDeployer, + transferProxyAdminOwnership, +} from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { checkDeployerRevoked } from '@graphprotocol/deployment/lib/preconditions.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { execute, graph, read } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Transfer DefaultAllocation governance from deployer + * + * - Revoke GOVERNOR_ROLE from deployment account + * - Transfer ProxyAdmin ownership to governor + * + * Role grants happen in 04_configure.ts. + * + * Usage: + * pnpm hardhat deploy --tags DefaultAllocation,transfer --network + */ +export default createActionModule(Contracts.issuance.DefaultAllocation, DeploymentActions.TRANSFER, async (env) => { + const readFn = read(env) + const executeFn = execute(env) + const client = graph.getPublicClient(env) as PublicClient + const deployer = requireDeployer(env) + const da = requireContract(env, Contracts.issuance.DefaultAllocation) + + env.showMessage(`\n========== Transfer ${Contracts.issuance.DefaultAllocation.name} ==========`) + + const precondition = await checkDeployerRevoked(client, da.address, deployer) + if (precondition.done) { + env.showMessage(`✓ Deployer GOVERNOR_ROLE already revoked`) + } else { + const GOVERNOR_ROLE = (await readFn(da, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` + + env.showMessage(`🔨 Revoking deployer GOVERNOR_ROLE...`) + await executeFn(da, { + account: deployer, + functionName: 'revokeRole', + args: [GOVERNOR_ROLE, deployer], + }) + env.showMessage(` ✓ revokeRole(GOVERNOR_ROLE) executed`) + } + + await transferProxyAdminOwnership(env, Contracts.issuance.DefaultAllocation) + + env.showMessage(`\n✅ ${Contracts.issuance.DefaultAllocation.name} governance transferred!\n`) +}) diff --git a/packages/deployment/deploy/allocate/default/09_end.ts b/packages/deployment/deploy/allocate/default/09_end.ts new file mode 100644 index 000000000..cacd93b61 --- /dev/null +++ b/packages/deployment/deploy/allocate/default/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.issuance.DefaultAllocation) diff --git a/packages/deployment/deploy/allocate/default/10_status.ts b/packages/deployment/deploy/allocate/default/10_status.ts new file mode 100644 index 000000000..012cc8be3 --- /dev/null +++ b/packages/deployment/deploy/allocate/default/10_status.ts @@ -0,0 +1,10 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +/** + * DefaultAllocation status — show detailed state of the default allocation proxy + * + * Usage: + * pnpm hardhat deploy --tags DefaultAllocation --network + */ +export default createStatusModule(Contracts.issuance.DefaultAllocation) diff --git a/packages/deployment/deploy/allocate/direct/01_impl.ts b/packages/deployment/deploy/allocate/direct/01_impl.ts index 413fff317..6ff6d6a56 100644 --- a/packages/deployment/deploy/allocate/direct/01_impl.ts +++ b/packages/deployment/deploy/allocate/direct/01_impl.ts @@ -1,82 +1,74 @@ -import { getTargetChainIdFromEnv } from '@graphprotocol/deployment/lib/address-book-utils.js' import { loadDirectAllocationArtifact } from '@graphprotocol/deployment/lib/artifact-loaders.js' import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { SpecialTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { computeArtifactBytecodeHash } from '@graphprotocol/deployment/lib/deploy-implementation.js' +import { buildDeploymentMetadata } from '@graphprotocol/deployment/lib/deployment-metadata.js' +import { ComponentTags, DeploymentActions, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' import { requireDeployer, requireGraphToken, showDeploymentStatus, } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' import { deploy, graph } from '@graphprotocol/deployment/rocketh/deploy.js' import type { DeployScriptModule } from '@rocketh/core/types' /** * Deploy shared DirectAllocation implementation * - * This implementation is shared by all DirectAllocation proxies: - * - PilotAllocation - * - ReclaimAddress_Treasury - * - (other ReclaimAddress_* instances) + * This implementation is shared by all DirectAllocation proxies + * (DefaultAllocation, ReclaimedRewards). Runs during both deploy AND upgrade + * actions — deploying the implementation is a prerequisite for proxy upgrades. * - * Deploying once and sharing reduces gas costs and ensures all instances - * are on the same version. + * Rocketh handles idempotency: if bytecode is unchanged, no redeployment occurs. * * Usage: - * pnpm hardhat deploy --tags direct-allocation-impl --network + * pnpm hardhat deploy --tags DirectAllocation_Implementation,deploy --network */ - const func: DeployScriptModule = async (env) => { - const deployFn = deploy(env) + // Run for both deploy and upgrade actions + if (shouldSkipAction(DeploymentActions.DEPLOY) && shouldSkipAction(DeploymentActions.UPGRADE)) return - const deployer = requireDeployer(env) + await syncComponentsFromRegistry(env, [ + Contracts.issuance.DirectAllocation_Implementation, + Contracts.horizon.L2GraphToken, + ]) - // Require L2GraphToken from deployments JSON (Graph Token on L2) + const deployFn = deploy(env) + const deployer = requireDeployer(env) const graphTokenDep = requireGraphToken(env) env.showMessage(`\n📦 Deploying shared ${Contracts.issuance.DirectAllocation_Implementation.name}...`) const artifact = loadDirectAllocationArtifact() - const result = await deployFn( - Contracts.issuance.DirectAllocation_Implementation.name, - { - account: deployer, - artifact, - args: [graphTokenDep.address], - }, - { - skipIfAlreadyDeployed: true, - }, - ) + const result = await deployFn(Contracts.issuance.DirectAllocation_Implementation.name, { + account: deployer, + artifact, + args: [graphTokenDep.address], + }) - showDeploymentStatus(env, Contracts.issuance.DirectAllocation_Implementation, result) - - // Set pendingImplementation for all proxies that use DirectAllocation - // This allows the upgrade scripts to read from address book instead of deployment records - const targetChainId = await getTargetChainIdFromEnv(env) - const addressBook = graph.getIssuanceAddressBook(targetChainId) - - const proxiesToUpdate = [Contracts.issuance.PilotAllocation.name] - for (const proxyName of proxiesToUpdate) { - try { - const entry = addressBook.getEntry(proxyName as Parameters[0]) - if (entry) { - addressBook.setPendingImplementation( - proxyName as Parameters[0], - result.address, - { - txHash: result.transaction?.hash, - }, - ) - env.showMessage(` ✓ Set pendingImplementation for ${proxyName}`) - } - } catch { - // Entry doesn't exist yet - will be created by deploy script - env.showMessage(` - ${proxyName} not in address book yet, skipping`) + // Persist to address book — only write metadata on new deployments + // to avoid overwriting stored hash with current artifact when deploy was a no-op + if (result.newlyDeployed) { + const metadata = buildDeploymentMetadata( + result, + computeArtifactBytecodeHash(Contracts.issuance.DirectAllocation_Implementation.artifact!), + ) + if (metadata) { + await graph.updateIssuanceAddressBook(env, { + name: Contracts.issuance.DirectAllocation_Implementation.name, + address: result.address, + deployment: metadata, + }) } } + + showDeploymentStatus(env, Contracts.issuance.DirectAllocation_Implementation, result) + + await syncComponentsFromRegistry(env, [Contracts.issuance.DirectAllocation_Implementation]) } -func.tags = Tags.directAllocationImpl -func.dependencies = [SpecialTags.SYNC] +func.tags = [ComponentTags.DIRECT_ALLOCATION_IMPL] +func.dependencies = [] +func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) && shouldSkipAction(DeploymentActions.UPGRADE) export default func diff --git a/packages/deployment/deploy/allocate/pilot/01_deploy.ts b/packages/deployment/deploy/allocate/pilot/01_deploy.ts deleted file mode 100644 index b59104f8e..000000000 --- a/packages/deployment/deploy/allocate/pilot/01_deploy.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { - actionTag, - ComponentTags, - DeploymentActions, - SpecialTags, - Tags, -} from '@graphprotocol/deployment/lib/deployment-tags.js' -import { deployProxyContract } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * Deploy PilotAllocation proxy using shared DirectAllocation implementation - * - * This deploys PilotAllocation as an OZ v5 TransparentUpgradeableProxy pointing to - * the shared DirectAllocation_Implementation. All DirectAllocation proxies - * share one implementation for efficiency. - * - * Architecture: - * - Implementation: Shared DirectAllocation_Implementation - * - Proxy: OZ v5 TransparentUpgradeableProxy with atomic initialization - * - Admin: Per-proxy ProxyAdmin (created by OZ v5 proxy, owned by governor) - * - * Usage: - * pnpm hardhat deploy --tags pilot-allocation-deploy --network - */ - -const func: DeployScriptModule = async (env) => { - env.showMessage(`\n📦 Deploying ${Contracts.issuance.PilotAllocation.name}...`) - - await deployProxyContract(env, { - contract: Contracts.issuance.PilotAllocation, - sharedImplementation: Contracts.issuance.DirectAllocation_Implementation, - // initializeArgs defaults to [governor] - }) -} - -func.tags = Tags.pilotAllocationDeploy -func.dependencies = [ - SpecialTags.SYNC, - ComponentTags.DIRECT_ALLOCATION_IMPL, - actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.DEPLOY), -] - -export default func diff --git a/packages/deployment/deploy/allocate/pilot/02_upgrade.ts b/packages/deployment/deploy/allocate/pilot/02_upgrade.ts deleted file mode 100644 index 37e3aa593..000000000 --- a/packages/deployment/deploy/allocate/pilot/02_upgrade.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { upgradeImplementation } from '@graphprotocol/deployment/lib/upgrade-implementation.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -// PilotAllocation Upgrade -// -// Upgrades PilotAllocation proxy to DirectAllocation implementation via per-proxy ProxyAdmin. -// The implementation is shared across multiple allocation proxies. -// -// Workflow: -// 1. Check for pending implementation in address book (set by direct-allocation-impl) -// 2. Generate governance TX (upgradeAndCall to per-proxy ProxyAdmin) -// 3. Fork mode: execute via governor impersonation -// 4. Production: output TX file for Safe execution -// -// Usage: -// FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags pilot-allocation-upgrade --network localhost - -const func: DeployScriptModule = async (env) => { - await upgradeImplementation(env, Contracts.issuance.PilotAllocation, { - implementationName: 'DirectAllocation', - }) -} - -func.tags = Tags.pilotAllocationUpgrade -func.dependencies = [ - actionTag(ComponentTags.PILOT_ALLOCATION, DeploymentActions.DEPLOY), - ComponentTags.DIRECT_ALLOCATION_IMPL, -] - -export default func diff --git a/packages/deployment/deploy/allocate/pilot/04_configure.ts b/packages/deployment/deploy/allocate/pilot/04_configure.ts deleted file mode 100644 index 780ca72da..000000000 --- a/packages/deployment/deploy/allocate/pilot/04_configure.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { getGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireContracts } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { execute, read } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * Configure PilotAllocation as IssuanceAllocator target - * - * Sets up PilotAllocation to receive tokens via allocator-minting from IssuanceAllocator. - * This requires IssuanceAllocator to be configured (deployer has GOVERNOR_ROLE or governance). - * - * Idempotent: checks if already configured, skips if so. - * - * Usage: - * pnpm hardhat deploy --tags pilot-allocation-configure --network - */ -const func: DeployScriptModule = async (env) => { - const readFn = read(env) - const executeFn = execute(env) - - // Get protocol governor from Controller - const governor = await getGovernor(env) - - const [pilotAllocation, issuanceAllocator] = requireContracts(env, [ - Contracts.issuance.PilotAllocation, - Contracts.issuance.IssuanceAllocator, - ]) - - env.showMessage(`\n========== Configure ${Contracts.issuance.PilotAllocation.name} ==========`) - env.showMessage(`${Contracts.issuance.PilotAllocation.name}: ${pilotAllocation.address}`) - env.showMessage(`${Contracts.issuance.IssuanceAllocator.name}: ${issuanceAllocator.address}`) - - // Check current allocation - try { - const allocation = (await readFn(issuanceAllocator, { - functionName: 'getTargetAllocation', - args: [pilotAllocation.address], - })) as [bigint, bigint, bigint] - - if (allocation[1] > 0n || allocation[2] > 0n) { - env.showMessage(`\n✓ ${Contracts.issuance.PilotAllocation.name} already configured as target`) - env.showMessage(` allocatorMintingRate: ${allocation[1]}`) - env.showMessage(` selfMintingRate: ${allocation[2]}`) - return - } - } catch { - // Not configured yet - } - - // Get current issuance rate to determine allocation - const issuancePerBlock = (await readFn(issuanceAllocator, { functionName: 'getIssuancePerBlock' })) as bigint - if (issuancePerBlock === 0n) { - env.showMessage( - `\n⚠️ ${Contracts.issuance.IssuanceAllocator.name} rate is 0, cannot configure ${Contracts.issuance.PilotAllocation.name} allocation`, - ) - env.showMessage(` Configure ${Contracts.issuance.IssuanceAllocator.name} first with setIssuancePerBlock()`) - return - } - - // Configure PilotAllocation with allocator-minting (IA mints to it) - // Default: small allocation for pilot testing - const pilotRate = issuancePerBlock / 100n // 1% of total issuance - - env.showMessage(`\n🔨 Configuring ${Contracts.issuance.PilotAllocation.name}...`) - env.showMessage(` Setting allocatorMintingRate: ${pilotRate} (1% of ${issuancePerBlock})`) - - try { - await executeFn(issuanceAllocator, { - account: governor, - functionName: 'setTargetAllocation', - args: [pilotAllocation.address, pilotRate, 0n], // allocatorMintingRate, selfMintingRate (PA doesn't self-mint) - }) - env.showMessage( - `\n✅ ${Contracts.issuance.PilotAllocation.name} configured as ${Contracts.issuance.IssuanceAllocator.name} target`, - ) - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - env.showMessage(`\n⚠️ Configuration failed: ${errorMessage.slice(0, 100)}...`) - env.showMessage(` This may require governance execution if deployer no longer has GOVERNOR_ROLE`) - } -} - -func.tags = Tags.pilotAllocationConfigure -func.dependencies = [ - actionTag(ComponentTags.PILOT_ALLOCATION, DeploymentActions.UPGRADE), - actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.CONFIGURE), -] - -export default func diff --git a/packages/deployment/deploy/allocate/pilot/09_end.ts b/packages/deployment/deploy/allocate/pilot/09_end.ts deleted file mode 100644 index 750e34f17..000000000 --- a/packages/deployment/deploy/allocate/pilot/09_end.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireUpgradeExecuted } from '@graphprotocol/deployment/lib/execute-governance.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * PilotAllocation end state - deployed, upgraded, and configured - * - * Aggregate tag that ensures PilotAllocation is fully ready: - * - Proxy and implementation deployed - * - Proxy upgraded to latest implementation - * - Configured as IssuanceAllocator target - * - * Usage: - * pnpm hardhat deploy --tags pilot-allocation --network - */ -const func: DeployScriptModule = async (env) => { - requireUpgradeExecuted(env, 'PilotAllocation') - env.showMessage(`\n✓ PilotAllocation ready`) -} - -func.tags = Tags.pilotAllocation -func.dependencies = [ - actionTag(ComponentTags.PILOT_ALLOCATION, DeploymentActions.DEPLOY), - actionTag(ComponentTags.PILOT_ALLOCATION, DeploymentActions.UPGRADE), - actionTag(ComponentTags.PILOT_ALLOCATION, DeploymentActions.CONFIGURE), -] - -export default func diff --git a/packages/deployment/deploy/common/00_sync.ts b/packages/deployment/deploy/common/00_sync.ts index 25be17d3e..de4ff446f 100644 --- a/packages/deployment/deploy/common/00_sync.ts +++ b/packages/deployment/deploy/common/00_sync.ts @@ -1,131 +1,25 @@ -import { existsSync } from 'node:fs' - -import { - getForkNetwork, - getForkStateDir, - getIssuanceAddressBookPath, -} from '@graphprotocol/deployment/lib/address-book-utils.js' -import { - type AddressBookType, - getContractMetadata, - getContractsByAddressBook, -} from '@graphprotocol/deployment/lib/contract-registry.js' import { SpecialTags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { - type AddressBookGroup, - buildContractSpec, - type ContractSpec, - syncContractGroups, -} from '@graphprotocol/deployment/lib/sync-utils.js' -import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import { runFullSync } from '@graphprotocol/deployment/lib/sync-utils.js' import type { DeployScriptModule } from '@rocketh/core/types' -// Sync - Synchronization between on-chain state and address books +// Sync — full reconciliation between on-chain state and address books. // -// For each address book (Horizon, SubgraphService, Issuance): -// - Sync proxy implementations with on-chain state +// For every deployable contract in every address book (Horizon, SubgraphService, +// Issuance): +// - Reconcile proxy implementations with on-chain state // - Import contract addresses into rocketh deployment records // - Validate prerequisites exist on-chain - -// Helper to filter deployable contracts from registry -function getDeployableContracts(addressBook: AddressBookType) { - return getContractsByAddressBook(addressBook) - .filter(([_, metadata]) => metadata.deployable !== false) - .map(([name]) => name) -} +// +// This script is the only one tagged with `SpecialTags.SYNC`. It runs when: +// - The user invokes `npx hardhat deploy --tags sync` directly +// - The `deploy:sync` Hardhat task is run (which delegates to the above) +// +// Per-component actions sync the contracts they touch immediately before and +// after their work, so this full sync is no longer required as an automatic +// dependency on every deployment script. const func: DeployScriptModule = async (env) => { - // Get chainId from provider (will be 31337 in fork mode) - const chainIdHex = await env.network.provider.request({ method: 'eth_chainId' }) - const providerChainId = Number(chainIdHex) - - // Determine target chain ID for address book lookups - const forkNetwork = getForkNetwork() - const isForking = graph.isForkMode() - const forkChainId = graph.getForkTargetChainId() - const targetChainId = forkChainId ?? providerChainId - - // Check for common misconfiguration: localhost without FORK_NETWORK - if (providerChainId === 31337 && !forkNetwork) { - throw new Error( - `Running on localhost (chainId 31337) without FORK_NETWORK set.\n\n` + - `If you're testing against a forked network, set the environment variable:\n` + - ` export FORK_NETWORK=arbitrumSepolia\n` + - ` npx hardhat deploy --tags sync --network localhost\n\n` + - `Or use ephemeral fork mode:\n` + - ` HARDHAT_FORK=arbitrumSepolia npx hardhat deploy --tags sync`, - ) - } - - if (forkNetwork) { - const forkStateDir = getForkStateDir(env.name, forkNetwork) - env.showMessage(`\n🔄 Sync: ${forkNetwork} fork (chainId: ${targetChainId})`) - env.showMessage(` Using fork-local address books (${forkStateDir}/)`) - } else { - env.showMessage(`\n🔄 Sync: ${env.name} (chainId: ${providerChainId})`) - } - - // Get address books (automatically uses fork-local copies in fork mode) - const horizonAddressBook = graph.getHorizonAddressBook(targetChainId) - const ssAddressBook = graph.getSubgraphServiceAddressBook(targetChainId) - - // Build contract groups - const groups: AddressBookGroup[] = [] - - // --- Horizon contracts --- - const horizonContracts: ContractSpec[] = getDeployableContracts('horizon').map((name) => { - const metadata = getContractMetadata('horizon', name) - if (!metadata) throw new Error(`Contract ${name} not found in horizon registry`) - return buildContractSpec('horizon', name, metadata, horizonAddressBook, targetChainId) - }) - groups.push({ label: 'Horizon', contracts: horizonContracts, addressBook: horizonAddressBook }) - - // --- SubgraphService contracts --- - const ssContracts: ContractSpec[] = getDeployableContracts('subgraph-service').map((name) => { - const metadata = getContractMetadata('subgraph-service', name) - if (!metadata) throw new Error(`Contract ${name} not found in subgraph-service registry`) - return buildContractSpec('subgraph-service', name, metadata, ssAddressBook, targetChainId) - }) - groups.push({ label: 'SubgraphService', contracts: ssContracts, addressBook: ssAddressBook }) - - // --- Issuance contracts --- - // Show all issuance contracts from registry (even if not deployed yet) - const issuanceBookPath = getIssuanceAddressBookPath() - const issuanceAddressBook = existsSync(issuanceBookPath) ? graph.getIssuanceAddressBook(targetChainId) : null - - if (issuanceAddressBook) { - // Show all deployable issuance contracts from registry (even if not deployed yet) - const issuanceContracts: ContractSpec[] = getDeployableContracts('issuance').map((name) => { - const metadata = getContractMetadata('issuance', name) - if (!metadata) throw new Error(`Contract ${name} not found in issuance registry`) - return buildContractSpec('issuance', name, metadata, issuanceAddressBook, targetChainId) - }) - - if (issuanceContracts.length > 0) { - groups.push({ label: 'Issuance', contracts: issuanceContracts, addressBook: issuanceAddressBook }) - } - } - - // Sync all contract groups - const result = await syncContractGroups(env, groups) - - if (!result.success) { - env.showMessage(`\n❌ Sync failed: address book does not match chain state.\n`) - env.showMessage(`The following contracts are in address book but have no code on-chain:`) - env.showMessage(` ${result.failures.join(', ')}\n`) - if (isForking) { - env.showMessage(`This is likely because the fork was restarted.\n`) - env.showMessage(`To fix, reset fork state and re-run:`) - env.showMessage(` npx hardhat deploy:reset-fork --network localhost`) - } else { - env.showMessage(`Possible causes:`) - env.showMessage(` 1. Address book has incorrect addresses for this network`) - env.showMessage(` 2. Running against wrong network`) - } - process.exit(1) - } - - env.showMessage(`\n✅ Sync complete: ${result.totalSynced} contracts synced\n`) + await runFullSync(env) } func.tags = [SpecialTags.SYNC] diff --git a/packages/deployment/deploy/gip/0088/09_end.ts b/packages/deployment/deploy/gip/0088/09_end.ts new file mode 100644 index 000000000..2cb8b7fda --- /dev/null +++ b/packages/deployment/deploy/gip/0088/09_end.ts @@ -0,0 +1,114 @@ +import { PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, REWARDS_MANAGER_ABI } from '@graphprotocol/deployment/lib/abis.js' +import { + addressEquals, + checkIssuanceAllocatorActivation, + isRewardsManagerUpgraded, +} from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { getResolvedSettingsForEnv } from '@graphprotocol/deployment/lib/deployment-config.js' +import { DeploymentActions, GoalTags, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContracts } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { DeployScriptModule } from '@rocketh/core/types' +import type { PublicClient } from 'viem' + +/** + * GIP-0088,all — Full GIP-0088 deployment verification + * + * Verifies all non-optional phases are complete: + * - Upgrade: RM upgraded (supports IIssuanceTarget) + * - Eligibility: REO integrated with RM, revertOnIneligible matches config + * - Issuance: IA connected to RM, minter role granted + * + * Does NOT verify optional goals (issuance-close-guard). + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088,all --network + */ +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.ALL)) return + await syncComponentsFromRegistry(env, [ + Contracts.issuance.IssuanceAllocator, + Contracts.horizon.RewardsManager, + Contracts.horizon.L2GraphToken, + Contracts.issuance.RewardsEligibilityOracleA, + ]) + const [issuanceAllocator, rewardsManager, graphToken] = requireContracts(env, [ + Contracts.issuance.IssuanceAllocator, + Contracts.horizon.RewardsManager, + Contracts.horizon.L2GraphToken, + ]) + + const client = graph.getPublicClient(env) as PublicClient + const failures: string[] = [] + + // Verify RM has been upgraded (supports IERC165) + const upgraded = await isRewardsManagerUpgraded(client, rewardsManager.address) + if (!upgraded) { + env.showMessage(`\n❌ ${Contracts.horizon.RewardsManager.name} not upgraded - run GIP-0088:upgrade,upgrade first\n`) + process.exit(1) + } + + // Verify IA activation state (issuance phase) + const activation = await checkIssuanceAllocatorActivation( + client, + issuanceAllocator.address, + rewardsManager.address, + graphToken.address, + ) + + if (!activation.iaIntegrated) failures.push('IA not integrated with RM') + if (!activation.iaMinter) failures.push('IA missing minter role') + + // Verify REO integration (eligibility phase) + const reo = env.getOrNull(Contracts.issuance.RewardsEligibilityOracleA.name) + if (reo) { + const currentOracle = (await client.readContract({ + address: rewardsManager.address as `0x${string}`, + abi: PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + functionName: 'getProviderEligibilityOracle', + })) as string + if (!addressEquals(currentOracle, reo.address)) { + failures.push('REO not integrated with RM') + } + } else { + failures.push('RewardsEligibilityOracleA not deployed') + } + + // Verify revertOnIneligible matches config + const settings = await getResolvedSettingsForEnv(env) + const desiredRevert = settings.rewardsManager.revertOnIneligible + try { + const onChainRevert = (await client.readContract({ + address: rewardsManager.address as `0x${string}`, + abi: REWARDS_MANAGER_ABI, + functionName: 'getRevertOnIneligible', + })) as boolean + if (onChainRevert !== desiredRevert) { + failures.push(`revertOnIneligible mismatch: on-chain=${onChainRevert}, config=${desiredRevert}`) + } + } catch { + failures.push('RM does not support getRevertOnIneligible (not upgraded?)') + } + + if (failures.length > 0) { + env.showMessage(`\n❌ GIP-0088 incomplete:`) + for (const f of failures) env.showMessage(` - ${f}`) + env.showMessage('') + process.exit(1) + } + + env.showMessage(`\n✅ GIP-0088 complete: all contracts deployed, upgraded, and configured\n`) +} + +func.tags = [GoalTags.GIP_0088] +func.dependencies = [ + GoalTags.GIP_0088_UPGRADE, + GoalTags.GIP_0088_ELIGIBILITY_INTEGRATE, + GoalTags.GIP_0088_ISSUANCE_CONNECT, + GoalTags.GIP_0088_ISSUANCE_ALLOCATE, +] +func.skip = async () => shouldSkipAction(DeploymentActions.ALL) + +export default func diff --git a/packages/deployment/deploy/gip/0088/10_status.ts b/packages/deployment/deploy/gip/0088/10_status.ts new file mode 100644 index 000000000..8da7509a8 --- /dev/null +++ b/packages/deployment/deploy/gip/0088/10_status.ts @@ -0,0 +1,179 @@ +import { + IISSUANCE_TARGET_INTERFACE_ID, + ISSUANCE_TARGET_ABI, + PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + SUBGRAPH_SERVICE_CLOSE_GUARD_ABI, +} from '@graphprotocol/deployment/lib/abis.js' +import { getAddressBookForType, getTargetChainIdFromEnv } from '@graphprotocol/deployment/lib/address-book-utils.js' +import { addressEquals, isRewardsManagerUpgraded } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts, type RegistryEntry } from '@graphprotocol/deployment/lib/contract-registry.js' +import { GoalTags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { showDetailedComponentStatus, showPendingGovernanceTxs } from '@graphprotocol/deployment/lib/status-detail.js' +import { getContractStatusLine, syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * GIP-0088 Status — Phase-structured deployment state display + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088 --network + */ +export default createStatusModule(GoalTags.GIP_0088, async (env) => { + // Sync the contracts this status touches via env.getOrNull so the read paths + // work without depending on a separate global sync run. + await syncComponentsFromRegistry(env, [ + Contracts.horizon.RewardsManager, + Contracts.horizon.L2GraphToken, + Contracts['subgraph-service'].SubgraphService, + Contracts.issuance.IssuanceAllocator, + Contracts.issuance.RewardsEligibilityOracleA, + Contracts.issuance.RecurringAgreementManager, + ]) + + const client = graph.getPublicClient(env) as PublicClient + const targetChainId = await getTargetChainIdFromEnv(env) + + env.showMessage('\n========== GIP-0088: Full Deployment Status ==========') + + // --- Upgrade phase --- + env.showMessage('\nUpgrade:') + + const upgradeContracts: RegistryEntry[] = [ + Contracts.horizon.RewardsManager, + Contracts.horizon.HorizonStaking, + Contracts['subgraph-service'].SubgraphService, + Contracts['subgraph-service'].DisputeManager, + Contracts.horizon.PaymentsEscrow, + Contracts.horizon.L2Curation, + Contracts.horizon.RecurringCollector, + ] + + const rm = env.getOrNull('RewardsManager') + + for (const contract of upgradeContracts) { + const ab = getAddressBookForType(contract.addressBook, targetChainId) + + const result = await getContractStatusLine(client, contract.addressBook, ab, contract.name) + env.showMessage(` ${result.line}`) + + // RM: semantic check — does the on-chain code support IIssuanceTarget? + if (contract === Contracts.horizon.RewardsManager && result.exists && rm) { + const upgraded = await isRewardsManagerUpgraded(client, rm.address) + env.showMessage(` ${upgraded ? '✓' : '✗'} implements IIssuanceTarget (${IISSUANCE_TARGET_INTERFACE_ID})`) + } + } + + // --- Eligibility phase --- + env.showMessage('\nEligibility:') + await showDetailedComponentStatus(env, Contracts.issuance.RewardsEligibilityOracleA, { showHints: false }) + + // --- Issuance phase --- + env.showMessage('\nIssuance:') + await showDetailedComponentStatus(env, Contracts.issuance.IssuanceAllocator, { showHints: false }) + + const ram = env.getOrNull('RecurringAgreementManager') + if (ram) { + await showDetailedComponentStatus(env, Contracts.issuance.RecurringAgreementManager, { showHints: false }) + } else { + env.showMessage(` ○ RecurringAgreementManager not deployed`) + } + + // --- Activation status --- + env.showMessage('\n--- Activation ---') + + // eligibility-integrate: RM.providerEligibilityOracle == REO_A + if (rm) { + const upgraded = await isRewardsManagerUpgraded(client, rm.address) + if (upgraded) { + const reo = env.getOrNull(Contracts.issuance.RewardsEligibilityOracleA.name) + const currentOracle = (await client.readContract({ + address: rm.address as `0x${string}`, + abi: PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + functionName: 'getProviderEligibilityOracle', + })) as string + + if (reo) { + const integrated = addressEquals(currentOracle, reo.address) + env.showMessage(` ${integrated ? '✓' : '✗'} eligibility-integrate: RM.providerEligibilityOracle == REO_A`) + } else { + env.showMessage(` ○ eligibility-integrate: REO_A not deployed`) + } + + // issuance-connect: RM.issuanceAllocator == IA + minter role + const ia = env.getOrNull('IssuanceAllocator') + if (ia) { + const currentIA = (await client.readContract({ + address: rm.address as `0x${string}`, + abi: ISSUANCE_TARGET_ABI, + functionName: 'getIssuanceAllocator', + })) as string + const iaConnected = addressEquals(currentIA, ia.address) + + const gt = env.getOrNull('L2GraphToken') + let isMinter = false + if (gt) { + const { GRAPH_TOKEN_ABI } = await import('@graphprotocol/deployment/lib/abis.js') + isMinter = (await client.readContract({ + address: gt.address as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'isMinter', + args: [ia.address as `0x${string}`], + })) as boolean + } + + env.showMessage( + ` ${iaConnected && isMinter ? '✓' : '✗'} issuance-connect: RM ↔ IA${!iaConnected ? ' (not connected)' : ''}${!isMinter ? ' (no minter role)' : ''}`, + ) + } else { + env.showMessage(` ○ issuance-connect: IA not deployed`) + } + + // issuance-allocate: IA.getTargetAllocation(RAM) configured + if (ram) { + env.showMessage(` ○ issuance-allocate: check via --tags ${GoalTags.GIP_0088_ISSUANCE_ALLOCATE}`) + } else { + env.showMessage(` ○ issuance-allocate: RAM not deployed`) + } + } else { + env.showMessage(' ○ RM not upgraded (activation blocked)') + } + } else { + env.showMessage(' ○ RM not in address book') + } + + // --- Optional status --- + env.showMessage('\n--- Optional (not planned) ---') + + // issuance-close-guard + const ss = env.getOrNull('SubgraphService') + if (ss) { + try { + const closeGuard = (await client.readContract({ + address: ss.address as `0x${string}`, + abi: SUBGRAPH_SERVICE_CLOSE_GUARD_ABI, + functionName: 'getBlockClosingAllocationWithActiveAgreement', + })) as boolean + env.showMessage(` ${closeGuard ? '✓' : '○'} issuance-close-guard: blockClosingAllocation = ${closeGuard}`) + } catch { + env.showMessage(` ○ issuance-close-guard: SS not upgraded`) + } + } else { + env.showMessage(` ○ issuance-close-guard: SS not deployed`) + } + + // --- Actions --- + env.showMessage('\n--- Actions ---') + env.showMessage(' Deploy & upgrade:') + env.showMessage(' --tags GIP-0088:upgrade,') + env.showMessage(' Activation (after upgrades executed):') + env.showMessage(' --tags GIP-0088:eligibility-integrate') + env.showMessage(' --tags GIP-0088:issuance-connect') + env.showMessage(' --tags GIP-0088:issuance-allocate') + env.showMessage(' Optional:') + env.showMessage(' --tags GIP-0088:issuance-close-guard') + + showPendingGovernanceTxs(env) + env.showMessage('') +}) diff --git a/packages/deployment/deploy/gip/0088/eligibility_integrate.ts b/packages/deployment/deploy/gip/0088/eligibility_integrate.ts new file mode 100644 index 000000000..47bd81f7b --- /dev/null +++ b/packages/deployment/deploy/gip/0088/eligibility_integrate.ts @@ -0,0 +1,74 @@ +import { PROVIDER_ELIGIBILITY_MANAGEMENT_ABI } from '@graphprotocol/deployment/lib/abis.js' +import { applyConfiguration } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { createRMIntegrationCondition } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { canSignAsGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' +import { ComponentTags, GoalTags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContracts } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + +/** + * GIP-0088:eligibility-integrate — Set RewardsEligibilityOracle on RewardsManager + * + * Governance TX: RM.setProviderEligibilityOracle(REO_A) + * + * Skips if oracle already set (any value, not just REO_A) to avoid + * accidentally overriding a live oracle configuration. + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:eligibility-integrate --network + */ +export default createActionModule( + GoalTags.GIP_0088_ELIGIBILITY_INTEGRATE, + async (env) => { + await syncComponentsFromRegistry(env, [ + Contracts.issuance.RewardsEligibilityOracleA, + Contracts.horizon.RewardsManager, + ]) + const [reo, rm] = requireContracts(env, [ + Contracts.issuance.RewardsEligibilityOracleA, + Contracts.horizon.RewardsManager, + ]) + const client = graph.getPublicClient(env) as PublicClient + + // Check if oracle already set — skip if any oracle configured (don't override) + try { + const currentOracle = (await client.readContract({ + address: rm.address as `0x${string}`, + abi: PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + functionName: 'getProviderEligibilityOracle', + })) as string + + if (currentOracle !== ZERO_ADDRESS) { + const isTarget = currentOracle.toLowerCase() === reo.address.toLowerCase() + env.showMessage(`\n ${isTarget ? '✓' : '○'} RM.providerEligibilityOracle already set: ${currentOracle}`) + if (!isTarget) { + env.showMessage(` (not REO_A — skipping to avoid override)`) + } + env.showMessage('') + return + } + } catch { + // Function not available — RM not upgraded, skip + env.showMessage(`\n ○ RM does not support getProviderEligibilityOracle — skipping\n`) + return + } + + const { governor, canSign } = await canSignAsGovernor(env) + + await applyConfiguration(env, client, [createRMIntegrationCondition(reo.address)], { + contractName: `${Contracts.horizon.RewardsManager.name}-REO`, + contractAddress: rm.address, + canExecuteDirectly: canSign, + executor: governor, + }) + }, + { + dependencies: [ComponentTags.REWARDS_MANAGER, ComponentTags.REWARDS_ELIGIBILITY_A], + }, +) diff --git a/packages/deployment/deploy/gip/0088/issuance_allocate.ts b/packages/deployment/deploy/gip/0088/issuance_allocate.ts new file mode 100644 index 000000000..525970477 --- /dev/null +++ b/packages/deployment/deploy/gip/0088/issuance_allocate.ts @@ -0,0 +1,193 @@ +import { ACCESS_CONTROL_ENUMERABLE_ABI, SET_TARGET_ALLOCATION_ABI } from '@graphprotocol/deployment/lib/abis.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { canSignAsGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' +import { getResolvedSettingsForEnv } from '@graphprotocol/deployment/lib/deployment-config.js' +import { ComponentTags, GoalTags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + createGovernanceTxBuilder, + executeTxBatchDirect, + saveGovernanceTx, +} from '@graphprotocol/deployment/lib/execute-governance.js' +import { formatGRT } from '@graphprotocol/deployment/lib/format.js' +import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { graph, read, tx } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' +import { encodeFunctionData, keccak256, parseUnits, toHex } from 'viem' + +/** + * GIP-0088:issuance-allocate — Allocate issuance to Recurring Agreement Manager + * + * Calls setTargetAllocation(RAM, allocatorMintingRate, selfMintingRate) so IA + * distributes minted GRT to RAM for agreement-based payments. + * + * Rates are read from config/.json5 (committed per-chain config). + * Skips if rate is 0 (not yet decided). + * + * Idempotent: checks on-chain state, skips if already configured. + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:issuance-allocate --network + */ +export default createActionModule( + GoalTags.GIP_0088_ISSUANCE_ALLOCATE, + async (env) => { + await syncComponentsFromRegistry(env, [ + Contracts.issuance.IssuanceAllocator, + Contracts.issuance.RecurringAgreementManager, + Contracts.horizon.RewardsManager, + ]) + + const client = graph.getPublicClient(env) as PublicClient + const readFn = read(env) + + const iaDep = env.getOrNull(Contracts.issuance.IssuanceAllocator.name) + const ramDep = env.getOrNull(Contracts.issuance.RecurringAgreementManager.name) + if (!iaDep || !ramDep) { + const missing = [!iaDep && 'IssuanceAllocator', !ramDep && 'RecurringAgreementManager'].filter(Boolean) + env.showMessage(`\n ○ Skipping RAM allocation — not deployed: ${missing.join(', ')}\n`) + return + } + const ia = iaDep + const ram = ramDep + + env.showMessage(`\n========== GIP-0088: Issuance Allocate ==========`) + env.showMessage(`IA: ${ia.address}`) + env.showMessage(`RAM: ${ram.address}`) + + // Load resolved settings + const settings = await getResolvedSettingsForEnv(env) + const allocatorMintingRate = parseUnits(settings.issuanceAllocator.ramAllocatorMintingGrtPerBlock, 18) + const selfMintingRate = parseUnits(settings.issuanceAllocator.ramSelfMintingGrtPerBlock, 18) + + if (allocatorMintingRate === 0n && selfMintingRate === 0n) { + env.showMessage('\n⚠️ RAM allocation rates not configured (both 0).') + env.showMessage(' Set ramAllocatorMintingGrtPerBlock in config/.json5') + env.showMessage(' Skipping RAM allocation configuration.\n') + return + } + + // Check current state + env.showMessage('\n📋 Checking current configuration...\n') + env.showMessage( + ` Config: allocatorMintingRate=${formatGRT(allocatorMintingRate)}, selfMintingRate=${formatGRT(selfMintingRate)}`, + ) + + let currentRamAlloc = 0n + let currentRamSelf = 0n + let ramAllocated = false + try { + const allocation = (await readFn(ia, { + functionName: 'getTargetAllocation', + args: [ram.address], + })) as { totalAllocationRate: bigint; allocatorMintingRate: bigint; selfMintingRate: bigint } + currentRamAlloc = allocation.allocatorMintingRate + currentRamSelf = allocation.selfMintingRate + ramAllocated = currentRamAlloc === allocatorMintingRate && currentRamSelf === selfMintingRate + env.showMessage( + ` On-chain: allocator=${formatGRT(currentRamAlloc)}, self=${formatGRT(currentRamSelf)} ${ramAllocated ? '✓' : '✗'}`, + ) + } catch { + env.showMessage(` RAM allocation: ✗ (not configured)`) + } + + if (ramAllocated) { + env.showMessage(`\n✅ RAM allocation already matches config\n`) + return + } + + // The allocator enforces a 100% invariant (sum of all targets == issuancePerBlock). + // RewardsManager was given 100% as self-minting in issuance-connect, so we must + // atomically rebalance: take from RM's self-minting and give to RAM, in the same batch. + const [rewardsManager] = requireContracts(env, [Contracts.horizon.RewardsManager]) + const rmAddress = rewardsManager.address as `0x${string}` + const rmAllocation = (await readFn(ia, { + functionName: 'getTargetAllocation', + args: [rmAddress], + })) as { totalAllocationRate: bigint; allocatorMintingRate: bigint; selfMintingRate: bigint } + env.showMessage( + ` RM on-chain: allocator=${formatGRT(rmAllocation.allocatorMintingRate)}, self=${formatGRT(rmAllocation.selfMintingRate)}`, + ) + + const newRamTotal = allocatorMintingRate + selfMintingRate + const currentRamTotal = currentRamAlloc + currentRamSelf + const delta = newRamTotal - currentRamTotal // signed: >0 RAM grows, <0 RAM shrinks + if (delta > 0n && rmAllocation.selfMintingRate < delta) { + env.showMessage( + `\n❌ Insufficient RM self-minting (${formatGRT(rmAllocation.selfMintingRate)}) to fund RAM increase (${formatGRT(delta)})\n`, + ) + process.exit(1) + } + const newRmSelf = rmAllocation.selfMintingRate - delta + + // Determine executor + const deployer = requireDeployer(env) + const GOVERNOR_ROLE = keccak256(toHex('GOVERNOR_ROLE')) + let deployerIsGovernor = false + try { + deployerIsGovernor = (await client.readContract({ + address: ia.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [GOVERNOR_ROLE, deployer as `0x${string}`], + })) as boolean + } catch { + // Storage not available (stale fork) — fall through to governor path + } + + const setRamData = encodeFunctionData({ + abi: SET_TARGET_ALLOCATION_ABI, + functionName: 'setTargetAllocation', + args: [ram.address as `0x${string}`, allocatorMintingRate, selfMintingRate], + }) + const setRmData = encodeFunctionData({ + abi: SET_TARGET_ALLOCATION_ABI, + functionName: 'setTargetAllocation', + args: [rmAddress, rmAllocation.allocatorMintingRate, newRmSelf], + }) + const ramLabel = `setTargetAllocation(RAM, ${formatGRT(allocatorMintingRate)}, ${formatGRT(selfMintingRate)})` + const rmLabel = `setTargetAllocation(RM, ${formatGRT(rmAllocation.allocatorMintingRate)}, ${formatGRT(newRmSelf)})` + + // Order matters: free budget first, then consume. + // delta > 0 (RAM grows): reduce RM first so default target absorbs the slack. + // delta < 0 (RAM shrinks): reduce RAM first so default target absorbs the slack. + const txs = + delta > 0n + ? [ + { data: setRmData, label: rmLabel }, + { data: setRamData, label: ramLabel }, + ] + : [ + { data: setRamData, label: ramLabel }, + { data: setRmData, label: rmLabel }, + ] + + if (deployerIsGovernor) { + env.showMessage('\n🔨 Executing as deployer...\n') + const txFn = tx(env) + for (const t of txs) { + await txFn({ account: deployer, to: ia.address, data: t.data }) + env.showMessage(` ✓ ${t.label}`) + } + env.showMessage(`\n✅ GIP-0088: Issuance Allocate — RAM allocation configured!\n`) + } else { + const { governor, canSign } = await canSignAsGovernor(env) + + const builder = await createGovernanceTxBuilder(env, `gip-0088-issuance-allocate`) + for (const t of txs) { + builder.addTx({ to: ia.address, value: '0', data: t.data }) + env.showMessage(` + ${t.label}`) + } + + if (canSign) { + env.showMessage('\n🔨 Executing configuration TX batch...\n') + await executeTxBatchDirect(env, builder, governor) + env.showMessage(`\n✅ GIP-0088: Issuance Allocate — RAM allocation configured!\n`) + } else { + saveGovernanceTx(env, builder, `GIP-0088: issuance-allocate`) + } + } + }, + { dependencies: [GoalTags.GIP_0088_ISSUANCE_CONNECT, ComponentTags.RECURRING_AGREEMENT_MANAGER] }, +) diff --git a/packages/deployment/deploy/gip/0088/issuance_close_guard.ts b/packages/deployment/deploy/gip/0088/issuance_close_guard.ts new file mode 100644 index 000000000..55f33040a --- /dev/null +++ b/packages/deployment/deploy/gip/0088/issuance_close_guard.ts @@ -0,0 +1,81 @@ +import { SUBGRAPH_SERVICE_CLOSE_GUARD_ABI } from '@graphprotocol/deployment/lib/abis.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { canSignAsGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' +import { ComponentTags, GoalTags, shouldSkipOptionalGoal } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + createGovernanceTxBuilder, + executeTxBatchDirect, + saveGovernanceTx, +} from '@graphprotocol/deployment/lib/execute-governance.js' +import { requireContract } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { DeployScriptModule } from '@rocketh/core/types' +import type { PublicClient } from 'viem' +import { encodeFunctionData } from 'viem' + +/** + * GIP-0088:issuance-close-guard — Prevent closing allocations with active agreements + * + * Optional governance TX: SS.setBlockClosingAllocationWithActiveAgreement(true) + * + * Not activated by `all` — requires explicit `--tags GIP-0088:issuance-close-guard`. + * + * Idempotent: reads on-chain state, skips if already enabled. + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:issuance-close-guard --network + */ +const func: DeployScriptModule = async (env) => { + if (shouldSkipOptionalGoal(GoalTags.GIP_0088_ISSUANCE_CLOSE_GUARD)) return + await syncComponentsFromRegistry(env, [Contracts['subgraph-service'].SubgraphService]) + + const client = graph.getPublicClient(env) as PublicClient + const ss = requireContract(env, Contracts['subgraph-service'].SubgraphService) + + env.showMessage(`\n========== GIP-0088: Issuance Close Guard ==========`) + env.showMessage(`${Contracts['subgraph-service'].SubgraphService.name}: ${ss.address}`) + + // Check current state + env.showMessage('\n📋 Checking current configuration...\n') + + const enabled = (await client.readContract({ + address: ss.address as `0x${string}`, + abi: SUBGRAPH_SERVICE_CLOSE_GUARD_ABI, + functionName: 'getBlockClosingAllocationWithActiveAgreement', + })) as boolean + env.showMessage(` blockClosingAllocationWithActiveAgreement: ${enabled ? '✓ true' : '✗ false'}`) + + if (enabled) { + env.showMessage(`\n✅ ${Contracts['subgraph-service'].SubgraphService.name} close guard already enabled\n`) + return + } + + const { governor, canSign } = await canSignAsGovernor(env) + + env.showMessage('\n🔨 Building configuration TX batch...\n') + + const builder = await createGovernanceTxBuilder(env, `gip-0088-issuance-close-guard`) + + const data = encodeFunctionData({ + abi: SUBGRAPH_SERVICE_CLOSE_GUARD_ABI, + functionName: 'setBlockClosingAllocationWithActiveAgreement', + args: [true], + }) + builder.addTx({ to: ss.address, value: '0', data }) + env.showMessage(` + setBlockClosingAllocationWithActiveAgreement(true)`) + + if (canSign) { + env.showMessage('\n🔨 Executing configuration TX batch...\n') + await executeTxBatchDirect(env, builder, governor) + env.showMessage(`\n✅ GIP-0088: allocation close guard enabled\n`) + } else { + saveGovernanceTx(env, builder, `GIP-0088: allocation close guard`) + } +} + +func.tags = [GoalTags.GIP_0088_ISSUANCE_CLOSE_GUARD] +func.dependencies = [ComponentTags.SUBGRAPH_SERVICE] +func.skip = async () => shouldSkipOptionalGoal(GoalTags.GIP_0088_ISSUANCE_CLOSE_GUARD) + +export default func diff --git a/packages/deployment/deploy/gip/0088/issuance_connect.ts b/packages/deployment/deploy/gip/0088/issuance_connect.ts new file mode 100644 index 000000000..30f8c170d --- /dev/null +++ b/packages/deployment/deploy/gip/0088/issuance_connect.ts @@ -0,0 +1,247 @@ +import { + GRAPH_TOKEN_ABI, + ISSUANCE_ALLOCATOR_ABI, + ISSUANCE_TARGET_ABI, + REWARDS_MANAGER_DEPRECATED_ABI, + SET_TARGET_ALLOCATION_ABI, +} from '@graphprotocol/deployment/lib/abis.js' +import { getTargetChainIdFromEnv } from '@graphprotocol/deployment/lib/address-book-utils.js' +import { requireRewardsManagerUpgraded } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { canSignAsGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' +import { ComponentTags, GoalTags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + createGovernanceTxBuilder, + executeTxBatchDirect, + saveGovernanceTx, +} from '@graphprotocol/deployment/lib/execute-governance.js' +import { formatGRT } from '@graphprotocol/deployment/lib/format.js' +import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' +import { encodeFunctionData } from 'viem' + +/** + * GIP-0088:issuance-connect — Connect Rewards Manager to Issuance Allocator + * + * - Configure RewardsManager to use IssuanceAllocator + * - Grant minter role to IssuanceAllocator on GraphToken + * + * Idempotent: checks on-chain state, skips if already activated. + * If the provider has access to the governor key, executes directly. + * Otherwise generates governance TX file. + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:issuance-connect --network + */ +export default createActionModule( + GoalTags.GIP_0088_ISSUANCE_CONNECT, + async (env) => { + await syncComponentsFromRegistry(env, [ + Contracts.issuance.IssuanceAllocator, + Contracts.horizon.RewardsManager, + Contracts.horizon.L2GraphToken, + Contracts.issuance.DefaultAllocation, + ]) + + const deployer = requireDeployer(env) + + // Check if the provider can sign as the protocol governor + const { governor, canSign } = await canSignAsGovernor(env) + + const [issuanceAllocator, rewardsManager, graphToken, defaultAllocation] = requireContracts(env, [ + Contracts.issuance.IssuanceAllocator, + Contracts.horizon.RewardsManager, + Contracts.horizon.L2GraphToken, + Contracts.issuance.DefaultAllocation, + ]) + + const iaAddress = issuanceAllocator.address + const rmAddress = rewardsManager.address + const gtAddress = graphToken.address + const daAddress = defaultAllocation.address + + // Create viem client for direct contract calls + const client = graph.getPublicClient(env) as PublicClient + + // Check if RewardsManager supports IIssuanceTarget (has been upgraded) + // Throws error if not upgraded + await requireRewardsManagerUpgraded(client, rmAddress, env) + + const targetChainId = await getTargetChainIdFromEnv(env) + + env.showMessage(`\n========== GIP-0088: Issuance Connect ==========`) + env.showMessage(`Network: ${env.name} (chainId=${targetChainId})`) + env.showMessage(`Deployer: ${deployer}`) + env.showMessage(`Protocol Governor (from Controller): ${governor}`) + env.showMessage(`${Contracts.issuance.IssuanceAllocator.name}: ${iaAddress}`) + env.showMessage(`${Contracts.horizon.RewardsManager.name}: ${rmAddress}`) + env.showMessage(`${Contracts.horizon.L2GraphToken.name}: ${gtAddress}\n`) + + // Check current state + env.showMessage('📋 Checking current activation state...\n') + + const checks = { + iaIntegrated: false, + iaMinter: false, + } + + // Check RM.getIssuanceAllocator() == IA + const currentIA = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: ISSUANCE_TARGET_ABI, + functionName: 'getIssuanceAllocator', + })) as string + checks.iaIntegrated = currentIA.toLowerCase() === iaAddress.toLowerCase() + env.showMessage(` IA integrated: ${checks.iaIntegrated ? '✓' : '✗'} (current: ${currentIA})`) + + // Check GraphToken.isMinter(IA) + checks.iaMinter = (await client.readContract({ + address: gtAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'isMinter', + args: [iaAddress as `0x${string}`], + })) as boolean + env.showMessage(` IA minter: ${checks.iaMinter ? '✓' : '✗'}`) + + // Check RM allocation on IA + let rmAllocationOk = false + try { + const rmAllocation = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getTargetAllocation', + args: [rmAddress as `0x${string}`], + })) as { totalAllocationRate: bigint; allocatorMintingRate: bigint; selfMintingRate: bigint } + const iaRate = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getIssuancePerBlock', + })) as bigint + rmAllocationOk = + rmAllocation.allocatorMintingRate === 0n && rmAllocation.selfMintingRate === iaRate && iaRate > 0n + env.showMessage( + ` RM allocation: ${rmAllocationOk ? '✓' : '✗'} (self: ${formatGRT(rmAllocation.selfMintingRate)}, allocator: ${formatGRT(rmAllocation.allocatorMintingRate)})`, + ) + } catch { + env.showMessage(` RM allocation: ✗ (not set)`) + } + + // All checks passed? + if (checks.iaIntegrated && checks.iaMinter && rmAllocationOk) { + env.showMessage(`\n✅ RM already connected to IssuanceAllocator\n`) + return + } + + // Migration invariant: IA rate must match RM rate before connection + if (!checks.iaIntegrated) { + const rmRate = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: REWARDS_MANAGER_DEPRECATED_ABI, + functionName: 'issuancePerBlock', + })) as bigint + + const iaRate = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getIssuancePerBlock', + })) as bigint + + if (iaRate !== rmRate) { + env.showMessage( + `\n❌ Migration invariant failed: IA.issuancePerBlock (${formatGRT(iaRate)}) != RM.issuancePerBlock (${formatGRT(rmRate)})`, + ) + env.showMessage(` IA must have the same overall rate as RM before connection.\n`) + process.exit(1) + } + + env.showMessage(` Migration invariant: ✓ IA rate == RM rate (${formatGRT(iaRate)})`) + } + + // Build TX batch — order: + // 1. IA.setTargetAllocation(RM, 0, rate) — register RM in IA first + // 2. RM.setIssuanceAllocator(IA) — flip RM to read from a fully-configured IA + // 3. GraphToken.addMinter(IA) — grant IA the minter role + // 4. IA.setDefaultTarget(DA) — install safety-net default + // Conceptually: configure IA's view of RM before RM starts reading from IA. Atomic + // within the batch either way, but this avoids a transient where RM is wired to an + // IA that has no allocation entry for it. + env.showMessage('\n🔨 Building activation TX batch...\n') + + const builder = await createGovernanceTxBuilder(env, `gip-0088-issuance-connect`) + + // 1. IA.setTargetAllocation(RM, 0, rate) — RM as 100% self-minting target + if (!rmAllocationOk) { + const iaRate = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getIssuancePerBlock', + })) as bigint + const data = encodeFunctionData({ + abi: SET_TARGET_ALLOCATION_ABI, + functionName: 'setTargetAllocation', + args: [rmAddress as `0x${string}`, 0n, iaRate], + }) + builder.addTx({ to: iaAddress, value: '0', data }) + env.showMessage(` + IA.setTargetAllocation(RM, 0, ${formatGRT(iaRate)})`) + } + + // 2. RM.setIssuanceAllocator(IA) — RM accepts IA as its allocator + if (!checks.iaIntegrated) { + const data = encodeFunctionData({ + abi: ISSUANCE_TARGET_ABI, + functionName: 'setIssuanceAllocator', + args: [iaAddress as `0x${string}`], + }) + builder.addTx({ to: rmAddress, value: '0', data }) + env.showMessage(` + RewardsManager.setIssuanceAllocator(${iaAddress})`) + } + + // 3. GraphToken.addMinter(IA) — IA needs minter role for allocator-minting + if (!checks.iaMinter) { + const data = encodeFunctionData({ + abi: GRAPH_TOKEN_ABI, + functionName: 'addMinter', + args: [iaAddress as `0x${string}`], + }) + builder.addTx({ to: gtAddress, value: '0', data }) + env.showMessage(` + GraphToken.addMinter(${iaAddress})`) + } + + // 4. IA.setDefaultTarget(DA) — safety net for unallocated issuance + let defaultTargetOk = false + try { + const currentDefault = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getTargetAt', + args: [0n], + })) as string + defaultTargetOk = currentDefault.toLowerCase() === daAddress.toLowerCase() + } catch { + // No targets yet + } + env.showMessage(` DA default target: ${defaultTargetOk ? '✓' : '✗'}`) + + if (!defaultTargetOk) { + const data = encodeFunctionData({ + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'setDefaultTarget', + args: [daAddress as `0x${string}`], + }) + builder.addTx({ to: iaAddress, value: '0', data }) + env.showMessage(` + IA.setDefaultTarget(${daAddress})`) + } + + if (canSign) { + env.showMessage('\n🔨 Executing activation TX batch...\n') + await executeTxBatchDirect(env, builder, governor) + env.showMessage(`\n✅ GIP-0088: Issuance Connect — RM connected to IssuanceAllocator!\n`) + } else { + saveGovernanceTx(env, builder, `GIP-0088: issuance-connect`) + } + }, + { dependencies: [ComponentTags.ISSUANCE_ALLOCATOR, ComponentTags.DEFAULT_ALLOCATION, ComponentTags.REWARDS_MANAGER] }, +) diff --git a/packages/deployment/deploy/gip/0088/upgrade/01_deploy.ts b/packages/deployment/deploy/gip/0088/upgrade/01_deploy.ts new file mode 100644 index 000000000..010564515 --- /dev/null +++ b/packages/deployment/deploy/gip/0088/upgrade/01_deploy.ts @@ -0,0 +1,47 @@ +import { + ComponentTags, + DeploymentActions, + GoalTags, + shouldSkipAction, +} from '@graphprotocol/deployment/lib/deployment-tags.js' +import type { DeployScriptModule } from '@rocketh/core/types' + +/** + * GIP-0088:upgrade — Deploy ALL contracts and implementations + * + * Deploys everything required for GIP-0088 in one step: + * - New implementations for existing proxies (RM, HS, SS, DM, PE, L2Curation) + * - New contracts (RC, IA, DA, Reclaim, RAM, REO A/B) + * + * The eligibility and issuance phases start from configure, not deploy. + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:upgrade,deploy --network + */ +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.DEPLOY)) return + env.showMessage('\n✓ GIP-0088 upgrade: all contracts and implementations deployed\n') +} + +func.tags = [GoalTags.GIP_0088_UPGRADE] +func.dependencies = [ + // New implementations for existing proxies + ComponentTags.REWARDS_MANAGER, + ComponentTags.HORIZON_STAKING, + ComponentTags.SUBGRAPH_SERVICE, + ComponentTags.DISPUTE_MANAGER, + ComponentTags.PAYMENTS_ESCROW, + ComponentTags.L2_CURATION, + // New contracts (proxy + implementation) + ComponentTags.RECURRING_COLLECTOR, + ComponentTags.ISSUANCE_ALLOCATOR, + ComponentTags.DIRECT_ALLOCATION_IMPL, + ComponentTags.DEFAULT_ALLOCATION, + ComponentTags.REWARDS_RECLAIM, + ComponentTags.RECURRING_AGREEMENT_MANAGER, + ComponentTags.REWARDS_ELIGIBILITY_A, + ComponentTags.REWARDS_ELIGIBILITY_B, +] +func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) + +export default func diff --git a/packages/deployment/deploy/gip/0088/upgrade/02_configure.ts b/packages/deployment/deploy/gip/0088/upgrade/02_configure.ts new file mode 100644 index 000000000..94e431e52 --- /dev/null +++ b/packages/deployment/deploy/gip/0088/upgrade/02_configure.ts @@ -0,0 +1,40 @@ +import { + ComponentTags, + DeploymentActions, + GoalTags, + shouldSkipAction, +} from '@graphprotocol/deployment/lib/deployment-tags.js' +import type { DeployScriptModule } from '@rocketh/core/types' + +/** + * GIP-0088:upgrade — Configure all contracts (deployer-only) + * + * Checkpoint: component 04_configure scripts do the work. + * + * Only items the deployer can perform run here. Items that require GOVERNOR_ROLE + * on contracts the deployer doesn't yet control (e.g. RC.setPauseGuardian, RM + * integration with Reclaim, deferred role grants on new contracts) are bundled + * into the upgrade governance batch by `04_upgrade.ts`. RC's `04_configure` + * is read-only — it just reports state. + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:upgrade,configure --network + */ +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.CONFIGURE)) return + env.showMessage('\n✓ GIP-0088 upgrade: contracts configured\n') +} + +func.tags = [GoalTags.GIP_0088_UPGRADE] +func.dependencies = [ + ComponentTags.RECURRING_COLLECTOR, + ComponentTags.ISSUANCE_ALLOCATOR, + ComponentTags.DEFAULT_ALLOCATION, + ComponentTags.REWARDS_RECLAIM, + ComponentTags.RECURRING_AGREEMENT_MANAGER, + ComponentTags.REWARDS_ELIGIBILITY_A, + ComponentTags.REWARDS_ELIGIBILITY_B, +] +func.skip = async () => shouldSkipAction(DeploymentActions.CONFIGURE) + +export default func diff --git a/packages/deployment/deploy/gip/0088/upgrade/03_transfer.ts b/packages/deployment/deploy/gip/0088/upgrade/03_transfer.ts new file mode 100644 index 000000000..272aa8f8c --- /dev/null +++ b/packages/deployment/deploy/gip/0088/upgrade/03_transfer.ts @@ -0,0 +1,39 @@ +import { + ComponentTags, + DeploymentActions, + GoalTags, + shouldSkipAction, +} from '@graphprotocol/deployment/lib/deployment-tags.js' +import type { DeployScriptModule } from '@rocketh/core/types' + +/** + * GIP-0088:upgrade — Transfer governance of all new contracts to protocol governor + * + * Checkpoint: component transfer scripts do the work. + * Covers all new contracts that were deployed with deployer as governor. + * + * Must run AFTER configure (deployer needs GOVERNOR_ROLE to configure) + * and BEFORE upgrade (governance must own proxies before upgrade TXs). + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:upgrade,transfer --network + */ +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.TRANSFER)) return + env.showMessage('\n✓ GIP-0088 upgrade: governance transferred\n') +} + +func.tags = [GoalTags.GIP_0088_UPGRADE] +func.dependencies = [ + ComponentTags.RECURRING_COLLECTOR, + ComponentTags.ISSUANCE_ALLOCATOR, + ComponentTags.DEFAULT_ALLOCATION, + ComponentTags.RECURRING_AGREEMENT_MANAGER, + ComponentTags.REWARDS_RECLAIM, + ComponentTags.REWARDS_ELIGIBILITY_A, + ComponentTags.REWARDS_ELIGIBILITY_B, + ComponentTags.REWARDS_ELIGIBILITY_MOCK, +] +func.skip = async () => shouldSkipAction(DeploymentActions.TRANSFER) + +export default func diff --git a/packages/deployment/deploy/gip/0088/upgrade/04_upgrade.ts b/packages/deployment/deploy/gip/0088/upgrade/04_upgrade.ts new file mode 100644 index 000000000..cdec30636 --- /dev/null +++ b/packages/deployment/deploy/gip/0088/upgrade/04_upgrade.ts @@ -0,0 +1,447 @@ +import { + ACCESS_CONTROL_ENUMERABLE_ABI, + ISSUANCE_ALLOCATOR_ABI, + ISSUANCE_TARGET_ABI, + RECURRING_COLLECTOR_PAUSE_ABI, + REWARDS_MANAGER_ABI, + REWARDS_MANAGER_DEPRECATED_ABI, +} from '@graphprotocol/deployment/lib/abis.js' +import { getAddressBookForType, getTargetChainIdFromEnv } from '@graphprotocol/deployment/lib/address-book-utils.js' +import { checkConfigurationStatus } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { getREOConditions } from '@graphprotocol/deployment/lib/contract-checks.js' +import { + type AddressBookType, + CONTRACT_REGISTRY, + type ContractMetadata, + Contracts, +} from '@graphprotocol/deployment/lib/contract-registry.js' +import { canSignAsGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' +import { getResolvedSettingsForEnv, type ResolvedSettings } from '@graphprotocol/deployment/lib/deployment-config.js' +import { DeploymentActions, GoalTags, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + createGovernanceTxBuilder, + executeTxBatchDirect, + saveGovernanceTx, +} from '@graphprotocol/deployment/lib/execute-governance.js' +import { formatGRT } from '@graphprotocol/deployment/lib/format.js' +import { + checkDefaultAllocationConfigured, + checkIAConfigured, + checkRAMConfigured, + checkReclaimRMIntegration, + checkReclaimRoles, + checkRMRevertOnIneligible, +} from '@graphprotocol/deployment/lib/preconditions.js' +import { runFullSync } from '@graphprotocol/deployment/lib/sync-utils.js' +import type { TxBuilder } from '@graphprotocol/deployment/lib/tx-builder.js' +import { buildUpgradeTxs } from '@graphprotocol/deployment/lib/upgrade-implementation.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { DeployScriptModule, Environment } from '@rocketh/core/types' +import type { PublicClient } from 'viem' +import { encodeFunctionData } from 'viem' + +/** + * GIP-0088:upgrade — Build the governance batch + * + * Single goal: assemble one TX batch that advances the deployment past the + * governance boundary. The batch contains three groups, each of which skips + * items already on-chain: + * + * 1. Proxy upgrades — every deployable proxy with a pendingImplementation + * 2. Existing-contract config — RC.setPauseGuardian, RM.setDefaultReclaimAddress + * 3. Deferred new-contract config — IA/DA/RAM/Reclaim/REO role grants and + * params that the deployer couldn't perform (no GOVERNOR_ROLE) or that + * depend on RM being upgraded + * + * Each helper takes the builder, adds zero or more TXs, and returns the count + * it added. The orchestrator just sums them, prints the result, and either + * executes or saves the batch. + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:upgrade,upgrade --network + * pnpm hardhat deploy:execute-governance --network + */ +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.UPGRADE)) return + + // The orchestration batch reads every deployable contract across all three + // address books, so we need a full sync first rather than a per-component one. + await runFullSync(env) + + const targetChainId = await getTargetChainIdFromEnv(env) + const { governor, canSign } = await canSignAsGovernor(env) + const pauseGuardian = await getPauseGuardian(env) + const client = graph.getPublicClient(env) as PublicClient + + env.showMessage('\n========== GIP-0088 Upgrade: Proxy Upgrades ==========\n') + + const builder = await createGovernanceTxBuilder(env, 'gip-0088-upgrades', { + name: 'GIP-0088 Proxy Upgrades', + description: 'Upgrade all proxy contracts with pending implementations', + }) + + const proxyCount = await collectProxyUpgrades(env, builder, targetChainId) + + const settings = await getResolvedSettingsForEnv(env) + + env.showMessage('\nOutstanding configuration:') + const existingCount = await collectExistingContractConfig(env, builder, client, pauseGuardian, settings) + const newCount = await collectDeferredNewContractConfig(env, builder, client, targetChainId, governor, pauseGuardian) + + const total = proxyCount + existingCount + newCount + if (total === 0) { + env.showMessage(' No pending upgrades found\n') + return + } + + if (canSign) { + env.showMessage('\n🔨 Executing upgrade TX batch...\n') + await executeTxBatchDirect(env, builder, governor) + env.showMessage('\n✅ GIP-0088 Upgrade: All proxy upgrades executed\n') + } else { + saveGovernanceTx(env, builder, 'GIP-0088 Proxy Upgrades') + } +} + +func.tags = [GoalTags.GIP_0088_UPGRADE] +func.skip = async () => shouldSkipAction(DeploymentActions.UPGRADE) + +export default func + +// ============================================================================ +// Group 1 — Proxy upgrades +// ============================================================================ + +/** + * Iterate every deployable proxy in the registry. For each one with a + * pendingImplementation in its address book, add the proxy upgrade TX. + */ +async function collectProxyUpgrades(env: Environment, builder: TxBuilder, targetChainId: number): Promise { + let added = 0 + const addressBooks: AddressBookType[] = ['horizon', 'subgraph-service', 'issuance'] + for (const abType of addressBooks) { + const bookRegistry = CONTRACT_REGISTRY[abType] + const ab = getAddressBookForType(abType, targetChainId) + + for (const [name, metadata] of Object.entries(bookRegistry)) { + const meta = metadata as ContractMetadata + if (!meta.deployable || !meta.proxyType) continue + if (!ab.entryExists(name)) continue + const entry = ab.getEntry(name) + + // Skip contracts with no pending implementation unless they have a + // shared implementation that might have changed (auto-detected by buildUpgradeTxs) + if (!entry?.pendingImplementation?.address && !meta.sharedImplementation) continue + + // Derive implementationName from sharedImplementation (e.g. 'DirectAllocation_Implementation' → 'DirectAllocation') + const implementationName = meta.sharedImplementation?.replace(/_Implementation$/, '') + + const result = await buildUpgradeTxs( + env, + { + contractName: name, + proxyType: meta.proxyType, + proxyAdminName: meta.proxyAdminName, + addressBook: abType, + implementationName, + }, + builder, + ) + if (result.upgraded) added++ + } + } + return added +} + +// ============================================================================ +// Group 2 — Existing contract config (RC, RM) +// ============================================================================ + +/** + * Bundle the few governance-only configure items on contracts that already + * existed before this deployment (typically the deployer does not hold + * GOVERNOR_ROLE on them — true on networks where RM was deployed by separate + * horizon-Ignition infrastructure; the dynamic role check is the source of truth): + * + * - RC.setPauseGuardian + * - RM.setDefaultReclaimAddress (only when RM has been upgraded) + * - RM.setRevertOnIneligible (driven by config; only when RM has been upgraded) + */ +async function collectExistingContractConfig( + env: Environment, + builder: TxBuilder, + client: PublicClient, + pauseGuardian: string, + settings: ResolvedSettings, +): Promise { + let added = 0 + + // RC.setPauseGuardian + const rc = env.getOrNull(Contracts.horizon.RecurringCollector.name) + if (rc) { + const isGuardian = (await client.readContract({ + address: rc.address as `0x${string}`, + abi: RECURRING_COLLECTOR_PAUSE_ABI, + functionName: 'pauseGuardians', + args: [pauseGuardian as `0x${string}`], + })) as boolean + if (!isGuardian) { + builder.addTx({ + to: rc.address, + value: '0', + data: encodeFunctionData({ + abi: RECURRING_COLLECTOR_PAUSE_ABI, + functionName: 'setPauseGuardian', + args: [pauseGuardian as `0x${string}`, true], + }), + }) + env.showMessage(` + ${Contracts.horizon.RecurringCollector.name}.setPauseGuardian(${pauseGuardian})`) + added++ + } + } + + // RM.setDefaultReclaimAddress — only after RM upgrade lands in the same batch + const reclaim = env.getOrNull(Contracts.issuance.ReclaimedRewards.name) + const rm = env.getOrNull(Contracts.horizon.RewardsManager.name) + if (reclaim && rm) { + const reclaimRMCheck = await checkReclaimRMIntegration(client, rm.address, reclaim.address) + if (!reclaimRMCheck.done && reclaimRMCheck.reason !== 'RM not upgraded') { + builder.addTx({ + to: rm.address, + value: '0', + data: encodeFunctionData({ + abi: REWARDS_MANAGER_ABI, + functionName: 'setDefaultReclaimAddress', + args: [reclaim.address as `0x${string}`], + }), + }) + env.showMessage(` + ${Contracts.horizon.RewardsManager.name}.setDefaultReclaimAddress(${reclaim.address})`) + added++ + } + } + + // RM.setRevertOnIneligible — driven by config; only after RM upgrade lands + if (rm) { + const desiredRevert = settings.rewardsManager.revertOnIneligible + const revertCheck = await checkRMRevertOnIneligible(client, rm.address, desiredRevert) + if (!revertCheck.done && revertCheck.reason !== 'RM not upgraded') { + builder.addTx({ + to: rm.address, + value: '0', + data: encodeFunctionData({ + abi: REWARDS_MANAGER_ABI, + functionName: 'setRevertOnIneligible', + args: [desiredRevert], + }), + }) + env.showMessage(` + ${Contracts.horizon.RewardsManager.name}.setRevertOnIneligible(${desiredRevert})`) + added++ + } + } + + return added +} + +// ============================================================================ +// Group 3 — Deferred new-contract config (IA, DA, RAM, Reclaim, REO A/B) +// ============================================================================ + +/** + * Bundle the configure items on new contracts that the deployer couldn't + * perform during `02_configure` because it lacks `GOVERNOR_ROLE` on the + * proxy (typical when forking an existing deployment whose proxies were + * already transferred). + */ +async function collectDeferredNewContractConfig( + env: Environment, + builder: TxBuilder, + client: PublicClient, + targetChainId: number, + governor: string, + pauseGuardian: string, +): Promise { + const grantHelper = createRoleGrantHelper(env, builder, client) + let added = 0 + + // IA: rate + roles + const ia = env.getOrNull(Contracts.issuance.IssuanceAllocator.name) + const rm = env.getOrNull(Contracts.horizon.RewardsManager.name) + if (ia && rm) { + const iaCheck = await checkIAConfigured(client, ia.address, rm.address, governor, pauseGuardian) + if (!iaCheck.done && iaCheck.reason !== 'RM.issuancePerBlock is 0') { + const rmRate = (await client.readContract({ + address: rm.address as `0x${string}`, + abi: REWARDS_MANAGER_DEPRECATED_ABI, + functionName: 'issuancePerBlock', + })) as bigint + const iaRate = (await client.readContract({ + address: ia.address as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getIssuancePerBlock', + })) as bigint + // The outer iaCheck already returns when RM rate is 0, so rmRate > 0n here. + if (iaRate !== rmRate) { + builder.addTx({ + to: ia.address, + value: '0', + data: encodeFunctionData({ + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'setIssuancePerBlock', + args: [rmRate], + }), + }) + env.showMessage(` + IA.setIssuancePerBlock(${formatGRT(rmRate)})`) + added++ + } + added += await grantHelper(ia.address, 'IA', 'GOVERNOR_ROLE', governor, 'governor') + added += await grantHelper(ia.address, 'IA', 'PAUSE_ROLE', pauseGuardian, 'pauseGuardian') + } + } + + // DA: roles + const da = env.getOrNull(Contracts.issuance.DefaultAllocation.name) + if (da) { + const daCheck = await checkDefaultAllocationConfigured(client, da.address, governor, pauseGuardian) + if (!daCheck.done) { + added += await grantHelper(da.address, 'DA', 'GOVERNOR_ROLE', governor, 'governor') + added += await grantHelper(da.address, 'DA', 'PAUSE_ROLE', pauseGuardian, 'pauseGuardian') + } + } + + // RAM: roles + setIssuanceAllocator + const ram = env.getOrNull(Contracts.issuance.RecurringAgreementManager.name) + const rcDep = env.getOrNull(Contracts.horizon.RecurringCollector.name) + const ss = env.getOrNull(Contracts['subgraph-service'].SubgraphService.name) + if (ram && rcDep && ss) { + const ramCheck = await checkRAMConfigured( + client, + ram.address, + rcDep.address, + ss.address, + ia?.address ?? '', + governor, + pauseGuardian, + ) + if (!ramCheck.done) { + added += await grantHelper(ram.address, 'RAM', 'COLLECTOR_ROLE', rcDep.address, 'RC') + added += await grantHelper(ram.address, 'RAM', 'DATA_SERVICE_ROLE', ss.address, 'SS') + added += await grantHelper(ram.address, 'RAM', 'GOVERNOR_ROLE', governor, 'governor') + added += await grantHelper(ram.address, 'RAM', 'PAUSE_ROLE', pauseGuardian, 'pauseGuardian') + if (ia) { + try { + const currentIA = (await client.readContract({ + address: ram.address as `0x${string}`, + abi: ISSUANCE_TARGET_ABI, + functionName: 'getIssuanceAllocator', + })) as string + if (currentIA.toLowerCase() !== ia.address.toLowerCase()) { + builder.addTx({ + to: ram.address, + value: '0', + data: encodeFunctionData({ + abi: ISSUANCE_TARGET_ABI, + functionName: 'setIssuanceAllocator', + args: [ia.address as `0x${string}`], + }), + }) + env.showMessage(` + RAM.setIssuanceAllocator(${ia.address})`) + added++ + } + } catch { + /* getter not available */ + } + } + } + } + + // Reclaim: roles only — RM integration is handled by collectExistingContractConfig + const reclaim = env.getOrNull(Contracts.issuance.ReclaimedRewards.name) + if (reclaim) { + const reclaimRoles = await checkReclaimRoles(client, reclaim.address, governor, pauseGuardian) + if (!reclaimRoles.done) { + added += await grantHelper(reclaim.address, 'Reclaim', 'GOVERNOR_ROLE', governor, 'governor') + added += await grantHelper(reclaim.address, 'Reclaim', 'PAUSE_ROLE', pauseGuardian, 'pauseGuardian') + } + } + + // REO A/B: params + roles. Driven by the same condition list as `04_configure`. + const issuanceBook = graph.getIssuanceAddressBook(targetChainId) + if (issuanceBook.entryExists('NetworkOperator')) { + const reoConditions = await getREOConditions(env) + for (const [label, entry] of [ + ['REO-A', Contracts.issuance.RewardsEligibilityOracleA], + ['REO-B', Contracts.issuance.RewardsEligibilityOracleB], + ] as const) { + const reoDep = env.getOrNull(entry.name) + if (!reoDep) continue + const reoConfig = await checkConfigurationStatus(client, reoDep.address, reoConditions) + if (reoConfig.allOk) continue + for (let i = 0; i < reoConditions.length; i++) { + if (reoConfig.conditions[i].ok) continue + const cond = reoConditions[i] + if (cond.type === 'role') { + added += await grantHelper(reoDep.address, label, cond.roleGetter, cond.targetAccount, cond.description) + } else { + builder.addTx({ + to: reoDep.address, + value: '0', + data: encodeFunctionData({ + abi: cond.abi as readonly unknown[], + functionName: cond.setter, + args: [cond.target], + }), + }) + env.showMessage(` + ${label}.${cond.setter}(${cond.target})`) + added++ + } + } + } + } + + return added +} + +/** + * Returns a closure that, when called, adds a `grantRole` TX if the role is + * not already held. Returns 1 if a TX was added, 0 otherwise. + */ +function createRoleGrantHelper(env: Environment, builder: TxBuilder, client: PublicClient) { + return async function addRoleGrantIfNeeded( + contractAddr: string, + contractName: string, + roleName: string, + account: string, + accountLabel: string, + ): Promise { + try { + const role = (await client.readContract({ + address: contractAddr as `0x${string}`, + abi: [ + { inputs: [], name: roleName, outputs: [{ type: 'bytes32' }], stateMutability: 'view', type: 'function' }, + ], + functionName: roleName, + })) as `0x${string}` + const has = (await client.readContract({ + address: contractAddr as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [role, account as `0x${string}`], + })) as boolean + if (has) return 0 + builder.addTx({ + to: contractAddr, + value: '0', + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [role, account as `0x${string}`], + }), + }) + env.showMessage(` + ${contractName}.grantRole(${roleName}, ${accountLabel})`) + return 1 + } catch { + /* role getter not available — skip */ + return 0 + } + } +} diff --git a/packages/deployment/deploy/gip/0088/upgrade/10_status.ts b/packages/deployment/deploy/gip/0088/upgrade/10_status.ts new file mode 100644 index 000000000..fdf49394d --- /dev/null +++ b/packages/deployment/deploy/gip/0088/upgrade/10_status.ts @@ -0,0 +1,331 @@ +import { IISSUANCE_TARGET_INTERFACE_ID } from '@graphprotocol/deployment/lib/abis.js' +import { getAddressBookForType, getTargetChainIdFromEnv } from '@graphprotocol/deployment/lib/address-book-utils.js' +import { checkConfigurationStatus } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { + getREOConditions, + getREOTransferGovernanceConditions, + isRewardsManagerUpgraded, +} from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts, type RegistryEntry } from '@graphprotocol/deployment/lib/contract-registry.js' +import { getGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' +import { getResolvedSettingsForEnv } from '@graphprotocol/deployment/lib/deployment-config.js' +import { ComponentTags, GoalTags, noTagsRequested } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { getDeployer, getProxyAdminAddress } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { + checkDefaultAllocationConfigured, + checkDeployerRevoked, + checkIAConfigured, + checkProxyAdminTransferred, + checkRAMConfigured, + checkReclaimRMIntegration, + checkReclaimRoles, + checkRMRevertOnIneligible, +} from '@graphprotocol/deployment/lib/preconditions.js' +import { showDetailedComponentStatus, showPendingGovernanceTxs } from '@graphprotocol/deployment/lib/status-detail.js' +import { checkAllProxyStates, getContractStatusLine, runFullSync } from '@graphprotocol/deployment/lib/sync-utils.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { DeployScriptModule } from '@rocketh/core/types' +import type { PublicClient } from 'viem' + +/** + * GIP-0088:upgrade status — full deployment state with next-step guidance + * + * Usage: + * pnpm hardhat deploy --tags GIP-0088:upgrade --network + */ +const func: DeployScriptModule = async (env) => { + if (noTagsRequested()) return + + // The upgrade status reads every contract in every address book — easier to + // run a full sync than to enumerate them. + await runFullSync(env) + + const client = graph.getPublicClient(env) as PublicClient + const targetChainId = await getTargetChainIdFromEnv(env) + + env.showMessage('\n========== GIP-0088 Upgrade ==========') + + // --- Proxy upgrades --- + env.showMessage('\nProxy upgrades:') + + const upgradeContracts: RegistryEntry[] = [ + Contracts.horizon.RewardsManager, + Contracts.horizon.HorizonStaking, + Contracts['subgraph-service'].SubgraphService, + Contracts['subgraph-service'].DisputeManager, + Contracts.horizon.PaymentsEscrow, + Contracts.horizon.L2Curation, + ] + + const rm = env.getOrNull('RewardsManager') + + for (const contract of upgradeContracts) { + const ab = getAddressBookForType(contract.addressBook, targetChainId) + + const result = await getContractStatusLine(client, contract.addressBook, ab, contract.name) + env.showMessage(` ${result.line}`) + + if (contract === Contracts.horizon.RewardsManager && result.exists && rm) { + const upgraded = await isRewardsManagerUpgraded(client, rm.address) + env.showMessage(` ${upgraded ? '✓' : '✗'} implements IIssuanceTarget (${IISSUANCE_TARGET_INTERFACE_ID})`) + } + } + + const { anyCodeChanged, anyPending } = checkAllProxyStates(targetChainId) + + // --- New contracts --- + env.showMessage('\nNew contracts:') + await showDetailedComponentStatus(env, Contracts.horizon.RecurringCollector, { showHints: false }) + await showDetailedComponentStatus(env, Contracts.issuance.IssuanceAllocator, { showHints: false }) + await showDetailedComponentStatus(env, Contracts.issuance.DefaultAllocation, { showHints: false }) + await showDetailedComponentStatus(env, Contracts.issuance.RecurringAgreementManager, { showHints: false }) + await showDetailedComponentStatus(env, Contracts.issuance.ReclaimedRewards, { showHints: false }) + await showDetailedComponentStatus(env, Contracts.issuance.RewardsEligibilityOracleA, { showHints: false }) + + // --- Next step --- + // Uses the same precondition checks as the action scripts (shared code, not copies) + const ia = env.getOrNull('IssuanceAllocator') + const da = env.getOrNull('DefaultAllocation') + const reoA = env.getOrNull('RewardsEligibilityOracleA') + const reoB = env.getOrNull('RewardsEligibilityOracleB') + const ram = env.getOrNull('RecurringAgreementManager') + const reclaim = env.getOrNull('ReclaimedRewards') + const rc = env.getOrNull('RecurringCollector') + const ss = env.getOrNull('SubgraphService') + + const anyNewContractMissing = !ia || !da || !reoA || !reoB || !ram || !reclaim + + if (anyNewContractMissing || !rm || (anyCodeChanged && !anyPending)) { + env.showMessage(`\n → Next: --tags GIP-0088:upgrade,deploy`) + const missing = [ + !ia && 'IssuanceAllocator', + !da && 'DefaultAllocation', + !reoA && 'REO-A', + !reoB && 'REO-B', + !ram && 'RAM', + !reclaim && 'Reclaim', + !rm && 'RM', + ].filter(Boolean) + if (missing.length > 0) env.showMessage(` Missing: ${missing.join(', ')}`) + if (anyCodeChanged && !anyPending) env.showMessage(` Code changed without pending implementation`) + } else { + const governor = await getGovernor(env) + const pauseGuardian = await getPauseGuardian(env) + + // Deployer address: from namedAccounts when key is loaded, otherwise infer + // from ProxyAdmin owner — if not governor, it's the deployer. + let deployer = getDeployer(env) + if (!deployer) { + try { + const proxyAdminAddr = await getProxyAdminAddress(client, ia.address) + const owner = (await client.readContract({ + address: proxyAdminAddr as `0x${string}`, + abi: [ + { inputs: [], name: 'owner', outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' }, + ], + functionName: 'owner', + })) as string + if (owner.toLowerCase() !== governor.toLowerCase()) deployer = owner + } catch { + // ProxyAdmin not readable — deployer stays undefined + } + } + + // Check configure state + // When deployer is available, classify issues as deployer-fixable vs deferred. + // When not (status-only run without deploy key), all issues are unclassified. + const configIssues: string[] = [] + const deferredIssues: string[] = [] + + // Helper: check if deployer has GOVERNOR_ROLE on a contract + // Returns false when deployer is not configured (status-only run without deploy key) + async function deployerHasGovernorRole(contractAddress: string): Promise { + if (!deployer) return false + try { + const role = (await client.readContract({ + address: contractAddress as `0x${string}`, + abi: [ + { + inputs: [], + name: 'GOVERNOR_ROLE', + outputs: [{ type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'GOVERNOR_ROLE', + })) as `0x${string}` + return (await client.readContract({ + address: contractAddress as `0x${string}`, + abi: [ + { + inputs: [{ type: 'bytes32' }, { type: 'address' }], + name: 'hasRole', + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'hasRole', + args: [role, deployer as `0x${string}`], + })) as boolean + } catch { + return false + } + } + + // Helper: classify a failing config check + async function classifyConfigIssue(label: string, reason: string, contractAddress: string): Promise { + if (await deployerHasGovernorRole(contractAddress)) { + configIssues.push(`${label}: ${reason}`) + } else { + deferredIssues.push(`${label}: ${reason}`) + } + } + + // Check each new contract + const iaConfig = await checkIAConfigured(client, ia.address, rm.address, governor, pauseGuardian) + if (!iaConfig.done && iaConfig.reason !== 'RM.issuancePerBlock is 0') { + await classifyConfigIssue('IA', iaConfig.reason!, ia.address) + } + + const daConfig = await checkDefaultAllocationConfigured(client, da.address, governor, pauseGuardian) + if (!daConfig.done) { + await classifyConfigIssue('DA', daConfig.reason!, da.address) + } + + if (rc && ss) { + const ramConfig = await checkRAMConfigured( + client, + ram.address, + rc.address, + ss.address, + ia.address, + governor, + pauseGuardian, + ) + if (!ramConfig.done) { + await classifyConfigIssue('RAM', ramConfig.reason!, ram.address) + } + } + + const reclaimRolesCheck = await checkReclaimRoles(client, reclaim.address, governor, pauseGuardian) + if (!reclaimRolesCheck.done) { + await classifyConfigIssue('Reclaim', reclaimRolesCheck.reason!, reclaim.address) + } + + // RM.setDefaultReclaimAddress — governance-only (target is RM, not Reclaim). + // Always deferred to the upgrade governance batch, never blocks configure/transfer. + const reclaimRMCheck = await checkReclaimRMIntegration(client, rm.address, reclaim.address) + if (!reclaimRMCheck.done && reclaimRMCheck.reason !== 'RM not upgraded') { + deferredIssues.push(`Reclaim: ${reclaimRMCheck.reason}`) + } + + // RM.setRevertOnIneligible — config-driven; same deferred-only treatment as + // setDefaultReclaimAddress (target is RM, governance-only setter). + const settings = await getResolvedSettingsForEnv(env) + const revertCheck = await checkRMRevertOnIneligible(client, rm.address, settings.rewardsManager.revertOnIneligible) + if (!revertCheck.done && revertCheck.reason !== 'RM not upgraded') { + deferredIssues.push(`RM: ${revertCheck.reason}`) + } + + // REO configure + const issuanceBook = graph.getIssuanceAddressBook(targetChainId) + const hasNetworkOperator = issuanceBook.entryExists('NetworkOperator') + if (hasNetworkOperator) { + const reoConditions = await getREOConditions(env) + for (const [label, addr] of [ + ['REO-A', reoA.address], + ['REO-B', reoB.address], + ] as const) { + const reoConfig = await checkConfigurationStatus(client, addr, reoConditions) + if (!reoConfig.allOk) { + const failing = reoConfig.conditions.filter((c) => !c.ok).map((c) => c.name) + await classifyConfigIssue(label, failing.join(', '), addr) + } + } + } else { + deferredIssues.push('NetworkOperator not configured') + } + + const anyConfigIssues = configIssues.length > 0 || deferredIssues.length > 0 + + // Check transfer state + // ProxyAdmin ownership is deployer-independent (checks owner vs governor). + // Deployer GOVERNOR_ROLE revocation needs the deployer address — checked + // when available, skipped otherwise (ProxyAdmin transfer is the primary signal). + let proxyAdminsTransferred = true + + for (const contract of [ia, da, ram, reclaim, reoA, reoB]) { + try { + const proxyAdminAddr = await getProxyAdminAddress(client, contract.address) + const paCheck = await checkProxyAdminTransferred(client, proxyAdminAddr, governor) + if (!paCheck.done) proxyAdminsTransferred = false + } catch { + // ProxyAdmin not readable — skip + } + } + + let deployerRolesRevoked = true + if (deployer) { + for (const contract of [ia, da, ram, reclaim]) { + const revoked = await checkDeployerRevoked(client, contract.address, deployer) + if (!revoked.done) deployerRolesRevoked = false + } + if (hasNetworkOperator) { + const reoTransferConds = getREOTransferGovernanceConditions(deployer) + const reoATransfer = await checkConfigurationStatus(client, reoA.address, reoTransferConds) + if (!reoATransfer.allOk) deployerRolesRevoked = false + const reoBTransfer = await checkConfigurationStatus(client, reoB.address, reoTransferConds) + if (!reoBTransfer.allOk) deployerRolesRevoked = false + } + } + + const needsTransfer = !proxyAdminsTransferred || !deployerRolesRevoked + + // Next-step guidance + // Lifecycle: deploy → configure → transfer → upgrade + // ProxyAdmin not transferred ⇒ deployer still has control ⇒ configure/transfer phase + // ProxyAdmin transferred ⇒ remaining issues need governance ⇒ upgrade phase + if (anyConfigIssues && !proxyAdminsTransferred) { + env.showMessage(`\n → Next: --tags GIP-0088:upgrade,configure`) + for (const issue of configIssues) env.showMessage(` ${issue}`) + if (deferredIssues.length > 0) { + env.showMessage(` Deferred (governance TX):`) + for (const issue of deferredIssues) env.showMessage(` ${issue}`) + } + } else if (needsTransfer) { + env.showMessage(`\n → Next: --tags GIP-0088:upgrade,transfer`) + } else if (anyPending || anyConfigIssues) { + env.showMessage(`\n → Next: --tags GIP-0088:upgrade,upgrade`) + if (deferredIssues.length > 0) { + env.showMessage(` Deferred config (governance TX):`) + for (const issue of deferredIssues) env.showMessage(` ${issue}`) + } + } + } + + showPendingGovernanceTxs(env) + env.showMessage(`\n Actions: --tags GIP-0088:upgrade,`) + env.showMessage('') +} + +func.tags = [GoalTags.GIP_0088_UPGRADE] +func.dependencies = [ + // Upgrade contracts + ComponentTags.RECURRING_COLLECTOR, + ComponentTags.REWARDS_MANAGER, + ComponentTags.HORIZON_STAKING, + ComponentTags.SUBGRAPH_SERVICE, + ComponentTags.DISPUTE_MANAGER, + ComponentTags.PAYMENTS_ESCROW, + ComponentTags.L2_CURATION, + // New contracts (shown in status) + ComponentTags.ISSUANCE_ALLOCATOR, + ComponentTags.DEFAULT_ALLOCATION, + ComponentTags.RECURRING_AGREEMENT_MANAGER, + ComponentTags.REWARDS_ELIGIBILITY_A, +] +func.skip = async () => noTagsRequested() + +export default func diff --git a/packages/deployment/deploy/horizon/curation/01_deploy.ts b/packages/deployment/deploy/horizon/curation/01_deploy.ts new file mode 100644 index 000000000..1a0d9c9b0 --- /dev/null +++ b/packages/deployment/deploy/horizon/curation/01_deploy.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createImplementationDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createImplementationDeployModule(Contracts.horizon.L2Curation) diff --git a/packages/deployment/deploy/horizon/curation/02_upgrade.ts b/packages/deployment/deploy/horizon/curation/02_upgrade.ts new file mode 100644 index 000000000..efb44379c --- /dev/null +++ b/packages/deployment/deploy/horizon/curation/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts.horizon.L2Curation) diff --git a/packages/deployment/deploy/horizon/curation/09_end.ts b/packages/deployment/deploy/horizon/curation/09_end.ts new file mode 100644 index 000000000..bd06ed9ad --- /dev/null +++ b/packages/deployment/deploy/horizon/curation/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.horizon.L2Curation) diff --git a/packages/deployment/deploy/horizon/curation/10_status.ts b/packages/deployment/deploy/horizon/curation/10_status.ts new file mode 100644 index 000000000..8a6d9f944 --- /dev/null +++ b/packages/deployment/deploy/horizon/curation/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.horizon.L2Curation) diff --git a/packages/deployment/deploy/horizon/payments-escrow/01_deploy.ts b/packages/deployment/deploy/horizon/payments-escrow/01_deploy.ts new file mode 100644 index 000000000..91d2db38b --- /dev/null +++ b/packages/deployment/deploy/horizon/payments-escrow/01_deploy.ts @@ -0,0 +1,58 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { deployImplementation, getImplementationConfig } from '@graphprotocol/deployment/lib/deploy-implementation.js' +import { ComponentTags, DeploymentActions, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { DeployScriptModule } from '@rocketh/core/types' + +// PaymentsEscrow Implementation Deployment +// +// Deploys a new PaymentsEscrow implementation if artifact bytecode differs from on-chain. +// +// Workflow: +// 1. Read current immutable values from on-chain contract +// 2. Compare artifact bytecode with on-chain bytecode (accounting for immutables) +// 3. If different, deploy new implementation +// 4. Store as "pendingImplementation" in horizon/addresses.json +// 5. Upgrade task (separate) handles TX generation and execution + +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.DEPLOY)) return + await syncComponentsFromRegistry(env, [Contracts.horizon.Controller, Contracts.horizon.PaymentsEscrow]) + + const controllerDep = env.getOrNull('Controller') + const escrowDep = env.getOrNull('PaymentsEscrow') + + if (!controllerDep || !escrowDep) { + throw new Error('Missing required contract deployments (Controller, PaymentsEscrow) after sync.') + } + + // Read current immutable value from on-chain contract + const client = graph.getPublicClient(env) + const thawingPeriod = await client.readContract({ + address: escrowDep.address as `0x${string}`, + abi: [ + { + name: 'WITHDRAW_ESCROW_THAWING_PERIOD', + type: 'function', + inputs: [], + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + }, + ], + functionName: 'WITHDRAW_ESCROW_THAWING_PERIOD', + }) + + env.showMessage(` PaymentsEscrow WITHDRAW_ESCROW_THAWING_PERIOD: ${thawingPeriod}`) + + await deployImplementation( + env, + getImplementationConfig('horizon', 'PaymentsEscrow', { + constructorArgs: [controllerDep.address, thawingPeriod], + }), + ) +} + +func.tags = [ComponentTags.PAYMENTS_ESCROW] +func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) +export default func diff --git a/packages/deployment/deploy/horizon/payments-escrow/02_upgrade.ts b/packages/deployment/deploy/horizon/payments-escrow/02_upgrade.ts new file mode 100644 index 000000000..25c8f13e1 --- /dev/null +++ b/packages/deployment/deploy/horizon/payments-escrow/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts.horizon.PaymentsEscrow) diff --git a/packages/deployment/deploy/horizon/payments-escrow/09_end.ts b/packages/deployment/deploy/horizon/payments-escrow/09_end.ts new file mode 100644 index 000000000..95272ed2d --- /dev/null +++ b/packages/deployment/deploy/horizon/payments-escrow/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.horizon.PaymentsEscrow) diff --git a/packages/deployment/deploy/horizon/payments-escrow/10_status.ts b/packages/deployment/deploy/horizon/payments-escrow/10_status.ts new file mode 100644 index 000000000..267692139 --- /dev/null +++ b/packages/deployment/deploy/horizon/payments-escrow/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.horizon.PaymentsEscrow) diff --git a/packages/deployment/deploy/horizon/recurring-collector/01_deploy.ts b/packages/deployment/deploy/horizon/recurring-collector/01_deploy.ts new file mode 100644 index 000000000..4f96b4c35 --- /dev/null +++ b/packages/deployment/deploy/horizon/recurring-collector/01_deploy.ts @@ -0,0 +1,48 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { getResolvedSettingsForEnv } from '@graphprotocol/deployment/lib/deployment-config.js' +import { ComponentTags, DeploymentActions, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { deployProxyContract } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import type { DeployScriptModule } from '@rocketh/core/types' + +/** + * Deploy RecurringCollector proxy and implementation + * + * Deploys OZ v5 TransparentUpgradeableProxy with atomic initialization. + * Deployer is the initial ProxyAdmin owner; ownership is transferred to + * the protocol governor in a separate governance step. + * + * RecurringCollector constructor takes (controller, revokeSignerThawingPeriod). + * initialize(eip712Name, eip712Version) sets up EIP-712 domain and pausability. + * + * On subsequent runs (proxy already deployed), deploys new implementation + * and stores it as pendingImplementation for governance upgrade. + * + * Usage: + * pnpm hardhat deploy --tags RecurringCollector:deploy --network + */ +const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.DEPLOY)) return + await syncComponentsFromRegistry(env, [Contracts.horizon.Controller, Contracts.horizon.RecurringCollector]) + + const controllerDep = env.getOrNull('Controller') + if (!controllerDep) { + throw new Error('Missing Controller deployment after sync.') + } + + const settings = await getResolvedSettingsForEnv(env) + const { revokeSignerThawingPeriod, eip712Name, eip712Version } = settings.recurringCollector + + env.showMessage(`\n📦 Deploying ${Contracts.horizon.RecurringCollector.name}`) + + await deployProxyContract(env, { + contract: Contracts.horizon.RecurringCollector, + constructorArgs: [controllerDep.address, revokeSignerThawingPeriod], + initializeArgs: [eip712Name, eip712Version], + }) +} + +func.tags = [ComponentTags.RECURRING_COLLECTOR] +func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) + +export default func diff --git a/packages/deployment/deploy/horizon/recurring-collector/02_upgrade.ts b/packages/deployment/deploy/horizon/recurring-collector/02_upgrade.ts new file mode 100644 index 000000000..f58136aad --- /dev/null +++ b/packages/deployment/deploy/horizon/recurring-collector/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts.horizon.RecurringCollector) diff --git a/packages/deployment/deploy/horizon/recurring-collector/04_configure.ts b/packages/deployment/deploy/horizon/recurring-collector/04_configure.ts new file mode 100644 index 000000000..023e95ef3 --- /dev/null +++ b/packages/deployment/deploy/horizon/recurring-collector/04_configure.ts @@ -0,0 +1,62 @@ +import { RECURRING_COLLECTOR_PAUSE_ABI } from '@graphprotocol/deployment/lib/abis.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { canSignAsGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContract } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph, tx } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' +import { encodeFunctionData } from 'viem' + +/** + * Configure RecurringCollector — set pause guardian + * + * RC uses Controller-based access control: setPauseGuardian requires + * msg.sender == Controller.getGovernor(). If the deployer is the + * Controller governor (e.g. testnet), this script sets it directly. + * Otherwise it reports the gap — the upgrade step (04_upgrade.ts) + * bundles it as a governance TX. + * + * Idempotent: checks on-chain state, skips if already set. + * + * Usage: + * pnpm hardhat deploy --tags RecurringCollector:configure --network + */ +export default createActionModule(Contracts.horizon.RecurringCollector, DeploymentActions.CONFIGURE, async (env) => { + const client = graph.getPublicClient(env) as PublicClient + const rc = requireContract(env, Contracts.horizon.RecurringCollector) + const pauseGuardian = await getPauseGuardian(env) + + env.showMessage(`\n========== Configure ${Contracts.horizon.RecurringCollector.name} ==========`) + + const isGuardian = (await client.readContract({ + address: rc.address as `0x${string}`, + abi: RECURRING_COLLECTOR_PAUSE_ABI, + functionName: 'pauseGuardians', + args: [pauseGuardian as `0x${string}`], + })) as boolean + + if (isGuardian) { + env.showMessage(` ✓ Pause guardian already set\n`) + return + } + + const { canSign } = await canSignAsGovernor(env) + if (!canSign) { + env.showMessage(` ○ Pause guardian not set — will be configured in upgrade step (governance TX)\n`) + return + } + + env.showMessage('\n🔨 Setting pause guardian as governor...\n') + const txFn = tx(env) + await txFn({ + account: 'governor', + to: rc.address as `0x${string}`, + data: encodeFunctionData({ + abi: RECURRING_COLLECTOR_PAUSE_ABI, + functionName: 'setPauseGuardian', + args: [pauseGuardian as `0x${string}`, true], + }), + }) + env.showMessage(` ✓ setPauseGuardian(${pauseGuardian})\n`) +}) diff --git a/packages/deployment/deploy/horizon/recurring-collector/05_transfer_governance.ts b/packages/deployment/deploy/horizon/recurring-collector/05_transfer_governance.ts new file mode 100644 index 000000000..672cc47d5 --- /dev/null +++ b/packages/deployment/deploy/horizon/recurring-collector/05_transfer_governance.ts @@ -0,0 +1,69 @@ +import { OZ_PROXY_ADMIN_ABI } from '@graphprotocol/deployment/lib/abis.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { getGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + getProxyAdminAddress, + requireContract, + requireDeployer, +} from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph, tx } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' +import { encodeFunctionData } from 'viem' + +/** + * Transfer RecurringCollector ProxyAdmin to protocol governor + * + * RC doesn't use BaseUpgradeable GOVERNOR_ROLE — only ProxyAdmin needs transfer. + * + * Idempotent: checks current owner, skips if already governor. + * + * Usage: + * pnpm hardhat deploy --tags RecurringCollector,transfer --network + */ +export default createActionModule(Contracts.horizon.RecurringCollector, DeploymentActions.TRANSFER, async (env) => { + const client = graph.getPublicClient(env) as PublicClient + const deployer = requireDeployer(env) + const governor = await getGovernor(env) + const rc = requireContract(env, Contracts.horizon.RecurringCollector) + + env.showMessage(`\n========== Transfer ${Contracts.horizon.RecurringCollector.name} ==========`) + + // Read ProxyAdmin from ERC1967 slot + const proxyAdminAddress = await getProxyAdminAddress(client, rc.address) + + const currentOwner = (await client.readContract({ + address: proxyAdminAddress as `0x${string}`, + abi: OZ_PROXY_ADMIN_ABI, + functionName: 'owner', + })) as string + + if (currentOwner.toLowerCase() === governor.toLowerCase()) { + env.showMessage(` ✓ ProxyAdmin already owned by governor\n`) + return + } + + if (currentOwner.toLowerCase() !== deployer.toLowerCase()) { + env.showMessage(` ○ ProxyAdmin owned by ${currentOwner}, not deployer — skipping\n`) + return + } + + env.showMessage(` Transferring ProxyAdmin ownership to governor...`) + env.showMessage(` ProxyAdmin: ${proxyAdminAddress}`) + env.showMessage(` From: ${deployer}`) + env.showMessage(` To: ${governor}`) + + const txFn = tx(env) + await txFn({ + account: deployer, + to: proxyAdminAddress as `0x${string}`, + data: encodeFunctionData({ + abi: OZ_PROXY_ADMIN_ABI, + functionName: 'transferOwnership', + args: [governor as `0x${string}`], + }), + }) + + env.showMessage(` ✓ ProxyAdmin ownership transferred to governor\n`) +}) diff --git a/packages/deployment/deploy/horizon/recurring-collector/09_end.ts b/packages/deployment/deploy/horizon/recurring-collector/09_end.ts new file mode 100644 index 000000000..5240c729c --- /dev/null +++ b/packages/deployment/deploy/horizon/recurring-collector/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.horizon.RecurringCollector) diff --git a/packages/deployment/deploy/horizon/recurring-collector/10_status.ts b/packages/deployment/deploy/horizon/recurring-collector/10_status.ts new file mode 100644 index 000000000..da1ecafc3 --- /dev/null +++ b/packages/deployment/deploy/horizon/recurring-collector/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.horizon.RecurringCollector) diff --git a/packages/deployment/deploy/horizon/staking/01_deploy.ts b/packages/deployment/deploy/horizon/staking/01_deploy.ts new file mode 100644 index 000000000..3b9f1c9d4 --- /dev/null +++ b/packages/deployment/deploy/horizon/staking/01_deploy.ts @@ -0,0 +1,15 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createImplementationDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createImplementationDeployModule( + Contracts.horizon.HorizonStaking, + (env) => { + const controller = env.getOrNull('Controller') + const subgraphService = env.getOrNull('SubgraphService') + if (!controller || !subgraphService) { + throw new Error('Missing required contract deployments (Controller, SubgraphService) after sync.') + } + return [controller.address, subgraphService.address] + }, + { prerequisites: [Contracts.horizon.Controller, Contracts['subgraph-service'].SubgraphService] }, +) diff --git a/packages/deployment/deploy/horizon/staking/02_upgrade.ts b/packages/deployment/deploy/horizon/staking/02_upgrade.ts new file mode 100644 index 000000000..d7abe8bbe --- /dev/null +++ b/packages/deployment/deploy/horizon/staking/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts.horizon.HorizonStaking) diff --git a/packages/deployment/deploy/horizon/staking/09_end.ts b/packages/deployment/deploy/horizon/staking/09_end.ts new file mode 100644 index 000000000..d374f7e79 --- /dev/null +++ b/packages/deployment/deploy/horizon/staking/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.horizon.HorizonStaking) diff --git a/packages/deployment/deploy/horizon/staking/10_status.ts b/packages/deployment/deploy/horizon/staking/10_status.ts new file mode 100644 index 000000000..22c2a940d --- /dev/null +++ b/packages/deployment/deploy/horizon/staking/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.horizon.HorizonStaking) diff --git a/packages/deployment/deploy/rewards/eligibility/01_deploy.ts b/packages/deployment/deploy/rewards/eligibility/01_deploy.ts deleted file mode 100644 index 11dd554a8..000000000 --- a/packages/deployment/deploy/rewards/eligibility/01_deploy.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { SpecialTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { deployProxyContract, requireGraphToken } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * Deploy RewardsEligibilityOracle proxy and implementation - * - * Deploys OZ v5 TransparentUpgradeableProxy with atomic initialization. - * Deployer receives GOVERNOR_ROLE (temporary, for configuration). - * - * See: docs/deploy/RewardsEligibilityOracleDeployment.md - * - * Usage: - * pnpm hardhat deploy --tags rewards-eligibility-deploy --network - */ - -const func: DeployScriptModule = async (env) => { - const graphToken = requireGraphToken(env).address - - env.showMessage(`\n📦 Deploying ${Contracts.issuance.RewardsEligibilityOracle.name} with GraphToken: ${graphToken}`) - - await deployProxyContract(env, { - contract: Contracts.issuance.RewardsEligibilityOracle, - constructorArgs: [graphToken], - }) -} - -func.tags = Tags.rewardsEligibilityDeploy -func.dependencies = [SpecialTags.SYNC] - -export default func diff --git a/packages/deployment/deploy/rewards/eligibility/02_upgrade.ts b/packages/deployment/deploy/rewards/eligibility/02_upgrade.ts deleted file mode 100644 index 4432d7391..000000000 --- a/packages/deployment/deploy/rewards/eligibility/02_upgrade.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { upgradeImplementation } from '@graphprotocol/deployment/lib/upgrade-implementation.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * Upgrade RewardsEligibilityOracle to pending implementation - * - * Generates governance TX batch for proxy upgrade, then exits. - * Execute separately via: pnpm hardhat deploy:execute-governance - * - * See: docs/deploy/RewardsEligibilityOracleDeployment.md - * - * Usage: - * pnpm hardhat deploy --tags rewards-eligibility-upgrade --network - */ - -const func: DeployScriptModule = async (env) => { - await upgradeImplementation(env, Contracts.issuance.RewardsEligibilityOracle) -} - -func.tags = Tags.rewardsEligibilityUpgrade -func.dependencies = [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.DEPLOY)] - -export default func diff --git a/packages/deployment/deploy/rewards/eligibility/04_configure.ts b/packages/deployment/deploy/rewards/eligibility/04_configure.ts deleted file mode 100644 index 849675917..000000000 --- a/packages/deployment/deploy/rewards/eligibility/04_configure.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { applyConfiguration } from '@graphprotocol/deployment/lib/apply-configuration.js' -import { checkREORole, getREOConditions } from '@graphprotocol/deployment/lib/contract-checks.js' -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' -import type { PublicClient } from 'viem' - -/** - * Configure RewardsEligibilityOracle (params + roles) - * - * See: docs/deploy/RewardsEligibilityOracleDeployment.md - */ -const func: DeployScriptModule = async (env) => { - const deployer = requireDeployer(env) - const [reo] = requireContracts(env, [Contracts.issuance.RewardsEligibilityOracle]) - const client = graph.getPublicClient(env) as PublicClient - - const canExecuteDirectly = (await checkREORole(client, reo.address, 'GOVERNOR_ROLE', deployer)).hasRole - - await applyConfiguration(env, client, await getREOConditions(env), { - contractName: Contracts.issuance.RewardsEligibilityOracle.name, - contractAddress: reo.address, - canExecuteDirectly, - executor: deployer, - }) -} - -func.tags = Tags.rewardsEligibilityConfigure -func.dependencies = [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.DEPLOY)] - -export default func diff --git a/packages/deployment/deploy/rewards/eligibility/05_transfer_governance.ts b/packages/deployment/deploy/rewards/eligibility/05_transfer_governance.ts deleted file mode 100644 index e19688c81..000000000 --- a/packages/deployment/deploy/rewards/eligibility/05_transfer_governance.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { applyConfiguration, checkConfigurationStatus } from '@graphprotocol/deployment/lib/apply-configuration.js' -import { getREOConditions, getREOTransferGovernanceConditions } from '@graphprotocol/deployment/lib/contract-checks.js' -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' -import type { PublicClient } from 'viem' - -/** - * Transfer governance of RewardsEligibilityOracle - * - * See: docs/deploy/RewardsEligibilityOracleDeployment.md - */ -const func: DeployScriptModule = async (env) => { - const deployer = requireDeployer(env) - const [reo] = requireContracts(env, [Contracts.issuance.RewardsEligibilityOracle]) - const client = graph.getPublicClient(env) as PublicClient - - // 1. Verify preconditions (same conditions as step 4) - env.showMessage(`\n📋 Verifying ${Contracts.issuance.RewardsEligibilityOracle.name} configuration...\n`) - const status = await checkConfigurationStatus(client, reo.address, await getREOConditions(env)) - for (const r of status.conditions) env.showMessage(` ${r.message}`) - if (!status.allOk) { - env.showMessage('\n❌ Configuration incomplete - run configure step first\n') - process.exit(1) - } - - // 2. Apply: revoke deployer's GOVERNOR_ROLE - await applyConfiguration(env, client, getREOTransferGovernanceConditions(deployer), { - contractName: `${Contracts.issuance.RewardsEligibilityOracle.name}-transfer-governance`, - contractAddress: reo.address, - canExecuteDirectly: true, - executor: deployer, - }) -} - -func.tags = Tags.rewardsEligibilityTransfer -func.dependencies = [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.CONFIGURE)] - -export default func diff --git a/packages/deployment/deploy/rewards/eligibility/06_integrate.ts b/packages/deployment/deploy/rewards/eligibility/06_integrate.ts deleted file mode 100644 index 3773c6982..000000000 --- a/packages/deployment/deploy/rewards/eligibility/06_integrate.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { applyConfiguration } from '@graphprotocol/deployment/lib/apply-configuration.js' -import { createRMIntegrationCondition } from '@graphprotocol/deployment/lib/contract-checks.js' -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { ComponentTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireContracts } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' -import type { PublicClient } from 'viem' - -/** - * Integrate RewardsEligibilityOracle with RewardsManager - * - * See: docs/deploy/RewardsEligibilityOracleDeployment.md - */ -const func: DeployScriptModule = async (env) => { - const [reo, rm] = requireContracts(env, [ - Contracts.issuance.RewardsEligibilityOracle, - Contracts.horizon.RewardsManager, - ]) - const client = graph.getPublicClient(env) as PublicClient - - // Apply: RM.providerEligibilityOracle = REO (always governance TX) - await applyConfiguration(env, client, [createRMIntegrationCondition(reo.address)], { - contractName: `${Contracts.horizon.RewardsManager.name}-REO`, - contractAddress: rm.address, - canExecuteDirectly: false, - }) -} - -func.tags = Tags.rewardsEligibilityIntegrate -func.dependencies = [Tags.rewardsEligibilityTransfer[0], ComponentTags.REWARDS_MANAGER] - -export default func diff --git a/packages/deployment/deploy/rewards/eligibility/09_complete.ts b/packages/deployment/deploy/rewards/eligibility/09_complete.ts deleted file mode 100644 index 0a97f6795..000000000 --- a/packages/deployment/deploy/rewards/eligibility/09_complete.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireUpgradeExecuted } from '@graphprotocol/deployment/lib/execute-governance.js' -import type { DeployScriptModule } from '@rocketh/core/types' - -/** - * RewardsEligibilityOracle complete - verifies full deployment - * - * Aggregate tag: runs deploy, upgrade, configure steps. - * Transfer-governance is separate (explicit action to relinquish control). - * - * See: docs/deploy/RewardsEligibilityOracleDeployment.md - * - * Usage: - * pnpm hardhat deploy --tags rewards-eligibility --network - */ -const func: DeployScriptModule = async (env) => { - requireUpgradeExecuted(env, Contracts.issuance.RewardsEligibilityOracle.name) - env.showMessage(`\n✓ ${Contracts.issuance.RewardsEligibilityOracle.name} ready`) -} - -func.tags = Tags.rewardsEligibility -func.dependencies = [ - actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.DEPLOY), - actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.UPGRADE), - actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.CONFIGURE), - actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.TRANSFER), - actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.INTEGRATE), - actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.VERIFY), -] - -export default func diff --git a/packages/deployment/deploy/rewards/eligibility/a/01_deploy.ts b/packages/deployment/deploy/rewards/eligibility/a/01_deploy.ts new file mode 100644 index 000000000..1bde8305b --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/a/01_deploy.ts @@ -0,0 +1,12 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { requireDeployer, requireGraphToken } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createProxyDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createProxyDeployModule( + Contracts.issuance.RewardsEligibilityOracleA, + (env) => ({ + constructorArgs: [requireGraphToken(env).address], + initializeArgs: [requireDeployer(env)], + }), + { prerequisites: [Contracts.horizon.L2GraphToken] }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/a/02_upgrade.ts b/packages/deployment/deploy/rewards/eligibility/a/02_upgrade.ts new file mode 100644 index 000000000..063a33cae --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/a/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts.issuance.RewardsEligibilityOracleA) diff --git a/packages/deployment/deploy/rewards/eligibility/a/04_configure.ts b/packages/deployment/deploy/rewards/eligibility/a/04_configure.ts new file mode 100644 index 000000000..26bb1e7c7 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/a/04_configure.ts @@ -0,0 +1,39 @@ +import { applyConfiguration } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { checkREORole, getREOConditions } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Configure RewardsEligibilityOracleA (params + roles) + * + * Deployer executes directly (has GOVERNOR_ROLE from deploy). + * If deployer doesn't have the role, skips — upgrade step handles it. + */ +export default createActionModule( + Contracts.issuance.RewardsEligibilityOracleA, + DeploymentActions.CONFIGURE, + async (env) => { + const [reo] = requireContracts(env, [Contracts.issuance.RewardsEligibilityOracleA]) + const client = graph.getPublicClient(env) as PublicClient + const deployer = requireDeployer(env) + + const deployerRole = await checkREORole(client, reo.address, 'GOVERNOR_ROLE', deployer) + if (!deployerRole.hasRole) { + env.showMessage( + `\n ○ ${Contracts.issuance.RewardsEligibilityOracleA.name}: deployer does not have GOVERNOR_ROLE — skipping\n`, + ) + return + } + + await applyConfiguration(env, client, await getREOConditions(env), { + contractName: Contracts.issuance.RewardsEligibilityOracleA.name, + contractAddress: reo.address, + canExecuteDirectly: true, + executor: deployer, + }) + }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/a/05_transfer_governance.ts b/packages/deployment/deploy/rewards/eligibility/a/05_transfer_governance.ts new file mode 100644 index 000000000..e09593859 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/a/05_transfer_governance.ts @@ -0,0 +1,45 @@ +import { applyConfiguration, checkConfigurationStatus } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { getREOConditions, getREOTransferGovernanceConditions } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + requireContracts, + requireDeployer, + transferProxyAdminOwnership, +} from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Transfer governance of RewardsEligibilityOracleA + */ +export default createActionModule( + Contracts.issuance.RewardsEligibilityOracleA, + DeploymentActions.TRANSFER, + async (env) => { + const deployer = requireDeployer(env) + const [reo] = requireContracts(env, [Contracts.issuance.RewardsEligibilityOracleA]) + const client = graph.getPublicClient(env) as PublicClient + + // 1. Verify preconditions (same conditions as step 4) + env.showMessage(`\n📋 Verifying ${Contracts.issuance.RewardsEligibilityOracleA.name} configuration...\n`) + const status = await checkConfigurationStatus(client, reo.address, await getREOConditions(env)) + for (const r of status.conditions) env.showMessage(` ${r.message}`) + if (!status.allOk) { + env.showMessage('\n ○ Configuration incomplete — skipping transfer\n') + return + } + + // 2. Apply: revoke deployer's GOVERNOR_ROLE + await applyConfiguration(env, client, getREOTransferGovernanceConditions(deployer), { + contractName: `${Contracts.issuance.RewardsEligibilityOracleA.name}-transfer-governance`, + contractAddress: reo.address, + canExecuteDirectly: true, + executor: deployer, + }) + + // 3. Transfer ProxyAdmin ownership to governor + await transferProxyAdminOwnership(env, Contracts.issuance.RewardsEligibilityOracleA) + }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/a/09_end.ts b/packages/deployment/deploy/rewards/eligibility/a/09_end.ts new file mode 100644 index 000000000..dd53f54ec --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/a/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.issuance.RewardsEligibilityOracleA) diff --git a/packages/deployment/deploy/rewards/eligibility/a/10_status.ts b/packages/deployment/deploy/rewards/eligibility/a/10_status.ts new file mode 100644 index 000000000..a42b58304 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/a/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.issuance.RewardsEligibilityOracleA) diff --git a/packages/deployment/deploy/rewards/eligibility/b/01_deploy.ts b/packages/deployment/deploy/rewards/eligibility/b/01_deploy.ts new file mode 100644 index 000000000..c360d882a --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/b/01_deploy.ts @@ -0,0 +1,12 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { requireDeployer, requireGraphToken } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createProxyDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createProxyDeployModule( + Contracts.issuance.RewardsEligibilityOracleB, + (env) => ({ + constructorArgs: [requireGraphToken(env).address], + initializeArgs: [requireDeployer(env)], + }), + { prerequisites: [Contracts.horizon.L2GraphToken] }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/b/02_upgrade.ts b/packages/deployment/deploy/rewards/eligibility/b/02_upgrade.ts new file mode 100644 index 000000000..1863d2847 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/b/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts.issuance.RewardsEligibilityOracleB) diff --git a/packages/deployment/deploy/rewards/eligibility/b/04_configure.ts b/packages/deployment/deploy/rewards/eligibility/b/04_configure.ts new file mode 100644 index 000000000..e06307f45 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/b/04_configure.ts @@ -0,0 +1,39 @@ +import { applyConfiguration } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { checkREORole, getREOConditions } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContracts, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Configure RewardsEligibilityOracleB (params + roles) + * + * Deployer executes directly (has GOVERNOR_ROLE from deploy). + * If deployer doesn't have the role, skips — upgrade step handles it. + */ +export default createActionModule( + Contracts.issuance.RewardsEligibilityOracleB, + DeploymentActions.CONFIGURE, + async (env) => { + const [reo] = requireContracts(env, [Contracts.issuance.RewardsEligibilityOracleB]) + const client = graph.getPublicClient(env) as PublicClient + const deployer = requireDeployer(env) + + const deployerRole = await checkREORole(client, reo.address, 'GOVERNOR_ROLE', deployer) + if (!deployerRole.hasRole) { + env.showMessage( + `\n ○ ${Contracts.issuance.RewardsEligibilityOracleB.name}: deployer does not have GOVERNOR_ROLE — skipping\n`, + ) + return + } + + await applyConfiguration(env, client, await getREOConditions(env), { + contractName: Contracts.issuance.RewardsEligibilityOracleB.name, + contractAddress: reo.address, + canExecuteDirectly: true, + executor: deployer, + }) + }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/b/05_transfer_governance.ts b/packages/deployment/deploy/rewards/eligibility/b/05_transfer_governance.ts new file mode 100644 index 000000000..87bcb281e --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/b/05_transfer_governance.ts @@ -0,0 +1,45 @@ +import { applyConfiguration, checkConfigurationStatus } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { getREOConditions, getREOTransferGovernanceConditions } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + requireContracts, + requireDeployer, + transferProxyAdminOwnership, +} from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Transfer governance of RewardsEligibilityOracleB + */ +export default createActionModule( + Contracts.issuance.RewardsEligibilityOracleB, + DeploymentActions.TRANSFER, + async (env) => { + const deployer = requireDeployer(env) + const [reo] = requireContracts(env, [Contracts.issuance.RewardsEligibilityOracleB]) + const client = graph.getPublicClient(env) as PublicClient + + // 1. Verify preconditions (same conditions as step 4) + env.showMessage(`\n📋 Verifying ${Contracts.issuance.RewardsEligibilityOracleB.name} configuration...\n`) + const status = await checkConfigurationStatus(client, reo.address, await getREOConditions(env)) + for (const r of status.conditions) env.showMessage(` ${r.message}`) + if (!status.allOk) { + env.showMessage('\n ○ Configuration incomplete — skipping transfer\n') + return + } + + // 2. Apply: revoke deployer's GOVERNOR_ROLE + await applyConfiguration(env, client, getREOTransferGovernanceConditions(deployer), { + contractName: `${Contracts.issuance.RewardsEligibilityOracleB.name}-transfer-governance`, + contractAddress: reo.address, + canExecuteDirectly: true, + executor: deployer, + }) + + // 3. Transfer ProxyAdmin ownership to governor + await transferProxyAdminOwnership(env, Contracts.issuance.RewardsEligibilityOracleB) + }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/b/09_end.ts b/packages/deployment/deploy/rewards/eligibility/b/09_end.ts new file mode 100644 index 000000000..3a11b891a --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/b/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.issuance.RewardsEligibilityOracleB) diff --git a/packages/deployment/deploy/rewards/eligibility/b/10_status.ts b/packages/deployment/deploy/rewards/eligibility/b/10_status.ts new file mode 100644 index 000000000..f8a4d48a8 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/b/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.issuance.RewardsEligibilityOracleB) diff --git a/packages/deployment/deploy/rewards/eligibility/mock/01_deploy.ts b/packages/deployment/deploy/rewards/eligibility/mock/01_deploy.ts new file mode 100644 index 000000000..0d687127c --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/mock/01_deploy.ts @@ -0,0 +1,12 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { requireDeployer, requireGraphToken } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createProxyDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createProxyDeployModule( + Contracts.issuance.RewardsEligibilityOracleMock, + (env) => ({ + constructorArgs: [requireGraphToken(env).address], + initializeArgs: [requireDeployer(env)], + }), + { prerequisites: [Contracts.horizon.L2GraphToken] }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/mock/02_upgrade.ts b/packages/deployment/deploy/rewards/eligibility/mock/02_upgrade.ts new file mode 100644 index 000000000..74e2374b8 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/mock/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts.issuance.RewardsEligibilityOracleMock) diff --git a/packages/deployment/deploy/rewards/eligibility/mock/05_transfer_governance.ts b/packages/deployment/deploy/rewards/eligibility/mock/05_transfer_governance.ts new file mode 100644 index 000000000..6be92ce32 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/mock/05_transfer_governance.ts @@ -0,0 +1,39 @@ +import { applyConfiguration } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { getREOTransferGovernanceConditions } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + requireContracts, + requireDeployer, + transferProxyAdminOwnership, +} from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Transfer governance of MockRewardsEligibilityOracle + * + * Revokes deployer's GOVERNOR_ROLE and transfers ProxyAdmin ownership + * to the protocol governor. + */ +export default createActionModule( + Contracts.issuance.RewardsEligibilityOracleMock, + DeploymentActions.TRANSFER, + async (env) => { + const deployer = requireDeployer(env) + const [reo] = requireContracts(env, [Contracts.issuance.RewardsEligibilityOracleMock]) + const client = graph.getPublicClient(env) as PublicClient + + // Revoke deployer's GOVERNOR_ROLE + await applyConfiguration(env, client, getREOTransferGovernanceConditions(deployer), { + contractName: `${Contracts.issuance.RewardsEligibilityOracleMock.name}-transfer-governance`, + contractAddress: reo.address, + canExecuteDirectly: true, + executor: deployer, + }) + + // Transfer ProxyAdmin ownership to governor + await transferProxyAdminOwnership(env, Contracts.issuance.RewardsEligibilityOracleMock) + }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/mock/06_integrate.ts b/packages/deployment/deploy/rewards/eligibility/mock/06_integrate.ts new file mode 100644 index 000000000..2bd1ed3ac --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/mock/06_integrate.ts @@ -0,0 +1,39 @@ +import { applyConfiguration } from '@graphprotocol/deployment/lib/apply-configuration.js' +import { createRMIntegrationCondition } from '@graphprotocol/deployment/lib/contract-checks.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { canSignAsGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' +import { ComponentTags, DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContracts } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Integrate MockRewardsEligibilityOracle with RewardsManager (testnet only) + * + * Points RewardsManager at the mock so indexers can control their own eligibility. + */ +export default createActionModule( + Contracts.issuance.RewardsEligibilityOracleMock, + DeploymentActions.INTEGRATE, + async (env) => { + const [reo, rm] = requireContracts(env, [ + Contracts.issuance.RewardsEligibilityOracleMock, + Contracts.horizon.RewardsManager, + ]) + const client = graph.getPublicClient(env) as PublicClient + + const { governor, canSign } = await canSignAsGovernor(env) + + await applyConfiguration(env, client, [createRMIntegrationCondition(reo.address)], { + contractName: `${Contracts.horizon.RewardsManager.name}-MockREO`, + contractAddress: rm.address, + canExecuteDirectly: canSign, + executor: governor, + }) + }, + { + extraDependencies: [ComponentTags.REWARDS_MANAGER], + prerequisites: [Contracts.horizon.RewardsManager], + }, +) diff --git a/packages/deployment/deploy/rewards/eligibility/mock/09_end.ts b/packages/deployment/deploy/rewards/eligibility/mock/09_end.ts new file mode 100644 index 000000000..98cacd97f --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/mock/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts.issuance.RewardsEligibilityOracleMock) diff --git a/packages/deployment/deploy/rewards/eligibility/mock/10_status.ts b/packages/deployment/deploy/rewards/eligibility/mock/10_status.ts new file mode 100644 index 000000000..611316b02 --- /dev/null +++ b/packages/deployment/deploy/rewards/eligibility/mock/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.issuance.RewardsEligibilityOracleMock) diff --git a/packages/deployment/deploy/rewards/manager/01_deploy.ts b/packages/deployment/deploy/rewards/manager/01_deploy.ts index 3d72bc314..2223ce0ed 100644 --- a/packages/deployment/deploy/rewards/manager/01_deploy.ts +++ b/packages/deployment/deploy/rewards/manager/01_deploy.ts @@ -1,21 +1,4 @@ -import { deployImplementation, getImplementationConfig } from '@graphprotocol/deployment/lib/deploy-implementation.js' -import { SpecialTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createImplementationDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' -// RewardsManager Implementation Deployment -// -// Deploys a new RewardsManager implementation if artifact bytecode differs from on-chain. -// -// Workflow: -// 1. Compare artifact bytecode with on-chain bytecode (accounting for immutables) -// 2. If different, deploy new implementation -// 3. Store as "pendingImplementation" in horizon/addresses.json -// 4. Upgrade task (separate) handles TX generation and execution - -const func: DeployScriptModule = async (env) => { - await deployImplementation(env, getImplementationConfig('horizon', 'RewardsManager')) -} - -func.tags = Tags.rewardsManagerDeploy -func.dependencies = [SpecialTags.SYNC] -export default func +export default createImplementationDeployModule(Contracts.horizon.RewardsManager) diff --git a/packages/deployment/deploy/rewards/manager/02_upgrade.ts b/packages/deployment/deploy/rewards/manager/02_upgrade.ts index effed5fe9..5c888723b 100644 --- a/packages/deployment/deploy/rewards/manager/02_upgrade.ts +++ b/packages/deployment/deploy/rewards/manager/02_upgrade.ts @@ -1,26 +1,4 @@ import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { ComponentTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { upgradeImplementation } from '@graphprotocol/deployment/lib/upgrade-implementation.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' -// RewardsManager Upgrade -// -// Generates governance TX batch and executes upgrade. -// -// Workflow: -// 1. Check for pending implementation in address book -// 2. Generate governance TX (upgrade + acceptProxy) -// 3. Fork mode: execute via governor impersonation -// 4. Production: output TX file for Safe execution -// -// Usage: -// FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags rewards-manager-upgrade --network localhost - -const func: DeployScriptModule = async (env) => { - await upgradeImplementation(env, Contracts.horizon.RewardsManager) -} - -func.tags = Tags.rewardsManagerUpgrade -func.dependencies = [ComponentTags.REWARDS_MANAGER_DEPLOY] - -export default func +export default createUpgradeModule(Contracts.horizon.RewardsManager) diff --git a/packages/deployment/deploy/rewards/manager/09_end.ts b/packages/deployment/deploy/rewards/manager/09_end.ts index d07b4cee5..ae4996ffd 100644 --- a/packages/deployment/deploy/rewards/manager/09_end.ts +++ b/packages/deployment/deploy/rewards/manager/09_end.ts @@ -1,19 +1,4 @@ -import { ComponentTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireUpgradeExecuted } from '@graphprotocol/deployment/lib/execute-governance.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' -/** - * RewardsManager end state - deployed and upgraded - * - * Usage: - * pnpm hardhat deploy --tags rewards-manager --network - */ -const func: DeployScriptModule = async (env) => { - requireUpgradeExecuted(env, 'RewardsManager') - env.showMessage(`\n✓ RewardsManager ready`) -} - -func.tags = Tags.rewardsManager -func.dependencies = [ComponentTags.REWARDS_MANAGER_DEPLOY, ComponentTags.REWARDS_MANAGER_UPGRADE] - -export default func +export default createEndModule(Contracts.horizon.RewardsManager) diff --git a/packages/deployment/deploy/rewards/manager/10_status.ts b/packages/deployment/deploy/rewards/manager/10_status.ts new file mode 100644 index 000000000..4b47d40bb --- /dev/null +++ b/packages/deployment/deploy/rewards/manager/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts.horizon.RewardsManager) diff --git a/packages/deployment/deploy/rewards/reclaim/01_deploy.ts b/packages/deployment/deploy/rewards/reclaim/01_deploy.ts index 520eef497..3ee161636 100644 --- a/packages/deployment/deploy/rewards/reclaim/01_deploy.ts +++ b/packages/deployment/deploy/rewards/reclaim/01_deploy.ts @@ -1,50 +1,45 @@ import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { ComponentTags, SpecialTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { deployProxyContract } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { ComponentTags, DeploymentActions, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { deployProxyContract, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' import type { DeployScriptModule } from '@rocketh/core/types' /** - * Deploy DirectAllocation proxies as reclaim addresses + * Deploy DirectAllocation proxy as default reclaim address * - * This script deploys DirectAllocation proxy instances for each reclaim reason. - * All proxies share the DirectAllocation_Implementation deployed by direct-allocation-impl. + * This script deploys a single DirectAllocation proxy instance used as the + * default reclaim address on RewardsManager for all reclaim reasons. + * The proxy uses the DirectAllocation_Implementation deployed by direct-allocation-impl. * * Deployed contracts: - * - ReclaimedRewardsForIndexerIneligible - * - ReclaimedRewardsForSubgraphDenied - * - ReclaimedRewardsForStalePoi - * - ReclaimedRewardsForZeroPoi - * - ReclaimedRewardsForCloseAllocation + * - ReclaimedRewards * * Usage: - * pnpm hardhat deploy --tags rewards-reclaim-deploy --network + * pnpm hardhat deploy --tags RewardsReclaim:deploy --network */ -// Reclaim contracts that share DirectAllocation implementation -const RECLAIM_CONTRACTS = [ - Contracts.issuance.ReclaimedRewardsForIndexerIneligible, - Contracts.issuance.ReclaimedRewardsForSubgraphDenied, - Contracts.issuance.ReclaimedRewardsForStalePoi, - Contracts.issuance.ReclaimedRewardsForZeroPoi, - Contracts.issuance.ReclaimedRewardsForCloseAllocation, -] as const - const func: DeployScriptModule = async (env) => { - env.showMessage(`\n📦 Deploying DirectAllocation reclaim address proxies...`) + if (shouldSkipAction(DeploymentActions.DEPLOY)) return + await syncComponentsFromRegistry(env, [ + Contracts.issuance.DirectAllocation_Implementation, + Contracts.horizon.RewardsManager, + Contracts.issuance.ReclaimedRewards, + ]) + + env.showMessage(`\n📦 Deploying DirectAllocation reclaim address proxy...`) env.showMessage(` Shared implementation: ${Contracts.issuance.DirectAllocation_Implementation.name}`) - for (const contract of RECLAIM_CONTRACTS) { - await deployProxyContract(env, { - contract, - sharedImplementation: Contracts.issuance.DirectAllocation_Implementation, - // initializeArgs defaults to [governor] - }) - } + await deployProxyContract(env, { + contract: Contracts.issuance.ReclaimedRewards, + sharedImplementation: Contracts.issuance.DirectAllocation_Implementation, + initializeArgs: [requireDeployer(env)], + }) - env.showMessage('\n✓ Reclaim addresses deployment complete') + env.showMessage('\n✓ Reclaim address deployment complete') } -func.tags = Tags.rewardsReclaimDeploy -func.dependencies = [SpecialTags.SYNC, ComponentTags.DIRECT_ALLOCATION_IMPL, ComponentTags.REWARDS_MANAGER] +func.tags = [ComponentTags.REWARDS_RECLAIM] +func.dependencies = [ComponentTags.DIRECT_ALLOCATION_IMPL, ComponentTags.REWARDS_MANAGER] +func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) export default func diff --git a/packages/deployment/deploy/rewards/reclaim/02_upgrade.ts b/packages/deployment/deploy/rewards/reclaim/02_upgrade.ts index 7fa17437f..bc27987a0 100644 --- a/packages/deployment/deploy/rewards/reclaim/02_upgrade.ts +++ b/packages/deployment/deploy/rewards/reclaim/02_upgrade.ts @@ -1,43 +1,36 @@ import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { ComponentTags, DeploymentActions, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' import { upgradeImplementation } from '@graphprotocol/deployment/lib/upgrade-implementation.js' import type { DeployScriptModule } from '@rocketh/core/types' // ReclaimedRewards Upgrade // -// Upgrades ReclaimedRewardsFor* proxies to DirectAllocation implementation via per-proxy ProxyAdmin. -// The implementation is shared across multiple allocation proxies. +// Upgrades ReclaimedRewards proxy to DirectAllocation implementation via per-proxy ProxyAdmin. // // Workflow: // 1. Check for pending implementation in address book (set by direct-allocation-impl) -// 2. Generate governance TX (upgradeAndCall to per-proxy ProxyAdmin) for each proxy +// 2. Generate governance TX (upgradeAndCall to per-proxy ProxyAdmin) // 3. Fork mode: execute via governor impersonation // 4. Production: output TX file for Safe execution // // Usage: -// FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags rewards-reclaim-upgrade --network localhost - -// Reclaim contracts that share DirectAllocation implementation -const RECLAIM_CONTRACTS = [ - Contracts.issuance.ReclaimedRewardsForIndexerIneligible, - Contracts.issuance.ReclaimedRewardsForSubgraphDenied, - Contracts.issuance.ReclaimedRewardsForStalePoi, - Contracts.issuance.ReclaimedRewardsForZeroPoi, - Contracts.issuance.ReclaimedRewardsForCloseAllocation, -] as const +// FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags RewardsReclaim:upgrade --network localhost const func: DeployScriptModule = async (env) => { - for (const contract of RECLAIM_CONTRACTS) { - await upgradeImplementation(env, contract, { - implementationName: 'DirectAllocation', - }) - } + if (shouldSkipAction(DeploymentActions.UPGRADE)) return + await syncComponentsFromRegistry(env, [ + Contracts.issuance.DirectAllocation_Implementation, + Contracts.issuance.ReclaimedRewards, + ]) + await upgradeImplementation(env, Contracts.issuance.ReclaimedRewards, { + implementationName: 'DirectAllocation', + }) + await syncComponentsFromRegistry(env, [Contracts.issuance.ReclaimedRewards]) } -func.tags = Tags.rewardsReclaimUpgrade -func.dependencies = [ - actionTag(ComponentTags.REWARDS_RECLAIM, DeploymentActions.DEPLOY), - ComponentTags.DIRECT_ALLOCATION_IMPL, -] +func.tags = [ComponentTags.REWARDS_RECLAIM] +func.dependencies = [ComponentTags.DIRECT_ALLOCATION_IMPL] +func.skip = async () => shouldSkipAction(DeploymentActions.UPGRADE) export default func diff --git a/packages/deployment/deploy/rewards/reclaim/04_configure.ts b/packages/deployment/deploy/rewards/reclaim/04_configure.ts index e545cd970..ad1afee4d 100644 --- a/packages/deployment/deploy/rewards/reclaim/04_configure.ts +++ b/packages/deployment/deploy/rewards/reclaim/04_configure.ts @@ -1,145 +1,144 @@ -import { REWARDS_MANAGER_ABI } from '@graphprotocol/deployment/lib/abis.js' -import { - getReclaimAddress, - RECLAIM_CONTRACT_NAMES, - RECLAIM_REASONS, - type ReclaimReasonKey, -} from '@graphprotocol/deployment/lib/contract-checks.js' +import { ACCESS_CONTROL_ENUMERABLE_ABI, REWARDS_MANAGER_ABI } from '@graphprotocol/deployment/lib/abis.js' import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { getGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { createGovernanceTxBuilder } from '@graphprotocol/deployment/lib/execute-governance.js' -import { requireContract } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' -import { execute, graph } from '@graphprotocol/deployment/rocketh/deploy.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { getGovernor, getPauseGuardian } from '@graphprotocol/deployment/lib/controller-utils.js' +import { ComponentTags, DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { requireContract, requireDeployer } from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { checkReclaimConfigured } from '@graphprotocol/deployment/lib/preconditions.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { graph, read, tx } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' import { encodeFunctionData } from 'viem' /** - * Configure RewardsManager with reclaim addresses + * Configure ReclaimedRewards — role grants only * - * Sets the reclaim addresses on RewardsManager for token recovery. - * This requires RewardsManager to be upgraded (governance operation). + * Grants GOVERNOR_ROLE to protocol governor and PAUSE_ROLE to pause guardian. + * Deployer executes directly (has GOVERNOR_ROLE from deploy). + * If deployer doesn't have the role, skips — upgrade step handles it. * - * Configured reasons: - * - INDEXER_INELIGIBLE → ReclaimedRewardsForIndexerIneligible - * - SUBGRAPH_DENIED → ReclaimedRewardsForSubgraphDenied - * - STALE_POI → ReclaimedRewardsForStalePoi - * - ZERO_POI → ReclaimedRewardsForZeroPoi - * - CLOSE_ALLOCATION → ReclaimedRewardsForCloseAllocation - * - * Idempotent: checks if already configured, skips if so. - * Generates Safe TX batch if direct execution fails. + * RM.setDefaultReclaimAddress is a governance TX bundled in the upgrade step. * * Usage: - * pnpm hardhat deploy --tags rewards-reclaim-configure --network + * pnpm hardhat deploy --tags RewardsReclaim:configure --network */ -const func: DeployScriptModule = async (env) => { - const executeFn = execute(env) - const client = graph.getPublicClient(env) - - // Get protocol governor from Controller - const governor = await getGovernor(env) - - const rewardsManager = requireContract(env, Contracts.horizon.RewardsManager) - - env.showMessage(`\n========== Configure ${Contracts.horizon.RewardsManager.name} Reclaim ==========`) - env.showMessage(`${Contracts.horizon.RewardsManager.name}: ${rewardsManager.address}`) - - // Find deployed reclaim addresses - const reclaimAddresses: { name: string; address: string; reasonKey: ReclaimReasonKey }[] = [] - - for (const [reasonKey, contractName] of Object.entries(RECLAIM_CONTRACT_NAMES)) { - const deployment = env.getOrNull(contractName) - if (deployment) { - reclaimAddresses.push({ - name: contractName, - address: deployment.address, - reasonKey: reasonKey as ReclaimReasonKey, - }) - } - } - - if (reclaimAddresses.length === 0) { - env.showMessage(`\n⚠️ No reclaim addresses deployed, skipping configuration`) - return - } - - env.showMessage(`\nFound ${reclaimAddresses.length} reclaim address(es):`) - for (const { name, address } of reclaimAddresses) { - env.showMessage(` ${name}: ${address}`) - } - - // Check current configuration - const needsConfiguration: typeof reclaimAddresses = [] - - for (const reclaim of reclaimAddresses) { - const reason = RECLAIM_REASONS[reclaim.reasonKey] - - // Check if RM has this reclaim address configured for this reason - const currentReclaim = await getReclaimAddress(client, rewardsManager.address, reason) - if (currentReclaim && currentReclaim.toLowerCase() === reclaim.address.toLowerCase()) { - env.showMessage(`\n✓ ${reclaim.name} already configured on RewardsManager`) - continue +export default createActionModule( + Contracts.issuance.ReclaimedRewards, + DeploymentActions.CONFIGURE, + async (env) => { + const client = graph.getPublicClient(env) as PublicClient + const readFn = read(env) + const deployer = requireDeployer(env) + const governor = await getGovernor(env) + const pauseGuardian = await getPauseGuardian(env) + + const rewardsManager = requireContract(env, Contracts.horizon.RewardsManager) + const reclaimedRewards = requireContract(env, Contracts.issuance.ReclaimedRewards) + + env.showMessage(`\n========== Configure ${Contracts.issuance.ReclaimedRewards.name} ==========`) + env.showMessage(`ReclaimedRewards: ${reclaimedRewards.address}`) + + // Check if fully configured (shared precondition check) + const precondition = await checkReclaimConfigured( + client, + rewardsManager.address, + reclaimedRewards.address, + governor, + pauseGuardian, + ) + if (precondition.done) { + env.showMessage(`\n✅ ${Contracts.issuance.ReclaimedRewards.name} already configured\n`) + return } - needsConfiguration.push(reclaim) - } - - if (needsConfiguration.length === 0) { - env.showMessage(`\n✓ All reclaim addresses already configured`) - return - } - - // Build TX batch - env.showMessage(`\n🔨 Building configuration TX batch...`) - - const builder = await createGovernanceTxBuilder(env, `configure-${Contracts.horizon.RewardsManager.name}-Reclaim`) - - for (const reclaim of needsConfiguration) { - const reason = RECLAIM_REASONS[reclaim.reasonKey] + // Check role grants + env.showMessage('\n📋 Checking configuration...\n') + + const GOVERNOR_ROLE = (await readFn(reclaimedRewards, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` + const PAUSE_ROLE = (await readFn(reclaimedRewards, { functionName: 'PAUSE_ROLE' })) as `0x${string}` + + const governorHasRole = (await client.readContract({ + address: reclaimedRewards.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [GOVERNOR_ROLE, governor as `0x${string}`], + })) as boolean + env.showMessage(` Governor GOVERNOR_ROLE: ${governorHasRole ? '✓' : '✗'}`) + + const pauseGuardianHasRole = (await client.readContract({ + address: reclaimedRewards.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [PAUSE_ROLE, pauseGuardian as `0x${string}`], + })) as boolean + env.showMessage(` PauseGuardian PAUSE_ROLE: ${pauseGuardianHasRole ? '✓' : '✗'}`) + + // RM integration status (informational — handled by upgrade step) try { - const data = encodeFunctionData({ + const currentDefault = (await client.readContract({ + address: rewardsManager.address as `0x${string}`, abi: REWARDS_MANAGER_ABI, - functionName: 'setReclaimAddress', - args: [reason as `0x${string}`, reclaim.address as `0x${string}`], - }) - builder.addTx({ to: rewardsManager.address, value: '0', data }) - env.showMessage(` + setReclaimAddress(${reclaim.reasonKey}, ${reclaim.address})`) + functionName: 'getDefaultReclaimAddress', + })) as string + const rmOk = currentDefault.toLowerCase() === reclaimedRewards.address.toLowerCase() + env.showMessage(` RM default reclaim: ${rmOk ? '✓' : '○ will be set in upgrade step (governance TX)'}`) } catch { - env.showMessage(` ⚠️ setReclaimAddress not available on RewardsManager interface`) - return + env.showMessage(` RM default reclaim: ○ RM not upgraded — will be set in upgrade step`) } - } - const txFile = builder.saveToFile() - env.showMessage(`\n✓ TX batch saved: ${txFile}`) + // Execute role grants as deployer + const deployerHasRole = (await client.readContract({ + address: reclaimedRewards.address as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [GOVERNOR_ROLE, deployer as `0x${string}`], + })) as boolean + + if (!deployerHasRole) { + env.showMessage( + `\n ○ Deployer does not have GOVERNOR_ROLE — skipping role grants (governance TX in upgrade step)\n`, + ) + return + } - // Try direct execution - env.showMessage(`\n🔐 Attempting direct execution...`) - try { - for (const reclaim of needsConfiguration) { - const reason = RECLAIM_REASONS[reclaim.reasonKey] + const txs: Array<{ to: string; data: `0x${string}`; label: string }> = [] + + if (!governorHasRole) { + txs.push({ + to: reclaimedRewards.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [GOVERNOR_ROLE, governor as `0x${string}`], + }), + label: `grantRole(GOVERNOR_ROLE, ${governor})`, + }) + } - await executeFn(rewardsManager, { - account: governor, - functionName: 'setReclaimAddress', - args: [reason, reclaim.address], + if (!pauseGuardianHasRole) { + txs.push({ + to: reclaimedRewards.address, + data: encodeFunctionData({ + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'grantRole', + args: [PAUSE_ROLE, pauseGuardian as `0x${string}`], + }), + label: `grantRole(PAUSE_ROLE, ${pauseGuardian})`, }) - env.showMessage(` ✓ setReclaimAddress(${reclaim.reasonKey}, ${reclaim.address}) executed`) } - env.showMessage(`\n✅ ${Contracts.horizon.RewardsManager.name} reclaim configuration complete!`) - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - env.showMessage(`\n⚠️ Direct execution failed: ${errorMessage.slice(0, 100)}...`) - env.showMessage(`\n📋 GOVERNANCE ACTION REQUIRED:`) - env.showMessage(` The ${Contracts.horizon.RewardsManager.name} reclaim configuration must be executed via Safe.`) - env.showMessage(` TX batch file: ${txFile}`) - env.showMessage(` Import this file into Safe Transaction Builder.`) - } -} - -func.tags = Tags.rewardsReclaimConfigure -func.dependencies = [actionTag(ComponentTags.REWARDS_RECLAIM, DeploymentActions.UPGRADE), ComponentTags.REWARDS_MANAGER] - -export default func + if (txs.length > 0) { + env.showMessage('\n🔨 Executing role grants as deployer...\n') + const txFn = tx(env) + for (const t of txs) { + await txFn({ account: deployer, to: t.to as `0x${string}`, data: t.data }) + env.showMessage(` ✓ ${t.label}`) + } + } + + env.showMessage(`\n✅ ${Contracts.issuance.ReclaimedRewards.name} role grants complete\n`) + }, + { + extraDependencies: [ComponentTags.REWARDS_MANAGER], + prerequisites: [Contracts.horizon.RewardsManager], + }, +) diff --git a/packages/deployment/deploy/rewards/reclaim/05_transfer_governance.ts b/packages/deployment/deploy/rewards/reclaim/05_transfer_governance.ts new file mode 100644 index 000000000..bdcd728b2 --- /dev/null +++ b/packages/deployment/deploy/rewards/reclaim/05_transfer_governance.ts @@ -0,0 +1,56 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { + requireContract, + requireDeployer, + transferProxyAdminOwnership, +} from '@graphprotocol/deployment/lib/issuance-deploy-utils.js' +import { checkDeployerRevoked } from '@graphprotocol/deployment/lib/preconditions.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { execute, graph, read } from '@graphprotocol/deployment/rocketh/deploy.js' +import type { PublicClient } from 'viem' + +/** + * Transfer ReclaimedRewards governance from deployer + * + * - Revoke GOVERNOR_ROLE from deployment account + * - Transfer ProxyAdmin ownership to governor + * + * Role grants (GOVERNOR_ROLE, PAUSE_ROLE) happen in 04_configure.ts. + * This script only revokes deployer access. + * + * Idempotent: checks on-chain state, skips if already transferred. + * + * Usage: + * pnpm hardhat deploy --tags RewardsReclaim,transfer --network + */ +export default createActionModule(Contracts.issuance.ReclaimedRewards, DeploymentActions.TRANSFER, async (env) => { + const readFn = read(env) + const executeFn = execute(env) + const client = graph.getPublicClient(env) as PublicClient + const deployer = requireDeployer(env) + const reclaim = requireContract(env, Contracts.issuance.ReclaimedRewards) + + env.showMessage(`\n========== Transfer ${Contracts.issuance.ReclaimedRewards.name} ==========`) + + // Check if deployer GOVERNOR_ROLE already revoked (shared precondition check) + const precondition = await checkDeployerRevoked(client, reclaim.address, deployer) + if (precondition.done) { + env.showMessage(`✓ Deployer GOVERNOR_ROLE already revoked`) + } else { + const GOVERNOR_ROLE = (await readFn(reclaim, { functionName: 'GOVERNOR_ROLE' })) as `0x${string}` + + env.showMessage(`🔨 Revoking deployer GOVERNOR_ROLE...`) + await executeFn(reclaim, { + account: deployer, + functionName: 'revokeRole', + args: [GOVERNOR_ROLE, deployer], + }) + env.showMessage(` ✓ revokeRole(GOVERNOR_ROLE) executed`) + } + + // Transfer ProxyAdmin ownership to governor + await transferProxyAdminOwnership(env, Contracts.issuance.ReclaimedRewards) + + env.showMessage(`\n✅ ${Contracts.issuance.ReclaimedRewards.name} governance transferred!\n`) +}) diff --git a/packages/deployment/deploy/rewards/reclaim/09_end.ts b/packages/deployment/deploy/rewards/reclaim/09_end.ts index 5043dfde4..46d6aa2dc 100644 --- a/packages/deployment/deploy/rewards/reclaim/09_end.ts +++ b/packages/deployment/deploy/rewards/reclaim/09_end.ts @@ -1,32 +1,4 @@ -import { RECLAIM_CONTRACT_NAMES } from '@graphprotocol/deployment/lib/contract-checks.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireUpgradeExecuted } from '@graphprotocol/deployment/lib/execute-governance.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' -/** - * RewardsReclaim end state - deployed, upgraded, and configured - * - * Aggregate tag that ensures ReclaimedRewardsFor* contracts are fully ready: - * - Proxies and shared implementation deployed - * - Proxies upgraded to latest implementation - * - Configured on RewardsManager - * - * Usage: - * pnpm hardhat deploy --tags rewards-reclaim --network - */ -const func: DeployScriptModule = async (env) => { - // Check all reclaim address proxies for pending upgrades - for (const contractName of Object.values(RECLAIM_CONTRACT_NAMES)) { - requireUpgradeExecuted(env, contractName) - } - env.showMessage(`\n✓ RewardsReclaim ready`) -} - -func.tags = Tags.rewardsReclaim -func.dependencies = [ - actionTag(ComponentTags.REWARDS_RECLAIM, DeploymentActions.DEPLOY), - actionTag(ComponentTags.REWARDS_RECLAIM, DeploymentActions.UPGRADE), - actionTag(ComponentTags.REWARDS_RECLAIM, DeploymentActions.CONFIGURE), -] - -export default func +export default createEndModule(Contracts.issuance.ReclaimedRewards) diff --git a/packages/deployment/deploy/rewards/reclaim/10_status.ts b/packages/deployment/deploy/rewards/reclaim/10_status.ts new file mode 100644 index 000000000..c5f778ac9 --- /dev/null +++ b/packages/deployment/deploy/rewards/reclaim/10_status.ts @@ -0,0 +1,14 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { ComponentTags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' +import { showDetailedComponentStatus } from '@graphprotocol/deployment/lib/status-detail.js' + +/** + * RewardsReclaim status - show detailed state of reclaim contract + * + * Usage: + * pnpm hardhat deploy --tags RewardsReclaim --network + */ +export default createStatusModule(ComponentTags.REWARDS_RECLAIM, async (env) => { + await showDetailedComponentStatus(env, Contracts.issuance.ReclaimedRewards) +}) diff --git a/packages/deployment/deploy/service/dispute/01_deploy.ts b/packages/deployment/deploy/service/dispute/01_deploy.ts new file mode 100644 index 000000000..3158750b9 --- /dev/null +++ b/packages/deployment/deploy/service/dispute/01_deploy.ts @@ -0,0 +1,12 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createImplementationDeployModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createImplementationDeployModule( + Contracts['subgraph-service'].DisputeManager, + (env) => { + const controller = env.getOrNull('Controller') + if (!controller) throw new Error('Missing Controller deployment after sync.') + return [controller.address] + }, + { prerequisites: [Contracts.horizon.Controller] }, +) diff --git a/packages/deployment/deploy/service/dispute/02_upgrade.ts b/packages/deployment/deploy/service/dispute/02_upgrade.ts new file mode 100644 index 000000000..99c75d9e3 --- /dev/null +++ b/packages/deployment/deploy/service/dispute/02_upgrade.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createUpgradeModule(Contracts['subgraph-service'].DisputeManager) diff --git a/packages/deployment/deploy/service/dispute/09_end.ts b/packages/deployment/deploy/service/dispute/09_end.ts new file mode 100644 index 000000000..5a1afb1a4 --- /dev/null +++ b/packages/deployment/deploy/service/dispute/09_end.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createEndModule(Contracts['subgraph-service'].DisputeManager) diff --git a/packages/deployment/deploy/service/dispute/10_status.ts b/packages/deployment/deploy/service/dispute/10_status.ts new file mode 100644 index 000000000..1039074c0 --- /dev/null +++ b/packages/deployment/deploy/service/dispute/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts['subgraph-service'].DisputeManager) diff --git a/packages/deployment/deploy/service/subgraph/01_deploy.ts b/packages/deployment/deploy/service/subgraph/01_deploy.ts index e90a2dbef..ff1b46b95 100644 --- a/packages/deployment/deploy/service/subgraph/01_deploy.ts +++ b/packages/deployment/deploy/service/subgraph/01_deploy.ts @@ -1,44 +1,146 @@ -import { deployImplementation, getImplementationConfig } from '@graphprotocol/deployment/lib/deploy-implementation.js' -import { SpecialTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { linkArtifactLibraries } from '@graphprotocol/deployment/lib/artifact-loaders.js' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { + deployImplementation, + getImplementationConfig, + loadArtifactFromSource, +} from '@graphprotocol/deployment/lib/deploy-implementation.js' +import { ComponentTags, DeploymentActions, shouldSkipAction } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { syncComponentsFromRegistry } from '@graphprotocol/deployment/lib/sync-utils.js' +import { deploy } from '@graphprotocol/deployment/rocketh/deploy.js' import type { DeployScriptModule } from '@rocketh/core/types' // SubgraphService Implementation Deployment // -// Deploys a new SubgraphService implementation if artifact bytecode differs from on-chain. +// SubgraphService uses external Solidity libraries that must be deployed first +// and linked into the implementation bytecode before deployment. +// +// Library dependency order: +// 1. StakeClaims (standalone, from horizon) +// 2. AllocationHandler (standalone) +// 3. IndexingAgreementDecoderRaw (standalone) +// 4. IndexingAgreementDecoder (links IndexingAgreementDecoderRaw) +// 5. IndexingAgreement (links IndexingAgreementDecoder) +// 6. SubgraphService (links all above) // // Workflow: -// 1. Compare artifact bytecode with on-chain bytecode (accounting for immutables) -// 2. If different, deploy new implementation +// 1. Deploy libraries in dependency order +// 2. Deploy SS implementation with linked libraries // 3. Store as "pendingImplementation" in subgraph-service/addresses.json // 4. Upgrade task (separate) handles TX generation and execution const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.DEPLOY)) return + await syncComponentsFromRegistry(env, [ + Contracts.horizon.Controller, + Contracts['subgraph-service'].DisputeManager, + Contracts.horizon.GraphTallyCollector, + Contracts.horizon.L2Curation, + Contracts.horizon.RecurringCollector, + Contracts['subgraph-service'].SubgraphService, + ]) + // Get constructor args from imported deployments const controllerDep = env.getOrNull('Controller') const disputeManagerDep = env.getOrNull('DisputeManager') const graphTallyCollectorDep = env.getOrNull('GraphTallyCollector') const curationDep = env.getOrNull('L2Curation') + const recurringCollectorDep = env.getOrNull('RecurringCollector') - if (!controllerDep || !disputeManagerDep || !graphTallyCollectorDep || !curationDep) { + if (!controllerDep || !disputeManagerDep || !graphTallyCollectorDep || !curationDep || !recurringCollectorDep) { throw new Error( - 'Missing required contract deployments (Controller, DisputeManager, GraphTallyCollector, L2Curation). ' + - 'The sync step should have imported these.', + 'Missing required contract deployments after sync ' + + '(Controller, DisputeManager, GraphTallyCollector, L2Curation, RecurringCollector).', ) } - await deployImplementation( - env, - getImplementationConfig('subgraph-service', 'SubgraphService', { - constructorArgs: [ - controllerDep.address, - disputeManagerDep.address, - graphTallyCollectorDep.address, - curationDep.address, - ], - }), + // Deploy libraries in dependency order + const deployFn = deploy(env) + const deployer = env.namedAccounts.deployer + if (!deployer) throw new Error('No deployer account configured') + + env.showMessage('\n📚 Deploying SubgraphService libraries...') + + // 1. StakeClaims (from horizon, standalone) + const stakeClaimsArtifact = loadArtifactFromSource({ + type: 'horizon', + path: 'contracts/data-service/libraries/StakeClaims.sol/StakeClaims', + }) + const stakeClaims = await deployFn('StakeClaims', { + account: deployer, + artifact: stakeClaimsArtifact, + args: [], + }) + env.showMessage(` StakeClaims: ${stakeClaims.address}`) + + // 2. AllocationHandler (standalone) + const allocationHandlerArtifact = loadArtifactFromSource({ + type: 'subgraph-service', + name: 'libraries/AllocationHandler', + }) + const allocationHandler = await deployFn('AllocationHandler', { + account: deployer, + artifact: allocationHandlerArtifact, + args: [], + }) + env.showMessage(` AllocationHandler: ${allocationHandler.address}`) + + // 3. IndexingAgreementDecoderRaw (standalone) + const decoderRawArtifact = loadArtifactFromSource({ + type: 'subgraph-service', + name: 'libraries/IndexingAgreementDecoderRaw', + }) + const decoderRaw = await deployFn('IndexingAgreementDecoderRaw', { + account: deployer, + artifact: decoderRawArtifact, + args: [], + }) + env.showMessage(` IndexingAgreementDecoderRaw: ${decoderRaw.address}`) + + // 4. IndexingAgreementDecoder (links IndexingAgreementDecoderRaw) + // Pre-link libraries into artifact so rocketh stores linked bytecode + // (rocketh's bytecode comparison breaks for unlinked artifacts — see linkArtifactLibraries) + const decoderArtifact = linkArtifactLibraries( + loadArtifactFromSource({ type: 'subgraph-service', name: 'libraries/IndexingAgreementDecoder' }), + { IndexingAgreementDecoderRaw: decoderRaw.address as `0x${string}` }, + ) + const decoder = await deployFn('IndexingAgreementDecoder', { account: deployer, artifact: decoderArtifact, args: [] }) + env.showMessage(` IndexingAgreementDecoder: ${decoder.address}`) + + // 5. IndexingAgreement (links IndexingAgreementDecoder) + const indexingAgreementArtifact = linkArtifactLibraries( + loadArtifactFromSource({ type: 'subgraph-service', name: 'libraries/IndexingAgreement' }), + { IndexingAgreementDecoder: decoder.address as `0x${string}` }, ) + const indexingAgreement = await deployFn('IndexingAgreement', { + account: deployer, + artifact: indexingAgreementArtifact, + args: [], + }) + env.showMessage(` IndexingAgreement: ${indexingAgreement.address}`) + + env.showMessage(' ✓ Libraries deployed\n') + + // 6. Deploy SubgraphService implementation with all libraries linked + const config = getImplementationConfig('subgraph-service', 'SubgraphService', { + constructorArgs: [ + controllerDep.address, + disputeManagerDep.address, + graphTallyCollectorDep.address, + curationDep.address, + recurringCollectorDep.address, + ], + }) + + await deployImplementation(env, config, { + StakeClaims: stakeClaims.address, + AllocationHandler: allocationHandler.address, + IndexingAgreement: indexingAgreement.address, + IndexingAgreementDecoder: decoder.address, + }) } -func.tags = Tags.subgraphServiceDeploy -func.dependencies = [SpecialTags.SYNC] +func.tags = [ComponentTags.SUBGRAPH_SERVICE] +func.dependencies = [ComponentTags.RECURRING_COLLECTOR] +func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) export default func diff --git a/packages/deployment/deploy/service/subgraph/02_upgrade.ts b/packages/deployment/deploy/service/subgraph/02_upgrade.ts index 6f4ece5d9..1395af76c 100644 --- a/packages/deployment/deploy/service/subgraph/02_upgrade.ts +++ b/packages/deployment/deploy/service/subgraph/02_upgrade.ts @@ -1,26 +1,4 @@ import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { upgradeImplementation } from '@graphprotocol/deployment/lib/upgrade-implementation.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' -// SubgraphService Upgrade -// -// Generates governance TX batch and executes upgrade. -// -// Workflow: -// 1. Check for pending implementation in address book -// 2. Generate governance TX (upgradeAndCall) -// 3. Fork mode: execute via governor impersonation -// 4. Production: output TX file for Safe execution -// -// Usage: -// FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags subgraph-service-upgrade --network localhost - -const func: DeployScriptModule = async (env) => { - await upgradeImplementation(env, Contracts['subgraph-service'].SubgraphService) -} - -func.tags = Tags.subgraphServiceUpgrade -func.dependencies = [actionTag(ComponentTags.SUBGRAPH_SERVICE, DeploymentActions.DEPLOY)] - -export default func +export default createUpgradeModule(Contracts['subgraph-service'].SubgraphService) diff --git a/packages/deployment/deploy/service/subgraph/04_configure.ts b/packages/deployment/deploy/service/subgraph/04_configure.ts new file mode 100644 index 000000000..61dfc3f17 --- /dev/null +++ b/packages/deployment/deploy/service/subgraph/04_configure.ts @@ -0,0 +1,22 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { DeploymentActions } from '@graphprotocol/deployment/lib/deployment-tags.js' +import { createActionModule } from '@graphprotocol/deployment/lib/script-factories.js' + +/** + * Configure SubgraphService + * + * In the current contract version, RecurringCollector is set as an immutable + * constructor argument — no runtime authorization is needed. + * + * This script is a no-op placeholder for future configuration needs. + * + * Usage: + * pnpm hardhat deploy --tags SubgraphService:configure --network + */ +export default createActionModule( + Contracts['subgraph-service'].SubgraphService, + DeploymentActions.CONFIGURE, + async (env) => { + env.showMessage(`\n✅ SubgraphService: RecurringCollector is set at construction time, no configuration needed\n`) + }, +) diff --git a/packages/deployment/deploy/service/subgraph/09_end.ts b/packages/deployment/deploy/service/subgraph/09_end.ts index 0a34b344e..786490018 100644 --- a/packages/deployment/deploy/service/subgraph/09_end.ts +++ b/packages/deployment/deploy/service/subgraph/09_end.ts @@ -1,22 +1,4 @@ -import { actionTag, ComponentTags, DeploymentActions, Tags } from '@graphprotocol/deployment/lib/deployment-tags.js' -import { requireUpgradeExecuted } from '@graphprotocol/deployment/lib/execute-governance.js' -import type { DeployScriptModule } from '@rocketh/core/types' +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createEndModule } from '@graphprotocol/deployment/lib/script-factories.js' -/** - * SubgraphService end state - deployed and upgraded - * - * Usage: - * pnpm hardhat deploy --tags subgraph-service --network - */ -const func: DeployScriptModule = async (env) => { - requireUpgradeExecuted(env, 'SubgraphService') - env.showMessage(`\n✓ SubgraphService ready`) -} - -func.tags = Tags.subgraphService -func.dependencies = [ - actionTag(ComponentTags.SUBGRAPH_SERVICE, DeploymentActions.DEPLOY), - actionTag(ComponentTags.SUBGRAPH_SERVICE, DeploymentActions.UPGRADE), -] - -export default func +export default createEndModule(Contracts['subgraph-service'].SubgraphService) diff --git a/packages/deployment/deploy/service/subgraph/10_status.ts b/packages/deployment/deploy/service/subgraph/10_status.ts new file mode 100644 index 000000000..aa66de54e --- /dev/null +++ b/packages/deployment/deploy/service/subgraph/10_status.ts @@ -0,0 +1,4 @@ +import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { createStatusModule } from '@graphprotocol/deployment/lib/script-factories.js' + +export default createStatusModule(Contracts['subgraph-service'].SubgraphService) diff --git a/packages/deployment/docs/Architecture.md b/packages/deployment/docs/Architecture.md index 4486b7afb..2704a722f 100644 --- a/packages/deployment/docs/Architecture.md +++ b/packages/deployment/docs/Architecture.md @@ -12,27 +12,32 @@ Unified deployment package for Graph Protocol contracts. ``` packages/deployment/ -├── deploy/ # hardhat-deploy scripts -│ ├── common/ # Validation, imports -│ ├── issuance/ # Issuance contracts -│ ├── contracts/ # Core protocol (RewardsManager) -│ └── subgraph-service/ # SubgraphService +├── deploy/ # hardhat-deploy / rocketh scripts +│ ├── common/ # 00_sync.ts +│ ├── horizon/ # RM, HS, PE, L2Curation, RC +│ ├── service/ # SubgraphService, DisputeManager +│ ├── allocate/ # IssuanceAllocator, DefaultAllocation, DirectAllocation +│ ├── agreement/ # RecurringAgreementManager +│ ├── rewards/ # RewardsEligibilityOracle (A/B/mock), Reclaim +│ └── gip/0088/ # GIP-0088 goal orchestration (upgrade phase + activation) +├── lib/ # Shared utilities (preconditions, contract registry, tags, ABIs, ...) ├── tasks/ # Hardhat tasks (deploy:*) -├── governance/ # Safe TX builders -├── deployments/ # Per-network artifacts -└── test/ # Integration tests +├── docs/ # This documentation +└── test/ # Unit tests (bytecode, registry, tx-builder, ...) ``` ## Tags -| Tag | Deploys | -| ---------------------- | ------------------------------------ | -| `sync` | Sync address books, import contracts | -| `rewards-manager` | RewardsManager implementation | -| `subgraph-service` | SubgraphService implementation | -| `upgrade` | Generate TX, execute upgrades | -| `issuance-proxy-admin` | GraphIssuanceProxyAdmin | -| `issuance-core` | All issuance contracts | +Two-dimensional tag model. See [`lib/deployment-tags.ts`](../lib/deployment-tags.ts) for the source of truth. + +| Kind | Examples | Purpose | +| --------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| Special | `sync` | Sync address books, import contracts | +| Component | `IssuanceAllocator`, `RewardsManager`, `RecurringAgreementManager`, `RewardsEligibilityOracleA`, ... | One per deployable contract | +| Action verb | `deploy`, `upgrade`, `configure`, `transfer`, `integrate`, `all` | Combined with a component or goal tag to gate work | +| Goal scope | `GIP-0088`, `GIP-0088:upgrade` | Multi-component orchestration for a deployment | +| Activation goal | `GIP-0088:eligibility-integrate`, `GIP-0088:issuance-connect`, `GIP-0088:issuance-allocate` | Per-step governance TX for the activation phases | +| Optional goal | `GIP-0088:eligibility-revert`, `GIP-0088:issuance-close-guard` | Excluded from `--tags ...,all` — must be requested explicitly | ## External Artifacts diff --git a/packages/deployment/docs/DeploymentSetup.md b/packages/deployment/docs/DeploymentSetup.md index c9a2534f3..4b4fd4f4d 100644 --- a/packages/deployment/docs/DeploymentSetup.md +++ b/packages/deployment/docs/DeploymentSetup.md @@ -124,6 +124,7 @@ npx hardhat deploy --skip-prompts --network arbitrumSepolia --tags | Network | Chain ID | RPC (default) | | --------------- | -------- | ---------------------------------------- | +| localNetwork | 1337 | `http://chain:8545` | | arbitrumSepolia | 421614 | | | arbitrumOne | 42161 | | @@ -157,6 +158,66 @@ export ARBISCAN_API_KEY=$(npx hardhat keystore get ARBISCAN_API_KEY) npx hardhat deploy --skip-prompts --network arbitrumSepolia --tags ``` +## Tagging Deployments (WIP) + +> This convention is a work in progress — feedback and changes welcome. + +After a deployment is committed, create an annotated git tag to record the deployment. +Tags use `deploy/{mainnet|testnet}/YYYY-MM-DD` format. The annotation is auto-generated +from address book diffs, listing which contracts changed. + +**Requires:** `jq` (`sudo apt install jq` / `brew install jq`) + +### Usage + +```bash +# Preview first +./scripts/tag-deployment.sh \ + --deployer "packages/deployment --tags RewardsManager" \ + --network arbitrumSepolia \ + --base main \ + --dry-run + +# Create the tag +./scripts/tag-deployment.sh \ + --deployer "packages/deployment --tags RewardsManager" \ + --network arbitrumSepolia \ + --base main + +# Push +git push origin deploy/testnet/2026-03-02 +``` + +The `--deployer` argument is free-form — describe what performed the deployment: + +- `"packages/deployment --tags RewardsManager,SubgraphService"` +- `"packages/horizon ignition migrate"` +- `"manual: forge script DeployFoo"` + +### Workflow + +1. Deploy contracts and update address books +2. Commit the address book changes +3. Run `tag-deployment.sh` (tag must point to a finalized commit) +4. Push branch and tag + +### Options + +| Option | Description | +| ------------------- | --------------------------------------------- | +| `--deployer ` | What performed the deployment (required) | +| `--network ` | `arbitrumOne` or `arbitrumSepolia` (required) | +| `--base ` | Git ref to diff against (default: `HEAD~1`) | +| `--dry-run` | Preview without creating tag | +| `--sign` | Force-sign the tag with `-s` | + +### Viewing tags + +```bash +git tag -l 'deploy/*' # List all deployment tags +git show --no-patch deploy/testnet/... # View tag annotation +``` + ## See Also - [LocalForkTesting.md](./LocalForkTesting.md) - Fork-based testing workflow diff --git a/packages/deployment/docs/Design.md b/packages/deployment/docs/Design.md index d53d22125..6eec92811 100644 --- a/packages/deployment/docs/Design.md +++ b/packages/deployment/docs/Design.md @@ -5,7 +5,7 @@ High-level architecture for the unified deployment system. **See also:** - [Architecture.md](./Architecture.md) - Package structure and organization -- [../deploy/ImplementationPrinciples.md](../deploy/ImplementationPrinciples.md) - Deploy script patterns and conventions +- [deploy/ImplementationPrinciples.md](./deploy/ImplementationPrinciples.md) - Deploy script patterns and conventions ## Components @@ -13,8 +13,8 @@ High-level architecture for the unified deployment system. - IssuanceAllocator - Upgradeable proxy managing issuance distribution - RewardsEligibilityOracle - Upgradeable proxy for eligibility verification -- PilotAllocation - Upgradeable proxy for allocation testing -- GraphIssuanceProxyAdmin - Shared proxy admin for issuance contracts +- ReclaimedRewards (DirectAllocation) - Upgradeable proxy for default reclaim address +- RecurringAgreementManager - Upgradeable proxy for agreement-based payments **Referenced contracts** (already deployed): @@ -26,16 +26,19 @@ High-level architecture for the unified deployment system. ``` packages/deployment/ -├── deploy/ # Numbered deployment scripts -│ ├── admin/ # GraphIssuanceProxyAdmin -│ ├── allocate/ # IssuanceAllocator, PilotAllocation -│ ├── common/ # Validation, external imports -│ ├── rewards/ # RewardsManager, RewardsEligibilityOracle -│ ├── service/ # SubgraphService -│ └── ImplementationPrinciples.md # Script patterns -├── lib/ # Shared utilities, Safe TX builder -├── tasks/ # Hardhat tasks -└── docs/ # Architecture documentation +├── deploy/ # Numbered deployment scripts (rocketh + hardhat-deploy) +│ ├── common/ # 00_sync.ts +│ ├── horizon/ # RewardsManager, HorizonStaking, PaymentsEscrow, L2Curation, RecurringCollector +│ ├── service/ # SubgraphService, DisputeManager +│ ├── allocate/ # IssuanceAllocator, DefaultAllocation, DirectAllocation impl +│ ├── agreement/ # RecurringAgreementManager +│ ├── rewards/ # RewardsEligibilityOracle (A/B/mock), Reclaim +│ └── gip/0088/ # GIP-0088 goal orchestration +├── lib/ # Shared utilities (preconditions, registry, tags, ABIs, governance) +├── tasks/ # Hardhat tasks (deploy:*) +├── docs/ # Architecture and operational documentation +│ └── deploy/ # Deploy-script principles and per-component design notes +└── test/ # Unit tests ``` ## Governance Model @@ -48,54 +51,46 @@ packages/deployment/ ### Proxy Administration -```mermaid -graph TB - Gov[Governance Multi-sig] - ExistingAdmin[GraphProxyAdmin] - NewAdmin[GraphIssuanceProxyAdmin] - - Gov -->|owns| ExistingAdmin - Gov -->|owns| NewAdmin - - LegacyContracts[Staking, Curation, EpochManager, RewardsManager] - IssuanceContracts[IssuanceAllocator, RewardsEligibilityOracle, PilotAllocation] - - ExistingAdmin -->|manages| LegacyContracts - NewAdmin -->|manages| IssuanceContracts -``` - -**Key principle:** Separate proxy admins for legacy vs new issuance contracts, both governance-owned. +Two distinct proxy patterns coexist: -### Component Administration +- **Legacy `GraphProxy`** (custom Graph Protocol pattern) — used by RewardsManager, HorizonStaking, L2Curation, EpochManager. A single shared `GraphProxyAdmin` (owned by governance) controls upgrades for all of them. +- **OZ v5 `TransparentUpgradeableProxy`** — used by every new contract this package deploys (IssuanceAllocator, DefaultAllocation, ReclaimedRewards, RecurringAgreementManager, RewardsEligibilityOracle A/B, RecurringCollector, SubgraphService, DisputeManager, PaymentsEscrow). Each proxy gets its own per-proxy `ProxyAdmin` created by the proxy constructor; ownership is transferred to governance in the transfer step. ```mermaid graph TB - ProxyAdmin[GraphIssuanceProxyAdmin] - - subgraph "Issuance Allocation" - IA[IssuanceAllocator] - IA_Impl[IssuanceAllocatorImplementation] - end + Gov[Governance Multi-sig] + GraphAdmin[GraphProxyAdmin] - subgraph "Allocation Instances" - PA[PilotAllocation] - PA_Impl[DirectAllocation shared impl] + subgraph "Legacy GraphProxy" + RM[RewardsManager] + HS[HorizonStaking] + L2C[L2Curation] end - subgraph "Rewards Eligibility" - REO[RewardsEligibilityOracle] - REO_Impl[RewardsEligibilityOracleImplementation] + subgraph "OZ v5 TransparentUpgradeableProxy
(per-proxy admin)" + IA[IssuanceAllocator] + DA[DefaultAllocation] + Reclaim[ReclaimedRewards] + RAM[RecurringAgreementManager] + REO[RewardsEligibilityOracle A/B] + RC[RecurringCollector] end - ProxyAdmin -->|upgrades| IA - ProxyAdmin -->|upgrades| PA - ProxyAdmin -->|upgrades| REO - - IA -.->|delegates to| IA_Impl - PA -.->|delegates to| PA_Impl - REO -.->|delegates to| REO_Impl + Gov -->|owns| GraphAdmin + GraphAdmin -->|upgrades| RM + GraphAdmin -->|upgrades| HS + GraphAdmin -->|upgrades| L2C + + Gov -.->|owns each per-proxy admin| IA + Gov -.->|owns each per-proxy admin| DA + Gov -.->|owns each per-proxy admin| Reclaim + Gov -.->|owns each per-proxy admin| RAM + Gov -.->|owns each per-proxy admin| REO + Gov -.->|owns each per-proxy admin| RC ``` +**Key principle:** Every proxy admin is governance-owned. Legacy contracts share a single `GraphProxyAdmin`; new contracts each have their own per-proxy admin created at construction. + ## Contract Integration ### RewardsEligibilityOracle Integration @@ -120,7 +115,7 @@ graph TB IA[IssuanceAllocator] subgraph "Allocator Minting" - PA[PilotAllocation] + RAM[RecurringAgreementManager] end subgraph "Self Minting" @@ -128,7 +123,7 @@ graph TB end GT -->|minting authority| IA - IA -->|distributes to| PA + IA -->|distributes to| RAM IA -->|allocates to| RM ``` @@ -146,13 +141,13 @@ graph TD RewardsEligibilityOracle[RewardsEligibilityOracle] IssuanceAllocator[IssuanceAllocator] - PilotAllocation[PilotAllocation] + RecurringAgreementManager[RecurringAgreementManager] RewardsManager -.->|queries| RewardsEligibilityOracle IssuanceAllocator -.->|integrates with| RewardsManager IssuanceAllocator -.->|mints from| GraphToken - IssuanceAllocator -.->|distributes to| PilotAllocation - PilotAllocation -.->|holds| GraphToken + IssuanceAllocator -.->|distributes to| RecurringAgreementManager + RecurringAgreementManager -.->|funds| PaymentsEscrow ``` ## Address Book Management @@ -206,41 +201,44 @@ sequenceDiagram ```mermaid sequenceDiagram participant Deployer - participant Deploy as hardhat-deploy - participant Admin as GraphIssuanceProxyAdmin + participant Deploy as rocketh + participant Admin as ProxyAdmin (per-proxy) participant Impl as Implementation participant Proxy as TransparentUpgradeableProxy participant Gov as Governance Note over Deployer,Gov: Initial Deployment - Deployer->>Deploy: Run deployment scripts - Deploy->>Impl: Deploy contract bytecode - Deploy->>Proxy: Deploy proxy with init - Proxy->>Impl: Initialize + Deployer->>Deploy: --tags Component,deploy + Deploy->>Impl: Deploy implementation + Deploy->>Proxy: Deploy proxy (constructor creates per-proxy Admin) + Proxy->>Impl: Initialize with deployer as governor - Note over Deployer,Gov: Configuration - Deploy->>Proxy: Perform initial configuration - Deploy->>Proxy: Grant GOVERNOR_ROLE to governance + Note over Deployer,Gov: Configure + Deployer->>Deploy: --tags Component,configure + Deploy->>Proxy: Set params, grant roles to gov + pause guardian - Note over Deployer,Gov: Governance Update - Deployer->>Deploy: Generate update proposal - Gov->>Proxy: Execute configuration update + Note over Deployer,Gov: Transfer + Deployer->>Deploy: --tags Component,transfer + Deploy->>Proxy: Revoke deployer GOVERNOR_ROLE + Deploy->>Admin: Transfer ProxyAdmin ownership to Gov Note over Deployer,Gov: Implementation Upgrade - Deployer->>Deploy: Deploy new implementation - Deploy->>Deploy: Generate upgrade proposal - Gov->>Admin: Execute upgrade - Admin->>Proxy: Upgrade to new implementation - - Note over Deployer,Gov: Verification - Deployer->>Deploy: Run sync (--tags sync) - Deploy->>Proxy: Check current implementation - Deploy->>Deploy: Update address book + Deployer->>Deploy: --tags Component,upgrade + Deploy->>Impl: Deploy new implementation + Deploy->>Deploy: Save governance TX batch + Gov->>Admin: Execute upgrade TX + Admin->>Proxy: upgradeAndCall(newImpl) + + Note over Deployer,Gov: Sync + Deployer->>Deploy: --tags sync + Deploy->>Proxy: Read current implementation + Deploy->>Deploy: Update address book (pending → active) ``` ## Conventions - TypeScript throughout (.ts) - TitleCase for documentation -- Deploy script patterns: [ImplementationPrinciples.md](../deploy/ImplementationPrinciples.md) -- All 01_deploy.ts scripts MUST depend on SpecialTags.SYNC +- Deploy script patterns: [ImplementationPrinciples.md](./deploy/ImplementationPrinciples.md) +- Deploy scripts sync the contracts they touch immediately before/after their action via `syncComponentFromRegistry`/`syncComponentsFromRegistry`. The full + global sync is opt-in via `npx hardhat deploy:sync` and is no longer an automatic dependency of every component script. diff --git a/packages/deployment/docs/Gip0088.md b/packages/deployment/docs/Gip0088.md new file mode 100644 index 000000000..3afd7d815 --- /dev/null +++ b/packages/deployment/docs/Gip0088.md @@ -0,0 +1,241 @@ +# GIP-0088: Deployment Guide + +Protocol upgrade deploying the Issuance Allocator, Rewards Eligibility Oracle, and on-chain indexing agreements, as specified by [GIP-0088](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0088.md). + +## Related GIPs + +| GIP | Title | What it specifies | +| ----------------------------------------------------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| [GIP-0076](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0076.md) | Issuance Allocator | Contract spec: governance-controlled issuance distribution across self-minting and allocator-minting targets | +| [GIP-0079](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0079.md) | Rewards Eligibility Oracle | Contract spec: quality-of-service gating on indexing rewards via authorized oracle | +| [GIP-0086](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0086.md) | RM and SS Upgrade | Contract upgrades: RM gains eligibility oracle hook + issuance allocator integration; SS gains agreement support | +| [GIP-0087](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0087.md) | On-Chain Indexing Agreements | Contract spec: RecurringCollector, RecurringAgreementManager, indexing agreement lifecycle in SubgraphService | +| [GIP-0088](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0088.md) | IA Deployment and IP Config | **Deployment proposal**: deploy IA (0076), connect to upgraded RM (0086), allocate to RAM (0087) | + +## Contracts + +### New contracts (deploy) + +| Contract | Package | GIP | Purpose | +| ------------------------------ | -------- | ---- | ----------------------------------------------------------- | +| IssuanceAllocator | issuance | 0076 | Governance-managed issuance distribution across targets | +| DefaultAllocation | issuance | 0076 | Default target safety net for unallocated issuance | +| ReclaimedRewards | issuance | 0076 | Default reclaim destination for reclaimed rewards | +| RecurringCollector | horizon | 0087 | EIP-712 collector for recurring payment agreement lifecycle | +| RecurringAgreementManager | issuance | 0087 | Protocol-funded indexing agreements and escrow management | +| RewardsEligibilityOracle (A/B) | issuance | 0079 | Quality-of-service gating on indexing rewards | + +### Existing contracts (upgrade implementation) + +| Contract | Package | GIP | Key changes | +| --------------- | ---------------- | --------- | ------------------------------------------------------------------------------------------------- | +| RewardsManager | contracts | 0086 | `setIssuanceAllocator()`, `IProviderEligibility` integration, `revertOnIneligible`, reclaim infra | +| SubgraphService | subgraph-service | 0086/0087 | Indexing agreement lifecycle, `enforceService`, `recurringCollector` integration | +| DisputeManager | subgraph-service | 0086/0087 | `createIndexingFeeDisputeV1()`, removes legacy dispute creation | +| HorizonStaking | horizon | 0086 | Removes HorizonStakingExtension, consolidates functionality | +| PaymentsEscrow | horizon | 0087 | `adjustThaw()` for payer thaw modification | +| L2Curation | contracts | 0086 | Removes staking as authorized `collect()` caller | + +## Deploy Scripts + +### GIP-0088 scripts (`deploy/gip/0088/`) + +**Upgrade phase** (`upgrade/`) — deploys, configures, transfers, and upgrades ALL contracts: + +| Script | `--tags` | What it does | +| -------------- | ---------------------------- | ------------------------------------------------------------------------------------- | +| `01_deploy` | `GIP-0088:upgrade,deploy` | Deploy all new contracts + implementations | +| `02_configure` | `GIP-0088:upgrade,configure` | Deployer-only configure: role grants and params on contracts where deployer is gov | +| `03_transfer` | `GIP-0088:upgrade,transfer` | Transfer governance of new contracts (revoke deployer role + ProxyAdmin to gov) | +| `04_upgrade` | `GIP-0088:upgrade,upgrade` | Bundle proxy upgrades + all deferred configure into one governance TX batch (details) | +| `10_status` | `GIP-0088:upgrade` | Show upgrade state and next step | + +`04_upgrade` builds a single governance TX batch containing: + +| Group | Items | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Proxy upgrades | Iterates registry; for any deployable proxy with `pendingImplementation`, adds the proxy upgrade TX | +| Existing-contract config | `RC.setPauseGuardian`, `RM.setDefaultReclaimAddress` | +| Deferred new-contract config | IA: `setIssuancePerBlock`, role grants. DA: role grants. RAM: role grants + `setIssuanceAllocator`. Reclaim: role grants. REO A/B: params + role grants | + +Items in groups 2 and 3 are added only when not already on-chain. The bundle exists because configure runs as the deployer and skips anything that requires `GOVERNOR_ROLE` on contracts the deployer doesn't yet control (or that depend on RM being upgraded). + +**Activation goals** — governance TXs that change protocol behaviour (after upgrade complete): + +| Script | `--tags` | What it does | +| ----------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `eligibility_integrate` | `GIP-0088:eligibility-integrate` | `RM.setProviderEligibilityOracle(REO_A)` | +| `issuance_connect` | `GIP-0088:issuance-connect` | `GraphToken.addMinter(IA)` → `RM.setIssuanceAllocator(IA)` → `IA.setTargetAllocation(RM, 0, rate)` (RM as 100% self-minting target) → `IA.setDefaultTarget(DA)` (safety net) | +| `issuance_allocate` | `GIP-0088:issuance-allocate` | `IA.setTargetAllocation(RAM, allocatorRate, selfRate)` (rates from `config/.json5`) | + +**Optional goals** — not planned for initial deployment: + +| Script | `--tags` | What it does | +| ---------------------- | ------------------------------- | ------------------------------------------------------- | +| `eligibility_revert` | `GIP-0088:eligibility-revert` | `RM.setRevertOnIneligible(true)` | +| `issuance_close_guard` | `GIP-0088:issuance-close-guard` | `SS.setBlockClosingAllocationWithActiveAgreement(true)` | + +**Overall** — `09_end` (`GIP-0088,all`) verifies all non-optional goals. `10_status` (`GIP-0088`) shows full deployment state. + +### Component lifecycle scripts + +Each contract has its own lifecycle scripts under `deploy/`. The GIP-0088 upgrade phase depends on component tags — it orchestrates the component scripts rather than duplicating their logic. + +## Deployment Process + +### How `--tags` drives the deployment + +The upgrade phase tag (`GIP-0088:upgrade`) combined with an action verb (`deploy`, `configure`, `transfer`, `upgrade`) selects which lifecycle step runs. Activation goals have their own tags. + +- `--tags GIP-0088:upgrade,deploy` — deploy all contracts +- `--tags GIP-0088:upgrade,configure` — configure all contracts +- `--tags GIP-0088:upgrade,transfer` — transfer to governance control +- `--tags GIP-0088:upgrade,upgrade` — generate proxy upgrade TX batch +- `--tags GIP-0088:upgrade` — show status and next step +- `--tags GIP-0088:eligibility-integrate` — integrate REO with RM (governance TX) +- `--tags GIP-0088:issuance-connect` — connect IA to RM + minter role (governance TX) +- `--tags GIP-0088:issuance-allocate` — allocate issuance to RAM (governance TX) +- `--tags GIP-0088` — overall status + +All scripts are idempotent — they check on-chain state and skip if already done. Scripts do not presume a particular starting state. + +Sync runs automatically as a dependency of all scripts. + +### Deployment sequence + +```bash +# Deploy and configure all contracts +pnpm hardhat deploy --tags GIP-0088:upgrade,deploy --network +pnpm hardhat deploy --tags GIP-0088:upgrade,configure --network + +# Check status before transferring governance +pnpm hardhat deploy --tags GIP-0088:upgrade --network + +# Transfer governance — after this, deployer has no special access +pnpm hardhat deploy --tags GIP-0088:upgrade,transfer --network + +# Generate proxy upgrade governance TX batch +pnpm hardhat deploy --tags GIP-0088:upgrade,upgrade --network +# → execute governance TXs (see Environments below) + +# Activation goals (each generates governance TXs independently) +pnpm hardhat deploy --tags GIP-0088:eligibility-integrate --network +pnpm hardhat deploy --tags GIP-0088:issuance-connect --network +pnpm hardhat deploy --tags GIP-0088:issuance-allocate --network +# → execute governance TXs + +# Verify +pnpm hardhat deploy --tags GIP-0088 --network +``` + +### Preconditions + +Each script checks its own preconditions and skips if not met. Scripts do not presume a particular starting state — they are goal-seeking, not sequential steps. + +#### Deploy (`GIP-0088:upgrade,deploy`) + +| Contract | Precondition | Notes | +| ------------------------------------------ | ------------ | ----------------------------------------------------- | +| RC | — | No dependencies | +| SS implementation | RC deployed | SS has RC address baked into bytecode via `Directory` | +| RM, HS, DM, PE, L2Curation implementations | — | No deploy-time dependencies | +| IA, DefaultAllocation, Reclaim | — | Independent | +| RAM | — | Independent | +| REO A, REO B | — | Independent | + +#### Configure (`GIP-0088:upgrade,configure`) + +| Contract | Precondition | Notes | +| -------- | --------------------------------- | ---------------------------------------------------------------------------------------------- | +| RC | Deployed | setPauseGuardian | +| IA | Deployed, 0 < RM.issuancePerBlock | Rates, RM as 100% self-minting target, grant governor/pause roles | +| DA | Deployed (+ IA deployed) | Grant governor/pause roles, set as IA default target | +| REO A/B | Deployed | Grant governor/pause/operator roles. Validation enabled by operator post-deploy. | +| RAM | Deployed (+ RC, SS, IA deployed) | Grant governor/pause/collector/data-service roles, set issuance allocator | +| Reclaim | Deployed | Grant governor/pause roles | +| Reclaim | RM upgraded | Sets RM.defaultReclaimAddress — skips if RM not yet upgraded (handled by `04_upgrade` instead) | + +#### Transfer (`GIP-0088:upgrade,transfer`) + +| Contract | Precondition | Notes | +| -------- | ------------------------------- | --------------------------------------------------------------------------- | +| RC | Deployed | ProxyAdmin only — RC has no `GOVERNOR_ROLE`. Skips if owner is not deployer | +| IA | Configured | Revokes deployer GOVERNOR_ROLE, transfers ProxyAdmin | +| DA | Configured | Revokes deployer GOVERNOR_ROLE, transfers ProxyAdmin | +| RAM | Configured | Revokes deployer GOVERNOR_ROLE, transfers ProxyAdmin | +| Reclaim | Configured | Revokes deployer GOVERNOR_ROLE, transfers ProxyAdmin | +| REO A | Configured (all conditions met) | Revokes deployer GOVERNOR_ROLE, transfers ProxyAdmin | +| REO B | Configured (all conditions met) | Revokes deployer GOVERNOR_ROLE, transfers ProxyAdmin | + +#### Upgrade (`GIP-0088:upgrade,upgrade`) + +State-driven: builds a single governance TX batch from three groups. Each group skips items already on-chain. + +| Group | Items | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Proxy upgrades | Iterates registry; for any deployable proxy with `pendingImplementation`, adds proxy upgrade TX | +| Existing-contract config | `RC.setPauseGuardian(pauseGuardian)`; `RM.setDefaultReclaimAddress(reclaim)` (only after RM upgrade — bundle order means RM upgrade executes first in the same batch) | +| Deferred new-contract config | IA: `setIssuancePerBlock`, `grantRole(GOVERNOR/PAUSE)`. DA: `grantRole(GOVERNOR/PAUSE)`. RAM: `grantRole(COLLECTOR/DATA_SERVICE/GOVERNOR/PAUSE)` + `setIssuanceAllocator`. Reclaim: `grantRole(GOVERNOR/PAUSE)`. REO A/B: param setters + role grants. | + +These deferred items exist because configure runs as the deployer and skips items requiring `GOVERNOR_ROLE` on contracts the deployer doesn't yet control, or items that depend on RM being upgraded. + +#### Activation goals + +| Goal | Precondition | Notes | +| ----------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `eligibility-integrate` | RM upgraded, REO A deployed, oracle not already set | `RM.setProviderEligibilityOracle(REO_A)`. Skips if any oracle already set (does not override). | +| `issuance-connect` | RM upgraded, IA deployed + configured (rate matches RM) | Builds TX batch in order: `GraphToken.addMinter(IA)` → `RM.setIssuanceAllocator(IA)` → `IA.setTargetAllocation(RM, 0, rate)` → `IA.setDefaultTarget(DA)`. Order matters: `setTargetAllocation` calls `RM.onIssuanceChange` which requires the allocator already be set. **Exits on invariant failure** (IA rate ≠ RM rate). | +| `issuance-allocate` | IA deployed, RAM deployed, issuance-connect done | `IA.setTargetAllocation(RAM, allocatorMintingRate, selfMintingRate)`. Rates from `config/.json5`, skips if both are 0. | + +#### Optional goals + +| Goal | Precondition | Notes | +| ---------------------- | -------------------------------------- | ----------------------------------------------------- | +| `eligibility-revert` | RM upgraded (supports IRewardsManager) | RM.setRevertOnIneligible(true) | +| `issuance-close-guard` | SS upgraded | SS.setBlockClosingAllocationWithActiveAgreement(true) | + +### Environments + +The same commands apply to all environments. What differs is how governance TXs are executed. + +| Environment | Governance execution | Speed | +| ----------------- | ------------------------------------------------- | -------- | +| Fork (localhost) | `deploy:execute-governance` impersonates governor | Instant | +| Testnet (Sepolia) | `deploy:execute-governance` signs with EOA key | ~minutes | +| Mainnet (Arb One) | TX batch uploaded to Safe for council multisig | ~days | + +#### Fork testing + +Validates the full flow using account impersonation. See [LocalForkTesting.md](LocalForkTesting.md). + +```bash +anvil --fork-url --chain-id 31337 +pnpm hardhat deploy:reset-fork --network localhost + +# Deploy, configure, transfer +pnpm hardhat deploy --tags GIP-0088:upgrade,deploy --network localhost --skip-prompts +pnpm hardhat deploy --tags GIP-0088:upgrade,configure --network localhost --skip-prompts +pnpm hardhat deploy --tags GIP-0088:upgrade,transfer --network localhost --skip-prompts + +# Proxy upgrades +pnpm hardhat deploy --tags GIP-0088:upgrade,upgrade --network localhost --skip-prompts +pnpm hardhat deploy:execute-governance --network localhost + +# Activation +pnpm hardhat deploy --tags GIP-0088:eligibility-integrate --network localhost --skip-prompts +pnpm hardhat deploy:execute-governance --network localhost +pnpm hardhat deploy --tags GIP-0088:issuance-connect --network localhost --skip-prompts +pnpm hardhat deploy:execute-governance --network localhost +pnpm hardhat deploy --tags GIP-0088:issuance-allocate --network localhost --skip-prompts +pnpm hardhat deploy:execute-governance --network localhost + +# Verify +pnpm hardhat deploy --tags GIP-0088 --network localhost --skip-prompts +``` + +## See Also + +- [GovernanceWorkflow.md](GovernanceWorkflow.md) — governance TX generation and execution across environments +- [LocalForkTesting.md](LocalForkTesting.md) — fork mode testing setup and workflow +- [Architecture.md](Architecture.md) — deployment package architecture +- [deploy/ImplementationPrinciples.md](deploy/ImplementationPrinciples.md) — patterns and rules for deploy scripts diff --git a/packages/deployment/docs/GovernanceWorkflow.md b/packages/deployment/docs/GovernanceWorkflow.md index cceb117a0..7b4ade2ed 100644 --- a/packages/deployment/docs/GovernanceWorkflow.md +++ b/packages/deployment/docs/GovernanceWorkflow.md @@ -13,12 +13,11 @@ In fork mode, governance transactions can be executed automatically via account ### Setup ```bash -# Start a fork of arbitrumSepolia -FORK_NETWORK=arbitrumSepolia npx hardhat node --network fork +# Ephemeral: run deployment directly (state lost on exit) +FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags IssuanceAllocator:deploy --network fork -# In another terminal, run deployments -export FORK_NETWORK=arbitrumSepolia -npx hardhat deploy --tags issuance-allocator-deploy --network fork +# Or persistent: start anvil in Terminal 1, run deploys in Terminal 2 +# See LocalForkTesting.md for persistent fork setup ``` ### Execution @@ -26,15 +25,15 @@ npx hardhat deploy --tags issuance-allocator-deploy --network fork When a deployment generates a governance TX batch: 1. The TX batch is saved to `fork/fork/arbitrumSepolia/txs/*.json` -2. The deployment exits with code 1 (expected state - waiting for governance) -3. Execute the governance TXs automatically: +2. The script returns (it does **not** exit) — subsequent scripts in the run keep going and check their own preconditions, so a single command can produce several TX batches +3. Execute the saved governance TXs: ```bash npx hardhat deploy:execute-governance --network fork ``` 4. This uses `hardhat_impersonateAccount` to execute as the governor -5. Continue with deployments +5. Re-run the deployment command to continue past the governance boundary ## Testnet Mode with EOA Governor @@ -93,14 +92,14 @@ On mainnet (and testnets where Safe is deployed), governance transactions with S ```bash export DEPLOYER_PRIVATE_KEY=0xYOUR_PRIVATE_KEY -npx hardhat deploy --tags issuance-allocator-deploy --network arbitrumSepolia +npx hardhat deploy --tags IssuanceAllocator:deploy --network arbitrumSepolia ``` When governance action is required, the deployment will: - Generate a TX batch file in `txs/arbitrumSepolia/*.json` - Display the file path -- Exit with code 1 +- Return (not exit) — the run continues and other scripts check their own preconditions #### 2. Review the TX Batch @@ -156,7 +155,7 @@ This updates the address books with the new on-chain state. Re-run the original deployment command: ```bash -npx hardhat deploy --tags issuance-allocator-deploy --network arbitrumSepolia +npx hardhat deploy --tags IssuanceAllocator:deploy --network arbitrumSepolia ``` The deployment will detect that governance has executed and continue to the next steps. @@ -167,7 +166,7 @@ The deployment will detect that governance has executed and continue to the next ```bash # 1. Deploy new implementation -npx hardhat deploy --tags rewards-manager-deploy --network arbitrumSepolia +npx hardhat deploy --tags RewardsManager:deploy --network arbitrumSepolia # This generates: txs/arbitrumSepolia/upgrade-RewardsManager.json @@ -181,7 +180,7 @@ npx hardhat deploy --tags sync --network arbitrumSepolia ```bash # Deploy and configure (generates governance TX if needed) -npx hardhat deploy --tags issuance-activation --network arbitrumSepolia +npx hardhat deploy --tags IssuanceActivation --network arbitrumSepolia # Execute via Safe UI @@ -223,15 +222,16 @@ txs//executed/*.json | **EOA Direct** | Testnet with EOA governor | Automatic with private key | `GOVERNOR_PRIVATE_KEY=0x...` | | **Safe Multisig** | Production/mainnet | Manual via Safe Transaction Builder | None (auto-detected) | +**Fork mode is network-aware**: `FORK_NETWORK` is automatically ignored on real networks (arbitrumSepolia, arbitrumOne). Fork mode only activates on local networks (localhost, fork, hardhat), so you don't need to unset it when switching to real deployments. + **Transaction batch files** (Safe Transaction Builder JSON format) are always created in `txs//*.json` regardless of execution mode. ### Usage Examples -**Local fork testing:** +**Local fork testing (ephemeral):** ```bash -FORK_NETWORK=arbitrumSepolia npx hardhat node --network fork -npx hardhat deploy:execute-governance --network fork +FORK_NETWORK=arbitrumSepolia npx hardhat deploy:execute-governance --network fork ``` **Fast testnet iteration (EOA):** @@ -287,13 +287,15 @@ npx hardhat deploy:execute-governance --network arbitrumSepolia # Governor: 0x... (EOA) ``` -### Exit Code 1 +### No Exit on Governance Save + +When a script generates a governance TX batch, it **returns** rather than exiting. This: -When a deployment generates a governance TX batch, it exits with code 1. This: +- Lets a single command produce multiple governance TX batches in one run (one per script that needs governance authority) +- Avoids implicit ordering coupling — every script checks its own on-chain preconditions and skips if they aren't met +- Is normal flow, not an error condition -- Signals to CI/CD that manual intervention is required -- Prevents subsequent deployment steps from running -- Is not an error - it's expected state when waiting for governance +To detect "needs governance" in CI/CD, check whether any files exist under `txs//` after a run, or use the goal status scripts (`--tags GIP-0088`). ## Troubleshooting @@ -355,18 +357,17 @@ npx hardhat deploy:execute-governance --network arbitrumSepolia Before executing on mainnet, always test in fork mode: ```bash -# 1. Fork mainnet -FORK_NETWORK=arbitrumOne npx hardhat node --network fork - -# 2. Deploy (generates governance TXs) +# 1. Deploy (generates governance TXs) export FORK_NETWORK=arbitrumOne -npx hardhat deploy --tags issuance-allocator-deploy --network fork +npx hardhat deploy --tags IssuanceAllocator:deploy --network fork -# 3. Execute governance TXs automatically +# 2. Execute governance TXs automatically npx hardhat deploy:execute-governance --network fork -# 4. Verify state +# 3. Verify state npx hardhat deploy:status --network fork ``` +For persistent fork testing (state survives across commands), see [LocalForkTesting.md](./LocalForkTesting.md). + This tests the full governance workflow without touching real funds or requiring actual Safe signatures. diff --git a/packages/deployment/docs/LocalForkTesting.md b/packages/deployment/docs/LocalForkTesting.md index 7e7d70fe6..f7247fba4 100644 --- a/packages/deployment/docs/LocalForkTesting.md +++ b/packages/deployment/docs/LocalForkTesting.md @@ -8,7 +8,7 @@ State is lost when the command exits. Good for quick testing. ```bash # Run full deployment flow against forked arbitrumSepolia -FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags sync,rewards-manager-deploy --network fork +FORK_NETWORK=arbitrumSepolia npx hardhat deploy --tags sync,RewardsManager:deploy --network fork ``` ## Persistent Fork (multiple sessions) @@ -23,12 +23,10 @@ anvil --fork-url https://sepolia-rollup.arbitrum.io/rpc --chain-id 31337 ```bash # Terminal 2 - run deploys against it -# FORK_NETWORK tells deploy scripts which address books to use -export FORK_NETWORK=arbitrumSepolia npx hardhat deploy:reset-fork --network localhost npx hardhat deploy:status --network localhost npx hardhat deploy --network localhost --skip-prompts --tags sync -npx hardhat deploy --network localhost --skip-prompts --tags rewards-manager +npx hardhat deploy --network localhost --skip-prompts --tags RewardsManager npx hardhat deploy:execute-governance --network localhost ``` @@ -38,18 +36,22 @@ Or for Arbitrum One: anvil --fork-url https://arb1.arbitrum.io/rpc --chain-id 31337 ``` -```bash -export FORK_NETWORK=arbitrumOne -# ... -``` - **Important**: - Terminal 1: Use anvil (from Foundry) instead of `hardhat node` - Hardhat v3's node command doesn't properly support the `--fork` flag - Terminal 1: Use `--chain-id 31337` - anvil defaults to the forked chain's ID (421614) but hardhat's localhost expects 31337 -- Terminal 2: Set `FORK_NETWORK` env var - tells deploy scripts to: - - Load the correct network's address books (not localhost's empty ones) - - Generate Safe TX files with the correct chainId (421614, not 31337) + +### Fork Network Detection + +The fork network (which chain is being forked) is **auto-detected** from anvil's RPC metadata. When you run against localhost, deploy scripts query `anvil_nodeInfo` to get the fork URL and match it against known network RPC hostnames. + +You can also set `FORK_NETWORK` explicitly to override auto-detection: + +```bash +export FORK_NETWORK=arbitrumSepolia +``` + +**Safe on real networks**: `FORK_NETWORK` is automatically ignored when running against real networks (`--network arbitrumSepolia`, `--network arbitrumOne`). Fork mode only activates on local networks (localhost, fork, hardhat), so you don't need to unset `FORK_NETWORK` when switching between fork testing and real deployments. ## Architecture @@ -80,12 +82,12 @@ deployments/ # Managed by rocketh (deployment records, .chain f ## Key Points -| Setting | Value | Purpose | -| --------------------- | ---------------------------------- | -------------------------------- | -| `FORK_NETWORK` | `arbitrumSepolia` or `arbitrumOne` | Which network to fork | -| `SHOW_ADDRESSES` | `0`, `1` (default), or `2` | Address display: none/short/full | -| `--network fork` | in-process EDR | Ephemeral, fast startup | -| `--network localhost` | external node | Persistent state | +| Setting | Value | Purpose | +| --------------------- | ---------------------------------- | -------------------------------------------------------------- | +| `FORK_NETWORK` | `arbitrumSepolia` or `arbitrumOne` | Override auto-detected fork network (ignored on real networks) | +| `SHOW_ADDRESSES` | `0`, `1` (default), or `2` | Address display: none/short/full | +| `--network fork` | in-process EDR | Ephemeral, fast startup | +| `--network localhost` | external node | Persistent state | ## Configuration @@ -136,6 +138,33 @@ npx hardhat deploy:reset-fork --network fork - **Foundry**: Install via `curl -L https://foundry.paradigm.xyz | bash && foundryup` +## Local Network + +The `localNetwork` network targets a Graph local network at chain ID 1337. +Unlike fork mode, contracts are deployed fresh from scratch. + +```bash +# Deploy a single contract via its component lifecycle +npx hardhat deploy --tags IssuanceAllocator,deploy --network localNetwork + +# Or run the full GIP-0088 upgrade phase +npx hardhat deploy --tags GIP-0088:upgrade,deploy --network localNetwork +``` + +**Key differences from fork mode:** + +- Chain ID 1337 (not 31337) +- No `FORK_NETWORK` env var needed +- Address books use `addresses-local-network.json` files that the dev environment must provide +- Deployer is also governor (direct execution, no governance batch files) +- Uses standard test mnemonic (`test test test ... junk`) + +**Environment:** + +- RPC: `http://chain:8545` (override with `LOCAL_NETWORK_RPC`) +- Address books must be populated by an upstream step that deploys Horizon + SubgraphService +- This package then deploys contracts on top (e.g., issuance) + ## See Also - [GovernanceWorkflow.md](./GovernanceWorkflow.md) - Production deployment flow diff --git a/packages/deployment/docs/SyncBytecodeDetectionFix.md b/packages/deployment/docs/SyncBytecodeDetectionFix.md new file mode 100644 index 000000000..5c4498fd1 --- /dev/null +++ b/packages/deployment/docs/SyncBytecodeDetectionFix.md @@ -0,0 +1,149 @@ +# Sync Bytecode Detection Fix + +## Issues Identified + +### Issue 1: Local Bytecode Changes Ignored + +**Problem**: Deploy incorrectly reported "implementation unchanged" when local bytecode had actually changed. + +**Evidence**: + +``` +Local artifact: 0x9c25d2f93e6a2a34cc19d00224872e288a8392d5d99b2df680b7e978d148d450 +On-chain: 0xfafdeb48fae37e277e007e7b977f3cd124065ac1c27ed5208982c2965cf07008 +Address book: 0x4805a902756c8f4421c2a2710dcc76885ffd01d7777bbe6cab010fe9748b7efa +``` + +All three hashes are different, yet deploy said "unchanged", meaning local changes would be ignored. + +### Issue 2: Confusing Sync Behavior + +**Problem**: Sync showed "code changed" but didn't handle the state appropriately: + +1. Showed △ (code changed) indicator +2. But didn't sync implementation to rocketh +3. Saved proxy record with wrong bytecode +4. This confused rocketh's change detection + +## Root Causes + +### Cause 1: Missing/Stale Bytecode Hash + +When the address book had no bytecode hash (or wrong hash): + +- Sync detected "code changed" ([sync-utils.ts:475-477](../lib/sync-utils.ts#L475-L477)) +- But only synced to rocketh if hash matched ([sync-utils.ts:653](../lib/sync-utils.ts#L653)) +- This left rocketh with incomplete/wrong state + +### Cause 2: Wrong Bytecode Stored for Proxy + +The sync step saved the **implementation's bytecode** under the **proxy's deployment record**: + +- Lines 508-532: Created proxy record with implementation artifact bytecode +- This is wrong - proxy should have its own bytecode (or none) +- Rocketh then compared wrong bytecode and gave incorrect results + +## Fixes Applied + +### Fix 1: Hash Comparison and Stale Record Cleanup ([sync-utils.ts:645-679](../lib/sync-utils.ts#L645-L679)) + +When sync processes an implementation: + +1. **Compare local artifact hash to address-book-stored hash** +2. **If hashes match**: sync the implementation record to rocketh normally +3. **If hashes don't match**: overwrite any stale rocketh record with empty bytecode, forcing a fresh deployment + + ```typescript + if (storedHash && localHash) { + hashMatches = storedHash === localHash + } + + // Clean up stale rocketh record if hash doesn't match + if (!hashMatches && existingImpl) { + // Overwrite stale record with empty bytecode - forces fresh deployment + await env.save(`${spec.name}_Implementation`, { + address: existingImpl.address, + bytecode: '0x', + deployedBytecode: undefined, + ... + }) + } + ``` + +This ensures rocketh correctly detects when local code has changed and triggers a new deployment. + +### Fix 2: Don't Store Wrong Bytecode for Proxy ([sync-utils.ts:508-532](../lib/sync-utils.ts#L508-L532)) + +Changed proxy record creation to **NOT include implementation bytecode**: + +```typescript +// Before: +bytecode: artifact.bytecode // ← Wrong! This is implementation bytecode +deployedBytecode: artifact.deployedBytecode + +// After: +bytecode: '0x' // ← Correct! Proxy record doesn't need bytecode +deployedBytecode: undefined +``` + +This ensures rocketh only uses implementation bytecode for the actual implementation record. + +## Expected Behavior After Fix + +### Scenario 1: Local Matches Address Book + +When local artifact hash matches the stored hash, sync proceeds normally and rocketh +correctly reports the implementation as unchanged. + +### Scenario 2: Local Code Changed + +**Before**: + +``` +△ SubgraphService @ 0xc24A3dAC... → 0x2af1b0ed... (code changed) +✓ SubgraphService implementation unchanged ← WRONG! +``` + +**After**: + +``` +△ SubgraphService @ 0xc24A3dAC... → 0x2af1b0ed... (local code changed) +📋 New SubgraphService implementation deployed: 0x... ← NEW! + Storing as pending implementation... +``` + +Deploy correctly detects the change and deploys new implementation. + +### Scenario 3: Stale Rocketh Record + +When the hash doesn't match and a stale rocketh record exists, sync overwrites it +with empty bytecode. This forces the next deploy to create a fresh implementation +record rather than incorrectly reporting "unchanged". + +## Testing + +To verify the fix works: + +```bash +# Clean build +cd packages/deployment +pnpm build + +# Run sync - should now show clearer messages +npx hardhat deploy --skip-prompts --network arbitrumSepolia --tags sync + +# Run deploy - should correctly detect local changes +npx hardhat deploy --skip-prompts --network arbitrumSepolia --tags SubgraphService +``` + +## Migration Notes + +- **No manual migration needed** - stale rocketh records are cleaned up automatically +- First sync after fix will detect hash mismatches and clear stale records +- Subsequent deploys will create fresh implementation records + +## Related Files + +- [sync-utils.ts](../lib/sync-utils.ts) - Main fix implementation +- [deploy-implementation.ts](../lib/deploy-implementation.ts) - Deploy logic (unchanged, now works correctly) +- [check-bytecode.ts](../scripts/check-bytecode.ts) - Diagnostic script for manual verification diff --git a/packages/deployment/docs/address-book/README.md b/packages/deployment/docs/address-book/README.md index cf9e48b0a..c7ffe7616 100644 --- a/packages/deployment/docs/address-book/README.md +++ b/packages/deployment/docs/address-book/README.md @@ -16,7 +16,7 @@ const addressBook = graph.getIssuanceAddressBook(chainId) // Write operations addressBook.setProxy('RewardsManager', proxyAddr, implAddr, adminAddr, 'transparent') -addressBook.setPendingImplementation('RewardsManager', newImplAddr, { txHash: '0x...' }) +addressBook.setPendingImplementationWithMetadata('RewardsManager', newImplAddr, deploymentMetadata) // Read operations const entry = addressBook.getEntry('RewardsManager') @@ -26,16 +26,16 @@ const entry = addressBook.getEntry('RewardsManager') ### Write Operations -| Method | Purpose | -| ------------------------------------------------- | ---------------------------------------- | -| `setContract(name, address)` | Non-proxied contract | -| `setProxy(name, proxy, impl, admin, type)` | All proxy fields | -| `setImplementation(name, impl)` | Active implementation | -| `setProxyAdmin(name, admin)` | Proxy admin | -| `setPendingImplementation(name, impl, metadata?)` | Pending implementation | -| `promotePendingImplementation(name)` | Move pending → active | -| `clearPendingImplementation(name)` | Clear pending | -| `setImplementationAndClearIfMatches(name, impl)` | Set impl + auto-clear pending if matches | +| Method | Purpose | +| ------------------------------------------------------------ | ---------------------------------------- | +| `setContract(name, address)` | Non-proxied contract | +| `setProxy(name, proxy, impl, admin, type)` | All proxy fields | +| `setImplementation(name, impl)` | Active implementation | +| `setProxyAdmin(name, admin)` | Proxy admin | +| `setPendingImplementationWithMetadata(name, impl, metadata)` | Pending implementation | +| `promotePendingImplementation(name)` | Move pending → active | +| `clearPendingImplementation(name)` | Clear pending | +| `setImplementationAndClearIfMatches(name, impl)` | Set impl + auto-clear pending if matches | ### Read Operations diff --git a/packages/deployment/docs/deploy/ImplementationPrinciples.md b/packages/deployment/docs/deploy/ImplementationPrinciples.md index 1c3134e2e..9226611a9 100644 --- a/packages/deployment/docs/deploy/ImplementationPrinciples.md +++ b/packages/deployment/docs/deploy/ImplementationPrinciples.md @@ -16,104 +16,134 @@ This document defines the core principles and patterns for writing deployment sc **Standard step objectives:** -- **01_deploy.ts** - Deploy proxy + implementation, initialize with deployer or governor - - MUST explicitly depend on `SpecialTags.SYNC` (even if also available transitively through other dependencies) +- **01_deploy.ts** - Deploy proxy + implementation, initialize with deployer + - Sync the contract being deployed (and any contracts it reads) immediately + before acting via `syncComponentFromRegistry` / + `syncComponentsFromRegistry`. The script factories + (`createProxyDeployModule`, `createImplementationDeployModule`, + `createUpgradeModule`, etc.) handle this automatically. + - For a global pre-deploy reconciliation, use `npx hardhat deploy:sync` + explicitly — it is no longer pulled in as an automatic dependency. - Each script should declare its own prerequisites explicitly, not rely on transitive dependencies - **02_upgrade.ts** - Handle proxy upgrades via governance (generates TX batch) -- **03-08 (flexible)** - Intermediate steps vary by component: - - Configure integration with other contracts - - Verify governance state - - Transfer governance roles - - Generate activation TX batches - - Deploy shared implementations +- **04_configure.ts** - Deployer-only configure: role grants and params on contracts where the deployer is governor +- **05_transfer_governance.ts** - Revoke deployer GOVERNOR_ROLE; transfer ProxyAdmin to protocol governor +- **06_integrate.ts** (optional) - Wire the contract into the rest of the protocol - **09_end.ts** - End state aggregate (only has dependencies and verification, no execution) +- **10_status.ts** - Read-only status display (see below) + +The `03_*` slot is intentionally left empty so that `02_upgrade` can be inserted as a clearly distinct phase without renumbering. The `04_configure` numbering is the actual convention used throughout the tree. + +### Principle: Status Scripts Are Read-Only + +**Rule**: `10_status.ts` scripts MUST be purely read-only. They MUST NOT make on-chain changes, write transactions, or modify any state. + +**Why**: When `--tags ` is run without an action verb, only status scripts execute. Users rely on this for safe inspection of deployment state at any time — during planning, mid-deployment, and in production. Any mutation in a status script would violate this trust and could cause unintended state changes. + +**How it works**: + +1. Status scripts use `createStatusModule()`, which gates on `noTagsRequested()` — they only run when tags are present but no action verb is included +2. Stage scripts (01-08) use `shouldSkipAction(verb)` — they skip when their action verb is absent from `--tags` +3. Combined: `--tags GIP-0088` alone runs only `10_status.ts` (status reads on-chain directly and does not need a global sync first) + +**Pattern**: + +```typescript +// Component status — delegates to showDetailedComponentStatus (reads only) +export default createStatusModule(Contracts.issuance.IssuanceAllocator) + +// Goal status — custom handler, must only use readContract/getCode +export default createStatusModule(GoalTags.GIP_0088, async (env) => { + const client = graph.getPublicClient(env) as PublicClient + // ✅ Read on-chain state and display + const value = await client.readContract({ ... }) + env.showMessage(` ${value ? '✓' : '✗'} check description`) + // ❌ NEVER: execute(), tx(), deploy(), process.exit(1), TxBuilder +}) +``` + +**Invariant**: If a script is named `10_status.ts`, it contains zero writes. No exceptions. #### Example: RewardsEligibilityOracle (simple - 4 steps) ``` -01_deploy.ts - Deploy proxy + implementation, initialize with governor -02_upgrade.ts - Handle upgrades -03_configure.ts - Integrate with RewardsManager +01_deploy.ts - Deploy proxy + implementation +02_upgrade.ts - Handle proxy upgrades (governance TX batch) +04_configure.ts - Deployer-only configure (params, role grants) 09_end.ts - End state aggregate +10_status.ts - Read-only status display ``` -#### Example: IssuanceAllocator (complex - 8 steps) +#### Example: RewardsEligibilityOracle (full lifecycle) ``` 01_deploy.ts - Deploy proxy + implementation -02_upgrade.ts - Handle upgrades -03_deploy.ts - Deploy DirectAllocation implementation -04_configure.ts - Configure issuance rate and allocations -05_verify_governance.ts - Verify governance state -06_transfer_governance.ts - Transfer roles to governance -07_activate.ts - Generate activation TX batch +02_upgrade.ts - Handle proxy upgrades +04_configure.ts - Configure params + role grants +05_transfer_governance.ts - Revoke deployer role + transfer ProxyAdmin +06_integrate.ts - Wire into RewardsManager (governance TX) 09_end.ts - End state aggregate +10_status.ts - Read-only status display ``` -**Note:** Steps 04-08 are flexible and vary by component. Always use `09_end.ts` for the final aggregate. +**Note:** Step `03_*` is intentionally left empty so `02_upgrade` stays a clearly separate phase. Steps 04-08 are flexible and vary by component. Always use `09_end.ts` for the aggregate and `10_status.ts` for read-only status. #### Tag structure in deployment-tags.ts ```typescript -// Example: RewardsEligibilityOracle lifecycle -rewardsEligibilityDeploy: [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.DEPLOY)], -rewardsEligibilityUpgrade: [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.UPGRADE)], -rewardsEligibilityConfigure: [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.CONFIGURE)], -rewardsEligibility: [ComponentTags.REWARDS_ELIGIBILITY], // Aggregate end state +// Component tags are PascalCase contract names matching the registry +ComponentTags = { + REWARDS_ELIGIBILITY_A: 'RewardsEligibilityOracleA', + // ... +} + +// Action verbs are appended via --tags Component,verb +// e.g. --tags RewardsEligibilityOracleA,deploy ``` ## Exit Codes and Flow Control -### Principle: Clean Exits for Expected Prerequisites +### Principle: Scripts Are Goal-Seeking, Not Sequential Steps -**Rule**: When a deployment step cannot complete due to an expected prerequisite state (NOT an exception), it MUST exit with code 1 to prevent subsequent steps from running. +**Rule**: Each script checks its own preconditions and skips if not met. Scripts return (not exit) when work cannot proceed — subsequent scripts check their own state independently. -**Rationale**: Steps should be able to rely on prerequisite steps stopping if not complete. This prevents cascading failures and incorrect state. +**Rationale**: Scripts run in sequence but must not assume a particular starting state. Each script is idempotent and goal-seeking: it checks on-chain state, does what's needed, and returns. **Examples**: ```typescript -// CORRECT: Exit with code 1 when prerequisite not met -export async function requireRewardsManagerUpgraded( - client: PublicClient, - rmAddress: string, - env: Environment, -): Promise { - const upgraded = await isRewardsManagerUpgraded(client, rmAddress) - if (!upgraded) { - env.showMessage(`\n❌ RewardsManager has not been upgraded yet`) - env.showMessage(` Run: npx hardhat deploy:execute-governance --network ${env.name}`) - process.exit(1) // Clean exit - prevents next steps - } -} - -// CORRECT: Exit after generating governance TX -const txFile = builder.saveToFile() -env.showMessage(`\n✓ TX batch saved: ${txFile}`) -env.showMessage('\n📋 GOVERNANCE ACTION REQUIRED') -process.exit(1) // Prevents next steps until governance TX executed +// CORRECT: Save governance TX and return (allows subsequent scripts to run) +saveGovernanceTx(env, builder, `ContractName activation`) +// Returns — subsequent scripts check their own preconditions -// WRONG: Returning allows next steps to run +// CORRECT: Skip when precondition not met if (!prerequisiteMet) { - env.showMessage('⚠️ Prerequisite not met') - return // ❌ Next step will still run! + env.showMessage(' ○ Prerequisite not met — skipping') + return +} + +// CORRECT: Use shared precondition check to skip if done +const precondition = await checkIAConfigured(client, ia.address, rm.address) +if (precondition.done) { + env.showMessage('✅ Already configured') + return } ``` ### When to Use Exit Code 1 -Use `process.exit(1)` when: +Use `process.exit(1)` only for: -- Waiting for a governance TX to be executed -- Waiting for a contract upgrade to complete -- Checking a required prerequisite state -- External action needed before continuing +- **Migration invariant violations** (data corruption risk, e.g. IA rate != RM rate before connection) +- **Verification failures** in `09_end` scripts +- **Sync failures** (can't proceed without address books) -Do NOT use `process.exit(1)` when: +Do NOT use `process.exit(1)` for: +- Governance TX generation (use `saveGovernanceTx` which returns) +- Preconditions not met (return/skip, let subsequent scripts check their own preconditions) - Configuration already correct (idempotent check passed) - Script successfully completed its work -- Skipping optional steps ### When to Throw Exceptions @@ -274,15 +304,17 @@ const value = (await client.readContract({ **Pattern**: ```typescript -import { createGovernanceTxBuilder, saveGovernanceTxAndExit } from '@graphprotocol/deployment/lib/execute-governance.js' -import { getGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' -import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' +import { + createGovernanceTxBuilder, + executeTxBatchDirect, + saveGovernanceTx, +} from '@graphprotocol/deployment/lib/execute-governance.js' +import { canSignAsGovernor } from '@graphprotocol/deployment/lib/controller-utils.js' -// Get protocol governor -const governor = await getGovernor(env) +const { governor, canSign } = await canSignAsGovernor(env) // Create TX builder (handles chainId, outputDir, template automatically) -const builder = createGovernanceTxBuilder(env, `action-${Contracts.ContractName.name}`, { +const builder = await createGovernanceTxBuilder(env, `action-${contractName}`, { name: 'Human Readable Name', description: 'What this TX batch does', }) @@ -291,9 +323,13 @@ const builder = createGovernanceTxBuilder(env, `action-${Contracts.ContractName. builder.addTx({ to: contractAddress, value: '0', data: encodedCalldata }) env.showMessage(` + ContractName.functionName(args)`) -// Save and exit using utility -saveGovernanceTxAndExit(env, builder, `${Contracts.ContractName.name} activation`) -// Never returns - exits with code 1 to prevent next steps +// Execute directly if possible, otherwise save for governance +if (canSign) { + await executeTxBatchDirect(env, builder, governor) +} else { + saveGovernanceTx(env, builder, `${contractName} activation`) +} +// Returns — does NOT exit. Subsequent scripts check their own preconditions. ``` ### Metadata Standards @@ -485,7 +521,7 @@ const contract = requireContract(env, 'RewardsManager') ``` deploy/ docs/deploy/ allocate/ IssuanceAllocatorDeployment.md - allocator/ PilotAllocationDeployment.md + allocator/ DirectAllocationDeployment.md 01_deploy.ts rewards/ 02_upgrade.ts RewardsEligibilityOracleDeployment.md 09_end.ts @@ -541,7 +577,7 @@ For contract architecture and technical details, see [IssuanceAllocator.md](../. For every deployment script: -- [ ] Uses `process.exit(1)` for expected prerequisite states +- [ ] Uses `return` (not `process.exit`) for precondition skips and governance TX saves - [ ] Throws exceptions only for unexpected errors - [ ] Is idempotent (checks state, skips if done) - [ ] Uses package imports (`@graphprotocol/deployment`) not relative paths @@ -551,13 +587,15 @@ For every deployment script: - [ ] Works in both fork and production modes - [ ] Has clear, actionable error messages with dynamic values - [ ] Includes comprehensive documentation -- [ ] Follows standard script structure (01_deploy, 02_upgrade, ..., 09_end) +- [ ] Follows standard script structure (01_deploy, 02_upgrade, ..., 09_end, 10_status) - [ ] Properly configures tags and dependencies - [ ] End state script is always `09_end.ts` with only dependencies +- [ ] `10_status.ts` is purely read-only (zero writes, zero TXs, zero exits) ### Anti-Patterns to Avoid -❌ Returning early without exit code when prerequisite not met +❌ Using `process.exit(1)` for precondition skips or governance TX saves (use `return`) +❌ Duplicating precondition checks instead of using shared functions from `lib/preconditions.ts` ❌ Duplicating code instead of using shared utilities ❌ Using relative imports (`../../lib/`) instead of package imports ❌ Using string literals instead of `Contracts` registry @@ -568,5 +606,5 @@ For every deployment script: ❌ Direct address book imports instead of `graph.get*AddressBook()` ❌ Vague error messages without actionable next steps ❌ Non-idempotent scripts that fail on re-run -❌ Generating governance TXs without exiting with code 1 ❌ Using non-standard end script numbering (use `09_end.ts` always) +❌ Any mutation (write, TX, deploy, exit) in a `10_status.ts` script diff --git a/packages/deployment/docs/deploy/IssuanceAllocatorDeployment.md b/packages/deployment/docs/deploy/IssuanceAllocatorDeployment.md index 553157fbd..60a110de5 100644 --- a/packages/deployment/docs/deploy/IssuanceAllocatorDeployment.md +++ b/packages/deployment/docs/deploy/IssuanceAllocatorDeployment.md @@ -1,160 +1,82 @@ # IssuanceAllocator Deployment -This document describes the deployment sequence for IssuanceAllocator. For contract architecture, behavior, and technical details, see [IssuanceAllocator.md](../../../../issuance/contracts/allocate/IssuanceAllocator.md). +This document describes how `IssuanceAllocator` is deployed by this package. For contract architecture, behaviour, and technical details, see [IssuanceAllocator.md](../../../issuance/contracts/allocate/IssuanceAllocator.md). -## Prerequisites +For the goal-level GIP-0088 workflow that orchestrates IA together with the rest of the upgrade, see [Gip0088.md](../Gip0088.md). -- GraphToken contract deployed -- RewardsManager upgraded with `setIssuanceAllocator()` function -- GraphIssuanceProxyAdmin deployed with protocol governance as owner +## Component overview -## Deployment Overview +`IssuanceAllocator` is a deployable proxy in the `issuance` address book: -The deployment strategy safely replicates existing issuance configuration during RewardsManager migration: +- Pattern: OpenZeppelin v5 `TransparentUpgradeableProxy` with a per-proxy `ProxyAdmin` created in the constructor. +- Access control: `BaseUpgradeable` (`GOVERNOR_ROLE`, `PAUSE_ROLE`). +- Component tag: `IssuanceAllocator`. Lifecycle actions: `deploy`, `upgrade`, `configure`, `transfer`. +- Default target: a separate `DefaultAllocation` proxy ([../../deploy/allocate/default/](../../deploy/allocate/default/)) that holds any unallocated issuance as a safety net. -- Default target starts as `address(0)` (that will not be minted to), allowing initial configuration without minting to any targets -- Deployment uses atomic initialization via proxy constructor (prevents front-running) -- Deployment account performs initial configuration, then transfers control to governance -- Granting of minter role can be delayed until replication of initial configuration with upgraded RewardsManager is verified to allow seamless transition to use of IssuanceAllocator -- **Governance control**: This contract uses OpenZeppelin's TransparentUpgradeableProxy pattern (not custom GraphProxy). GraphIssuanceProxyAdmin (owned by protocol governance) controls upgrades, while GOVERNOR_ROLE controls operations. The same governance address should have both roles. +## Lifecycle scripts -For the general governance-gated upgrade workflow, see [GovernanceWorkflow.md](../../../docs/GovernanceWorkflow.md). +| Script | Tag | Actor | Purpose | +| -------------------------------------------------------------------------------------- | ----------------------------- | ---------- | -------------------------------------------------------------------------- | +| [01_deploy.ts](../../deploy/allocate/allocator/01_deploy.ts) | `IssuanceAllocator,deploy` | Deployer | Deploy proxy + implementation, initialize with deployer as governor | +| [02_upgrade.ts](../../deploy/allocate/allocator/02_upgrade.ts) | `IssuanceAllocator,upgrade` | Governance | Build governance TX batch upgrading the proxy to its pendingImplementation | +| [04_configure.ts](../../deploy/allocate/allocator/04_configure.ts) | `IssuanceAllocator,configure` | Deployer | Set issuance rate (matches RM), grant `GOVERNOR_ROLE` and `PAUSE_ROLE` | +| [06_transfer_governance.ts](../../deploy/allocate/allocator/06_transfer_governance.ts) | `IssuanceAllocator,transfer` | Deployer | Revoke deployer `GOVERNOR_ROLE`, transfer per-proxy ProxyAdmin to gov | +| [09_end.ts](../../deploy/allocate/allocator/09_end.ts) | `IssuanceAllocator,all` | - | Aggregate end state — verifies upgrade has been executed | +| [10_status.ts](../../deploy/allocate/allocator/10_status.ts) | `IssuanceAllocator` | - | Read-only status display | -## Deployment Sequence +`03_*`, `05_*`, and `07_08_*` slots are intentionally empty (per [ImplementationPrinciples.md](ImplementationPrinciples.md)). -### Step 1: Deploy and Initialize (deployment account) +## What does NOT happen here -**Script:** [01_deploy.ts](./01_deploy.ts) +The following operations are part of GIP-0088 activation, not the IA component lifecycle. They live in [../../deploy/gip/0088/](../../deploy/gip/0088/) and are governance TXs: -- Deploy IssuanceAllocator implementation with GraphToken address -- Deploy TransparentUpgradeableProxy with implementation, GraphIssuanceProxyAdmin, and initialization data -- **Atomic initialization**: `initialize(deploymentAccountAddress)` called via proxy constructor -- Deployment account receives GOVERNOR_ROLE (temporary, for configuration) -- Automatically creates default target at `targetAddresses[0] = address(0)` -- Sets `lastDistributionBlock = block.number` -- **Security**: Front-running prevented by atomic deployment + initialization +- `IA.setTargetAllocation(RM, 0, rate)` — registers RM as the 100% self-minting target +- `IA.setDefaultTarget(DA)` — wires the safety net +- `RM.setIssuanceAllocator(IA)` — RM starts querying IA for its issuance rate +- `GraphToken.addMinter(IA)` — gives IA minter authority (only needed for allocator-minting targets) +- `IA.setTargetAllocation(RAM, allocatorRate, selfRate)` — distributes issuance to `RecurringAgreementManager` -### Step 2: Set Issuance Rate (deployment account) +These are bundled into the `GIP-0088:upgrade,upgrade` and `GIP-0088:issuance-connect` / `GIP-0088:issuance-allocate` governance batches. See [Gip0088.md](../Gip0088.md) for the full picture. -**Script:** [02_configure.ts](./02_configure.ts) +## Single-component usage -- Query current rate from RewardsManager: `rate = rewardsManager.issuancePerBlock()` -- Call `setIssuancePerBlock(rate)` to replicate existing rate -- All issuance allocated to default target (`address(0)`) -- No tokens minted (default target cannot receive mints) +```bash +# Read-only status +pnpm hardhat deploy --tags IssuanceAllocator --network -### Step 3: Assign RewardsManager Allocation (deployment account) +# Lifecycle steps +pnpm hardhat deploy --tags IssuanceAllocator,deploy --network +pnpm hardhat deploy --tags IssuanceAllocator,configure --network +pnpm hardhat deploy --tags IssuanceAllocator,transfer --network +pnpm hardhat deploy --tags IssuanceAllocator,upgrade --network +``` -**Script:** [02_configure.ts](./02_configure.ts) +The same scripts run as part of the goal-level GIP-0088 flow when invoked via `--tags GIP-0088:upgrade,`. -- Call `setTargetAllocation(rewardsManagerAddress, 0, issuancePerBlock)` -- `allocatorMintingRate = 0` (RewardsManager will self-mint) -- `selfMintingRate = issuancePerBlock` (RewardsManager receives 100% allocation) -- Default target automatically adjusts to zero allocation +## Verification checklist -### Step 4: Verify Configuration Before Transfer (deployment account) +Run `--tags IssuanceAllocator` (component status) or `--tags GIP-0088:upgrade` (goal status) to inspect on-chain state. The status output already covers everything below — this list is for reviewing a finished deployment by hand. -**Script:** [02_configure.ts](./02_configure.ts) +### Bytecode -- Verify contract is not paused (`paused()` returns false) -- Verify `getIssuancePerBlock()` returns expected rate (matches RewardsManager) -- Verify `getTargetAllocation(rewardsManager)` shows correct self-minting configuration -- Verify only two targets exist: `targetAddresses[0] = address(0)` and `targetAddresses[1] = rewardsManager` -- Verify default target is `address(0)` with zero allocation -- Contract is ready to transfer control to governance +- Implementation bytecode matches the expected `IssuanceAllocator` contract -### Step 5: Distribute Issuance (anyone - no role required) +### Access control -**Script:** [02_configure.ts](./02_configure.ts) +- Protocol governor holds `GOVERNOR_ROLE` +- Pause guardian holds `PAUSE_ROLE` +- Deployer does **not** hold `GOVERNOR_ROLE` (asserted by `checkDeployerRevoked` in the transfer step) +- Per-proxy `ProxyAdmin` is owned by the protocol governor -- Call `distributeIssuance()` to bring contract to fully current state -- Updates `lastDistributionBlock` to current block -- Verifies distribution mechanism is functioning correctly -- No tokens minted (no minter role yet, all allocation to self-minting RM) +### Configuration -### Step 6: Set Pause Controls and Transfer Governance (deployment account) +- `getIssuancePerBlock()` matches `RewardsManager.issuancePerBlock()` +- `paused()` is `false` -**Script:** [03_transfer_governance.ts](./03_transfer_governance.ts) +### Activation (GIP-0088) -- Grant PAUSE_ROLE to pause guardian (same account as used for RewardsManager pause control) -- Grant GOVERNOR_ROLE to actual governor address (protocol governance multisig) -- Revoke GOVERNOR_ROLE from deployment account (MUST grant to governance first, then revoke) -- **Note**: Upgrade control (via GraphIssuanceProxyAdmin) is separate from GOVERNOR_ROLE - -### Step 7: Verify Deployment and Configuration (governor) - -**Script:** [04_verify.ts](./04_verify.ts) - -**Bytecode verification:** - -- Verify deployed implementation bytecode matches expected contract - -**Access control:** - -- Verify governance address has GOVERNOR_ROLE -- Verify deployment account does NOT have GOVERNOR_ROLE -- Verify pause guardian has PAUSE_ROLE -- **Off-chain**: Review all RoleGranted events since deployment to verify no other addresses have GOVERNOR_ROLE or PAUSE_ROLE - -**Pause state:** - -- Verify contract is not paused (`paused()` returns false) - -**Issuance rate:** - -- Verify `getIssuancePerBlock()` matches RewardsManager rate exactly - -**Target configuration:** - -- Verify only two targets exist: `targetAddresses[0] = address(0)` and `targetAddresses[1] = rewardsManager` -- Verify default target is `address(0)` with zero allocation -- Verify `getTargetAllocation(rewardsManager)` shows correct self-minting allocation (100%) - -**Proxy configuration:** - -- Verify GraphIssuanceProxyAdmin controls the proxy -- Verify GraphIssuanceProxyAdmin owner is protocol governance - -### Step 8: Configure RewardsManager (governor) - -**Script:** [05_configure_rewards_manager.ts](./05_configure_rewards_manager.ts) - -- Call `rewardsManager.setIssuanceAllocator(issuanceAllocatorAddress)` -- RewardsManager will now query IssuanceAllocator for its issuance rate -- RewardsManager continues to mint tokens itself (self-minting) - -### Step 9: Grant Minter Role (governor, only when configuration verified) - -**Script:** [06_grant_minter.ts](./06_grant_minter.ts) - -- Grant minter role to IssuanceAllocator on Graph Token - -### Step 10: Set Default Target (governor, optional, recommended) - -**Script:** [07_set_default_target.ts](./07_set_default_target.ts) - -- Call `setDefaultTarget()` to receive future unallocated issuance - -## Normal Operation - -After deployment: - -1. Targets or external actors call `distributeIssuance()` periodically -2. Governor adjusts issuance rates as needed via `setIssuancePerBlock()` -3. Governor adds/removes/modifies targets via `setTargetAllocation()` overloads -4. Self-minting targets query their allocation via `getTargetIssuancePerBlock()` - -## Emergency Scenarios - -- **Gas limit issues**: Use pause, individual notifications, and `minDistributedBlock` parameters with `distributePendingIssuance()` -- **Target failures**: Use `forceTargetNoChangeNotificationBlock()` to skip notification, then remove problematic targets by setting both rates to 0 -- **Configuration while paused**: Call `distributePendingIssuance(blockNumber)` first, then use `minDistributedBlock` parameter in setter functions - -## L1 Bridge Integration - -When `setIssuancePerBlock()` is called, the L1GraphTokenGateway's `updateL2MintAllowance()` function must be called to ensure the bridge can mint the correct amount of tokens on L2. - -## See Also - -- [IssuanceAllocator.md](../../../../issuance/contracts/allocate/IssuanceAllocator.md) - Contract architecture and technical details -- [GovernanceWorkflow.md](../../../docs/GovernanceWorkflow.md) - General governance-gated upgrade workflow +- `RewardsManager.getIssuanceAllocator()` returns the IA address +- `GraphToken.isMinter(IA)` is `true` (only when allocator-minting targets exist) +- `getTargetAllocation(RM)` shows `selfMintingRate == issuancePerBlock`, `allocatorMintingRate == 0` +- `getTargetAllocation(RAM)` matches `config/.json5` rates +- Default target points at `DefaultAllocation` diff --git a/packages/deployment/docs/deploy/RewardsEligibilityOracleDeployment.md b/packages/deployment/docs/deploy/RewardsEligibilityOracleDeployment.md index 9a5c1bfde..e8e9d2968 100644 --- a/packages/deployment/docs/deploy/RewardsEligibilityOracleDeployment.md +++ b/packages/deployment/docs/deploy/RewardsEligibilityOracleDeployment.md @@ -5,7 +5,7 @@ Deployment guide for RewardsEligibilityOracle (REO). **Related:** - [Contract specification](../../../issuance/contracts/eligibility/RewardsEligibilityOracle.md) - architecture, operations, troubleshooting -- [GovernanceWorkflow.md](./GovernanceWorkflow.md) - Safe TX execution +- [GovernanceWorkflow.md](../GovernanceWorkflow.md) - Safe TX execution ## Prerequisites @@ -17,26 +17,41 @@ Deployment guide for RewardsEligibilityOracle (REO). All scripts are idempotent. -| Script | Tag | Actor | Purpose | -| --------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------- | -------------------------------------- | -| [01_deploy.ts](../../deploy/rewards/eligibility/01_deploy.ts) | `rewards-eligibility-deploy` | Deployer | Deploy proxy + implementation | -| [02_upgrade.ts](../../deploy/rewards/eligibility/02_upgrade.ts) | `rewards-eligibility-upgrade` | Governance | Upgrade implementation | -| [04_configure.ts](../../deploy/rewards/eligibility/04_configure.ts) | `rewards-eligibility-configure` | Deployer/Governance | Set parameters | -| [05_transfer_governance.ts](../../deploy/rewards/eligibility/05_transfer_governance.ts) | `rewards-eligibility-transfer-governance` | Deployer | Grant roles, transfer to governance | -| [06_integrate.ts](../../deploy/rewards/eligibility/06_integrate.ts) | `rewards-eligibility-integrate` | Governance | Connect to RewardsManager | -| [09_complete.ts](../../deploy/rewards/eligibility/09_complete.ts) | `rewards-eligibility` | - | Aggregate (deploy, upgrade, configure) | +| Script | Tag | Actor | Purpose | +| --------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------- | ----------------------------------------- | +| [01_deploy.ts](../../deploy/rewards/eligibility/01_deploy.ts) | `RewardsEligibilityOracle{A,B}:deploy` | Deployer | Deploy proxy + implementation | +| [02_upgrade.ts](../../deploy/rewards/eligibility/02_upgrade.ts) | `RewardsEligibilityOracle{A,B}:upgrade` | Governance | Upgrade implementation | +| [04_configure.ts](../../deploy/rewards/eligibility/04_configure.ts) | `RewardsEligibilityOracle{A,B}:configure` | Deployer/Governance | Set parameters | +| [05_transfer_governance.ts](../../deploy/rewards/eligibility/05_transfer_governance.ts) | `RewardsEligibilityOracle{A,B}:transfer` | Deployer | Revoke deployer role, transfer ProxyAdmin | +| [09_end.ts](../../deploy/rewards/eligibility/09_end.ts) | `RewardsEligibilityOracle{A,B}` | - | Aggregate (deploy, upgrade, configure) | + +Integration with `RewardsManager` is **not** a per-component lifecycle action. Only one of REO-A or REO-B is integrated at a time, which is a goal-level decision. Use the GIP-0088 activation tag instead: + +```bash +pnpm hardhat deploy --tags GIP-0088:eligibility-integrate --network +``` + +The testnet-only `MockRewardsEligibilityOracle` is a separate, opt-in path with its own per-component [`06_integrate.ts`](../../deploy/rewards/eligibility/mock/06_integrate.ts). It is **not** part of any GIP-0088 phase tag, so `--tags all` will not pull it in — it runs only when `RewardsEligibilityOracleMock` is named explicitly: + +```bash +pnpm hardhat deploy --tags RewardsEligibilityOracleMock,integrate --network +``` + +Intentionally kept off the default deployment path and outside the governance-tx flow; not intended for mainnet. Its governance-tx batch name is `RewardsManager-MockREO` (vs `RewardsManager-REO` for the GIP-0088 A/B activation) so the two cannot collide on the same filesystem. ### Quick Start ```bash -# Full deployment (new install) -pnpm hardhat deploy --tags rewards-eligibility --network +# Read-only status (no --tags = no mutations) +pnpm hardhat deploy --tags RewardsEligibilityOracleA --network # Individual steps -pnpm hardhat deploy --tags rewards-eligibility-deploy --network -pnpm hardhat deploy --tags rewards-eligibility-configure --network -pnpm hardhat deploy --tags rewards-eligibility-transfer-governance --network -pnpm hardhat deploy --tags rewards-eligibility-integrate --network +pnpm hardhat deploy --tags RewardsEligibilityOracleA,deploy --network +pnpm hardhat deploy --tags RewardsEligibilityOracleA,configure --network +pnpm hardhat deploy --tags RewardsEligibilityOracleA,transfer --network + +# Integrate (only one of A/B at a time — goal-level) +pnpm hardhat deploy --tags GIP-0088:eligibility-integrate --network ``` ## Verification Checklist diff --git a/packages/deployment/hardhat.config.ts b/packages/deployment/hardhat.config.ts index 08b85b027..43256ddca 100644 --- a/packages/deployment/hardhat.config.ts +++ b/packages/deployment/hardhat.config.ts @@ -11,12 +11,17 @@ import hardhatDeploy from 'hardhat-deploy' import checkDeployerTask from './tasks/check-deployer.js' // Import tasks (HH v3 task API) import deploymentStatusTask from './tasks/deployment-status.js' +import { ethBalanceTask, ethCheckKeyTask, ethFundTask } from './tasks/eth-tasks.js' import executeGovernanceTask from './tasks/execute-governance.js' import grantRoleTask from './tasks/grant-role.js' +import { grtBalanceTask, grtMintTask, grtStatusTask, grtTransferTask } from './tasks/grt-tasks.js' import listPendingTask from './tasks/list-pending-implementations.js' import listRolesTask from './tasks/list-roles.js' +import { reoDisableTask, reoEnableTask, reoIndexersTask, reoStatusTask } from './tasks/reo-tasks.js' import resetForkTask from './tasks/reset-fork.js' import revokeRoleTask from './tasks/revoke-role.js' +import { ssStatusTask } from './tasks/ss-tasks.js' +import syncTask from './tasks/sync.js' import verifyContractTask from './tasks/verify-contract.js' // ESM compatibility @@ -26,6 +31,14 @@ const __dirname = path.dirname(__filename) // Package paths const packageRoot = __dirname +// Hardhat v3 does not auto-set HARDHAT_NETWORK (v2 did). +// isLocalNetworkMode() in address-book-utils.ts relies on this env var to +// select addresses-local-network.json over addresses.json. +const networkArg = process.argv.find((_, i, a) => a[i - 1] === '--network') +if (networkArg === 'localNetwork') { + process.env.HARDHAT_NETWORK = 'localNetwork' +} + // RPC URLs with defaults const ARBITRUM_ONE_RPC = process.env.ARBITRUM_ONE_RPC || 'https://arb1.arbitrum.io/rpc' const ARBITRUM_SEPOLIA_RPC = process.env.ARBITRUM_SEPOLIA_RPC || 'https://sepolia-rollup.arbitrum.io/rpc' @@ -50,10 +63,94 @@ function getDeployerKeyName(networkName: string): string { } /** - * Get accounts config for a network using configVariable for lazy resolution + * Parse --tags from process.argv. + * Returns null when --tags is not present. + */ +function parseTagsFromArgv(): string[] | null { + const argv = process.argv + for (let i = 0; i < argv.length; i++) { + const a = argv[i] + if (a === '--tags') { + if (i + 1 >= argv.length) return null + return argv[i + 1].split(',') + } + if (a.startsWith('--tags=')) { + return a.slice('--tags='.length).split(',') + } + } + return null +} + +/** + * Detect whether the current invocation needs a deployer account. + * + * The deployer key is only needed when the `deploy` task is invoked with + * action verbs in `--tags` that perform mutations (deploy, upgrade, configure, + * transfer, integrate, all). Status-only runs (`--tags Component` without + * action verbs) are read-only and don't need the deployer key. + * + * Other tasks (reo:enable, grant-role, eth:fund, ...) resolve keys at + * execution time via resolveConfigVar(), and read-only tasks need no key + * at all. + * + * Gating configVariable() on this lets the hardhat-keystore plugin prompt for + * the password only when the user actually runs a mutating deploy action, + * instead of on every `deploy` invocation. + */ +function getTaskName(): string | null { + for (const arg of process.argv.slice(2)) { + if (arg.startsWith('-')) continue + return arg + } + return null +} + +function needsDeployerAccount(): boolean { + // Non-deploy tasks resolve keys at runtime; deploy:sync is read-only + if (getTaskName() !== 'deploy') return false + + // Status-only runs (no action verbs in --tags) don't need a signer + const tags = parseTagsFromArgv() + if (!tags) return false + + const ACTION_VERBS = ['deploy', 'upgrade', 'configure', 'transfer', 'integrate', 'all'] + return tags.some((tag) => ACTION_VERBS.includes(tag)) +} + +/** + * Dummy private key used when no real deployer key is needed. + * + * Rocketh requires at least one account to resolve namedAccounts.deployer. + * For status-only runs we provide this throwaway key so environment creation + * succeeds without prompting the keystore. The resulting address + * (0x7E5F...95Bdf) is filtered out by getDeployer() — status scripts infer + * the real deployer from the ProxyAdmin owner on-chain. + */ +const DUMMY_DEPLOYER_KEY = '0x0000000000000000000000000000000000000000000000000000000000000001' + +/** + * Get accounts config for a network. + * + * When the deploy task is invoked with action verbs (deploy, upgrade, etc.), + * returns a configVariable so the hardhat-keystore plugin resolves the + * deployer key from the keystore (with env-var fallback). + * + * For status-only deploy runs and all other tasks, returns a dummy key so + * rocketh can initialise namedAccounts without a keystore prompt. Signing + * tasks resolve keys themselves via resolveConfigVar(). + * + * Set the key via either: + * npx hardhat keystore set ARBITRUM_SEPOLIA_DEPLOYER_KEY + * export ARBITRUM_SEPOLIA_DEPLOYER_KEY=0x... */ const getNetworkAccounts = (networkName: string) => { - return [configVariable(getDeployerKeyName(networkName))] + if (!needsDeployerAccount()) return [DUMMY_DEPLOYER_KEY] + const keyName = getDeployerKeyName(networkName) + if (networkName === networkArg && !process.env[keyName]) { + console.log(`\n Deployer key: ${keyName}`) + console.log(` Set via: npx hardhat keystore set ${keyName}\n`) + } + return [configVariable(keyName)] } // Fork network detection (HARDHAT_FORK is the standard for hardhat-deploy v2) @@ -67,10 +164,23 @@ const config: HardhatUserConfig = { tasks: [ checkDeployerTask, deploymentStatusTask, + ethBalanceTask, + ethCheckKeyTask, + ethFundTask, executeGovernanceTask, grantRoleTask, + grtBalanceTask, + grtMintTask, + grtStatusTask, + grtTransferTask, listPendingTask, listRolesTask, + reoDisableTask, + reoEnableTask, + reoIndexersTask, + reoStatusTask, + ssStatusTask, + syncTask, resetForkTask, revokeRoleTask, verifyContractTask, @@ -78,6 +188,17 @@ const config: HardhatUserConfig = { // Chain descriptors for fork execution and local development chainDescriptors: { + // Graph Local Network (chainId 1337) + 1337: { + name: 'Graph Local Network', + hardforkHistory: { + berlin: { blockNumber: 0 }, + london: { blockNumber: 0 }, + merge: { blockNumber: 0 }, + shanghai: { blockNumber: 0 }, + cancun: { blockNumber: 0 }, + }, + }, // Local hardhat network (for non-fork runs) 31337: { name: 'Hardhat Local', @@ -155,6 +276,16 @@ const config: HardhatUserConfig = { } : undefined, }, + // Graph Local Network — chainId 1337, contracts deployed fresh by an + // upstream step that populates addresses-local-network.json files. + localNetwork: { + type: 'http', + url: process.env.LOCAL_NETWORK_RPC || 'http://chain:8545', + chainId: 1337, + accounts: { + mnemonic: 'test test test test test test test test test test test junk', + }, + }, arbitrumOne: { type: 'http', chainId: 42161, @@ -172,11 +303,11 @@ const config: HardhatUserConfig = { // External artifacts are loaded via direct imports in deploy scripts // Contract verification config (hardhat-verify v3) - // API key resolves from keystore or env: npx hardhat keystore set ARBISCAN_API_KEY - // Sourcify and Blockscout disabled - they don't work reliably for Arbitrum + // API key from keystore, gated to deploy:verify to avoid prompting on every task. + // Set via: npx hardhat keystore set ARBISCAN_API_KEY verify: { etherscan: { - apiKey: configVariable('ARBISCAN_API_KEY'), + apiKey: getTaskName() === 'deploy:verify' ? configVariable('ARBISCAN_API_KEY') : '', }, sourcify: { enabled: false, diff --git a/packages/deployment/lib/abis.ts b/packages/deployment/lib/abis.ts index b7b0868b2..ece524796 100644 --- a/packages/deployment/lib/abis.ts +++ b/packages/deployment/lib/abis.ts @@ -1,86 +1,86 @@ /** * Shared ABI definitions for contract interactions * - * These ABIs are loaded from @graphprotocol/interfaces artifacts to ensure they stay in sync - * with the actual contract interfaces. The interfaces package is the canonical source for ABIs. + * Generated ABIs are produced by `pnpm generate:abis` from contract artifacts. + * The contract registry drives which ABIs and interface IDs are generated. + * Only ACCESS_CONTROL_ENUMERABLE_ABI is hand-maintained (generic role queries). */ -import { readFileSync } from 'node:fs' -import { createRequire } from 'node:module' -import type { Abi } from 'viem' +// Re-export all generated typed ABIs, aliases, and interface IDs +export { + CONTROLLER_ABI, + DIRECT_ALLOCATION_ABI, + GRAPH_PROXY_ADMIN_ABI, + GRAPH_TOKEN_ABI, + IERC165_ABI, + IERC165_INTERFACE_ID, + IISSUANCE_TARGET_INTERFACE_ID, + INITIALIZE_GOVERNOR_ABI, + IREWARDS_MANAGER_INTERFACE_ID, + ISSUANCE_ALLOCATOR_ABI, + ISSUANCE_TARGET_ABI, + OZ_PROXY_ADMIN_ABI, + PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + REWARDS_ELIGIBILITY_ORACLE_ABI, + REWARDS_MANAGER_ABI, + REWARDS_MANAGER_DEPRECATED_ABI, + SET_TARGET_ALLOCATION_ABI, +} from './generated/abis.js' -const require = createRequire(import.meta.url) - -// Helper to load ABI from interface artifact -function loadAbi(artifactPath: string): Abi { - const artifact = JSON.parse(readFileSync(require.resolve(artifactPath), 'utf-8')) - return artifact.abi as Abi -} - -// Interface IDs - these mirror the values the compiler derives from the -// corresponding ABI. Cross-checked by test/interface-id-stability.test.ts; -// update both together whenever an interface changes. -export const IERC165_INTERFACE_ID = '0x01ffc9a7' as const -export const IISSUANCE_TARGET_INTERFACE_ID = '0x19f6601a' as const -export const IREWARDS_MANAGER_INTERFACE_ID = '0x8469b577' as const - -export const REWARDS_MANAGER_ABI = loadAbi( - '@graphprotocol/interfaces/artifacts/contracts/contracts/rewards/IRewardsManager.sol/IRewardsManager.json', -) - -// Deprecated interface includes legacy functions like issuancePerBlock() -export const REWARDS_MANAGER_DEPRECATED_ABI = loadAbi( - '@graphprotocol/interfaces/artifacts/contracts/contracts/rewards/IRewardsManagerDeprecated.sol/IRewardsManagerDeprecated.json', -) - -export const CONTROLLER_ABI = loadAbi( - '@graphprotocol/interfaces/artifacts/contracts/toolshed/IControllerToolshed.sol/IControllerToolshed.json', -) - -// Core interfaces -export const GRAPH_TOKEN_ABI = loadAbi( - '@graphprotocol/interfaces/artifacts/contracts/contracts/token/IGraphToken.sol/IGraphToken.json', -) - -export const GRAPH_PROXY_ADMIN_ABI = loadAbi( - '@graphprotocol/interfaces/artifacts/contracts/contracts/upgrades/IGraphProxyAdmin.sol/IGraphProxyAdmin.json', -) - -export const IERC165_ABI = loadAbi( - '@graphprotocol/interfaces/artifacts/@openzeppelin/contracts/introspection/IERC165.sol/IERC165.json', -) - -// Issuance interfaces -export const ISSUANCE_TARGET_ABI = loadAbi( - '@graphprotocol/interfaces/artifacts/contracts/issuance/allocate/IIssuanceTarget.sol/IIssuanceTarget.json', -) - -// --- ABIs loaded from @graphprotocol/horizon (OZ contracts) --- -// These are not in interfaces package, load from horizon build - -export const OZ_PROXY_ADMIN_ABI = loadAbi( - '@graphprotocol/horizon/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json', -) - -// --- ABIs loaded from @graphprotocol/issuance --- -// Full contract ABIs for deployment operations that need access to all methods - -export const ISSUANCE_ALLOCATOR_ABI = loadAbi( - '@graphprotocol/issuance/artifacts/contracts/allocate/IssuanceAllocator.sol/IssuanceAllocator.json', -) - -export const DIRECT_ALLOCATION_ABI = loadAbi( - '@graphprotocol/issuance/artifacts/contracts/allocate/DirectAllocation.sol/DirectAllocation.json', -) +// ============================================================================ +// Hand-rolled minimal ABIs (not in @graphprotocol/interfaces) +// ============================================================================ -export const REWARDS_ELIGIBILITY_ORACLE_ABI = loadAbi( - '@graphprotocol/issuance/artifacts/contracts/eligibility/RewardsEligibilityOracle.sol/RewardsEligibilityOracle.json', -) +/** + * Minimal ABI for RecurringCollector pause guardian management + * + * RC's pause guardian functions are not part of an interface in + * @graphprotocol/interfaces. Used by RC configure and the GIP-0088 upgrade + * batch to manage `setPauseGuardian` / `pauseGuardians`. + */ +export const RECURRING_COLLECTOR_PAUSE_ABI = [ + { + inputs: [{ name: '_pauseGuardian', type: 'address' }], + name: 'pauseGuardians', + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: '_pauseGuardian', type: 'address' }, + { name: '_allowed', type: 'bool' }, + ], + name: 'setPauseGuardian', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const -// Convenience re-exports for specific function subsets -// These reference the full ABIs above - viem will find the right function by name -export { ISSUANCE_ALLOCATOR_ABI as SET_TARGET_ALLOCATION_ABI } -export { DIRECT_ALLOCATION_ABI as INITIALIZE_GOVERNOR_ABI } +/** + * Minimal ABI for SubgraphService allocation close guard + * + * `blockClosingAllocationWithActiveAgreement` is part of the SS interface but + * not generated yet. Used by `GIP-0088:issuance-close-guard` and the goal + * status display. + */ +export const SUBGRAPH_SERVICE_CLOSE_GUARD_ABI = [ + { + inputs: [], + name: 'getBlockClosingAllocationWithActiveAgreement', + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'enabled', type: 'bool' }], + name: 'setBlockClosingAllocationWithActiveAgreement', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const // ============================================================================ // Generic ABIs for role enumeration diff --git a/packages/deployment/lib/address-book-ops.ts b/packages/deployment/lib/address-book-ops.ts index 66927b862..9f6f506f3 100644 --- a/packages/deployment/lib/address-book-ops.ts +++ b/packages/deployment/lib/address-book-ops.ts @@ -17,7 +17,7 @@ * * // Write operations * addressBook.setProxy('RewardsManager', proxyAddr, implAddr, adminAddr, 'transparent') - * addressBook.setPendingImplementation('RewardsManager', newImplAddr, { txHash: '0x...' }) + * addressBook.setPendingImplementationWithMetadata('RewardsManager', newImplAddr, metadata) * ``` */ @@ -143,56 +143,6 @@ export class AddressBookOps { }) } - /** - * Set pending implementation - * - * Stores an implementation address in the pendingImplementation field. - * Only one pending implementation can exist at a time (replaces any existing pending). - * - * @example - * ```typescript - * ops.setPendingImplementation('RewardsManager', '0xNewImpl...', { - * txHash: '0xabc...', - * }) - * ``` - * - * @throws Error if contract not found in address book - * @throws Error if contract is not a proxy - */ - setPendingImplementation( - name: ContractName, - implementationAddress: string, - metadata?: { - txHash?: string - timestamp?: string - }, - ): void { - const entry = this.addressBook.getEntry(name as string) - - if (!entry) { - throw new Error(`Contract ${name} not found in address book`) - } - - if (!entry.proxy) { - throw new Error(`Contract ${name} is not a proxy contract`) - } - - const pendingImplementation: PendingImplementation = { - address: implementationAddress, - deployment: { - txHash: metadata?.txHash ?? '', - argsData: '0x', - bytecodeHash: '', - ...(metadata?.timestamp && { timestamp: metadata.timestamp }), - }, - } - - this.addressBook.setEntry(name, { - ...entry, - pendingImplementation, - }) - } - /** * Promote pending implementation to active * @@ -379,10 +329,8 @@ export class AddressBookOps { } /** - * Set pending implementation with full deployment metadata - * - * Enhanced version of setPendingImplementation that includes full deployment metadata - * for verification and record reconstruction. + * Set pending implementation with full deployment metadata for verification + * and record reconstruction. * * @example * ```typescript diff --git a/packages/deployment/lib/address-book-utils.ts b/packages/deployment/lib/address-book-utils.ts index 0de0db016..086b5eb34 100644 --- a/packages/deployment/lib/address-book-utils.ts +++ b/packages/deployment/lib/address-book-utils.ts @@ -28,28 +28,156 @@ import { } from '@graphprotocol/toolshed/deployments' import { config as rockethConfig } from '../rocketh/config.js' +import type { AnyAddressBookOps } from './address-book-ops.js' import { AddressBookOps } from './address-book-ops.js' +import type { AddressBookType } from './contract-registry.js' const require = createRequire(import.meta.url) +// ============================================================================ +// Fork Auto-Detection +// ============================================================================ + +/** + * Build a map from RPC URL hostname to network name using rocketh config. + * Used by autoDetectForkNetwork() to match anvil's forkUrl. + */ +function buildRpcHostToNetworkMap(): Map { + const map = new Map() + const environments = rockethConfig.environments + const chains = rockethConfig.chains + if (!environments || !chains) return map + + for (const [envName, envConfig] of Object.entries(environments)) { + const chainId = (envConfig as { chain: number }).chain + const chainConfig = (chains as Record)[chainId] as + | { info?: { rpcUrls?: { default?: { http?: readonly string[] } } } } + | undefined + const rpcUrls = chainConfig?.info?.rpcUrls?.default?.http + if (!rpcUrls) continue + + for (const rpcUrl of rpcUrls) { + try { + const hostname = new URL(rpcUrl).hostname + map.set(hostname, { name: envName, chainId }) + } catch { + // Skip invalid URLs + } + } + } + return map +} + +/** + * Auto-detect the fork network by querying anvil's `anvil_nodeInfo` RPC method. + * + * If FORK_NETWORK is already set, this is a no-op. + * If the provider is an anvil fork, extracts the fork URL and matches it + * against known network RPC hostnames from rocketh config. + * + * On success, sets process.env.FORK_NETWORK so all downstream synchronous + * functions (isForkMode, getForkNetwork, etc.) work without changes. + * + * @param rpcUrl - The RPC URL to query (default: http://127.0.0.1:8545) + * @returns The detected network name, or null if not a fork / not detectable + */ +export async function autoDetectForkNetwork(rpcUrl = 'http://127.0.0.1:8545'): Promise { + // Already set — nothing to do + if (process.env.FORK_NETWORK || process.env.HARDHAT_FORK) { + return process.env.FORK_NETWORK || process.env.HARDHAT_FORK || null + } + + try { + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', method: 'anvil_nodeInfo', params: [], id: 1 }), + }) + const json = (await response.json()) as { + result?: { forkConfig?: { forkUrl?: string } } + } + const forkUrl = json.result?.forkConfig?.forkUrl + if (!forkUrl) return null + + // Match fork URL hostname against known networks + const hostMap = buildRpcHostToNetworkMap() + const forkHostname = new URL(forkUrl).hostname + const match = hostMap.get(forkHostname) + if (!match) return null + + // Set env var so all synchronous fork detection works downstream + process.env.FORK_NETWORK = match.name + return match.name + } catch { + // Not reachable or not anvil — not a fork + return null + } +} + // ============================================================================ // Fork Mode Detection // ============================================================================ +/** Network names that are local/test and support fork mode */ +const LOCAL_NETWORKS = new Set(['localhost', 'fork', 'hardhat']) + +/** + * Check if the current network is a local network. + * Uses explicit networkName if provided, falls back to HARDHAT_NETWORK env var. + * Returns true if network is unknown (preserves existing behavior for callers + * that don't pass context). + */ +function isLocalNetwork(networkName?: string): boolean { + const name = networkName ?? process.env.HARDHAT_NETWORK + if (name === undefined) return true + return LOCAL_NETWORKS.has(name) +} + /** - * Check if running in fork mode + * Check if running in fork mode. + * + * Fork mode requires both: + * 1. FORK_NETWORK or HARDHAT_FORK env var is set + * 2. The current network is local (localhost, fork, hardhat) + * + * This prevents fork mode from activating when running against real networks + * even if FORK_NETWORK is still set in the environment. + * + * @param networkName - Optional network name for explicit check (e.g., env.name). + * Falls back to HARDHAT_NETWORK env var if not provided. */ -export function isForkMode(): boolean { +export function isForkMode(networkName?: string): boolean { + if (!isLocalNetwork(networkName)) return false return !!(process.env.HARDHAT_FORK || process.env.FORK_NETWORK) } /** - * Get the fork network name from environment + * Get the fork network name from environment. + * Returns null if not in fork mode or if running on a real network. + * + * @param networkName - Optional network name for explicit check. + * Falls back to HARDHAT_NETWORK env var if not provided. */ -export function getForkNetwork(): string | null { +export function getForkNetwork(networkName?: string): string | null { + if (!isLocalNetwork(networkName)) return null return process.env.HARDHAT_FORK || process.env.FORK_NETWORK || null } +// ============================================================================ +// Local Network Detection +// ============================================================================ + +/** + * Check if running against the Graph local network (chainId 1337). + * + * The local network deploys contracts from scratch. Address books use + * addresses-local-network.json files that the orchestrating dev environment + * is expected to symlink (or otherwise create) before this code path runs. + */ +export function isLocalNetworkMode(): boolean { + return process.env.HARDHAT_NETWORK === 'localNetwork' +} + /** * Get the fork state directory for a given network. * All fork-related state (address books, governance TXs) is stored here. @@ -75,8 +203,8 @@ export function getForkStateDir(envName: string, forkNetwork: string): string { * const forkChainId = getForkTargetChainId() * const targetChainId = forkChainId ?? providerChainId */ -export function getForkTargetChainId(): number | null { - const forkNetwork = getForkNetwork() +export function getForkTargetChainId(networkName?: string): number | null { + const forkNetwork = getForkNetwork(networkName) if (!forkNetwork) return null // Look up chain ID from rocketh config environments @@ -117,14 +245,28 @@ export function getForkTargetChainId(): number | null { * const addressBook = getIssuanceAddressBook(targetChainId) */ export async function getTargetChainIdFromEnv(env: Environment): Promise { - const forkChainId = getForkTargetChainId() + const forkChainId = getForkTargetChainId(env.name) if (forkChainId !== null) { return forkChainId } // Not in fork mode - get actual chain ID from provider const chainIdHex = await env.network.provider.request({ method: 'eth_chainId' }) - return Number(chainIdHex) + const providerChainId = Number(chainIdHex) + + // If we're on local chain 31337 without FORK_NETWORK set, the user is most + // likely running against an anvil fork. Try auto-detecting once so callers + // (per-component sync, status scripts) can resolve the right address book + // without requiring the global sync script to have run first. + if (providerChainId === 31337 && !getForkNetwork(env.name)) { + const detected = await autoDetectForkNetwork() + if (detected) { + const detectedForkChainId = getForkTargetChainId(env.name) + if (detectedForkChainId !== null) return detectedForkChainId + } + } + + return providerChainId } // ============================================================================ @@ -206,6 +348,7 @@ export function ensureForkAddressBooks(): { /** * Get the path to the Horizon address book. * In fork mode, returns path to fork-local copy. + * In local network mode, returns path to addresses-local-network.json. * In normal mode, returns path to package address book. */ export function getHorizonAddressBookPath(): string { @@ -213,12 +356,16 @@ export function getHorizonAddressBookPath(): string { const { horizonPath } = ensureForkAddressBooks() return horizonPath } + if (isLocalNetworkMode()) { + return require.resolve('@graphprotocol/horizon/addresses-local-network.json') + } return require.resolve('@graphprotocol/horizon/addresses.json') } /** * Get the path to the SubgraphService address book. * In fork mode, returns path to fork-local copy. + * In local network mode, returns path to addresses-local-network.json. * In normal mode, returns path to package address book. */ export function getSubgraphServiceAddressBookPath(): string { @@ -226,12 +373,16 @@ export function getSubgraphServiceAddressBookPath(): string { const { subgraphServicePath } = ensureForkAddressBooks() return subgraphServicePath } + if (isLocalNetworkMode()) { + return require.resolve('@graphprotocol/subgraph-service/addresses-local-network.json') + } return require.resolve('@graphprotocol/subgraph-service/addresses.json') } /** * Get the path to the Issuance address book. * In fork mode, returns path to fork-local copy. + * In local network mode, returns path to addresses-local-network.json. * In normal mode, returns path to package address book. */ export function getIssuanceAddressBookPath(): string { @@ -239,6 +390,9 @@ export function getIssuanceAddressBookPath(): string { const { issuancePath } = ensureForkAddressBooks() return issuancePath } + if (isLocalNetworkMode()) { + return require.resolve('@graphprotocol/issuance/addresses-local-network.json') + } return require.resolve('@graphprotocol/issuance/addresses.json') } @@ -284,3 +438,24 @@ export function getIssuanceAddressBook(chainId?: number): AddressBookOps( env.showMessage(`\n✅ ${contractName} configuration updated\n`) return { status, changesNeeded: true, executedDirectly: true } } else { - // Never returns - exits with code 1 - saveGovernanceTxAndExit(env, builder, `${contractName} configuration`) - // TypeScript doesn't know saveGovernanceTxAndExit never returns - throw new Error('unreachable') + saveGovernanceTx(env, builder, `${contractName} configuration`) + return { status, changesNeeded: true, executedDirectly: false } } } diff --git a/packages/deployment/lib/artifact-loaders.ts b/packages/deployment/lib/artifact-loaders.ts index 786f47773..e48c6e587 100644 --- a/packages/deployment/lib/artifact-loaders.ts +++ b/packages/deployment/lib/artifact-loaders.ts @@ -3,6 +3,8 @@ import { createRequire } from 'node:module' import type { Artifact } from '@rocketh/core/types' +import type { LibraryArtifactResolver, LinkReferences } from './bytecode-utils.js' + // Create require for JSON imports in ESM const require = createRequire(import.meta.url) @@ -31,8 +33,10 @@ export function loadContractsArtifact(contractPath: string, contractName: string * @param contractName - Contract name (e.g., 'SubgraphService') */ export function loadSubgraphServiceArtifact(contractName: string): Artifact { + // Support subdirectory names like 'libraries/IndexingAgreement' + const baseName = contractName.includes('/') ? contractName.split('/').pop()! : contractName const artifactPath = require.resolve( - `@graphprotocol/subgraph-service/artifacts/contracts/${contractName}.sol/${contractName}.json`, + `@graphprotocol/subgraph-service/artifacts/contracts/${contractName}.sol/${baseName}.json`, ) const artifact = JSON.parse(readFileSync(artifactPath, 'utf-8')) @@ -41,6 +45,8 @@ export function loadSubgraphServiceArtifact(contractName: string): Artifact { bytecode: artifact.bytecode as `0x${string}`, deployedBytecode: artifact.deployedBytecode as `0x${string}`, metadata: artifact.metadata || '', + linkReferences: artifact.linkReferences, + deployedLinkReferences: artifact.deployedLinkReferences, } } @@ -57,6 +63,8 @@ export function loadIssuanceArtifact(artifactSubpath: string): Artifact { bytecode: artifact.bytecode as `0x${string}`, deployedBytecode: artifact.deployedBytecode as `0x${string}`, metadata: artifact.metadata || '', + linkReferences: artifact.linkReferences, + deployedLinkReferences: artifact.deployedLinkReferences, } } @@ -66,13 +74,15 @@ export function loadIssuanceArtifact(artifactSubpath: string): Artifact { * @param artifactSubpath - Path within build/contracts/ (e.g., '@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin') */ export function loadHorizonBuildArtifact(artifactSubpath: string): Artifact { - const artifactPath = require.resolve(`@graphprotocol/horizon/build/contracts/${artifactSubpath}.json`) + const artifactPath = require.resolve(`@graphprotocol/horizon/artifacts/${artifactSubpath}.json`) const artifact = JSON.parse(readFileSync(artifactPath, 'utf-8')) return { abi: artifact.abi, bytecode: artifact.bytecode as `0x${string}`, deployedBytecode: artifact.deployedBytecode as `0x${string}`, metadata: artifact.metadata || '', + linkReferences: artifact.linkReferences, + deployedLinkReferences: artifact.deployedLinkReferences, } } @@ -92,6 +102,88 @@ export function loadOpenZeppelinArtifact(contractName: string): Artifact { } } +/** + * Create a library artifact resolver for a given package. + * + * Library artifacts live at /artifacts//.json, + * mirroring the linkReferences source paths from Hardhat compilation. + */ +function createPackageLibraryResolver(packagePrefix: string): LibraryArtifactResolver { + return (sourcePath: string, libraryName: string) => { + try { + const libPath = require.resolve(`${packagePrefix}/${sourcePath}/${libraryName}.json`) + const artifact = JSON.parse(readFileSync(libPath, 'utf-8')) + return { + deployedBytecode: artifact.deployedBytecode as string, + deployedLinkReferences: artifact.deployedLinkReferences as LinkReferences | undefined, + } + } catch { + return undefined + } + } +} + +/** + * Get a library artifact resolver for the given artifact source type. + * Returns undefined if the source type doesn't support library resolution. + */ +export function getLibraryResolver(sourceType: string): LibraryArtifactResolver | undefined { + switch (sourceType) { + case 'subgraph-service': + return createPackageLibraryResolver('@graphprotocol/subgraph-service/artifacts') + case 'horizon': + return createPackageLibraryResolver('@graphprotocol/horizon/artifacts') + case 'issuance': + return createPackageLibraryResolver('@graphprotocol/issuance/artifacts') + case 'contracts': + return createPackageLibraryResolver('@graphprotocol/contracts/artifacts') + default: + return undefined + } +} + +/** + * Pre-link library addresses into an artifact's creation bytecode. + * + * Rocketh's deploy() stores the artifact's bytecode verbatim but compares + * against linked bytecode on subsequent runs. For artifacts with library + * references this causes a permanent mismatch (unlinked placeholders vs + * resolved addresses), triggering a redeploy every time. + * + * Call this before passing the artifact to rocketh's deploy(). The returned + * artifact has fully resolved bytecode and cleared linkReferences, so + * rocketh stores what it will compare against next run. + * + * @param artifact - Artifact with unlinked bytecode and linkReferences + * @param libraries - Map of library name → deployed address + */ +export function linkArtifactLibraries(artifact: Artifact, libraries: Record): Artifact { + let bytecode = artifact.bytecode as string + + if (artifact.linkReferences) { + for (const [, fileReferences] of Object.entries( + artifact.linkReferences as Record>>, + )) { + for (const [libName, fixups] of Object.entries(fileReferences)) { + const addr = libraries[libName] + if (!addr) continue + for (const fixup of fixups) { + bytecode = + bytecode.substring(0, 2 + fixup.start * 2) + + addr.substring(2) + + bytecode.substring(2 + (fixup.start + fixup.length) * 2) + } + } + } + } + + return { + ...artifact, + bytecode: bytecode as `0x${string}`, + linkReferences: undefined, + } +} + /** * Load OpenZeppelin TransparentUpgradeableProxy artifact (v5) */ diff --git a/packages/deployment/lib/bytecode-utils.ts b/packages/deployment/lib/bytecode-utils.ts index 38825df29..f08795b48 100644 --- a/packages/deployment/lib/bytecode-utils.ts +++ b/packages/deployment/lib/bytecode-utils.ts @@ -1,16 +1,31 @@ -import { keccak256 } from 'ethers' +import { keccak256, toUtf8Bytes } from 'ethers' /** * Bytecode utilities for smart contract deployment. * * These utilities handle bytecode hashing for change detection: * - Strip Solidity CBOR metadata (varies between compilations) + * - Resolve library placeholders using actual library bytecode * - Compute stable bytecode hash for comparison * * This allows detecting when local artifact code has changed by comparing * stored bytecodeHash with the current artifact's hash. */ +/** + * Hardhat artifact link references: sourcePath → libraryName → offsets[] + */ +export type LinkReferences = Record>> + +/** + * Resolves a library artifact given its source path and name. + * Returns the artifact's deployedBytecode and its own linkReferences (for recursion). + */ +export type LibraryArtifactResolver = ( + sourcePath: string, + libraryName: string, +) => { deployedBytecode: string; deployedLinkReferences?: LinkReferences } | undefined + /** * Strip Solidity metadata from bytecode. * Metadata is CBOR-encoded at the end, with last 2 bytes indicating length. @@ -33,19 +48,102 @@ export function stripMetadata(bytecode: string): string { } /** - * Compute a stable hash of bytecode for change detection. + * Compute the Solidity library placeholder hash for a given source path and name. + * This is keccak256("sourcePath:libraryName") truncated to 34 hex chars (17 bytes). + */ +function libraryPlaceholderHash(sourcePath: string, libraryName: string): string { + return keccak256(toUtf8Bytes(`${sourcePath}:${libraryName}`)).slice(2, 36) +} + +/** + * Resolve library placeholders in bytecode using actual library bytecode hashes. * - * Strips CBOR metadata suffix before hashing to ensure the hash is stable - * across recompilations that don't change the actual contract logic. + * For each library in deployedLinkReferences, computes its bytecode hash + * (recursively resolving its own library deps) and substitutes that hash + * (truncated to 20 bytes / 40 hex chars) into the placeholder slots. * - * Use this to detect when local artifact bytecode has changed since deployment. + * This means the final hash reflects both the contract's code and all + * transitive library code. If any library changes, the hash changes. + */ +function resolveLibraryPlaceholders( + bytecode: string, + linkReferences: LinkReferences | undefined, + resolver: LibraryArtifactResolver | undefined, +): string { + if (!linkReferences || !resolver) { + // No link references or no resolver — zero out any remaining placeholders + return bytecode.replace(/__\$[0-9a-fA-F]{34}\$__/g, '0'.repeat(40)) + } + + let result = bytecode + for (const [sourcePath, libraries] of Object.entries(linkReferences)) { + for (const libraryName of Object.keys(libraries)) { + const placeholderHash = libraryPlaceholderHash(sourcePath, libraryName) + const placeholder = `__\\$${placeholderHash}\\$__` + + const libArtifact = resolver(sourcePath, libraryName) + let replacement: string + if (libArtifact) { + // Recursively compute the library's bytecode hash (handles nested deps) + const libHash = computeBytecodeHashWithLibraries( + libArtifact.deployedBytecode, + libArtifact.deployedLinkReferences, + resolver, + ) + // Use first 40 hex chars (20 bytes) of the hash as the replacement + replacement = libHash.slice(2, 42) + } else { + // Library artifact not available — zero fill + replacement = '0'.repeat(40) + } + + result = result.replace(new RegExp(placeholder, 'g'), replacement) + } + } + + // Zero any remaining unresolved placeholders (shouldn't happen but defensive) + return result.replace(/__\$[0-9a-fA-F]{34}\$__/g, '0'.repeat(40)) +} + +/** + * Compute a stable hash of bytecode for change detection, with library resolution. * - * @param bytecode - The bytecode to hash (typically artifact.deployedBytecode) - * @returns keccak256 hash of the bytecode with metadata stripped + * Normalizations applied before hashing: + * - Strip CBOR metadata suffix (varies between compilations) + * - Resolve library placeholders with actual library bytecode hashes + * + * @param bytecode - The bytecode to hash + * @param linkReferences - Artifact's deployedLinkReferences (optional) + * @param resolver - Function to load library artifacts (optional) + * @returns keccak256 hash of the normalized bytecode */ -export function computeBytecodeHash(bytecode: string): string { +function computeBytecodeHashWithLibraries( + bytecode: string, + linkReferences: LinkReferences | undefined, + resolver: LibraryArtifactResolver | undefined, +): string { const stripped = stripMetadata(bytecode) - // Ensure 0x prefix for keccak256 - const prefixed = stripped.startsWith('0x') ? stripped : `0x${stripped}` + const resolved = resolveLibraryPlaceholders(stripped, linkReferences, resolver) + const prefixed = resolved.startsWith('0x') ? resolved : `0x${resolved}` return keccak256(prefixed) } + +/** + * Compute a stable hash of bytecode for change detection. + * + * For simple contracts (no library references), pass just the bytecode. + * For contracts with external libraries, pass linkReferences and a resolver + * to include transitive library code in the hash. + * + * @param bytecode - The bytecode to hash (typically artifact.deployedBytecode) + * @param linkReferences - Artifact's deployedLinkReferences (optional) + * @param resolver - Function to load library artifacts for recursive resolution (optional) + * @returns keccak256 hash of the bytecode with metadata stripped + */ +export function computeBytecodeHash( + bytecode: string, + linkReferences?: LinkReferences, + resolver?: LibraryArtifactResolver, +): string { + return computeBytecodeHashWithLibraries(bytecode, linkReferences, resolver) +} diff --git a/packages/deployment/lib/contract-checks.ts b/packages/deployment/lib/contract-checks.ts index 412b5243e..74e446779 100644 --- a/packages/deployment/lib/contract-checks.ts +++ b/packages/deployment/lib/contract-checks.ts @@ -1,5 +1,5 @@ import type { Environment } from '@rocketh/core/types' -import type { PublicClient } from 'viem' +import type { Abi, PublicClient } from 'viem' import { ACCESS_CONTROL_ENUMERABLE_ABI, @@ -7,6 +7,8 @@ import { IERC165_ABI, IERC165_INTERFACE_ID, IISSUANCE_TARGET_INTERFACE_ID, + ISSUANCE_TARGET_ABI, + PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, REWARDS_ELIGIBILITY_ORACLE_ABI, REWARDS_MANAGER_ABI, REWARDS_MANAGER_DEPRECATED_ABI, @@ -100,7 +102,7 @@ export async function checkIssuanceAllocatorActivation( // Check RM.issuanceAllocator() == IA const currentIA = (await client.readContract({ address: rmAddress as `0x${string}`, - abi: REWARDS_MANAGER_ABI, + abi: ISSUANCE_TARGET_ABI, functionName: 'getIssuanceAllocator', })) as string @@ -136,58 +138,6 @@ export async function isIssuanceAllocatorActivated( return status.iaIntegrated && status.iaMinter } -// Well-known reclaim reasons (bytes32) -// These correspond to the condition identifiers in RewardsCondition.sol (keccak256 of condition string) -// Each reason maps to a contract: ReclaimedRewardsFor -export const RECLAIM_REASONS = { - indexerIneligible: '0xfcadc72cad493def76767524554db9da829b6aca9457c0187f63000dba3c9439', - subgraphDenied: '0xc0f4a5620db2f97e7c3a4ba7058497eaa0d497538b2666d66bd6932f25345c88', - stalePoi: '0xe677423ace949fe7684efc4b33b0b10dc0f71b38c22370d74dad5ff6bec3e311', - zeroPoi: '0xf067261e30ea99a11911c4e98249a1645a4870b3ef56b8aa8b8967e15a543095', - closeAllocation: '0x3021a5ea86e7115dadc0819121dc2b1f58b45c2372d2e93b593567f0dd797df8', -} as const - -// Mapping from reclaim reason keys to deployed contract names -export const RECLAIM_CONTRACT_NAMES = { - indexerIneligible: 'ReclaimedRewardsForIndexerIneligible', - subgraphDenied: 'ReclaimedRewardsForSubgraphDenied', - stalePoi: 'ReclaimedRewardsForStalePoi', - zeroPoi: 'ReclaimedRewardsForZeroPoi', - closeAllocation: 'ReclaimedRewardsForCloseAllocation', -} as const - -export type ReclaimReasonKey = keyof typeof RECLAIM_REASONS - -/** - * Get the reclaim address for a given reason from RewardsManager - * - * @param client - Viem public client - * @param rmAddress - RewardsManager address - * @param reason - The reason identifier (bytes32) - * @returns The reclaim address for that reason, or null if not set or function doesn't exist - */ -export async function getReclaimAddress( - client: PublicClient, - rmAddress: string, - reason: string, -): Promise { - try { - const reclaimAddress = (await client.readContract({ - address: rmAddress as `0x${string}`, - abi: REWARDS_MANAGER_ABI, - functionName: 'getReclaimAddress', - args: [reason as `0x${string}`], - })) as string - // Zero address means not set - if (reclaimAddress === '0x0000000000000000000000000000000000000000') { - return null - } - return reclaimAddress - } catch { - return null - } -} - /** * Get issuancePerBlock from RewardsManager */ @@ -201,11 +151,11 @@ export async function getRewardsManagerRawIssuanceRate(client: PublicClient, rmA } // ============================================================================ -// RewardsEligibilityOracle Role Checks +// REO Role Checks // ============================================================================ /** - * Result of checking OPERATOR_ROLE assignment on RewardsEligibilityOracle + * Result of checking OPERATOR_ROLE assignment on an REO instance */ export interface OperatorRoleCheckResult { /** Whether the check passed (correct assignment state) */ @@ -221,7 +171,7 @@ export interface OperatorRoleCheckResult { } /** - * Check OPERATOR_ROLE assignment on RewardsEligibilityOracle + * Check OPERATOR_ROLE assignment on an REO instance * * This is the SINGLE authoritative check for OPERATOR_ROLE correctness. * Used by both deployment scripts and status checks. @@ -231,7 +181,7 @@ export interface OperatorRoleCheckResult { * - If expectedOperator is null: exactly 0 holders * * @param client - Viem public client - * @param reoAddress - RewardsEligibilityOracle address + * @param reoAddress - REO instance address * @param expectedOperator - Expected operator address (from address book), or null if not configured * @returns Check result with pass/fail status and details */ @@ -359,7 +309,7 @@ export interface ParamCondition { description: string /** ABI for contract reads/writes */ - abi: readonly unknown[] + abi: Abi /** Function name to read current value */ getter: string @@ -391,7 +341,7 @@ export interface RoleCondition { description: string /** ABI for contract reads/writes */ - abi: readonly unknown[] + abi: Abi /** Function name to get role bytes32 (e.g., 'PAUSE_ROLE') */ roleGetter: string @@ -519,7 +469,7 @@ export async function checkConditions( } // ============================================================================ -// RewardsEligibilityOracle Conditions +// REO Conditions // ============================================================================ /** Default REO configuration values */ @@ -558,11 +508,6 @@ export function createREOParamConditions( ] } -/** - * @deprecated Use createREOParamConditions for param-only or createREOConditions for all - */ -export const createREOConditions = createREOParamConditions - /** * REO role condition targets */ @@ -620,7 +565,10 @@ export function createREORoleConditions(targets: REORoleTargets): RoleCondition[ export function createAllREOConditions( paramTargets: { eligibilityPeriod?: bigint; oracleUpdateTimeout?: bigint } = {}, roleTargets: REORoleTargets, -): ConfigCondition[] { + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): ConfigCondition[] { + // Note: setEligibilityValidation requires OPERATOR_ROLE, not GOVERNOR_ROLE. + // It is enabled by the network operator after deployment, not in the configure step. return [...createREOParamConditions(paramTargets), ...createREORoleConditions(roleTargets)] } @@ -653,7 +601,8 @@ export function createREODeployerRevokeCondition(deployer: string): RoleConditio * * Requires NetworkOperator to be configured in the issuance address book. */ -export async function getREOConditions(env: Environment): Promise[]> { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function getREOConditions(env: Environment): Promise[]> { const governor = await getGovernor(env) const pauseGuardian = await getPauseGuardian(env) const ab = graph.getIssuanceAddressBook(await getTargetChainIdFromEnv(env)) @@ -678,7 +627,7 @@ export function getREOTransferGovernanceConditions(deployer: string): ConfigCond } // ============================================================================ -// RewardsEligibilityOracle Role Checks +// REO Role Checks // ============================================================================ /** @@ -696,7 +645,7 @@ export interface RoleCheckResult { } /** - * Check if an account has a specific role on RewardsEligibilityOracle + * Check if an account has a specific role on an REO instance */ export async function checkREORole( client: PublicClient, @@ -751,8 +700,8 @@ export function formatAddress(address: string): string { export function createRMIntegrationCondition(reoAddress: string): ParamCondition { return { name: 'providerEligibilityOracle', - description: 'RewardsEligibilityOracle', - abi: REWARDS_MANAGER_ABI, + description: 'REO instance', + abi: PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, getter: 'getProviderEligibilityOracle', setter: 'setProviderEligibilityOracle', target: reoAddress, diff --git a/packages/deployment/lib/contract-registry.ts b/packages/deployment/lib/contract-registry.ts index cb2271885..06b2f640a 100644 --- a/packages/deployment/lib/contract-registry.ts +++ b/packages/deployment/lib/contract-registry.ts @@ -8,12 +8,15 @@ * the same contract name appears in multiple address books. */ +import { ComponentTags } from './deployment-tags.js' + /** * Artifact source configuration - where to load contract ABI and bytecode from */ export type ArtifactSource = | { type: 'contracts'; path: string; name: string } | { type: 'subgraph-service'; name: string } + | { type: 'horizon'; path: string } | { type: 'issuance'; path: string } | { type: 'openzeppelin'; name: string } @@ -30,6 +33,17 @@ export type ProxyType = 'graph' | 'transparent' */ export type AddressBookType = 'horizon' | 'subgraph-service' | 'issuance' +/** + * Interface ABI configuration for typed ABI generation. + * Maps an export name to an interface in @graphprotocol/interfaces. + */ +export interface InterfaceAbiConfig { + /** Export name for the generated ABI constant (e.g. 'REWARDS_MANAGER_ABI') */ + name: string + /** Interface name in @graphprotocol/interfaces artifacts (e.g. 'IRewardsManager') */ + interface: string +} + /** * Contract metadata specification * Note: addressBook is no longer a field - it's implied by the registry namespace @@ -69,6 +83,53 @@ export interface ContractMetadata { * Used by roles:list task to enumerate role holders. */ roles?: readonly string[] + + /** + * Component tag for deployment lifecycle management. + * Used by script factories to derive action tags (deploy, upgrade, etc.) + * and dependencies without per-script boilerplate. + * + * Must match the PascalCase contract name in deployment-tags.ts ComponentTags. + * Example: 'PaymentsEscrow' → tags: 'PaymentsEscrow:upgrade', deps: 'PaymentsEscrow:deploy' + * + * Multiple contracts may share a componentTag when they form a single + * deployment unit (e.g., REO A/B instances share 'RewardsEligibility'). + */ + componentTag?: string + + /** + * Lifecycle actions available for this component beyond the standard deploy+upgrade. + * Used by status modules to show available `--tags` actions. + * + * When omitted, defaults to ['deploy', 'upgrade'] for deployable proxy contracts, + * or ['deploy'] for non-proxy deployable contracts. + * Always includes 'all' implicitly. + */ + lifecycleActions?: readonly string[] + + /** + * Interface ABIs to generate for this contract. + * Used by the ABI codegen script to produce typed `as const` exports. + * Each entry maps to an interface artifact in @graphprotocol/interfaces. + * The codegen also extracts the interfaceId from the factory class. + */ + interfaces?: readonly InterfaceAbiConfig[] + + /** + * Generate a typed ABI from the contract's full artifact. + * Value is the export name (e.g. 'ISSUANCE_ALLOCATOR_ABI'). + * Requires `artifact` to be set on this entry. + */ + generateAbi?: string + + /** + * Name of the shared implementation entry when this proxy uses an + * implementation deployed separately (e.g. DirectAllocation_Implementation). + * + * Used by the upgrade pipeline to auto-detect when the shared implementation + * has been redeployed and set pendingImplementation accordingly. + */ + sharedImplementation?: string } // ============================================================================ @@ -78,34 +139,71 @@ export interface ContractMetadata { const HORIZON_CONTRACTS = { RewardsManager: { artifact: { type: 'contracts', path: 'rewards', name: 'RewardsManager' }, + interfaces: [ + { name: 'REWARDS_MANAGER_ABI', interface: 'IRewardsManager' }, + { name: 'REWARDS_MANAGER_DEPRECATED_ABI', interface: 'IRewardsManagerDeprecated' }, + { name: 'PROVIDER_ELIGIBILITY_MANAGEMENT_ABI', interface: 'IProviderEligibilityManagement' }, + ], proxyType: 'graph', proxyAdminName: 'GraphProxyAdmin', prerequisite: true, deployable: true, + componentTag: ComponentTags.REWARDS_MANAGER, + lifecycleActions: ['deploy', 'upgrade'], }, GraphProxyAdmin: { + interfaces: [{ name: 'GRAPH_PROXY_ADMIN_ABI', interface: 'IGraphProxyAdmin' }], prerequisite: true, }, L2GraphToken: { artifact: { type: 'contracts', path: 'l2/token', name: 'L2GraphToken' }, + interfaces: [{ name: 'GRAPH_TOKEN_ABI', interface: 'IGraphToken' }], prerequisite: true, }, Controller: { + interfaces: [{ name: 'CONTROLLER_ABI', interface: 'IControllerToolshed' }], prerequisite: true, }, GraphTallyCollector: { prerequisite: true, }, + RecurringCollector: { + artifact: { type: 'horizon', path: 'contracts/payments/collectors/RecurringCollector.sol/RecurringCollector' }, + proxyType: 'transparent', + deployable: true, + componentTag: ComponentTags.RECURRING_COLLECTOR, + lifecycleActions: ['deploy', 'upgrade', 'configure', 'transfer'], + }, L2Curation: { + artifact: { type: 'contracts', path: 'l2/curation', name: 'L2Curation' }, + proxyType: 'graph', + proxyAdminName: 'GraphProxyAdmin', prerequisite: true, + deployable: true, + componentTag: ComponentTags.L2_CURATION, + }, + HorizonStaking: { + artifact: { type: 'horizon', path: 'contracts/staking/HorizonStaking.sol/HorizonStaking' }, + proxyType: 'graph', + proxyAdminName: 'GraphProxyAdmin', + prerequisite: true, + deployable: true, + componentTag: ComponentTags.HORIZON_STAKING, + }, + GraphPayments: { + prerequisite: true, + }, + PaymentsEscrow: { + artifact: { type: 'horizon', path: 'contracts/payments/PaymentsEscrow.sol/PaymentsEscrow' }, + proxyType: 'transparent', + prerequisite: true, + deployable: true, + componentTag: ComponentTags.PAYMENTS_ESCROW, }, // Contracts deployed by other systems (placeholders for address book type completeness) EpochManager: {}, - GraphPayments: {}, - HorizonStaking: {}, L2GNS: {}, L2GraphTokenGateway: {}, - PaymentsEscrow: {}, SubgraphNFT: {}, } as const satisfies Record @@ -122,6 +220,8 @@ const SUBGRAPH_SERVICE_CONTRACTS = { proxyType: 'transparent', // proxyAdminName omitted - auto-generates as DisputeManager_ProxyAdmin prerequisite: true, + deployable: true, + componentTag: ComponentTags.DISPUTE_MANAGER, }, SubgraphService: { artifact: { type: 'subgraph-service', name: 'SubgraphService' }, @@ -129,6 +229,8 @@ const SUBGRAPH_SERVICE_CONTRACTS = { // proxyAdminName omitted - auto-generates as SubgraphService_ProxyAdmin prerequisite: true, deployable: true, + componentTag: ComponentTags.SUBGRAPH_SERVICE, + lifecycleActions: ['deploy', 'upgrade', 'configure'], }, // Contracts deployed by other systems (placeholders for address book type completeness) // These exist in the subgraph-service address book but are managed elsewhere @@ -144,7 +246,9 @@ const SUBGRAPH_SERVICE_CONTRACTS = { // ============================================================================ // NOTE: Issuance contracts use OZ v5 TransparentUpgradeableProxy which creates -// a per-proxy ProxyAdmin in the constructor. The ProxyAdmin address is stored +// a per-proxy ProxyAdmin in the constructor. The deployer is the initial ProxyAdmin +// owner to allow post-deployment configuration; ownership is transferred to the +// protocol governor in the transfer-governance step. The ProxyAdmin address is stored // inline in each contract's address book entry (proxyAdmin field), similar to // subgraph-service contracts. @@ -158,54 +262,84 @@ const ISSUANCE_CONTRACTS = { IssuanceAllocator: { artifact: { type: 'issuance', path: 'contracts/allocate/IssuanceAllocator.sol/IssuanceAllocator' }, + generateAbi: 'ISSUANCE_ALLOCATOR_ABI', proxyType: 'transparent', // Per-proxy ProxyAdmin - address stored in address book entry's proxyAdmin field deployable: true, roles: BASE_ROLES, + componentTag: ComponentTags.ISSUANCE_ALLOCATOR, + lifecycleActions: ['deploy', 'upgrade', 'configure', 'transfer'], }, - PilotAllocation: { - artifact: { type: 'issuance', path: 'contracts/allocate/PilotAllocation.sol/PilotAllocation' }, + RecurringAgreementManager: { + artifact: { + type: 'issuance', + path: 'contracts/agreement/RecurringAgreementManager.sol/RecurringAgreementManager', + }, proxyType: 'transparent', deployable: true, - roles: BASE_ROLES, + roles: [...BASE_ROLES, 'DATA_SERVICE_ROLE', 'COLLECTOR_ROLE', 'AGREEMENT_MANAGER_ROLE'] as const, + componentTag: ComponentTags.RECURRING_AGREEMENT_MANAGER, + lifecycleActions: ['deploy', 'upgrade', 'configure', 'transfer'], }, - RewardsEligibilityOracle: { + // A/B instances of RewardsEligibilityOracle - both share the same contract artifact + // but deploy as independent proxies. Only one is active (integrated with RewardsManager) at a time. + RewardsEligibilityOracleA: { artifact: { type: 'issuance', path: 'contracts/eligibility/RewardsEligibilityOracle.sol/RewardsEligibilityOracle' }, + generateAbi: 'REWARDS_ELIGIBILITY_ORACLE_ABI', proxyType: 'transparent', deployable: true, roles: [...BASE_ROLES, 'ORACLE_ROLE'] as const, + componentTag: ComponentTags.REWARDS_ELIGIBILITY_A, + // Integration with RewardsManager is a goal-level activation + // (--tags GIP-0088:eligibility-integrate), not a per-component lifecycle action. + lifecycleActions: ['deploy', 'upgrade', 'configure', 'transfer'], }, - DirectAllocation_Implementation: { - artifact: { type: 'issuance', path: 'contracts/allocate/DirectAllocation.sol/DirectAllocation' }, - deployable: true, - roles: BASE_ROLES, - }, - // Reclaim addresses for different reward reclaim reasons - // All share DirectAllocation implementation (per-proxy ProxyAdmin for each) - ReclaimedRewardsForIndexerIneligible: { + RewardsEligibilityOracleB: { + artifact: { type: 'issuance', path: 'contracts/eligibility/RewardsEligibilityOracle.sol/RewardsEligibilityOracle' }, proxyType: 'transparent', deployable: true, - roles: BASE_ROLES, + roles: [...BASE_ROLES, 'ORACLE_ROLE'] as const, + componentTag: ComponentTags.REWARDS_ELIGIBILITY_B, + lifecycleActions: ['deploy', 'upgrade', 'configure', 'transfer'], }, - ReclaimedRewardsForSubgraphDenied: { + // Testnet mock REO - indexers control own eligibility, upgradeable for deployment consistency + RewardsEligibilityOracleMock: { + artifact: { + type: 'issuance', + path: 'contracts/eligibility/mocks/MockRewardsEligibilityOracle.sol/MockRewardsEligibilityOracle', + }, proxyType: 'transparent', deployable: true, roles: BASE_ROLES, + componentTag: ComponentTags.REWARDS_ELIGIBILITY_MOCK, + lifecycleActions: ['deploy', 'upgrade', 'transfer', 'integrate'], }, - ReclaimedRewardsForStalePoi: { - proxyType: 'transparent', + DirectAllocation_Implementation: { + artifact: { type: 'issuance', path: 'contracts/allocate/DirectAllocation.sol/DirectAllocation' }, + generateAbi: 'DIRECT_ALLOCATION_ABI', deployable: true, roles: BASE_ROLES, + componentTag: ComponentTags.DIRECT_ALLOCATION_IMPL, }, - ReclaimedRewardsForZeroPoi: { + // Default target for IA — safety net for unallocated issuance + // Uses DirectAllocation implementation (per-proxy ProxyAdmin) + DefaultAllocation: { proxyType: 'transparent', + sharedImplementation: 'DirectAllocation_Implementation', deployable: true, roles: BASE_ROLES, + componentTag: ComponentTags.DEFAULT_ALLOCATION, + lifecycleActions: ['deploy', 'upgrade', 'configure', 'transfer'], }, - ReclaimedRewardsForCloseAllocation: { + // Default reclaim address — receives reclaimed rewards for all reasons + // Uses DirectAllocation implementation (per-proxy ProxyAdmin) + ReclaimedRewards: { proxyType: 'transparent', + sharedImplementation: 'DirectAllocation_Implementation', deployable: true, roles: BASE_ROLES, + componentTag: ComponentTags.REWARDS_RECLAIM, + lifecycleActions: ['deploy', 'upgrade', 'configure', 'transfer'], }, } as const satisfies Record diff --git a/packages/deployment/lib/controller-utils.ts b/packages/deployment/lib/controller-utils.ts index 7180a8872..4dce12c4a 100644 --- a/packages/deployment/lib/controller-utils.ts +++ b/packages/deployment/lib/controller-utils.ts @@ -6,6 +6,35 @@ import { Contracts } from './contract-registry.js' import { requireContract } from './issuance-deploy-utils.js' import { graph } from '../rocketh/deploy.js' +/** + * Check if the provider can sign as the protocol governor + * + * With a mnemonic (local network), all derived accounts are available via eth_accounts. + * With explicit keys (production), only configured accounts are available. + * + * @param env - Deployment environment + * @returns Governor address and whether the provider can sign as governor + */ +export async function canSignAsGovernor(env: Environment): Promise<{ governor: string; canSign: boolean }> { + const governor = await getGovernor(env) + const accounts = (await env.network.provider.request({ method: 'eth_accounts' })) as string[] + const canSign = accounts.some((a) => a.toLowerCase() === governor.toLowerCase()) + + // Verify the rocketh named account 'governor' matches the on-chain governor. + // If they disagree, tx({ account: 'governor' }) would send from the wrong address. + if (canSign && env.namedAccounts['governor']) { + const named = env.namedAccounts['governor'] as string + if (named.toLowerCase() !== governor.toLowerCase()) { + throw new Error( + `Named account 'governor' (${named}) does not match Controller.getGovernor() (${governor}). ` + + `Check rocketh account config — mnemonic index may not match the on-chain governor.`, + ) + } + } + + return { governor, canSign } +} + /** * Get the protocol governor address from the Controller contract * diff --git a/packages/deployment/lib/deploy-implementation.ts b/packages/deployment/lib/deploy-implementation.ts index f08c4398a..dbaacc92b 100644 --- a/packages/deployment/lib/deploy-implementation.ts +++ b/packages/deployment/lib/deploy-implementation.ts @@ -1,16 +1,19 @@ import type { Artifact, Environment } from '@rocketh/core/types' -import { getAddress } from 'viem' +import { encodeAbiParameters, getAddress } from 'viem' -import { getTargetChainIdFromEnv } from './address-book-utils.js' -import type { AnyAddressBookOps } from './address-book-ops.js' +import { getAddressBookForType, getTargetChainIdFromEnv } from './address-book-utils.js' import { + getLibraryResolver, + linkArtifactLibraries, loadContractsArtifact, + loadHorizonBuildArtifact, loadIssuanceArtifact, loadOpenZeppelinArtifact, loadSubgraphServiceArtifact, } from './artifact-loaders.js' import { computeBytecodeHash } from './bytecode-utils.js' import { getContractMetadata, type AddressBookType, type ArtifactSource, type ProxyType } from './contract-registry.js' +import { buildDeploymentMetadata } from './deployment-metadata.js' import { deploy, graph } from '../rocketh/deploy.js' // Re-export artifact loaders for backwards compatibility @@ -100,11 +103,11 @@ export interface ImplementationDeployConfig { /** * Name of the proxy admin deployment record. - * e.g., 'GraphProxyAdmin', 'GraphIssuanceProxyAdmin' + * e.g., 'GraphProxyAdmin' for legacy GraphProxy contracts. * * Optional: If omitted, defaults to `${contractName}_ProxyAdmin`. - * This allows contracts with inline proxy admin addresses (stored in address book entry) - * to work without explicitly specifying the deployment record name. + * Per-proxy admins (OZ v5 TransparentUpgradeableProxy contracts) follow this + * default and store the admin address inline in their address book entry. */ proxyAdminName?: string @@ -136,7 +139,7 @@ export interface ImplementationDeployResult { } /** - * Load artifact based on source configuration + * Load artifact based on source configuration. Throws if the artifact can't be loaded. */ export function loadArtifactFromSource(source: ArtifactSource): Artifact { switch (source.type) { @@ -144,6 +147,8 @@ export function loadArtifactFromSource(source: ArtifactSource): Artifact { return loadContractsArtifact(source.path, source.name) case 'subgraph-service': return loadSubgraphServiceArtifact(source.name) + case 'horizon': + return loadHorizonBuildArtifact(source.path) case 'issuance': return loadIssuanceArtifact(source.path) case 'openzeppelin': @@ -151,6 +156,55 @@ export function loadArtifactFromSource(source: ArtifactSource): Artifact { } } +/** + * Like {@link loadArtifactFromSource}, but returns `undefined` instead of throwing. + * Intended for sync-style flows where a missing artifact shouldn't abort the pass. + */ +export function tryLoadArtifactFromSource(source: ArtifactSource | undefined): Artifact | undefined { + if (!source) return undefined + try { + return loadArtifactFromSource(source) + } catch { + return undefined + } +} + +/** + * Compute the bytecode hash for an artifact source, with library resolution. + * + * This is the canonical bytecodeHash recorded in address-book deployment metadata — + * deploy-time, sync backfill, and `checkShouldSync` all agree on this fingerprint. + * + * Use this instead of calling `computeBytecodeHash` directly on rocketh's linked + * `deployedBytecode`: linked bytecode has real library addresses substituted in, + * so its hash diverges from the placeholder-fingerprint hash this helper produces + * for any library-using contract. + * + * Throws if the artifact cannot be loaded. + */ +export function computeArtifactBytecodeHash(source: ArtifactSource): string { + const artifact = loadArtifactFromSource(source) + return computeBytecodeHash( + artifact.deployedBytecode ?? '0x', + artifact.deployedLinkReferences, + getLibraryResolver(source.type), + ) +} + +/** + * Like {@link computeArtifactBytecodeHash}, but returns `undefined` instead of + * throwing if the artifact can't be loaded. Intended for sync-style flows where + * a missing artifact shouldn't abort the whole pass. + */ +export function tryComputeArtifactBytecodeHash(source: ArtifactSource | undefined): string | undefined { + if (!source) return undefined + try { + return computeArtifactBytecodeHash(source) + } catch { + return undefined + } +} + /** * Build ImplementationDeployConfig from registry metadata * @@ -236,6 +290,7 @@ export function hasImplementationConfig(addressBook: AddressBookType, contractNa export async function deployImplementation( env: Environment, config: ImplementationDeployConfig, + libraries?: Record, ): Promise { const { contractName, proxyAdminName, constructorArgs = [], proxyType = 'graph', addressBook = 'horizon' } = config @@ -270,30 +325,62 @@ export async function deployImplementation( throw new Error(`${proxyAdminDeploymentName} not imported. Run sync step first.`) } - // 2) Load artifact - const artifact = loadArtifactFromSource(artifactSource) + // 2) Load artifact (pre-link libraries so rocketh stores linked bytecode) + const rawArtifact = loadArtifactFromSource(artifactSource) + const artifact = libraries + ? linkArtifactLibraries(rawArtifact, libraries as Record) + : rawArtifact const implDeploymentName = `${contractName}_Implementation` // Get address book to check pending implementation const targetChainId = await getTargetChainIdFromEnv(env) - const addressBookInstance: AnyAddressBookOps = - addressBook === 'subgraph-service' - ? graph.getSubgraphServiceAddressBook(targetChainId) - : addressBook === 'issuance' - ? graph.getIssuanceAddressBook(targetChainId) - : graph.getHorizonAddressBook(targetChainId) + const addressBookInstance = getAddressBookForType(addressBook, targetChainId) // Compute local artifact bytecode hash (for storing with deployment) - const localBytecodeHash = computeBytecodeHash(artifact.deployedBytecode ?? '0x') + const localBytecodeHash = computeArtifactBytecodeHash(artifactSource) + + // 3) Pre-check: skip deployment if bytecodeHash and constructor args match + // Rocketh's comparison can false-positive when sync creates bare records (e.g., wrong + // argsData, unlinked library bytecodes). The content-aware bytecodeHash handles both + // cases — it strips CBOR metadata and resolves library references by content hash. + const contractEntry = addressBookInstance.entryExists(contractName) + ? addressBookInstance.getEntry(contractName) + : null + const pendingImpl = contractEntry?.pendingImplementation + const storedMetadata = pendingImpl?.deployment ?? addressBookInstance.getDeploymentMetadata(contractName) + + if (storedMetadata?.bytecodeHash && storedMetadata.bytecodeHash === localBytecodeHash) { + // Bytecode matches — also verify constructor args (immutable values) + let argsMatch = !storedMetadata.argsData // no stored args = can't compare, assume match + if (storedMetadata.argsData) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const constructorDef = (artifact.abi as any[])?.find((item: any) => item.type === 'constructor') + const localArgsData = + constructorDef?.inputs?.length && constructorArgs.length + ? encodeAbiParameters(constructorDef.inputs, constructorArgs as readonly unknown[]) + : '0x' + argsMatch = localArgsData === storedMetadata.argsData + } - // 3) Deploy implementation - let rocketh decide based on its own records + if (argsMatch) { + const existingAddress = pendingImpl?.address ?? contractEntry?.implementation + if (existingAddress) { + env.showMessage(`\n✓ ${contractName} implementation unchanged`) + return { + deployed: false, + address: existingAddress, + bytecodeChanged: false, + } + } + } + } + + // 4) Deploy implementation - let rocketh decide based on its own records // Sync handles pending: if pending hash matches local, rocketh has bytecode to compare // If pending hash differs, sync skipped bytecode so rocketh will deploy fresh - const impl = await deployFn(implDeploymentName, { - account: deployer, - artifact, - args: constructorArgs, - }) + // Libraries are pre-linked into the artifact (step 2) so rocketh stores linked + // bytecode — its CBOR-stripping comparison then matches on subsequent runs. + const impl = await deployFn(implDeploymentName, { account: deployer, artifact, args: constructorArgs }) if (!impl.newlyDeployed) { env.showMessage(`\n✓ ${contractName} implementation unchanged`) @@ -333,13 +420,10 @@ export async function deployImplementation( } // Store with full deployment metadata for verification and reconstruction - addressBookInstance.setPendingImplementationWithMetadata(contractName, impl.address, { - txHash: impl.transaction?.hash ?? '', - argsData: impl.argsData ?? '0x', - bytecodeHash: localBytecodeHash, - ...(blockNumber !== undefined && { blockNumber }), - ...(timestamp && { timestamp }), - }) + const metadata = buildDeploymentMetadata(impl, localBytecodeHash, { blockNumber, timestamp }) + if (metadata) { + addressBookInstance.setPendingImplementationWithMetadata(contractName, impl.address, metadata) + } env.showMessage(`✓ Pending implementation stored with deployment metadata.`) env.showMessage(` Run upgrade task to generate TX and execute.`) diff --git a/packages/deployment/lib/deploy-standalone.ts b/packages/deployment/lib/deploy-standalone.ts new file mode 100644 index 000000000..4593cbaa0 --- /dev/null +++ b/packages/deployment/lib/deploy-standalone.ts @@ -0,0 +1,71 @@ +import type { Environment } from '@rocketh/core/types' + +import type { RegistryEntry } from './contract-registry.js' +import { loadArtifactFromSource } from './deploy-implementation.js' +import { requireDeployer } from './issuance-deploy-utils.js' +import { deploy, graph } from '../rocketh/deploy.js' + +/** + * Configuration for deploying a standalone (non-proxy) contract + */ +export interface StandaloneDeployConfig { + /** Contract registry entry (provides addressBook and artifact config) */ + contract: RegistryEntry + /** Constructor arguments */ + constructorArgs?: unknown[] +} + +/** + * Deploy a standalone (non-proxy) contract and update the address book + * + * This utility handles the common pattern for deploying contracts that + * are not behind a proxy (e.g., helper contracts). + * + * - Loads artifact from registry metadata + * - Deploys via rocketh (idempotent - skips if bytecode unchanged) + * - Updates the appropriate address book (horizon or issuance) + * + * @example + * ```typescript + * await deployStandaloneContract(env, { + * contract: Contracts.horizon.GraphTallyCollector, + * constructorArgs: [controllerAddress], + * }) + * ``` + */ +export async function deployStandaloneContract( + env: Environment, + config: StandaloneDeployConfig, +): Promise<{ address: string; newlyDeployed: boolean }> { + const { contract, constructorArgs = [] } = config + + if (!contract.artifact) { + throw new Error(`No artifact configured for ${contract.name} in registry`) + } + + const deployer = requireDeployer(env) + const artifact = loadArtifactFromSource(contract.artifact) + const deployFn = deploy(env) + + const result = await deployFn(contract.name, { + account: deployer, + artifact, + args: constructorArgs, + }) + + if (result.newlyDeployed) { + env.showMessage(`\n✓ ${contract.name} deployed at ${result.address}`) + } else { + env.showMessage(`\n✓ ${contract.name} unchanged at ${result.address}`) + } + + await graph.updateAddressBookForContract(env, contract, { + name: contract.name, + address: result.address, + }) + + return { + address: result.address, + newlyDeployed: !!result.newlyDeployed, + } +} diff --git a/packages/deployment/lib/deployment-config.ts b/packages/deployment/lib/deployment-config.ts new file mode 100644 index 000000000..b26cfb29e --- /dev/null +++ b/packages/deployment/lib/deployment-config.ts @@ -0,0 +1,131 @@ +import { readFileSync } from 'node:fs' +import { resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import type { Environment } from '@rocketh/core/types' +import JSON5 from 'json5' + +import { getTargetChainIdFromEnv } from './address-book-utils.js' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +/** Chain ID to config file name mapping */ +const CHAIN_CONFIG_MAP: Record = { + 1337: 'localNetwork', + 42161: 'arbitrumOne', + 421614: 'arbitrumSepolia', +} + +/** + * Raw on-disk shape of `config/.json5`. Every field is optional — + * networks override only what they need; the rest comes from `DEFAULT_SETTINGS`. + */ +interface DeploymentConfigFile { + IssuanceAllocator?: { + ramAllocatorMintingGrtPerBlock?: string + ramSelfMintingGrtPerBlock?: string + } + RewardsManager?: { + revertOnIneligible?: boolean + } + RecurringCollector?: { + revokeSignerThawingPeriod?: string + eip712Name?: string + eip712Version?: string + } +} + +/** + * Fully-resolved deployment settings for a given chain. + * + * Every field is concrete — defaults from `DEFAULT_SETTINGS` are applied for + * any field a network's config file omits. Consumers (deploy scripts and + * status checks) read this directly without per-call `??` fallbacks, so the + * "expected value" lives in exactly one place per field. + */ +export interface ResolvedSettings { + rewardsManager: { + /** Revert on reward claim attempts by ineligible indexers. */ + revertOnIneligible: boolean + } + issuanceAllocator: { + /** GRT/block minted by IA and routed to RAM. `'0'` means unconfigured (skip allocation). */ + ramAllocatorMintingGrtPerBlock: string + /** GRT/block self-minted by RAM. `'0'` means RAM does not self-mint. */ + ramSelfMintingGrtPerBlock: string + } + recurringCollector: { + /** Signer revocation thaw period in seconds (constructor arg). */ + revokeSignerThawingPeriod: string + /** EIP-712 domain name (init arg). */ + eip712Name: string + /** EIP-712 domain version (init arg). */ + eip712Version: string + } +} + +const DEFAULT_SETTINGS: ResolvedSettings = { + rewardsManager: { + revertOnIneligible: true, + }, + issuanceAllocator: { + ramAllocatorMintingGrtPerBlock: '0', + ramSelfMintingGrtPerBlock: '0', + }, + recurringCollector: { + revokeSignerThawingPeriod: '28800', // ~1 day at 3s blocks + eip712Name: 'RecurringCollector', + eip712Version: '1', + }, +} + +function loadConfigFile(chainId: number): DeploymentConfigFile { + const networkName = CHAIN_CONFIG_MAP[chainId] + if (!networkName) return {} + + const configPath = resolve(__dirname, '..', 'config', `${networkName}.json5`) + try { + const raw = readFileSync(configPath, 'utf-8') + return JSON5.parse(raw) + } catch { + return {} + } +} + +/** + * Get fully-resolved deployment settings for a chain. + * + * Reads `config/.json5` (if present) and applies `DEFAULT_SETTINGS` + * for any field the network omits. Pure / sync — safe to call from non-deploy + * contexts (e.g. the status task). Returns full defaults for unknown chains. + */ +export function getResolvedSettings(chainId: number): ResolvedSettings { + const file = loadConfigFile(chainId) + return { + rewardsManager: { + revertOnIneligible: file.RewardsManager?.revertOnIneligible ?? DEFAULT_SETTINGS.rewardsManager.revertOnIneligible, + }, + issuanceAllocator: { + ramAllocatorMintingGrtPerBlock: + file.IssuanceAllocator?.ramAllocatorMintingGrtPerBlock ?? + DEFAULT_SETTINGS.issuanceAllocator.ramAllocatorMintingGrtPerBlock, + ramSelfMintingGrtPerBlock: + file.IssuanceAllocator?.ramSelfMintingGrtPerBlock ?? + DEFAULT_SETTINGS.issuanceAllocator.ramSelfMintingGrtPerBlock, + }, + recurringCollector: { + revokeSignerThawingPeriod: + file.RecurringCollector?.revokeSignerThawingPeriod ?? + DEFAULT_SETTINGS.recurringCollector.revokeSignerThawingPeriod, + eip712Name: file.RecurringCollector?.eip712Name ?? DEFAULT_SETTINGS.recurringCollector.eip712Name, + eip712Version: file.RecurringCollector?.eip712Version ?? DEFAULT_SETTINGS.recurringCollector.eip712Version, + }, + } +} + +/** + * Convenience wrapper for deploy scripts that have an `env` but not a chainId. + */ +export async function getResolvedSettingsForEnv(env: Environment): Promise { + const chainId = await getTargetChainIdFromEnv(env) + return getResolvedSettings(chainId) +} diff --git a/packages/deployment/lib/deployment-metadata.ts b/packages/deployment/lib/deployment-metadata.ts new file mode 100644 index 000000000..936b445c3 --- /dev/null +++ b/packages/deployment/lib/deployment-metadata.ts @@ -0,0 +1,59 @@ +import type { DeploymentMetadata } from '@graphprotocol/toolshed/deployments' + +/** + * Subset of rocketh's `Deployment` / `DeployContractResult` shape needed + * to materialize a `DeploymentMetadata` entry. `receipt.blockNumber` may be + * a hex string (`DeployResult`), a bigint (viem receipt) or a number. + */ +type DeploymentResult = { + transaction?: { hash?: string } + argsData?: string + receipt?: { blockNumber?: `0x${string}` | bigint | number } +} + +/** + * Optional overrides for fields rocketh's result may not carry directly. + * `blockNumber` overrides any value extracted from `result.receipt.blockNumber`. + */ +type MetadataOverrides = { + blockNumber?: number + timestamp?: string +} + +/** + * Coerce rocketh's `receipt.blockNumber` (hex string, bigint, or number) to a plain + * number. Returns `undefined` for missing values. Use this everywhere instead of + * inline `parseInt`/`Number` so the conversion stays consistent. + */ +export function toBlockNumber(raw: `0x${string}` | bigint | number | undefined): number | undefined { + if (raw === undefined) return undefined + if (typeof raw === 'string') return Number(BigInt(raw)) + return Number(raw) +} + +/** + * Build a `DeploymentMetadata` entry from a rocketh deployment result. + * + * Returns `undefined` when the essential fields (txHash, argsData) are missing — + * callers should skip recording rather than write a half-populated entry with + * an empty sentinel txHash. + * + * @param result - Rocketh deployment / pending-impl result + * @param bytecodeHash - Pre-computed bytecode hash (hashing inputs vary by caller) + * @param overrides - Optional blockNumber / timestamp (e.g. fetched from a separate receipt query) + */ +export function buildDeploymentMetadata( + result: DeploymentResult, + bytecodeHash: string, + overrides?: MetadataOverrides, +): DeploymentMetadata | undefined { + if (!result.transaction?.hash || !result.argsData) return undefined + const blockNumber = overrides?.blockNumber ?? toBlockNumber(result.receipt?.blockNumber) + return { + txHash: result.transaction.hash, + argsData: result.argsData, + bytecodeHash, + ...(blockNumber !== undefined && { blockNumber }), + ...(overrides?.timestamp && { timestamp: overrides.timestamp }), + } +} diff --git a/packages/deployment/lib/deployment-tags.ts b/packages/deployment/lib/deployment-tags.ts index 26bf286b6..9db4bbdad 100644 --- a/packages/deployment/lib/deployment-tags.ts +++ b/packages/deployment/lib/deployment-tags.ts @@ -1,15 +1,13 @@ /** - * Deployment Tag Library - Standardized tags for deployment scripts + * Deployment Tag Library * - * This module provides: - * - Constants for all deployment tags - * - Utilities to generate action-specific tags - * - Type safety for tag usage + * Tags select components, skip functions gate actions: + * - Component tags: PascalCase contract name (e.g., 'IssuanceAllocator') + * - Action verbs: deploy, upgrade, configure, transfer, integrate, all + * - Phase scopes: GIP-NNNN:phase (e.g., 'GIP-0088:upgrade') + * - Activation goals: GIP-NNNN:phase-action (e.g., 'GIP-0088:eligibility-integrate') * - * Tag Patterns: - * - Component tags: Base identifier (e.g., 'issuance-allocator') - * - Action tags: Component + suffix (e.g., 'issuance-allocator-deploy') - * - Category tags: Grouping tags (e.g., 'issuance-core') + * Usage: --tags IssuanceAllocator,deploy → matches component, deploy runs, others skip */ /** @@ -21,42 +19,66 @@ export const DeploymentActions = { CONFIGURE: 'configure', TRANSFER: 'transfer', INTEGRATE: 'integrate', - VERIFY: 'verify', + ALL: 'all', } as const /** - * Core component tags (base identifiers) + * Core component tags (PascalCase contract names matching the registry) */ export const ComponentTags = { // Core contracts with full lifecycle (deploy + upgrade + configure) - ISSUANCE_ALLOCATOR: 'issuance-allocator', - PILOT_ALLOCATION: 'pilot-allocation', - REWARDS_RECLAIM: 'rewards-reclaim', + ISSUANCE_ALLOCATOR: 'IssuanceAllocator', + DEFAULT_ALLOCATION: 'DefaultAllocation', + REWARDS_RECLAIM: 'RewardsReclaim', // Implementations and support contracts - DIRECT_ALLOCATION_IMPL: 'direct-allocation-impl', - REWARDS_ELIGIBILITY: 'rewards-eligibility', + DIRECT_ALLOCATION_IMPL: 'DirectAllocation_Implementation', + REWARDS_ELIGIBILITY_A: 'RewardsEligibilityOracleA', + REWARDS_ELIGIBILITY_B: 'RewardsEligibilityOracleB', + REWARDS_ELIGIBILITY_MOCK: 'RewardsEligibilityOracleMock', - // Process tags (not contract deployments) - ISSUANCE_ACTIVATION: 'issuance-activation', - VERIFY_GOVERNANCE: 'verify-governance', - - // External dependencies (Horizon contracts) - REWARDS_MANAGER: 'rewards-manager', - REWARDS_MANAGER_DEPLOY: 'rewards-manager-deploy', - REWARDS_MANAGER_UPGRADE: 'rewards-manager-upgrade', + // Horizon contracts + RECURRING_COLLECTOR: 'RecurringCollector', + REWARDS_MANAGER: 'RewardsManager', + HORIZON_STAKING: 'HorizonStaking', + PAYMENTS_ESCROW: 'PaymentsEscrow', // SubgraphService contracts - SUBGRAPH_SERVICE: 'subgraph-service', + SUBGRAPH_SERVICE: 'SubgraphService', + DISPUTE_MANAGER: 'DisputeManager', + + // Legacy contracts (graph proxy, upgrade only) + L2_CURATION: 'L2Curation', + + // Issuance agreement contracts + RECURRING_AGREEMENT_MANAGER: 'RecurringAgreementManager', } as const /** - * Category tags for grouping deployments + * Goal tags - deployment goals that orchestrate component lifecycles + * + * Two-dimensional: phase scope × action verbs. + * - Phase scopes select which contracts (`GIP-0088:upgrade`, `GIP-0088:eligibility`, etc.) + * - Action verbs select which lifecycle step (`deploy`, `configure`, `transfer`, `upgrade`) + * - Activation goals are phase-scoped governance TXs (`GIP-0088:eligibility-integrate`) + * - Optional goals bypass the `all` wildcard + * + * Combined: `--tags GIP-0088:issuance,deploy` */ -export const CategoryTags = { - ISSUANCE_CORE: 'issuance-core', - ISSUANCE_GOVERNANCE: 'issuance-governance', - ISSUANCE: 'issuance', +export const GoalTags = { + // Overall GIP scope (status + verification) + GIP_0088: 'GIP-0088', + + // Upgrade phase (deploy, configure, transfer, upgrade — combined with action verbs) + GIP_0088_UPGRADE: 'GIP-0088:upgrade', + + // Activation goals (governance TXs — after upgrade complete) + GIP_0088_ELIGIBILITY_INTEGRATE: 'GIP-0088:eligibility-integrate', + GIP_0088_ISSUANCE_CONNECT: 'GIP-0088:issuance-connect', + GIP_0088_ISSUANCE_ALLOCATE: 'GIP-0088:issuance-allocate', + + // Optional goals (not activated by `all`) + GIP_0088_ISSUANCE_CLOSE_GUARD: 'GIP-0088:issuance-close-guard', } as const /** @@ -67,74 +89,62 @@ export const SpecialTags = { } as const /** - * Generate action tag from component and action + * Parse the value of --tags from argv. + * + * Supports both `--tags foo,bar` (space) and `--tags=foo,bar` (equals). + * Returns null when not present or when the space form has no following arg. + */ +function parseTagsArg(): string[] | null { + const argv = process.argv + for (let i = 0; i < argv.length; i++) { + const a = argv[i] + if (a === '--tags') { + if (i + 1 >= argv.length) return null + return argv[i + 1].split(',') + } + if (a.startsWith('--tags=')) { + return a.slice('--tags='.length).split(',') + } + } + return null +} + +/** + * Check whether --tags was specified on the command line. + * + * Returns true (skip) when no --tags are present. Used by status modules + * to skip when the user didn't request any specific component. */ -export function actionTag( - component: string, - action: (typeof DeploymentActions)[keyof typeof DeploymentActions], -): string { - return `${component}-${action}` +export function noTagsRequested(): boolean { + return parseTagsArg() === null } /** - * Common tag patterns for deployment scripts - * Note: Arrays are not readonly to match DeployScriptModule.tags type (string[]) + * Check whether a deploy script should skip based on action verbs in --tags. + * + * Returns true (skip) when: + * - No --tags specified at all (safety: require explicit tags for mutations) + * - The verb is not present in the requested tags + * + * The 'all' verb is a wildcard: `--tags Component,all` activates every action + * (deploy, upgrade, configure, transfer, integrate) plus the end verification. + * + * Used by script factories and custom deploy scripts to gate mutations. + */ +export function shouldSkipAction(verb: string): boolean { + const tags = parseTagsArg() + if (tags === null) return true + return !tags.includes(verb) && !tags.includes(DeploymentActions.ALL) +} + +/** + * Check whether an optional goal should skip. + * + * Unlike `shouldSkipAction`, this does NOT respond to the `all` wildcard. + * Optional goals only run when their specific tag is explicitly requested. */ -export const Tags = { - // IssuanceAllocator lifecycle - issuanceAllocatorDeploy: [ - actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.DEPLOY), - CategoryTags.ISSUANCE_CORE, - ] as string[], - issuanceAllocatorUpgrade: [actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.UPGRADE)] as string[], - issuanceAllocatorConfigure: [actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.CONFIGURE)] as string[], - issuanceTransfer: [actionTag(ComponentTags.ISSUANCE_ALLOCATOR, DeploymentActions.TRANSFER)] as string[], - issuanceAllocator: [ComponentTags.ISSUANCE_ALLOCATOR] as string[], // Aggregate - - // PilotAllocation lifecycle - pilotAllocationDeploy: [ - actionTag(ComponentTags.PILOT_ALLOCATION, DeploymentActions.DEPLOY), - CategoryTags.ISSUANCE_CORE, - ] as string[], - pilotAllocationUpgrade: [actionTag(ComponentTags.PILOT_ALLOCATION, DeploymentActions.UPGRADE)] as string[], - pilotAllocationConfigure: [actionTag(ComponentTags.PILOT_ALLOCATION, DeploymentActions.CONFIGURE)] as string[], - pilotAllocation: [ComponentTags.PILOT_ALLOCATION] as string[], // Aggregate - - // Rewards reclaim lifecycle - rewardsReclaimDeploy: [actionTag(ComponentTags.REWARDS_RECLAIM, DeploymentActions.DEPLOY)] as string[], - rewardsReclaimUpgrade: [actionTag(ComponentTags.REWARDS_RECLAIM, DeploymentActions.UPGRADE)] as string[], - rewardsReclaimConfigure: [actionTag(ComponentTags.REWARDS_RECLAIM, DeploymentActions.CONFIGURE)] as string[], - rewardsReclaim: [ComponentTags.REWARDS_RECLAIM] as string[], // Aggregate - - // RewardsEligibilityOracle lifecycle - rewardsEligibilityDeploy: [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.DEPLOY)] as string[], - rewardsEligibilityUpgrade: [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.UPGRADE)] as string[], - rewardsEligibilityConfigure: [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.CONFIGURE)] as string[], - rewardsEligibilityTransfer: [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.TRANSFER)] as string[], - rewardsEligibilityIntegrate: [actionTag(ComponentTags.REWARDS_ELIGIBILITY, DeploymentActions.INTEGRATE)] as string[], - rewardsEligibility: [ComponentTags.REWARDS_ELIGIBILITY] as string[], // Aggregate - - // Support contracts - directAllocationImpl: [ComponentTags.DIRECT_ALLOCATION_IMPL] as string[], - - // Process steps - issuanceActivation: [ComponentTags.ISSUANCE_ACTIVATION] as string[], - verifyGovernance: [ - ComponentTags.VERIFY_GOVERNANCE, - CategoryTags.ISSUANCE_GOVERNANCE, - CategoryTags.ISSUANCE, - ] as string[], - - // Top-level aggregate - issuanceAllocation: ['issuance-allocation'] as string[], - - // Horizon RewardsManager lifecycle - rewardsManagerDeploy: [ComponentTags.REWARDS_MANAGER_DEPLOY] as string[], - rewardsManagerUpgrade: [ComponentTags.REWARDS_MANAGER_UPGRADE] as string[], - rewardsManager: [ComponentTags.REWARDS_MANAGER] as string[], - - // SubgraphService lifecycle - subgraphServiceDeploy: [actionTag(ComponentTags.SUBGRAPH_SERVICE, DeploymentActions.DEPLOY)] as string[], - subgraphServiceUpgrade: [actionTag(ComponentTags.SUBGRAPH_SERVICE, DeploymentActions.UPGRADE)] as string[], - subgraphService: [ComponentTags.SUBGRAPH_SERVICE] as string[], +export function shouldSkipOptionalGoal(goalTag: string): boolean { + const tags = parseTagsArg() + if (tags === null) return true + return !tags.includes(goalTag) } diff --git a/packages/deployment/lib/deployment-validation.ts b/packages/deployment/lib/deployment-validation.ts index 9c53c4bdb..e811b3f8e 100644 --- a/packages/deployment/lib/deployment-validation.ts +++ b/packages/deployment/lib/deployment-validation.ts @@ -11,6 +11,7 @@ import type { AnyAddressBookOps } from './address-book-ops.js' import type { ArtifactSource } from './contract-registry.js' import { computeBytecodeHash } from './bytecode-utils.js' import { + getLibraryResolver, loadContractsArtifact, loadIssuanceArtifact, loadOpenZeppelinArtifact, @@ -141,7 +142,12 @@ export async function validateContract( } if (loadedArtifact?.deployedBytecode && metadata?.bytecodeHash) { - const localHash = computeBytecodeHash(loadedArtifact.deployedBytecode) + const libResolver = getLibraryResolver(artifact.type) + const localHash = computeBytecodeHash( + loadedArtifact.deployedBytecode, + loadedArtifact.deployedLinkReferences, + libResolver, + ) if (metadata.bytecodeHash !== localHash) { return { contract: contractName, @@ -178,7 +184,7 @@ export async function validateContract( } // Optional: Verify argsData matches transaction - if (options.verifyArgsData && metadata?.txHash && loadedArtifact?.bytecode) { + if (options.verifyArgsData && metadata?.txHash && metadata?.argsData && loadedArtifact?.bytecode) { try { const tx = await client.getTransaction({ hash: metadata.txHash as `0x${string}` }) if (tx?.input) { diff --git a/packages/deployment/lib/execute-governance.ts b/packages/deployment/lib/execute-governance.ts index 0b9733103..e39cde9cc 100644 --- a/packages/deployment/lib/execute-governance.ts +++ b/packages/deployment/lib/execute-governance.ts @@ -35,7 +35,7 @@ interface SafeTxBatch { * @param networkName - Network name (e.g., 'fork', 'localhost', 'arbitrumSepolia') */ export function getGovernanceTxDir(networkName: string): string { - const forkNetwork = getForkNetwork() + const forkNetwork = getForkNetwork(networkName) if (forkNetwork) { return path.join(getForkStateDir(networkName, forkNetwork), 'txs') } @@ -117,41 +117,42 @@ export async function createGovernanceTxBuilder( * Save governance TX batch and exit with code 1 * * Standard completion pattern for scripts that generate governance TX batches. - * This function: - * 1. Saves the TX batch to file - * 2. Displays appropriate messages - * 3. Exits with code 1 to prevent subsequent deployment steps + * Saves the TX batch to file and displays a message. + * Returns the saved file path so the caller can continue. + * + * Subsequent scripts that depend on this TX being executed should check + * their own preconditions and exit if not met. * * @param env - Deployment environment * @param builder - TX builder with batched transactions - * @param contractName - Optional contract name for contextual message (e.g., "IssuanceAllocator activation") - * @returns Never returns (exits process) + * @param contractName - Optional contract name for contextual message + * @returns Path to the saved TX file */ -export function saveGovernanceTxAndExit( +export function saveGovernanceTx( env: Environment, builder: { saveToFile: () => string }, contractName?: string, -): never { +): string { const txFile = builder.saveToFile() - env.showMessage(`\n✓ TX batch saved: ${txFile}`) + env.showMessage(` ✓ Governance TX saved: ${txFile}`) - env.showMessage('\n📋 GOVERNANCE ACTION REQUIRED:') if (contractName) { env.showMessage(` ${contractName} requires governance execution`) } - env.showMessage(` TX batch: ${txFile}`) - env.showMessage('\nNext steps:') - env.showMessage(' 1. Execute governance TX (see options below)') - env.showMessage(' 2. Run: npx hardhat deploy --tags sync --network ' + env.name) - env.showMessage(' 3. Continue deployment') - env.showMessage('\nExecution options:') - env.showMessage(' • Fork testing: npx hardhat deploy:execute-governance --network fork') - env.showMessage(' • EOA governor: Set GOVERNOR_PRIVATE_KEY and run deploy:execute-governance') - env.showMessage(' • Safe multisig: https://app.safe.global/ → Transaction Builder → Upload JSON') - env.showMessage('\nSee: packages/deployment/docs/GovernanceWorkflow.md\n') - - // Exit with code 1 to prevent subsequent steps from running until governance TX is executed - // This is expected prerequisite state, not an error + env.showMessage(` Run: npx hardhat deploy:execute-governance --network ${env.name}`) + + return txFile +} + +/** + * @deprecated Use `saveGovernanceTx` instead. This function exits the process. + */ +export function saveGovernanceTxAndExit( + env: Environment, + builder: { saveToFile: () => string }, + contractName?: string, +): never { + saveGovernanceTx(env, builder, contractName) process.exit(1) } @@ -219,12 +220,14 @@ export interface ExecuteGovernanceOptions { name?: string /** Governor private key (from keystore or env var) */ governorPrivateKey?: string + /** Lazy resolver for governor key - defers keystore access until actually needed */ + resolveGovernorKey?: () => Promise } export async function executeGovernanceTxs(env: Environment, options?: ExecuteGovernanceOptions): Promise { - const { name, governorPrivateKey } = options ?? {} + const { name, governorPrivateKey, resolveGovernorKey } = options ?? {} // Determine TX directory - in fork mode, also check source network's TX directory - const forkNetwork = getForkNetwork() + const forkNetwork = getForkNetwork(env.name) let txDir = getGovernanceTxDir(env.name) let sourceNetworkFallback = false @@ -278,8 +281,8 @@ export async function executeGovernanceTxs(env: Environment, options?: ExecuteGo transport: custom(env.network.provider), }) - // Check if in fork mode - const inForkMode = isForkMode() + // Check if in fork mode (network-aware: ignores FORK_NETWORK on real networks) + const inForkMode = isForkMode(env.name) if (!inForkMode) { // Not in fork mode - check if governor is EOA or Safe @@ -310,8 +313,9 @@ export async function executeGovernanceTxs(env: Environment, options?: ExecuteGo return 0 } - // Governor is an EOA - if (!governorPrivateKey) { + // Governor is an EOA - resolve key now (deferred to avoid keystore prompt in fork mode) + const resolvedKey = governorPrivateKey ?? (await resolveGovernorKey?.()) + if (!resolvedKey) { const keyName = `${networkToEnvPrefix(env.name)}_GOVERNOR_KEY` env.showMessage(`\n❌ Cannot execute governance TXs on ${env.name}`) env.showMessage(` Governor address: ${governor} (EOA)`) @@ -333,7 +337,7 @@ export async function executeGovernanceTxs(env: Environment, options?: ExecuteGo // Have private key - execute as EOA env.showMessage(`\n🔓 Executing ${files.length} governance TX batch(es)...`) env.showMessage(` Governor: ${governor} (EOA)`) - return await executeWithEOA(env, publicClient, files, txDir, governorPrivateKey) + return await executeWithEOA(env, publicClient, files, txDir, resolvedKey) } // Fork mode - use impersonation diff --git a/packages/deployment/lib/format.ts b/packages/deployment/lib/format.ts new file mode 100644 index 000000000..fd1bf1359 --- /dev/null +++ b/packages/deployment/lib/format.ts @@ -0,0 +1,10 @@ +/** + * Formatting helpers for human-readable display of on-chain values. + */ + +import { formatEther } from 'viem' + +/** Format a wei amount as GRT (e.g. `6036500000000000000n` → `"6.0365 GRT"`). */ +export function formatGRT(wei: bigint): string { + return `${formatEther(wei)} GRT` +} diff --git a/packages/deployment/lib/issuance-deploy-utils.ts b/packages/deployment/lib/issuance-deploy-utils.ts index bd1b5f486..704bde2e2 100644 --- a/packages/deployment/lib/issuance-deploy-utils.ts +++ b/packages/deployment/lib/issuance-deploy-utils.ts @@ -5,16 +5,19 @@ import { encodeFunctionData } from 'viem' import { Contracts, type RegistryEntry } from './contract-registry.js' import { getGovernor } from './controller-utils.js' +import { loadTransparentProxyArtifact } from './artifact-loaders.js' +import { INITIALIZE_GOVERNOR_ABI, OZ_PROXY_ADMIN_ABI } from './abis.js' +import { computeBytecodeHash } from './bytecode-utils.js' +import { getAddressBookForType, getTargetChainIdFromEnv } from './address-book-utils.js' import { + computeArtifactBytecodeHash, deployImplementation, getImplementationConfig, getOnChainImplementation, loadArtifactFromSource, } from './deploy-implementation.js' -import { loadTransparentProxyArtifact } from './artifact-loaders.js' -import { INITIALIZE_GOVERNOR_ABI } from './abis.js' -import { computeBytecodeHash } from './bytecode-utils.js' -import { deploy, graph } from '../rocketh/deploy.js' +import { buildDeploymentMetadata } from './deployment-metadata.js' +import { deploy, execute, graph } from '../rocketh/deploy.js' /** ERC1967 admin slot: keccak256("eip1967.proxy.admin") - 1 */ const ERC1967_ADMIN_SLOT = '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103' @@ -36,6 +39,25 @@ export function requireDeployer(env: Environment): string { return deployer } +/** + * Address derived from the dummy private key (0x…001) used for status-only runs. + * Filtered out so status scripts don't mistake it for the real deployer. + */ +const DUMMY_DEPLOYER_ADDRESS = '0x7e5f4552091a69125d5dfcb7b8c2659029395bdf' + +/** + * Get deployer address if available (non-throwing). + * + * Returns undefined when the deploy key is not loaded (e.g. status-only runs + * where the keystore password is not prompted). Status scripts infer the real + * deployer from the ProxyAdmin owner on-chain instead. + */ +export function getDeployer(env: Environment): string | undefined { + const deployer = env.namedAccounts.deployer + if (!deployer || deployer.toLowerCase() === DUMMY_DEPLOYER_ADDRESS) return undefined + return deployer +} + /** * Require a contract deployment to exist, throwing a helpful error if not found */ @@ -117,7 +139,7 @@ export function showDeploymentStatus( if (result.newlyDeployed) { env.showMessage(`✓ ${contract.name} deployed at ${result.address}`) } else { - env.showMessage(`✓ ${contract.name} deployed at ${result.address}`) + env.showMessage(`✓ ${contract.name} unchanged at ${result.address}`) } } @@ -145,7 +167,8 @@ export function showProxyDeploymentStatus( } /** - * Update issuance address book with proxy deployment information + * Update address book with proxy deployment information. + * Routes to the correct address book based on contract.addressBook. */ export async function updateProxyAddressBook( env: Environment, @@ -155,13 +178,15 @@ export async function updateProxyAddressBook( implAddress?: string, proxyAdminAddress?: string, implementationDeployment?: DeploymentMetadata, + proxyDeployment?: DeploymentMetadata, ) { - await graphUtils.updateIssuanceAddressBook(env, { + await graphUtils.updateAddressBookForContract(env, contract, { name: contract.name, address: proxyAddress, proxy: 'transparent', proxyAdmin: proxyAdminAddress, implementation: implAddress, + proxyDeployment, implementationDeployment, }) } @@ -234,7 +259,8 @@ export interface ProxyDeployConfig { * * Uses OpenZeppelin v5's per-proxy ProxyAdmin pattern: * - Each proxy creates its own ProxyAdmin in the constructor - * - Governor owns all per-proxy ProxyAdmins + * - Deployer is the initial ProxyAdmin owner (for post-deployment configuration) + * - Ownership is transferred to governor in the transfer-governance step * - No shared ProxyAdmin required * * Deployment scenarios: @@ -270,12 +296,57 @@ export async function deployProxyContract( if (existingProxy) { if (sharedImplementation) { - // Shared implementation - just report status + // Shared implementation — detect if redeployed and set pendingImplementation env.showMessage(`✓ ${contract.name} proxy already deployed at ${existingProxy.address}`) env.showMessage(` Uses shared implementation: ${sharedImplementation.name}`) - // Check current implementation status + const implDep = env.getOrNull(sharedImplementation.name) + if (!implDep) { + // Missing impl record means the impl's deploy script didn't run, or sync + // skipped seeding because the artifact couldn't be verified against the + // address book. Either way, silently treating this as "no change" would + // mask a drift between artifact and on-chain bytecode (the shared impl + // bug fixed alongside this guard). Fail loud instead. + throw new Error( + `${contract.name}: shared implementation ${sharedImplementation.name} not in env. ` + + `Ensure ${sharedImplementation.name} is listed in dependencies and its deploy script ran successfully.`, + ) + } + const client = graph.getPublicClient(env) + const onChainImpl = await getOnChainImplementation(client, existingProxy.address, 'transparent') + + if (onChainImpl.toLowerCase() !== implDep.address.toLowerCase()) { + // Shared implementation changed — store as pending for governance upgrade + const targetChainId = await getTargetChainIdFromEnv(env) + const addressBook = getAddressBookForType(contract.addressBook, targetChainId) + + // Get deployment metadata from the shared implementation's address book entry + const implMetadata = addressBook.getDeploymentMetadata(sharedImplementation.name) + if (!implMetadata) { + throw new Error( + `${contract.name}: deployment metadata missing for ${sharedImplementation.name}. ` + + `Run ${sharedImplementation.name}'s deploy script (or sync) before re-running.`, + ) + } + addressBook.setPendingImplementationWithMetadata(contract.name, implDep.address, implMetadata) + + env.showMessage(``) + env.showMessage(`⚠️ UPGRADE REQUIRED`) + env.showMessage(` Proxy: ${existingProxy.address}`) + env.showMessage(` Current (on-chain): ${onChainImpl}`) + env.showMessage(` New implementation: ${implDep.address}`) + env.showMessage(``) + env.showMessage(` Stored as pending — run upgrade task to generate governance TX.`) + + return { + address: existingProxy.address, + newlyDeployed: false, + upgraded: true, + } + } + + // No change — check existing pending status await checkPendingUpgrade(env, client, contract, existingProxy.address, 'transparent') return { @@ -315,10 +386,10 @@ export async function deployProxyContract( // Fresh deployment - deploy implementation first, then OZ v5 proxy if (sharedImplementation) { - return deployProxyWithSharedImpl(env, contract, sharedImplementation, governor, actualInitializeArgs, deployer) + return deployProxyWithSharedImpl(env, contract, sharedImplementation, actualInitializeArgs, deployer) } - return deployProxyWithOwnImpl(env, contract, governor, constructorArgs, actualInitializeArgs, deployer) + return deployProxyWithOwnImpl(env, contract, constructorArgs, actualInitializeArgs, deployer) } /** @@ -327,7 +398,6 @@ export async function deployProxyContract( async function deployProxyWithOwnImpl( env: Environment, contract: RegistryEntry, - governor: string, constructorArgs: unknown[], initializeArgs: unknown[], deployer: string, @@ -348,16 +418,17 @@ async function deployProxyWithOwnImpl( env.showMessage(` Implementation deployed at ${implResult.address}`) - // Encode initialize call + // Encode initialize call using the contract's own ABI const initCalldata = encodeFunctionData({ - abi: INITIALIZE_GOVERNOR_ABI, + abi: implArtifact.abi, functionName: 'initialize', args: initializeArgs as [`0x${string}`], }) // Deploy OZ v5 TransparentUpgradeableProxy // Constructor: (address _logic, address initialOwner, bytes memory _data) - // The proxy creates its own ProxyAdmin owned by initialOwner (governor) + // Deployer is the initial ProxyAdmin owner to allow post-deployment configuration. + // Ownership is transferred to the protocol governor in the transfer-governance step. // Use issuance-compiled proxy artifact (0.8.34) for consistent verification const proxyArtifact = loadTransparentProxyArtifact() const proxyResult = await deployFn( @@ -365,7 +436,7 @@ async function deployProxyWithOwnImpl( { account: deployer, artifact: proxyArtifact, - args: [implResult.address, governor, initCalldata], + args: [implResult.address, deployer, initCalldata], }, { skipIfAlreadyDeployed: true }, ) @@ -380,16 +451,15 @@ async function deployProxyWithOwnImpl( abi: implArtifact.abi, }) - // Build implementation deployment metadata for address book (only if we have required fields) - let implementationDeployment: DeploymentMetadata | undefined - if (implResult.transaction?.hash && implResult.argsData && implResult.deployedBytecode) { - implementationDeployment = { - txHash: implResult.transaction.hash, - argsData: implResult.argsData, - bytecodeHash: computeBytecodeHash(implResult.deployedBytecode), - ...(implResult.receipt?.blockNumber && { blockNumber: Number(implResult.receipt.blockNumber) }), - } - } + // Build implementation deployment metadata. Hash from the artifact (with library resolution) + // so the stored value stays in lockstep with sync's artifact-side comparison. + const implementationDeployment = buildDeploymentMetadata(implResult, computeArtifactBytecodeHash(contract.artifact!)) + + // Build proxy deployment metadata from the proxy artifact bytecode + const proxyDeployment = buildDeploymentMetadata( + proxyResult, + computeBytecodeHash(proxyArtifact.deployedBytecode ?? '0x'), + ) // Update address book with per-proxy ProxyAdmin and deployment metadata await updateProxyAddressBook( @@ -400,12 +470,13 @@ async function deployProxyWithOwnImpl( implResult.address, proxyAdminAddress, implementationDeployment, + proxyDeployment, ) if (proxyResult.newlyDeployed) { env.showMessage(`✓ ${contract.name} proxy deployed at ${proxyResult.address}`) env.showMessage(` Implementation: ${implResult.address}`) - env.showMessage(` ProxyAdmin (per-proxy): ${proxyAdminAddress}`) + env.showMessage(` ProxyAdmin (per-proxy, deployer-owned): ${proxyAdminAddress}`) } else { env.showMessage(`✓ ${contract.name} already deployed at ${proxyResult.address}`) } @@ -424,7 +495,6 @@ async function deployProxyWithSharedImpl( env: Environment, contract: RegistryEntry, sharedImplementation: RegistryEntry, - governor: string, initializeArgs: unknown[], deployer: string, ): Promise<{ address: string; newlyDeployed: boolean; upgraded: boolean }> { @@ -447,6 +517,8 @@ async function deployProxyWithSharedImpl( // Deploy OZ v5 TransparentUpgradeableProxy // Constructor: (address _logic, address initialOwner, bytes memory _data) + // Deployer is the initial ProxyAdmin owner to allow post-deployment configuration. + // Ownership is transferred to the protocol governor in the transfer-governance step. // Use issuance-compiled proxy artifact (0.8.34) for consistent verification const proxyArtifact = loadTransparentProxyArtifact() const proxyResult = await deployFn( @@ -454,7 +526,7 @@ async function deployProxyWithSharedImpl( { account: deployer, artifact: proxyArtifact, - args: [implDep.address, governor, initCalldata], + args: [implDep.address, deployer, initCalldata], }, { skipIfAlreadyDeployed: true }, ) @@ -469,13 +541,28 @@ async function deployProxyWithSharedImpl( abi: implDep.abi, }) - // Update address book with per-proxy ProxyAdmin - await updateProxyAddressBook(env, graph, contract, proxyResult.address, implDep.address, proxyAdminAddress) + // Build proxy deployment metadata from the proxy artifact bytecode + const proxyDeployment = buildDeploymentMetadata( + proxyResult, + computeBytecodeHash(proxyArtifact.deployedBytecode ?? '0x'), + ) + + // Update address book with per-proxy ProxyAdmin and proxy deployment metadata + await updateProxyAddressBook( + env, + graph, + contract, + proxyResult.address, + implDep.address, + proxyAdminAddress, + undefined, + proxyDeployment, + ) if (proxyResult.newlyDeployed) { env.showMessage(`✓ ${contract.name} proxy deployed at ${proxyResult.address}`) env.showMessage(` Implementation: ${implDep.address}`) - env.showMessage(` ProxyAdmin (per-proxy): ${proxyAdminAddress}`) + env.showMessage(` ProxyAdmin (per-proxy, deployer-owned): ${proxyAdminAddress}`) } else { env.showMessage(`✓ ${contract.name} already deployed at ${proxyResult.address}`) } @@ -486,3 +573,74 @@ async function deployProxyWithSharedImpl( upgraded: false, } } + +/** + * Transfer ProxyAdmin ownership for an issuance contract from deployer to governor. + * + * Reads the per-proxy ProxyAdmin address from the address book entry's proxyAdmin field, + * checks current ownership, and transfers if needed. Idempotent: skips if already owned + * by the target governor. + * + * @param env - Deployment environment + * @param contract - Registry entry for the contract whose ProxyAdmin to transfer + * @returns Whether a transfer was executed + * + * @example + * ```typescript + * await transferProxyAdminOwnership(env, Contracts.issuance.IssuanceAllocator) + * ``` + */ +export async function transferProxyAdminOwnership(env: Environment, contract: RegistryEntry): Promise { + const deployer = requireDeployer(env) + const governor = await getGovernor(env) + const client = graph.getPublicClient(env) as PublicClient + + // Get ProxyAdmin address from address book + const targetChainId = await getTargetChainIdFromEnv(env) + const ab = graph.getIssuanceAddressBook(targetChainId) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const entry = ab.getEntry(contract.name as any) + const proxyAdminAddress = entry?.proxyAdmin + + if (!proxyAdminAddress) { + throw new Error(`No proxyAdmin found in address book for ${contract.name}`) + } + + // Check current owner + const currentOwner = (await client.readContract({ + address: proxyAdminAddress as `0x${string}`, + abi: OZ_PROXY_ADMIN_ABI, + functionName: 'owner', + })) as string + + if (currentOwner.toLowerCase() === governor.toLowerCase()) { + env.showMessage(` ProxyAdmin ownership already transferred to governor: ${proxyAdminAddress}`) + return false + } + + if (currentOwner.toLowerCase() !== deployer.toLowerCase()) { + throw new Error( + `ProxyAdmin ${proxyAdminAddress} owned by ${currentOwner}, expected deployer ${deployer}. ` + + `Cannot transfer ownership.`, + ) + } + + // Transfer ownership to governor + env.showMessage(` Transferring ProxyAdmin ownership to governor...`) + env.showMessage(` ProxyAdmin: ${proxyAdminAddress}`) + env.showMessage(` From: ${deployer}`) + env.showMessage(` To: ${governor}`) + + const executeFn = execute(env) + await executeFn( + { address: proxyAdminAddress as `0x${string}`, abi: OZ_PROXY_ADMIN_ABI }, + { + account: deployer, + functionName: 'transferOwnership', + args: [governor as `0x${string}`], + }, + ) + + env.showMessage(` ✓ ProxyAdmin ownership transferred to governor`) + return true +} diff --git a/packages/deployment/lib/oz-proxy-verify.ts b/packages/deployment/lib/oz-proxy-verify.ts index 79c5609d6..2e3b0f305 100644 --- a/packages/deployment/lib/oz-proxy-verify.ts +++ b/packages/deployment/lib/oz-proxy-verify.ts @@ -110,6 +110,39 @@ export function getEtherscanBrowserUrl(chainId: number): string { return url } +/** + * Check if a contract is already verified on Etherscan. + * + * Queries the getsourcecode API — a verified contract has a non-empty + * SourceCode field. Returns the explorer URL if verified, undefined otherwise. + */ +export async function checkEtherscanVerified( + address: string, + apiKey: string, + chainId: number, +): Promise { + const apiUrl = getApiUrl() + const browserUrl = getEtherscanBrowserUrl(chainId) + + const params = new URLSearchParams({ + module: 'contract', + action: 'getsourcecode', + address, + apikey: apiKey, + }) + + try { + const response = await fetch(`${apiUrl}?chainid=${chainId}&${params.toString()}`) + const data = (await response.json()) as { status: string; result: Array<{ SourceCode?: string }> } + if (data.status === '1' && data.result?.[0]?.SourceCode) { + return `${browserUrl}/address/${address}#code` + } + } catch { + // Network error — assume not verified, let the caller proceed + } + return undefined +} + /** * Verify OZ TransparentUpgradeableProxy via Etherscan API * @@ -200,6 +233,12 @@ export async function verifyOZProxy( return { success: true, url } } + // "Already Verified" can appear during polling (not just at submission) + if (checkResult.result?.toLowerCase().includes('already verified')) { + const url = `${browserUrl}/address/${address}#code` + return { success: true, url, message: 'Already verified' } + } + // Verification failed return { success: false, message: checkResult.result } } diff --git a/packages/deployment/lib/preconditions.ts b/packages/deployment/lib/preconditions.ts new file mode 100644 index 000000000..8f000597a --- /dev/null +++ b/packages/deployment/lib/preconditions.ts @@ -0,0 +1,380 @@ +/** + * Shared Precondition Checks + * + * Each function answers "is this action step done?" for a specific component. + * Used by BOTH action scripts (to skip if done) and status scripts (for next-step hints). + * + * This is the SINGLE SOURCE OF TRUTH for precondition logic. + * Action scripts and status scripts must call the same functions — no copies. + * + * Configure checks: params, integration references, and role GRANTS (PAUSE_ROLE, GOVERNOR_ROLE) + * Transfer checks: deployer GOVERNOR_ROLE REVOKE + ProxyAdmin ownership + */ + +import type { PublicClient } from 'viem' +import { keccak256, toHex } from 'viem' + +import { + ACCESS_CONTROL_ENUMERABLE_ABI, + ISSUANCE_ALLOCATOR_ABI, + ISSUANCE_TARGET_ABI, + OZ_PROXY_ADMIN_ABI, + REWARDS_MANAGER_ABI, + REWARDS_MANAGER_DEPRECATED_ABI, +} from './abis.js' + +// ============================================================================ +// Result type +// ============================================================================ + +/** + * Result of a precondition check + * + * @property done - true if the action step is complete (on-chain state matches target) + * @property reason - why not done (human-readable, for status display) + */ +export interface PreconditionResult { + done: boolean + reason?: string +} + +// ============================================================================ +// Helpers +// ============================================================================ + +// Precomputed role hashes (matches BaseUpgradeable constants) +const GOVERNOR_ROLE = keccak256(toHex('GOVERNOR_ROLE')) +const PAUSE_ROLE = keccak256(toHex('PAUSE_ROLE')) + +/** Check if account has a role on a contract */ +async function hasRole( + client: PublicClient, + contractAddress: string, + role: `0x${string}`, + account: string, +): Promise { + return (await client.readContract({ + address: contractAddress as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [role, account as `0x${string}`], + })) as boolean +} + +/** + * Check role grants common to all deployer-initialized contracts + * + * Configure must grant: + * - GOVERNOR_ROLE to protocol governor + * - PAUSE_ROLE to pause guardian + */ +async function checkRoleGrants( + client: PublicClient, + contractAddress: string, + governor: string, + pauseGuardian: string, +): Promise<{ governorOk: boolean; pauseOk: boolean; reasons: string[] }> { + const governorOk = await hasRole(client, contractAddress, GOVERNOR_ROLE, governor) + const pauseOk = await hasRole(client, contractAddress, PAUSE_ROLE, pauseGuardian) + + const reasons: string[] = [] + if (!governorOk) reasons.push('governor missing GOVERNOR_ROLE') + if (!pauseOk) reasons.push('pauseGuardian missing PAUSE_ROLE') + + return { governorOk, pauseOk, reasons } +} + +// ============================================================================ +// Configure checks +// ============================================================================ + +/** + * Check if IssuanceAllocator is configured + * + * Matches the skip logic in allocate/allocator/04_configure.ts: + * - RM.issuancePerBlock must be > 0 (RM initialized) + * - IA.getIssuancePerBlock() must equal RM rate + * - governor has GOVERNOR_ROLE + * - pauseGuardian has PAUSE_ROLE + * + * Note: RM target allocation (setTargetAllocation) is an activation step + * in issuance-connect, not a configure step. + */ +export async function checkIAConfigured( + client: PublicClient, + iaAddress: string, + rmAddress: string, + governor: string, + pauseGuardian: string, +): Promise { + // Check RM issuance rate + const rmIssuanceRate = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: REWARDS_MANAGER_DEPRECATED_ABI, + functionName: 'issuancePerBlock', + })) as bigint + + if (rmIssuanceRate === 0n) { + return { done: false, reason: 'RM.issuancePerBlock is 0' } + } + + // Check IA rate matches RM + const iaIssuanceRate = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getIssuancePerBlock', + })) as bigint + + const rateOk = iaIssuanceRate === rmIssuanceRate && iaIssuanceRate > 0n + + // Check role grants + const roles = await checkRoleGrants(client, iaAddress, governor, pauseGuardian) + + if (rateOk && roles.governorOk && roles.pauseOk) { + return { done: true } + } + + const reasons: string[] = [] + if (!rateOk) reasons.push('rate mismatch') + reasons.push(...roles.reasons) + return { done: false, reason: reasons.join(', ') } +} + +/** + * Check if RecurringAgreementManager is configured + * + * Matches the skip logic in agreement/manager/04_configure.ts: + * - RC has COLLECTOR_ROLE + * - SS has DATA_SERVICE_ROLE + * - RAM.getIssuanceAllocator() == IA + * - governor has GOVERNOR_ROLE + * - pauseGuardian has PAUSE_ROLE + */ +export async function checkRAMConfigured( + client: PublicClient, + ramAddress: string, + rcAddress: string, + ssAddress: string, + iaAddress: string, + governor: string, + pauseGuardian: string, +): Promise { + const COLLECTOR_ROLE = keccak256(toHex('COLLECTOR_ROLE')) + const DATA_SERVICE_ROLE = keccak256(toHex('DATA_SERVICE_ROLE')) + + const rcHasCollectorRole = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [COLLECTOR_ROLE, rcAddress as `0x${string}`], + })) as boolean + + const ssHasDataServiceRole = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [DATA_SERVICE_ROLE, ssAddress as `0x${string}`], + })) as boolean + + let iaConfigured = false + try { + const currentIA = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: ISSUANCE_TARGET_ABI, + functionName: 'getIssuanceAllocator', + })) as string + iaConfigured = currentIA.toLowerCase() === iaAddress.toLowerCase() + } catch { + // Not set + } + + // Check role grants + const roles = await checkRoleGrants(client, ramAddress, governor, pauseGuardian) + + if (rcHasCollectorRole && ssHasDataServiceRole && iaConfigured && roles.governorOk && roles.pauseOk) { + return { done: true } + } + + const reasons: string[] = [] + if (!rcHasCollectorRole) reasons.push('RC missing COLLECTOR_ROLE') + if (!ssHasDataServiceRole) reasons.push('SS missing DATA_SERVICE_ROLE') + if (!iaConfigured) reasons.push('IssuanceAllocator not set') + reasons.push(...roles.reasons) + return { done: false, reason: reasons.join(', ') } +} + +/** + * Check Reclaim role grants only (governor has GOVERNOR_ROLE, pauseGuardian has PAUSE_ROLE) + * + * Use this when you need to know whether the deployer (with Reclaim GOVERNOR_ROLE) can + * fix the issue. The RM integration is governance-only and should be checked separately + * via checkReclaimRMIntegration. + */ +export async function checkReclaimRoles( + client: PublicClient, + reclaimAddress: string, + governor: string, + pauseGuardian: string, +): Promise { + const roles = await checkRoleGrants(client, reclaimAddress, governor, pauseGuardian) + if (roles.governorOk && roles.pauseOk) { + return { done: true } + } + return { done: false, reason: roles.reasons.join(', ') } +} + +/** + * Check RM integration with Reclaim: RM.getDefaultReclaimAddress() == reclaim address + * + * This is governance-only — only an account with GOVERNOR_ROLE on RM can fix it, + * which the deployer never has. Status logic should always treat a failure here + * as deferred (governance TX), not blocking on configure. + */ +export async function checkReclaimRMIntegration( + client: PublicClient, + rmAddress: string, + reclaimAddress: string, +): Promise { + try { + const currentDefault = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: REWARDS_MANAGER_ABI, + functionName: 'getDefaultReclaimAddress', + })) as string + + if (currentDefault.toLowerCase() === reclaimAddress.toLowerCase()) { + return { done: true } + } + return { done: false, reason: 'default reclaim address not set' } + } catch { + // Function not available — RM not upgraded + return { done: false, reason: 'RM not upgraded' } + } +} + +/** + * Check whether RM.getRevertOnIneligible() matches the desired value from config. + * + * Governance-only setter on RM — failure is deferred to the upgrade governance batch + * unless the deployer holds GOVERNOR_ROLE on RM (true on fresh networks where RM is + * deployed from scratch with the deployer as initial governor; false on networks + * where RM was deployed by separate horizon-Ignition infrastructure). + */ +export async function checkRMRevertOnIneligible( + client: PublicClient, + rmAddress: string, + desired: boolean, +): Promise { + try { + const onChain = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: REWARDS_MANAGER_ABI, + functionName: 'getRevertOnIneligible', + })) as boolean + if (onChain === desired) return { done: true } + return { done: false, reason: `revertOnIneligible=${onChain}, expected ${desired}` } + } catch { + return { done: false, reason: 'RM not upgraded' } + } +} + +/** + * Check if ReclaimedRewards is fully configured (roles + RM integration) + * + * Convenience wrapper that combines checkReclaimRoles and checkReclaimRMIntegration. + * Use the split functions when callers need to distinguish deployer-fixable role + * issues from governance-only RM integration issues. + */ +export async function checkReclaimConfigured( + client: PublicClient, + rmAddress: string, + reclaimAddress: string, + governor: string, + pauseGuardian: string, +): Promise { + const roles = await checkReclaimRoles(client, reclaimAddress, governor, pauseGuardian) + const rmIntegration = await checkReclaimRMIntegration(client, rmAddress, reclaimAddress) + + if (roles.done && rmIntegration.done) { + return { done: true } + } + + // If roles are done but RM not upgraded, report that specifically + if (roles.done && rmIntegration.reason === 'RM not upgraded') { + return { done: false, reason: 'RM not upgraded' } + } + + const reasons: string[] = [] + if (!roles.done && roles.reason) reasons.push(roles.reason) + if (!rmIntegration.done && rmIntegration.reason) reasons.push(rmIntegration.reason) + return { done: false, reason: reasons.join(', ') } +} + +/** + * Check if DefaultAllocation is configured + * + * - governor has GOVERNOR_ROLE on DefaultAllocation + * - pauseGuardian has PAUSE_ROLE on DefaultAllocation + * + * Note: IA.setDefaultTarget(DA) is an activation step in issuance-connect. + */ +export async function checkDefaultAllocationConfigured( + client: PublicClient, + daAddress: string, + governor: string, + pauseGuardian: string, +): Promise { + const roles = await checkRoleGrants(client, daAddress, governor, pauseGuardian) + + if (roles.governorOk && roles.pauseOk) { + return { done: true } + } + + return { done: false, reason: roles.reasons.join(', ') } +} + +// ============================================================================ +// Transfer checks +// ============================================================================ + +/** + * Check if deployer GOVERNOR_ROLE is revoked on a contract + * + * Transfer = revoke deployer access. Role grants happen in configure. + * Generic check used for IA, RAM, Reclaim. + */ +export async function checkDeployerRevoked( + client: PublicClient, + contractAddress: string, + deployer: string, +): Promise { + const deployerHasRole = await hasRole(client, contractAddress, GOVERNOR_ROLE, deployer) + + if (!deployerHasRole) { + return { done: true } + } + return { done: false, reason: 'deployer GOVERNOR_ROLE not revoked' } +} + +/** + * Check if ProxyAdmin ownership is transferred to governor + * + * Generic check used for any contract with an OZ v5 per-proxy ProxyAdmin. + * Used by transfer scripts for IA, RAM, Reclaim, REO. + */ +export async function checkProxyAdminTransferred( + client: PublicClient, + proxyAdminAddress: string, + governor: string, +): Promise { + const currentOwner = (await client.readContract({ + address: proxyAdminAddress as `0x${string}`, + abi: OZ_PROXY_ADMIN_ABI, + functionName: 'owner', + })) as string + + if (currentOwner.toLowerCase() === governor.toLowerCase()) { + return { done: true } + } + return { done: false, reason: `ProxyAdmin owned by ${currentOwner}, not governor` } +} diff --git a/packages/deployment/lib/script-factories.ts b/packages/deployment/lib/script-factories.ts new file mode 100644 index 000000000..6c1bb1de5 --- /dev/null +++ b/packages/deployment/lib/script-factories.ts @@ -0,0 +1,384 @@ +/** + * Deploy Script Factories - Create deployment modules with standard framework plumbing + * + * Two flavors: + * + * **Contract-based** (component lifecycle): + * Derive tags from registry componentTag. Action-verb skip gating. + * Post-action sync. Use for standard deploy/upgrade/configure/transfer steps. + * + * **Tag-based** (goals, multi-contract status, standalone actions): + * Accept a tag string directly. Skip when no --tags specified. + * Custom execute callback handles all logic. + * + * Skip gating uses func.skip (checked by rocketh's executor via patch) + * with early returns as a safety net. + */ + +import type { DeployScriptModule, Environment } from '@rocketh/core/types' + +import type { RegistryEntry } from './contract-registry.js' +import { deployImplementation, getImplementationConfig } from './deploy-implementation.js' +import { DeploymentActions, noTagsRequested, shouldSkipAction } from './deployment-tags.js' +import { requireUpgradeExecuted } from './execute-governance.js' +import { deployProxyContract } from './issuance-deploy-utils.js' +import { showDetailedComponentStatus } from './status-detail.js' +import { syncComponentFromRegistry, syncComponentsFromRegistry } from './sync-utils.js' +import type { ImplementationUpgradeOverrides } from './upgrade-implementation.js' +import { upgradeImplementation } from './upgrade-implementation.js' + +/** + * Require that the registry entry has a componentTag, throwing a clear error if not. + */ +function requireComponentTag(contract: RegistryEntry): string { + if (!contract.componentTag) { + throw new Error( + `Contract '${contract.name}' has no componentTag in the registry. ` + + `Add a componentTag to use script factories.`, + ) + } + return contract.componentTag +} + +/** + * Create a standard upgrade deploy script module. + * + * Generates a governance TX to upgrade the contract's proxy to its pending implementation. + * Tags and dependencies are derived from the contract's componentTag. + * + * @example Standard single-contract upgrade: + * ```typescript + * import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' + * import { createUpgradeModule } from '@graphprotocol/deployment/lib/script-factories.js' + * + * export default createUpgradeModule(Contracts.horizon.PaymentsEscrow) + * ``` + * + * @example Upgrade with implementation name override: + * ```typescript + * export default createUpgradeModule(Contracts.issuance.SomeProxy, { + * overrides: { implementationName: 'DifferentImpl' }, + * }) + * ``` + */ +export function createUpgradeModule( + contract: RegistryEntry, + options?: { + overrides?: ImplementationUpgradeOverrides + extraDependencies?: string[] + /** Additional contracts to sync alongside `contract` before the upgrade runs. */ + prerequisites?: RegistryEntry[] + }, +): DeployScriptModule { + const tag = requireComponentTag(contract) + + const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.UPGRADE)) return + await syncComponentsFromRegistry(env, [contract, ...(options?.prerequisites ?? [])]) + await upgradeImplementation(env, contract, options?.overrides) + await syncComponentFromRegistry(env, contract) + } + + func.tags = [tag] + func.dependencies = options?.extraDependencies ?? [] + func.skip = async () => shouldSkipAction(DeploymentActions.UPGRADE) + + return func +} + +/** + * Create a standard end/complete deploy script module. + * + * Gates on `--tags ...,all`. Verifies the upgrade governance TX has been + * executed and shows a ready message. The actual lifecycle actions a component + * needs are encoded in its dependency chain via the component tag, not in this + * factory. + * + * @example + * ```typescript + * export default createEndModule(Contracts.horizon.PaymentsEscrow) + * ``` + */ +export function createEndModule(contract: RegistryEntry): DeployScriptModule { + const tag = requireComponentTag(contract) + + const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.ALL)) return + requireUpgradeExecuted(env, contract.name) + env.showMessage(`\n✓ ${contract.name} ready`) + } + + func.tags = [tag] + func.dependencies = [] + func.skip = async () => shouldSkipAction(DeploymentActions.ALL) + + return func +} + +/** + * Create a status deploy script module. + * + * Syncs the component with on-chain state and shows its current status. + * Tagged with the bare component name so `--tags IssuanceAllocator` is a + * safe, read-only operation. + * + * @example Single contract (default status display): + * ```typescript + * export default createStatusModule(Contracts.horizon.PaymentsEscrow) + * ``` + * + * @example Custom status with tag (multi-contract or cross-component): + * ```typescript + * export default createStatusModule(GoalTags.GIP_0088, async (env) => { + * // custom multi-phase status display + * }) + * ``` + */ +export function createStatusModule(contract: RegistryEntry): DeployScriptModule +export function createStatusModule(tag: string, execute: (env: Environment) => Promise): DeployScriptModule +export function createStatusModule( + contractOrTag: RegistryEntry | string, + execute?: (env: Environment) => Promise, +): DeployScriptModule { + const tag = typeof contractOrTag === 'string' ? contractOrTag : requireComponentTag(contractOrTag) + + const func: DeployScriptModule = async (env) => { + if (noTagsRequested()) return + if (execute) { + await execute(env) + } else { + await showDetailedComponentStatus(env, contractOrTag as RegistryEntry) + } + } + + func.tags = [tag] + func.dependencies = [] + func.skip = async () => noTagsRequested() + + return func +} + +// ============================================================================ +// Action Factories (custom logic with standard framework plumbing) +// ============================================================================ + +/** + * Create a deploy script module for a custom action. + * + * Two forms: + * + * **Contract-based** (component lifecycle steps): + * Uses action verb gating (`shouldSkipAction`) and post-action sync. + * Requires both component tag AND action verb in `--tags`. + * + * **Tag-based** (goal scripts, standalone actions): + * Uses tag gating (`noTagsRequested`). The tag in `--tags` is sufficient. + * No post-action sync — the execute callback handles everything. + * + * @example Contract-based configure: + * ```typescript + * export default createActionModule( + * Contracts.horizon.RecurringCollector, + * DeploymentActions.CONFIGURE, + * async (env) => { ... }, + * ) + * ``` + * + * @example Tag-based goal action: + * ```typescript + * export default createActionModule( + * GoalTags.GIP_0088_ISSUANCE_CONNECT, + * async (env) => { ... }, + * { dependencies: [ComponentTags.ISSUANCE_ALLOCATOR] }, + * ) + * ``` + */ +export function createActionModule( + contract: RegistryEntry, + action: (typeof DeploymentActions)[keyof typeof DeploymentActions], + execute: (env: Environment) => Promise, + options?: { extraDependencies?: string[]; prerequisites?: RegistryEntry[] }, +): DeployScriptModule +export function createActionModule( + tag: string, + execute: (env: Environment) => Promise, + options?: { dependencies?: string[] }, +): DeployScriptModule +export function createActionModule( + contractOrTag: RegistryEntry | string, + actionOrExecute: (typeof DeploymentActions)[keyof typeof DeploymentActions] | ((env: Environment) => Promise), + executeOrOptions?: ((env: Environment) => Promise) | { dependencies?: string[] }, + maybeOptions?: { extraDependencies?: string[]; prerequisites?: RegistryEntry[] }, +): DeployScriptModule { + if (typeof contractOrTag === 'string') { + // Tag-based: (tag, execute, options?) + const tag = contractOrTag + const execute = actionOrExecute as (env: Environment) => Promise + const options = executeOrOptions as { dependencies?: string[] } | undefined + + const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(tag)) return + await execute(env) + } + + func.tags = [tag] + func.dependencies = options?.dependencies ?? [] + func.skip = async () => shouldSkipAction(tag) + + return func + } + + // Contract-based: (contract, action, execute, options?) + const tag = requireComponentTag(contractOrTag) + const action = actionOrExecute as string + const execute = executeOrOptions as (env: Environment) => Promise + + const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(action)) return + await syncComponentsFromRegistry(env, [contractOrTag, ...(maybeOptions?.prerequisites ?? [])]) + await execute(env) + await syncComponentFromRegistry(env, contractOrTag) + } + + func.tags = [tag] + func.dependencies = maybeOptions?.extraDependencies ?? [] + func.skip = async () => shouldSkipAction(action) + + return func +} + +// ============================================================================ +// Deploy Factories +// ============================================================================ + +/** + * Options shared by deploy factories + */ +interface DeployModuleOptions { + /** Additional tags beyond the derived deploy action tag */ + extraTags?: string[] + /** Additional rocketh dependency tags */ + extraDependencies?: string[] + /** + * Additional registry entries to sync immediately before the action runs. + * Use for contracts read via `env.getOrNull(...)` inside `resolveArgs` / + * `resolveConstructorArgs` (e.g. Controller, shared implementations). + */ + prerequisites?: RegistryEntry[] +} + +/** + * Create a deploy module for prerequisite contracts (existing proxy, new implementation). + * + * Uses `deployImplementation` + `getImplementationConfig` to deploy a new implementation + * and store it as pendingImplementation for governance upgrade. + * + * @param contract - Registry entry (must have prerequisite: true, artifact, proxyType) + * @param resolveConstructorArgs - Optional callback to resolve constructor args from env. + * Called with the deployment environment. Return the args array. + * Omit for contracts with no constructor args (e.g., RewardsManager). + * + * @example No constructor args: + * ```typescript + * export default createImplementationDeployModule(Contracts.horizon.RewardsManager) + * ``` + * + * @example With synced dependency args: + * ```typescript + * export default createImplementationDeployModule( + * Contracts['subgraph-service'].DisputeManager, + * (env) => { + * const controller = env.getOrNull('Controller') + * if (!controller) throw new Error('Missing Controller') + * return [controller.address] + * }, + * ) + * ``` + */ +export function createImplementationDeployModule( + contract: RegistryEntry, + resolveConstructorArgs?: (env: Environment) => Promise | unknown[], + options?: DeployModuleOptions, +): DeployScriptModule { + const tag = requireComponentTag(contract) + + const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.DEPLOY)) return + await syncComponentsFromRegistry(env, [contract, ...(options?.prerequisites ?? [])]) + const constructorArgs = resolveConstructorArgs ? await resolveConstructorArgs(env) : undefined + await deployImplementation( + env, + getImplementationConfig(contract.addressBook, contract.name, constructorArgs ? { constructorArgs } : undefined), + ) + await syncComponentFromRegistry(env, contract) + } + + func.tags = [tag, ...(options?.extraTags ?? [])] + func.dependencies = options?.extraDependencies ?? [] + func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) + + return func +} + +/** + * Create a deploy module for new contracts (fresh proxy + implementation). + * + * Uses `deployProxyContract` to deploy an OZ v5 TransparentUpgradeableProxy with + * atomic initialization. On subsequent runs, deploys new implementation and stores + * as pendingImplementation. + * + * @param contract - Registry entry (must have deployable: true, artifact, proxyType) + * @param resolveArgs - Optional callback to resolve constructor and initialize args. + * Omit initializeArgs to use default [governor]. + * + * @example With graphToken constructor and deployer init: + * ```typescript + * export default createProxyDeployModule( + * Contracts.issuance.RewardsEligibilityOracleA, + * (env) => ({ + * constructorArgs: [requireGraphToken(env).address], + * initializeArgs: [requireDeployer(env)], + * }), + * ) + * ``` + * + * @example With default initialize args [governor]: + * ```typescript + * export default createProxyDeployModule( + * Contracts.issuance.RecurringAgreementManager, + * (env) => ({ + * constructorArgs: [requireGraphToken(env).address, paymentsEscrow.address], + * }), + * ) + * ``` + */ +export function createProxyDeployModule( + contract: RegistryEntry, + resolveArgs?: (env: Environment) => Promise | ProxyDeployArgs, + options?: DeployModuleOptions, +): DeployScriptModule { + const tag = requireComponentTag(contract) + + const func: DeployScriptModule = async (env) => { + if (shouldSkipAction(DeploymentActions.DEPLOY)) return + await syncComponentsFromRegistry(env, [contract, ...(options?.prerequisites ?? [])]) + const args = resolveArgs ? await resolveArgs(env) : {} + await deployProxyContract(env, { + contract, + constructorArgs: args.constructorArgs, + initializeArgs: args.initializeArgs, + }) + await syncComponentFromRegistry(env, contract) + } + + func.tags = [tag, ...(options?.extraTags ?? [])] + func.dependencies = options?.extraDependencies ?? [] + func.skip = async () => shouldSkipAction(DeploymentActions.DEPLOY) + + return func +} + +interface ProxyDeployArgs { + constructorArgs?: unknown[] + initializeArgs?: unknown[] +} diff --git a/packages/deployment/lib/status-detail.ts b/packages/deployment/lib/status-detail.ts new file mode 100644 index 000000000..a9a4cede8 --- /dev/null +++ b/packages/deployment/lib/status-detail.ts @@ -0,0 +1,1135 @@ +/** + * Status Detail - Detailed contract status with integration checks + * + * Extracted from deployment-status task so deploy scripts (10_status.ts) + * can show the same detail view. The task delegates to these functions. + */ + +import type { Environment } from '@rocketh/core/types' +import type { PublicClient } from 'viem' + +import { + ACCESS_CONTROL_ENUMERABLE_ABI, + CONTROLLER_ABI, + IISSUANCE_TARGET_INTERFACE_ID, + IREWARDS_MANAGER_INTERFACE_ID, + ISSUANCE_ALLOCATOR_ABI, + ISSUANCE_TARGET_ABI, + PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + REWARDS_ELIGIBILITY_ORACLE_ABI, + REWARDS_MANAGER_ABI, +} from './abis.js' +import type { AddressBookOps } from './address-book-ops.js' +import { getAddressBookForType, getTargetChainIdFromEnv } from './address-book-utils.js' +import { + checkIssuanceAllocatorActivation, + checkOperatorRole, + formatAddress, + supportsInterface, +} from './contract-checks.js' +import type { RegistryEntry } from './contract-registry.js' +import { getResolvedSettings } from './deployment-config.js' +import { countPendingGovernanceTxs } from './execute-governance.js' +import { formatGRT } from './format.js' +import { getContractStatusLine, type ContractStatusResult, type ProxyAdminOwnershipContext } from './sync-utils.js' +import { graph } from '../rocketh/deploy.js' + +// ============================================================================ +// Integration Check Types & Helpers +// ============================================================================ + +/** Integration check result */ +export interface IntegrationCheck { + ok: boolean | null // null = not applicable / not deployed + label: string +} + +function formatCheck(check: IntegrationCheck): string { + const icon = check.ok === null ? '○' : check.ok ? '✓' : '✗' + return ` ${icon} ${check.label}` +} + +function formatWarnings(warnings: string[] | undefined): string[] { + if (!warnings) return [] + return warnings.map((w) => ` ⚠ ${w}`) +} + +/** Format proxy admin detail lines */ +function formatProxyAdminDetail(result: ContractStatusResult): string[] { + if (!result.proxyAdminAddress) return [] + const lines: string[] = [] + const ownerIcon = result.proxyAdminOwner === 'governor' ? '✓' : result.proxyAdminOwner === 'unknown' ? '○' : '⚠' + const ownerRole = + result.proxyAdminOwner === 'governor' + ? 'governor' + : result.proxyAdminOwner === 'deployer' + ? 'deployer' + : result.proxyAdminOwner === 'other' + ? 'not governor' + : 'unknown' + const ownerAddr = result.proxyAdminOwnerAddress ? ` ${result.proxyAdminOwnerAddress}` : '' + lines.push(` ProxyAdmin: ${result.proxyAdminAddress}`) + lines.push(` ${ownerIcon} ProxyAdmin owner:${ownerAddr} (${ownerRole})`) + return lines +} + +// ============================================================================ +// Ownership Context Resolution +// ============================================================================ + +/** + * Resolve governor/deployer context for proxy admin ownership checks + */ +export async function resolveOwnershipContext( + client: PublicClient, + env: Environment, + chainId: number, +): Promise { + const horizonAddressBook = graph.getHorizonAddressBook(chainId) + try { + const controllerAddress = horizonAddressBook.entryExists('Controller') + ? horizonAddressBook.getEntry('Controller')?.address + : null + if (!controllerAddress) return undefined + + const governor = (await client.readContract({ + address: controllerAddress as `0x${string}`, + abi: CONTROLLER_ABI, + functionName: 'getGovernor', + })) as string + + if (!governor) return undefined + + // Deployer is best-effort: available when provider has accounts (fork/local) + let deployer: string | undefined + try { + const accounts = (await env.network.provider.request({ method: 'eth_accounts' })) as string[] | undefined + if (accounts && accounts.length > 0) { + deployer = accounts[0] + } + } catch { + // No accounts available (read-only provider) + } + + return { governor, deployer } + } catch { + return undefined + } +} + +// ============================================================================ +// Integration Check Functions +// ============================================================================ + +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + +export async function getRewardsManagerChecks( + client: PublicClient, + horizonBook: AddressBookOps, + chainId: number, + issuanceBook?: AddressBookOps, + ssBook?: AddressBookOps, +): Promise { + const checks: IntegrationCheck[] = [] + const rmAddress = horizonBook.entryExists('RewardsManager') ? horizonBook.getEntry('RewardsManager')?.address : null + + if (!rmAddress) return checks + + // Interface support + const supportsRewardsManager = await supportsInterface(client, rmAddress, IREWARDS_MANAGER_INTERFACE_ID) + checks.push({ ok: supportsRewardsManager, label: `implements IRewardsManager (${IREWARDS_MANAGER_INTERFACE_ID})` }) + + const supportsIssuanceTarget = await supportsInterface(client, rmAddress, IISSUANCE_TARGET_INTERFACE_ID) + checks.push({ ok: supportsIssuanceTarget, label: `implements IIssuanceTarget (${IISSUANCE_TARGET_INTERFACE_ID})` }) + + if (!supportsRewardsManager) return checks + + // Helper: read a contract value, returning null on failure + async function rmRead(functionName: string, abi: readonly unknown[] = REWARDS_MANAGER_ABI): Promise { + try { + return (await client.readContract({ + address: rmAddress as `0x${string}`, + abi, + functionName, + })) as T + } catch { + return null + } + } + + // Issuance rates + const rawRate = await rmRead('getRawIssuancePerBlock') + const allocatedRate = await rmRead('getAllocatedIssuancePerBlock') + if (rawRate !== null) { + checks.push({ ok: rawRate > 0n, label: `issuancePerBlock: ${formatGRT(rawRate)} (raw)` }) + } + if (allocatedRate !== null) { + checks.push({ + ok: allocatedRate > 0n, + label: `issuancePerBlock: ${formatGRT(allocatedRate)} (after IA allocation)`, + }) + } + + // SubgraphService + const ss = await rmRead('subgraphService') + if (ss !== null) { + const expected = ssBook?.entryExists('SubgraphService') + ? (ssBook.getEntry('SubgraphService')?.address ?? null) + : null + const matches = expected ? ss.toLowerCase() === expected.toLowerCase() : null + checks.push({ + ok: ss !== ZERO_ADDRESS ? matches : false, + label: `subgraphService: ${ss}${matches === false && expected ? ` (expected ${expected})` : ''}`, + }) + } + + // IssuanceAllocator + const ia = await rmRead('getIssuanceAllocator', ISSUANCE_TARGET_ABI) + if (ia !== null) { + const iaBook = issuanceBook?.entryExists('IssuanceAllocator') + ? issuanceBook.getEntry('IssuanceAllocator')?.address + : null + const isSet = ia !== ZERO_ADDRESS + const matches = iaBook ? ia.toLowerCase() === iaBook.toLowerCase() : null + checks.push({ + ok: isSet ? matches : null, + label: isSet + ? `issuanceAllocator: ${ia}${matches === false ? ` (expected ${iaBook!})` : ''}` + : 'issuanceAllocator: not set', + }) + } + + // Provider eligibility oracle + const reo = await rmRead('getProviderEligibilityOracle', PROVIDER_ELIGIBILITY_MANAGEMENT_ABI) + if (reo !== null) { + const reoA = issuanceBook?.entryExists('RewardsEligibilityOracleA') + ? issuanceBook.getEntry('RewardsEligibilityOracleA')?.address + : null + const isSet = reo !== ZERO_ADDRESS + const matchesA = reoA ? reo.toLowerCase() === reoA.toLowerCase() : null + checks.push({ + ok: isSet ? matchesA : null, + label: isSet + ? `providerEligibilityOracle: ${reo}${matchesA === false ? ' (not REO-A)' : matchesA ? ' (REO-A)' : ''}` + : 'providerEligibilityOracle: not set', + }) + } else { + checks.push({ ok: null, label: 'providerEligibilityOracle: not set' }) + } + + // Revert on ineligible — compare against resolved settings + const revertOnIneligible = await rmRead('getRevertOnIneligible') + if (revertOnIneligible !== null) { + const desired = getResolvedSettings(chainId).rewardsManager.revertOnIneligible + const matches = revertOnIneligible === desired + checks.push({ + ok: matches, + label: `revertOnIneligible: ${revertOnIneligible}${matches ? '' : ` (expected ${desired})`}`, + }) + } + + // Default reclaim address + const defaultReclaim = await rmRead('getDefaultReclaimAddress') + if (defaultReclaim !== null) { + const expectedAddr = issuanceBook?.entryExists('ReclaimedRewards') + ? issuanceBook.getEntry('ReclaimedRewards')?.address + : null + const isSet = defaultReclaim !== ZERO_ADDRESS + const matches = isSet && expectedAddr ? defaultReclaim.toLowerCase() === expectedAddr.toLowerCase() : null + checks.push({ + ok: isSet ? (matches ?? true) : null, + label: isSet + ? `defaultReclaimAddress: ${defaultReclaim}${matches === false ? ` (expected ${expectedAddr!})` : ''}` + : 'defaultReclaimAddress: not set', + }) + } + + return checks +} + +export async function getIssuanceAllocatorChecks( + client: PublicClient, + horizonBook: AddressBookOps, + issuanceBook: AddressBookOps, +): Promise { + const checks: IntegrationCheck[] = [] + + const iaAddress = issuanceBook.entryExists('IssuanceAllocator') + ? issuanceBook.getEntry('IssuanceAllocator')?.address + : null + const rmAddress = horizonBook.entryExists('RewardsManager') ? horizonBook.getEntry('RewardsManager')?.address : null + const gtAddress = horizonBook.entryExists('L2GraphToken') ? horizonBook.getEntry('L2GraphToken')?.address : null + + if (!iaAddress || !rmAddress || !gtAddress) return checks + + const rmSupportsTarget = await supportsInterface(client, rmAddress, IISSUANCE_TARGET_INTERFACE_ID) + checks.push({ ok: rmSupportsTarget, label: `RM implements IIssuanceTarget (${IISSUANCE_TARGET_INTERFACE_ID})` }) + + if (rmSupportsTarget) { + const activation = await checkIssuanceAllocatorActivation(client, iaAddress, rmAddress, gtAddress) + checks.push({ ok: activation.iaIntegrated, label: 'RM.issuanceAllocator == this' }) + checks.push({ ok: activation.iaMinter, label: 'GraphToken.MINTER_ROLE granted' }) + } else { + checks.push({ ok: null, label: 'RM.issuanceAllocator == this (RM not upgraded)' }) + checks.push({ ok: null, label: 'GraphToken.MINTER_ROLE granted (RM not upgraded)' }) + } + + try { + const targetCount = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getTargetCount', + })) as bigint + const hasDefaultTarget = targetCount > 0n + checks.push({ ok: hasDefaultTarget, label: 'defaultTarget configured' }) + } catch { + // Function not available + } + + // Confirm 100% allocation: getTotalAllocation().totalAllocationRate == issuancePerBlock. + // Once a real defaultTarget is set (issuance-connect), the contract reports + // exactly issuancePerBlock; if it doesn't, the default is still address(0) + // and some issuance is unallocated (not minted). Skipped (○) when + // issuancePerBlock is 0 — the IA hasn't been configured with a rate yet, + // so the question is not yet meaningful. + try { + const issuancePerBlock = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getIssuancePerBlock', + })) as bigint + const totalAllocation = (await client.readContract({ + address: iaAddress as `0x${string}`, + abi: ISSUANCE_ALLOCATOR_ABI, + functionName: 'getTotalAllocation', + })) as { totalAllocationRate: bigint; allocatorMintingRate: bigint; selfMintingRate: bigint } + if (issuancePerBlock === 0n) { + checks.push({ ok: null, label: '100% allocated (issuancePerBlock not set)' }) + } else { + const fullyAllocated = totalAllocation.totalAllocationRate === issuancePerBlock + checks.push({ + ok: fullyAllocated, + label: `100% allocated (${formatGRT(totalAllocation.totalAllocationRate)} of ${formatGRT(issuancePerBlock)})`, + }) + } + } catch { + // Function not available + } + + return checks +} + +export async function getRewardsEligibilityOracleChecks( + client: PublicClient, + horizonBook: AddressBookOps, + issuanceBook: AddressBookOps, + entryName: string, +): Promise { + const checks: IntegrationCheck[] = [] + + const reoAddress = issuanceBook.entryExists(entryName) ? issuanceBook.getEntry(entryName)?.address : null + const rmAddress = horizonBook.entryExists('RewardsManager') ? horizonBook.getEntry('RewardsManager')?.address : null + const controllerAddress = horizonBook.entryExists('Controller') ? horizonBook.getEntry('Controller')?.address : null + + if (!reoAddress || !rmAddress) return checks + + let governor: string | null = null + let pauseGuardian: string | null = null + if (controllerAddress) { + try { + governor = (await client.readContract({ + address: controllerAddress as `0x${string}`, + abi: [ + { + inputs: [], + name: 'getGovernor', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'getGovernor', + })) as string + } catch { + // Controller doesn't have getGovernor + } + try { + pauseGuardian = (await client.readContract({ + address: controllerAddress as `0x${string}`, + abi: [ + { + inputs: [], + name: 'pauseGuardian', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'pauseGuardian', + })) as string + } catch { + // Controller doesn't have pauseGuardian + } + } + + try { + const governorRole = (await client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'GOVERNOR_ROLE', + })) as `0x${string}` + + if (governor) { + const governorHasRole = (await client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'hasRole', + args: [governorRole, governor as `0x${string}`], + })) as boolean + checks.push({ ok: governorHasRole, label: 'governor has GOVERNOR_ROLE' }) + } + } catch { + // Role check not available + } + + try { + const pauseRole = (await client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'PAUSE_ROLE', + })) as `0x${string}` + + if (pauseGuardian) { + const pauseGuardianHasRole = (await client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'hasRole', + args: [pauseRole, pauseGuardian as `0x${string}`], + })) as boolean + checks.push({ ok: pauseGuardianHasRole, label: 'pause guardian has PAUSE_ROLE' }) + } + } catch { + // Role check not available + } + + const networkOperator = issuanceBook.entryExists('NetworkOperator') + ? (issuanceBook.getEntry('NetworkOperator')?.address ?? null) + : null + + try { + const operatorCheck = await checkOperatorRole(client, reoAddress, networkOperator) + const statusOk = networkOperator === null ? false : operatorCheck.ok + checks.push({ ok: statusOk, label: operatorCheck.message }) + } catch { + checks.push({ ok: null, label: 'OPERATOR_ROLE (check failed)' }) + } + + try { + const currentREO = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + functionName: 'getProviderEligibilityOracle', + })) as string + const configured = currentREO.toLowerCase() === reoAddress.toLowerCase() + checks.push({ ok: configured, label: 'RM.providerEligibilityOracle == this' }) + } catch { + // Function not available on old RM + } + + try { + const enabled = (await client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getEligibilityValidation', + })) as boolean + checks.push({ ok: enabled, label: 'eligibility validation enabled' }) + } catch { + // Function not available + } + + try { + const lastUpdate = (await client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getLastOracleUpdateTime', + })) as bigint + const hasUpdates = lastUpdate > 0n + checks.push({ ok: hasUpdates, label: 'oracle has processed updates' }) + } catch { + // Function not available + } + + return checks +} + +export async function getReclaimAddressChecks( + client: PublicClient, + horizonBook: AddressBookOps, + issuanceBook: AddressBookOps, +): Promise { + const checks: IntegrationCheck[] = [] + + const rmAddress = horizonBook.entryExists('RewardsManager') ? horizonBook.getEntry('RewardsManager')?.address : null + const reclaimAddress = issuanceBook.entryExists('ReclaimedRewards') + ? issuanceBook.getEntry('ReclaimedRewards')?.address + : null + + if (!rmAddress || !reclaimAddress) return checks + + try { + const defaultReclaim = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: REWARDS_MANAGER_ABI, + functionName: 'getDefaultReclaimAddress', + })) as string + const configured = defaultReclaim.toLowerCase() === reclaimAddress.toLowerCase() + checks.push({ ok: configured, label: 'configured as RM.defaultReclaimAddress' }) + } catch { + checks.push({ ok: false, label: 'configured as RM.defaultReclaimAddress' }) + } + + return checks +} + +// Minimal ABI for RecurringAgreementManager-specific view functions +const RECURRING_AGREEMENT_MANAGER_ABI = [ + { + inputs: [], + name: 'COLLECTOR_ROLE', + outputs: [{ type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'DATA_SERVICE_ROLE', + outputs: [{ type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getCollectorCount', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'paused', + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, +] as const + +export async function getRecurringAgreementManagerChecks( + client: PublicClient, + horizonBook: AddressBookOps, + issuanceBook: AddressBookOps, + ssBook: AddressBookOps, +): Promise { + const checks: IntegrationCheck[] = [] + + const ramAddress = issuanceBook.entryExists('RecurringAgreementManager') + ? issuanceBook.getEntry('RecurringAgreementManager')?.address + : null + if (!ramAddress) return checks + + // COLLECTOR_ROLE → RecurringCollector + const rcAddress = horizonBook.entryExists('RecurringCollector') + ? horizonBook.getEntry('RecurringCollector')?.address + : null + if (rcAddress) { + try { + const collectorRole = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: RECURRING_AGREEMENT_MANAGER_ABI, + functionName: 'COLLECTOR_ROLE', + })) as `0x${string}` + const hasRole = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [collectorRole, rcAddress as `0x${string}`], + })) as boolean + checks.push({ ok: hasRole, label: 'RecurringCollector has COLLECTOR_ROLE' }) + } catch { + // Role check not available + } + } + + // DATA_SERVICE_ROLE → SubgraphService + const ssAddress = ssBook?.entryExists('SubgraphService') ? ssBook.getEntry('SubgraphService')?.address : null + if (ssAddress) { + try { + const dataServiceRole = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: RECURRING_AGREEMENT_MANAGER_ABI, + functionName: 'DATA_SERVICE_ROLE', + })) as `0x${string}` + const hasRole = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: ACCESS_CONTROL_ENUMERABLE_ABI, + functionName: 'hasRole', + args: [dataServiceRole, ssAddress as `0x${string}`], + })) as boolean + checks.push({ ok: hasRole, label: 'SubgraphService has DATA_SERVICE_ROLE' }) + } catch { + // Role check not available + } + } + + // IssuanceAllocator + const iaAddress = issuanceBook.entryExists('IssuanceAllocator') + ? issuanceBook.getEntry('IssuanceAllocator')?.address + : null + try { + const currentIA = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: ISSUANCE_TARGET_ABI, + functionName: 'getIssuanceAllocator', + })) as string + const isSet = currentIA !== ZERO_ADDRESS + const matches = iaAddress ? currentIA.toLowerCase() === iaAddress.toLowerCase() : null + checks.push({ + ok: isSet ? matches : false, + label: isSet + ? `issuanceAllocator: ${formatAddress(currentIA)}${matches === false ? ` (expected ${formatAddress(iaAddress!)})` : ''}` + : 'issuanceAllocator: not set', + }) + } catch { + // Function not available + } + + // Provider eligibility oracle + try { + const reo = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + functionName: 'getProviderEligibilityOracle', + })) as string + const reoA = issuanceBook.entryExists('RewardsEligibilityOracleA') + ? issuanceBook.getEntry('RewardsEligibilityOracleA')?.address + : null + const isSet = reo !== ZERO_ADDRESS + const matchesA = reoA ? reo.toLowerCase() === reoA.toLowerCase() : null + checks.push({ + ok: isSet ? matchesA : null, + label: isSet + ? `providerEligibilityOracle: ${reo}${matchesA === false ? ' (not REO-A)' : matchesA ? ' (REO-A)' : ''}` + : 'providerEligibilityOracle: not set', + }) + } catch { + // Function not available + } + + // Paused state + try { + const paused = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: RECURRING_AGREEMENT_MANAGER_ABI, + functionName: 'paused', + })) as boolean + checks.push({ ok: !paused, label: paused ? 'PAUSED' : 'not paused' }) + } catch { + // Function not available + } + + // Collector count + try { + const count = (await client.readContract({ + address: ramAddress as `0x${string}`, + abi: RECURRING_AGREEMENT_MANAGER_ABI, + functionName: 'getCollectorCount', + })) as bigint + checks.push({ ok: null, label: `collectors: ${count}` }) + } catch { + // Function not available + } + + return checks +} + +// ============================================================================ +// Horizon / SubgraphService Contract Checks +// ============================================================================ + +// Minimal ABIs for contracts not in the abis.ts module +const PAUSABLE_ABI = [ + { inputs: [], name: 'paused', outputs: [{ type: 'bool' }], stateMutability: 'view', type: 'function' }, +] as const + +const PAUSE_GUARDIAN_ABI = [ + { + inputs: [{ name: '_pauseGuardian', type: 'address' }], + name: 'pauseGuardians', + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, +] as const + +const DISPUTE_MANAGER_ABI = [ + { inputs: [], name: 'arbitrator', outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' }, + { inputs: [], name: 'getDisputePeriod', outputs: [{ type: 'uint64' }], stateMutability: 'view', type: 'function' }, + { inputs: [], name: 'disputeDeposit', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function' }, + { + inputs: [], + name: 'getFishermanRewardCut', + outputs: [{ type: 'uint32' }], + stateMutability: 'view', + type: 'function', + }, + { inputs: [], name: 'maxSlashingCut', outputs: [{ type: 'uint32' }], stateMutability: 'view', type: 'function' }, + { inputs: [], name: 'subgraphService', outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' }, +] as const + +const SUBGRAPH_SERVICE_ABI = [ + { + inputs: [], + name: 'getProvisionTokensRange', + outputs: [{ type: 'uint256' }, { type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getDelegationRatio', + outputs: [{ type: 'uint32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'stakeToFeesRatio', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'curationFeesCut', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getDisputeManager', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getGraphTallyCollector', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { inputs: [], name: 'getCuration', outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' }, +] as const + +/** PPM denominator (1,000,000) for percentage display */ +const PPM = 1_000_000 + +export async function getRecurringCollectorChecks( + client: PublicClient, + address: string, + horizonBook: AddressBookOps, +): Promise { + const checks: IntegrationCheck[] = [] + + // Pause guardian + try { + const controllerAddress = horizonBook.entryExists('Controller') ? horizonBook.getEntry('Controller')?.address : null + if (controllerAddress) { + // pauseGuardian is a public storage variable auto-getter, not in IControllerToolshed + const pauseGuardian = (await client.readContract({ + address: controllerAddress as `0x${string}`, + abi: [ + { + inputs: [], + name: 'pauseGuardian', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + ] as const, + functionName: 'pauseGuardian', + })) as string + const isGuardian = (await client.readContract({ + address: address as `0x${string}`, + abi: PAUSE_GUARDIAN_ABI, + functionName: 'pauseGuardians', + args: [pauseGuardian as `0x${string}`], + })) as boolean + checks.push({ ok: isGuardian, label: `pauseGuardian: ${pauseGuardian} ${isGuardian ? '' : '(not set)'}` }) + } + } catch { + // Not available + } + + // Paused state + try { + const paused = (await client.readContract({ + address: address as `0x${string}`, + abi: PAUSABLE_ABI, + functionName: 'paused', + })) as boolean + checks.push({ ok: !paused, label: paused ? 'PAUSED' : 'not paused' }) + } catch { + // paused() not available + } + + // Thawing period + try { + const thawing = (await client.readContract({ + address: address as `0x${string}`, + abi: [ + { + inputs: [], + name: 'REVOKE_AUTHORIZATION_THAWING_PERIOD', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'REVOKE_AUTHORIZATION_THAWING_PERIOD', + })) as bigint + checks.push({ ok: null, label: `REVOKE_AUTHORIZATION_THAWING_PERIOD: ${thawing}` }) + } catch { + // Not available + } + + return checks +} + +export async function getDisputeManagerChecks( + client: PublicClient, + address: string, + horizonBook: AddressBookOps, + ssBook: AddressBookOps, +): Promise { + const checks: IntegrationCheck[] = [] + + async function dmRead(functionName: (typeof DISPUTE_MANAGER_ABI)[number]['name']): Promise { + try { + return (await client.readContract({ + address: address as `0x${string}`, + abi: DISPUTE_MANAGER_ABI, + functionName, + })) as T + } catch { + return null + } + } + + // Arbitrator + const arbitrator = await dmRead('arbitrator') + if (arbitrator !== null) { + checks.push({ ok: arbitrator !== ZERO_ADDRESS, label: `arbitrator: ${arbitrator}` }) + } + + // SubgraphService reference + const ss = await dmRead('subgraphService') + if (ss !== null) { + const expected = ssBook?.entryExists('SubgraphService') + ? (ssBook.getEntry('SubgraphService')?.address ?? null) + : null + const matches = expected ? ss.toLowerCase() === expected.toLowerCase() : null + checks.push({ + ok: ss !== ZERO_ADDRESS ? matches : false, + label: `subgraphService: ${ss}${matches === false && expected ? ` (expected ${expected})` : ''}`, + }) + } + + // Dispute period + const disputePeriod = await dmRead('getDisputePeriod') + if (disputePeriod !== null) { + checks.push({ ok: disputePeriod > 0n, label: `disputePeriod: ${disputePeriod}s` }) + } + + // Dispute deposit + const disputeDeposit = await dmRead('disputeDeposit') + if (disputeDeposit !== null) { + checks.push({ ok: disputeDeposit > 0n, label: `disputeDeposit: ${formatGRT(disputeDeposit)}` }) + } + + // Fisherman reward cut (PPM) + const fishermanCut = await dmRead('getFishermanRewardCut') + if (fishermanCut !== null) { + checks.push({ + ok: null, + label: `fishermanRewardCut: ${fishermanCut} (${((fishermanCut / PPM) * 100).toFixed(2)}%)`, + }) + } + + // Max slashing cut (PPM) + const maxSlashing = await dmRead('maxSlashingCut') + if (maxSlashing !== null) { + checks.push({ ok: null, label: `maxSlashingCut: ${maxSlashing} (${((maxSlashing / PPM) * 100).toFixed(2)}%)` }) + } + + return checks +} + +export async function getSubgraphServiceChecks( + client: PublicClient, + address: string, + horizonBook: AddressBookOps, + ssBook: AddressBookOps, +): Promise { + const checks: IntegrationCheck[] = [] + + async function ssRead(functionName: (typeof SUBGRAPH_SERVICE_ABI)[number]['name']): Promise { + try { + return (await client.readContract({ + address: address as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName, + })) as T + } catch { + return null + } + } + + // DisputeManager reference + const dm = await ssRead('getDisputeManager') + if (dm !== null) { + const expected = ssBook?.entryExists('DisputeManager') ? (ssBook.getEntry('DisputeManager')?.address ?? null) : null + const matches = expected ? dm.toLowerCase() === expected.toLowerCase() : null + checks.push({ + ok: dm !== ZERO_ADDRESS ? matches : false, + label: `disputeManager: ${dm}${matches === false && expected ? ` (expected ${expected})` : ''}`, + }) + } + + // GraphTallyCollector reference + const gtc = await ssRead('getGraphTallyCollector') + if (gtc !== null) { + const expected = horizonBook.entryExists('GraphTallyCollector') + ? (horizonBook.getEntry('GraphTallyCollector')?.address ?? null) + : null + const matches = expected ? gtc.toLowerCase() === expected.toLowerCase() : null + checks.push({ + ok: gtc !== ZERO_ADDRESS ? matches : false, + label: `graphTallyCollector: ${gtc}${matches === false && expected ? ` (expected ${expected})` : ''}`, + }) + } + + // Curation reference + const curation = await ssRead('getCuration') + if (curation !== null) { + const expected = horizonBook.entryExists('L2Curation') + ? (horizonBook.getEntry('L2Curation')?.address ?? null) + : null + const matches = expected ? curation.toLowerCase() === expected.toLowerCase() : null + checks.push({ + ok: curation !== ZERO_ADDRESS ? matches : false, + label: `curation: ${curation}${matches === false && expected ? ` (expected ${expected})` : ''}`, + }) + } + + // Provision tokens range + const provisionRange = await ssRead('getProvisionTokensRange') + if (provisionRange !== null) { + checks.push({ + ok: null, + label: `provisionTokensRange: [${formatGRT(provisionRange[0])}, ${formatGRT(provisionRange[1])}]`, + }) + } + + // Delegation ratio + const delegationRatio = await ssRead('getDelegationRatio') + if (delegationRatio !== null) { + checks.push({ ok: null, label: `delegationRatio: ${delegationRatio}` }) + } + + // Stake to fees ratio + const stakeToFees = await ssRead('stakeToFeesRatio') + if (stakeToFees !== null) { + checks.push({ ok: null, label: `stakeToFeesRatio: ${stakeToFees}` }) + } + + // Curation fees cut (PPM) + const curationCut = await ssRead('curationFeesCut') + if (curationCut !== null) { + checks.push({ + ok: null, + label: `curationFeesCut: ${curationCut} (${((Number(curationCut) / PPM) * 100).toFixed(2)}%)`, + }) + } + + return checks +} + +// ============================================================================ +// High-Level Status Display +// ============================================================================ + +/** + * Show detailed status for a single component from the registry. + * + * Displays: status line + proxy admin detail + contract-specific integration checks. + * This is the detail view shown when running `--tags IssuanceAllocator`. + */ +export async function showDetailedComponentStatus( + env: Environment, + contract: RegistryEntry, + options?: { showHints?: boolean }, +): Promise { + const chainId = await getTargetChainIdFromEnv(env) + const client = graph.getPublicClient(env) as PublicClient + + // Resolve address books + const horizonBook = graph.getHorizonAddressBook(chainId) + const addressBook = + contract.addressBook === 'horizon' ? horizonBook : getAddressBookForType(contract.addressBook, chainId) + + // Resolve ownership context + const ownershipCtx = await resolveOwnershipContext(client, env, chainId) + + // Get status line with detail + const result = await getContractStatusLine( + client, + contract.addressBook, + addressBook, + contract.name, + undefined, + ownershipCtx, + ) + env.showMessage(` ${result.line}`) + for (const line of formatWarnings(result.warnings)) { + env.showMessage(line) + } + // Show ProxyAdmin detail for OZ v5 transparent proxies (not old Graph proxies, + // which are controller-governed and don't expose owner()) + if (contract.proxyType !== 'graph') { + for (const line of formatProxyAdminDetail(result)) { + env.showMessage(line) + } + } + + // Verification status from address book + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (result.exists && (addressBook as any).entryExists(contract.name)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const entry = (addressBook as any).getEntry(contract.name) + if (entry.proxy) { + const proxyVerified = entry.proxyDeployment?.verified + const implVerified = entry.implementationDeployment?.verified + env.showMessage(` ${proxyVerified ? '✓' : '✗'} proxy verified${proxyVerified ? `: ${proxyVerified}` : ''}`) + env.showMessage(` ${implVerified ? '✓' : '✗'} impl verified${implVerified ? `: ${implVerified}` : ''}`) + } else { + const verified = entry.deployment?.verified + env.showMessage(` ${verified ? '✓' : '✗'} verified${verified ? `: ${verified}` : ''}`) + } + } + + const showHints = options?.showHints !== false + + // Contract-specific integration checks + if (!result.exists) { + if (showHints && contract.componentTag && contract.deployable) { + showLifecycleHints(env, contract, result) + } + return result + } + + const issuanceBook = contract.addressBook === 'issuance' ? addressBook : graph.getIssuanceAddressBook(chainId) + + let checks: IntegrationCheck[] = [] + if (contract.name === 'RewardsManager') { + checks = await getRewardsManagerChecks( + client, + horizonBook, + chainId, + issuanceBook, + graph.getSubgraphServiceAddressBook(chainId), + ) + } else if (contract.name === 'IssuanceAllocator') { + checks = await getIssuanceAllocatorChecks(client, horizonBook, issuanceBook) + } else if ( + contract.name === 'RewardsEligibilityOracleA' || + contract.name === 'RewardsEligibilityOracleB' || + contract.name === 'RewardsEligibilityOracleMock' + ) { + checks = await getRewardsEligibilityOracleChecks(client, horizonBook, issuanceBook, contract.name) + } else if (contract.name === 'RecurringAgreementManager') { + checks = await getRecurringAgreementManagerChecks( + client, + horizonBook, + issuanceBook, + graph.getSubgraphServiceAddressBook(chainId), + ) + } else if (contract.name === 'ReclaimedRewards') { + checks = await getReclaimAddressChecks(client, horizonBook, issuanceBook) + } else if (contract.name === 'RecurringCollector') { + const addr = horizonBook.entryExists('RecurringCollector') + ? horizonBook.getEntry('RecurringCollector')?.address + : null + if (addr) checks = await getRecurringCollectorChecks(client, addr, horizonBook) + } else if (contract.name === 'DisputeManager') { + const ssBook = graph.getSubgraphServiceAddressBook(chainId) + const addr = ssBook.entryExists('DisputeManager') ? ssBook.getEntry('DisputeManager')?.address : null + if (addr) checks = await getDisputeManagerChecks(client, addr, horizonBook, ssBook) + } else if (contract.name === 'SubgraphService') { + const ssBook = graph.getSubgraphServiceAddressBook(chainId) + const addr = ssBook.entryExists('SubgraphService') ? ssBook.getEntry('SubgraphService')?.address : null + if (addr) checks = await getSubgraphServiceChecks(client, addr, horizonBook, ssBook) + } + + for (const check of checks) { + env.showMessage(formatCheck(check)) + } + + // Lifecycle action hints + if (showHints && contract.componentTag && contract.deployable) { + showLifecycleHints(env, contract, result) + } + + return result +} + +/** + * Show available lifecycle actions and state-based hint for a component. + */ +function showLifecycleHints(env: Environment, contract: RegistryEntry, result: ContractStatusResult): void { + const tag = contract.componentTag! + + // State-based hint + if (!result.exists) { + env.showMessage(`\n → Not deployed. Run with: --tags ${tag},deploy`) + } else if (result.codeChanged && !result.hasPendingImplementation) { + env.showMessage(`\n → Code changed. Run with: --tags ${tag},deploy`) + } else if (result.hasPendingImplementation) { + env.showMessage(`\n → Pending implementation. Run with: --tags ${tag},upgrade`) + } else { + env.showMessage(`\n → Up to date`) + } + + // Available actions — use explicit list if provided, otherwise derive from metadata + let actions: readonly string[] + if (contract.lifecycleActions) { + actions = contract.lifecycleActions + } else { + const derived: string[] = ['deploy'] + if (contract.proxyType) derived.push('upgrade') + actions = derived + } + env.showMessage(` Actions: --tags ${tag},<${[...actions, 'all'].join('|')}>`) +} + +/** + * Show pending governance TX count with execute command if any exist. + * Call once at the end of a status display, not per-component. + */ +export function showPendingGovernanceTxs(env: Environment): void { + const count = countPendingGovernanceTxs(env.name) + if (count > 0) { + env.showMessage(`\n ⚠ ${count} pending governance TX(s)`) + env.showMessage(` Run: npx hardhat deploy:execute-governance --network ${env.name}`) + } +} diff --git a/packages/deployment/lib/sync-utils.ts b/packages/deployment/lib/sync-utils.ts index 4680158e4..dd8734d12 100644 --- a/packages/deployment/lib/sync-utils.ts +++ b/packages/deployment/lib/sync-utils.ts @@ -1,32 +1,47 @@ -import type { Artifact, Environment } from '@rocketh/core/types' +import { existsSync } from 'node:fs' + +import type { Environment } from '@rocketh/core/types' import type { DeploymentMetadata } from '@graphprotocol/toolshed/deployments' import { - loadContractsArtifact, - loadIssuanceArtifact, - loadOpenZeppelinArtifact, - loadSubgraphServiceArtifact, -} from './artifact-loaders.js' + autoDetectForkNetwork, + getAddressBookForType, + getForkNetwork, + getForkStateDir, + getForkTargetChainId, + getIssuanceAddressBookPath, + getTargetChainIdFromEnv, + isForkMode, +} from './address-book-utils.js' import { computeBytecodeHash } from './bytecode-utils.js' import { type AddressBookType, type ArtifactSource, type ContractMetadata, + type RegistryEntry, getAddressBookEntryName, getContractMetadata, + getContractsByAddressBook, } from './contract-registry.js' -import { getOnChainImplementation } from './deploy-implementation.js' +import { SpecialTags } from './deployment-tags.js' +import { + computeArtifactBytecodeHash, + getOnChainImplementation, + tryComputeArtifactBytecodeHash, + tryLoadArtifactFromSource, +} from './deploy-implementation.js' +import { toBlockNumber } from './deployment-metadata.js' import { graph } from '../rocketh/deploy.js' import type { AnyAddressBookOps } from './address-book-ops.js' /** * Format an address based on SHOW_ADDRESSES environment variable * - 0: return empty string (no addresses shown) - * - 1: return truncated address (0x1234567890...) + * - 1: return truncated address (0x1234...5678) * - 2 (default): return full address */ function formatAddress(address: string): string { - const showAddresses = process.env.SHOW_ADDRESSES ?? '1' + const showAddresses = process.env.SHOW_ADDRESSES ?? '2' if (showAddresses === '0') { return '' @@ -38,26 +53,6 @@ function formatAddress(address: string): string { } } -/** - * Load artifact from any supported source type - */ -function loadArtifactFromSource(source: ArtifactSource): Artifact | undefined { - try { - switch (source.type) { - case 'contracts': - return loadContractsArtifact(source.path, source.name) - case 'subgraph-service': - return loadSubgraphServiceArtifact(source.name) - case 'issuance': - return loadIssuanceArtifact(source.path) - case 'openzeppelin': - return loadOpenZeppelinArtifact(source.name) - } - } catch { - return undefined - } -} - // ============================================================================ // Sync Change Detection & Record Reconstruction // ============================================================================ @@ -109,15 +104,12 @@ export function checkShouldSync( // Check bytecode hash if deployment metadata exists const metadata = addressBook.getDeploymentMetadata(contractName) if (metadata?.bytecodeHash && artifact) { - const loadedArtifact = loadArtifactFromSource(artifact) - if (loadedArtifact?.deployedBytecode) { - const localHash = computeBytecodeHash(loadedArtifact.deployedBytecode) - if (metadata.bytecodeHash !== localHash) { - return { - shouldSync: false, - reason: 'local bytecode changed since deployment', - warning: `${contractName}: local bytecode differs from deployed (hash mismatch)`, - } + const localHash = tryComputeArtifactBytecodeHash(artifact) + if (localHash && metadata.bytecodeHash !== localHash) { + return { + shouldSync: false, + reason: 'local bytecode changed since deployment', + warning: `${contractName}: local bytecode differs from deployed (hash mismatch)`, } } } @@ -164,13 +156,13 @@ export function reconstructDeploymentRecord( } // Verify bytecode hash matches if available - const loadedArtifact = loadArtifactFromSource(artifact) + const loadedArtifact = tryLoadArtifactFromSource(artifact) if (!loadedArtifact) { return undefined } if (deploymentMetadata.bytecodeHash && loadedArtifact.deployedBytecode) { - const localHash = computeBytecodeHash(loadedArtifact.deployedBytecode) + const localHash = computeArtifactBytecodeHash(artifact) if (deploymentMetadata.bytecodeHash !== localHash) { // Bytecode has changed - cannot reconstruct reliably return undefined @@ -188,33 +180,85 @@ export function reconstructDeploymentRecord( } /** - * Create deployment metadata from a deployment result + * Check if local artifact bytecode differs from what was last deployed. * - * Helper to create DeploymentMetadata from rocketh deployment results - * for storage in address book. + * Compares the local artifact's bytecodeHash against the stored hash in the + * address book. The stored hash is recorded from the local artifact at deploy + * time, so this is a local-to-local comparison (no on-chain bytecode fetch). * - * @param txHash - Transaction hash of deployment - * @param argsData - ABI-encoded constructor arguments - * @param deployedBytecode - Deployed bytecode for hash computation - * @param blockNumber - Block number of deployment - * @param timestamp - Block timestamp (ISO 8601) + * @returns codeChanged flag and the computed localHash (needed for hashMatches checks) */ -export function createDeploymentMetadata( - txHash: string, - argsData: string, - deployedBytecode: string, - blockNumber?: number, - timestamp?: string, -): DeploymentMetadata { - return { - txHash, - argsData, - bytecodeHash: computeBytecodeHash(deployedBytecode), - ...(blockNumber !== undefined && { blockNumber }), - ...(timestamp && { timestamp }), +function checkCodeChanged( + artifactSource: ArtifactSource | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + addressBook: any, + contractName: string, +): { codeChanged: boolean; localHash?: string } { + if (!artifactSource) return { codeChanged: false } + + const localArtifact = tryLoadArtifactFromSource(artifactSource) + const localHash = tryComputeArtifactBytecodeHash(artifactSource) + + const deploymentMetadata = addressBook.getDeploymentMetadata(contractName) + if (deploymentMetadata?.bytecodeHash && localHash) { + return { codeChanged: localHash !== deploymentMetadata.bytecodeHash, localHash } + } + if (localArtifact?.deployedBytecode) { + // No stored bytecodeHash but artifact exists - untracked/legacy state + return { codeChanged: true, localHash } + } + return { codeChanged: false, localHash } +} + +/** + * Decide whether sync should seed rocketh's record from the local artifact. + * + * Seeding writes the local artifact's bytecode into rocketh's deployment + * record. That's correct when the artifact reflects what's deployed on-chain, + * and harmful when the artifact has drifted: rocketh's native bytecode + * comparison would then match its (just-seeded) record against the artifact + * and skip the redeploy that the drift demands — the address book never + * advances, and proxies that depend on the impl miss their pendingImplementation. + * + * Gate (only contracts we ourselves deploy carry the dedup-masking risk): + * - Synthetic names not in the registry → seed (proxy sync recurses with + * `${name}_Implementation` names that aren't real entries; the proxy path + * already has its own hashMatches gate before recursing). + * - Prerequisites → seed (deployed externally; never run through deployFn). + * - No artifact → seed (no local bytecode to compare against). + * + * Within the gated set: skip the seed only on a *verified mismatch* — i.e. + * we have a stored hash and the local artifact's hash differs. If there's no + * stored hash at all (no entry, or entry without a hash), fall through to + * the legacy seed: there's nothing to mask. + */ +export function shouldSeedRocketh( + spec: ContractSpec, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + addressBook: any, +): { seed: boolean; reason: string } { + const registered = getContractMetadata(spec.addressBookType, spec.name) + if (!registered) return { seed: true, reason: 'unregistered name (legacy seed)' } + if (spec.prerequisite) return { seed: true, reason: 'prerequisite (legacy seed)' } + if (!spec.artifact) return { seed: true, reason: 'no artifact (legacy seed)' } + + if (!addressBook?.entryExists?.(spec.name)) { + return { seed: true, reason: 'no entry, nothing to mask (legacy seed)' } } + + const storedHash = addressBook.getDeploymentMetadata?.(spec.name)?.bytecodeHash + const { codeChanged, localHash } = checkCodeChanged(spec.artifact, addressBook, spec.name) + + if (!storedHash || !localHash) return { seed: true, reason: 'no hash to compare (legacy seed)' } + if (codeChanged) return { seed: false, reason: 'artifact unverified vs. address book' } + return { seed: true, reason: 'artifact verified' } } +/** + * Proxy admin ownership state + */ +export type ProxyAdminOwner = 'governor' | 'deployer' | 'other' | 'unknown' + /** * Input for proxy status line generation */ @@ -233,6 +277,8 @@ interface ProxyStatusInput { syncNotes?: string[] /** Whether local bytecode differs from deployed (shows △ icon) */ codeChanged?: boolean + /** ProxyAdmin ownership state — 'deployer' shows 🔑 warning icon */ + proxyAdminOwner?: ProxyAdminOwner } /** @@ -270,9 +316,13 @@ function formatProxyStatusLine(input: ProxyStatusInput): ProxyStatusResult { notes.push('code changed') } + // ProxyAdmin ownership warning: 🔑 when known to be non-governor (deployer or other) + const adminIcon = + input.proxyAdminOwner && input.proxyAdminOwner !== 'governor' && input.proxyAdminOwner !== 'unknown' ? ' 🔑' : '' + // Format the line const suffix = notes.length > 0 ? ` (${notes.join(', ')})` : '' - const line = `${codeIcon} ${statusIcon} ${input.name} @ ${formatAddress(input.proxyAddress)} → ${formatAddress(input.implAddress)}${suffix}` + const line = `${codeIcon} ${statusIcon} ${input.name} @ ${formatAddress(input.proxyAddress)} → ${formatAddress(input.implAddress)}${suffix}${adminIcon}` return { line } } @@ -293,6 +343,9 @@ export interface ContractSpec { artifact?: ArtifactSource /** If true, address-only placeholder (code not required) */ addressOnly?: boolean + /** ABI-encoded constructor args from address book deployment metadata. + * Used to seed rocketh records with real argsData instead of '0x'. */ + deploymentArgsData?: string /** Proxy sync fields (if present, will sync implementation with on-chain) */ proxy?: { proxyAdminAddress: string @@ -342,6 +395,15 @@ export function buildContractSpec( throw new Error(`${addressBookEntryName} not found in address book for chainId ${targetChainId}`) } + // Get deployment argsData from address book for accurate rocketh record seeding + let deploymentArgsData: string | undefined + if (entry) { + const deploymentMeta = entry.proxy ? entry.implementationDeployment : entry.deployment + if (deploymentMeta?.argsData && deploymentMeta.argsData !== '0x') { + deploymentArgsData = deploymentMeta.argsData + } + } + const spec: ContractSpec = { name: contractName, addressBookType, @@ -349,6 +411,7 @@ export function buildContractSpec( prerequisite: metadata.prerequisite ?? false, artifact: metadata.artifact, addressOnly: metadata.addressOnly, + deploymentArgsData, } // Add proxy configuration if this is a proxied contract @@ -395,7 +458,7 @@ export interface SyncResult { /** * Sync a single contract - returns status and whether it succeeded */ -async function syncContract( +export async function syncContract( env: Environment, // eslint-disable-next-line @typescript-eslint/no-explicit-any client: any, @@ -463,20 +526,13 @@ async function syncContract( // Get updated entry for formatProxyStatusLine const updatedEntry = spec.proxy.addressBook.getEntry(spec.name) - // Check if local bytecode differs from deployed (via bytecodeHash) - // If artifact exists but no bytecodeHash stored, assume code changed (untracked state) - let codeChanged = false - if (spec.proxy.artifact) { - const deploymentMetadata = spec.proxy.addressBook.getDeploymentMetadata(spec.name) - const localArtifact = loadArtifactFromSource(spec.proxy.artifact) - if (deploymentMetadata?.bytecodeHash && localArtifact?.deployedBytecode) { - const localHash = computeBytecodeHash(localArtifact.deployedBytecode) - codeChanged = localHash !== deploymentMetadata.bytecodeHash - } else if (localArtifact?.deployedBytecode) { - // No stored bytecodeHash but artifact exists - untracked/legacy state - codeChanged = true - } - } + const pendingImpl = updatedEntry.pendingImplementation + const implAddress = pendingImpl?.address ?? updatedEntry.implementation + const implDeployment = pendingImpl + ? pendingImpl.deployment + : spec.proxy.addressBook.getDeploymentMetadata(spec.name) + + const { codeChanged, localHash } = checkCodeChanged(spec.proxy.artifact, spec.proxy.addressBook, spec.name) const result = formatProxyStatusLine({ name: spec.name, @@ -507,36 +563,29 @@ async function syncContract( if (!existing) { // No existing record - create from artifact + // IMPORTANT: For proxy contracts, we only load the ABI, not bytecode + // The artifact is for the implementation, not the proxy itself let abi: readonly unknown[] = [] - let bytecode: `0x${string}` = '0x' - let deployedBytecode: `0x${string}` | undefined if (spec.artifact) { - const artifact = loadArtifactFromSource(spec.artifact) + const artifact = tryLoadArtifactFromSource(spec.artifact) if (artifact?.abi) { abi = artifact.abi } - if (artifact?.bytecode) { - bytecode = artifact.bytecode as `0x${string}` - } - if (artifact?.deployedBytecode) { - deployedBytecode = artifact.deployedBytecode as `0x${string}` - } } await env.save(spec.name, { address: spec.address as `0x${string}`, abi: abi as typeof abi & readonly unknown[], - bytecode, - deployedBytecode, + bytecode: '0x' as `0x${string}`, // Don't store impl bytecode for proxy record + deployedBytecode: undefined, argsData: '0x' as `0x${string}`, metadata: '', } as unknown as Parameters[1]) } else if (addressChanged) { - // Address changed - update address but preserve existing bytecode - // This handles the case where address book points to new address + // Address changed - update address and clear bytecode (proxy address changed) let abi: readonly unknown[] = existing.abi as readonly unknown[] // Update ABI from artifact if available (ABI doesn't affect change detection) if (spec.artifact) { - const artifact = loadArtifactFromSource(spec.artifact) + const artifact = tryLoadArtifactFromSource(spec.artifact) if (artifact?.abi) { abi = artifact.abi } @@ -544,10 +593,10 @@ async function syncContract( await env.save(spec.name, { address: spec.address as `0x${string}`, abi: abi as typeof abi & readonly unknown[], - bytecode: existing.bytecode as `0x${string}`, - deployedBytecode: existing.deployedBytecode as `0x${string}`, - argsData: existing.argsData as `0x${string}`, - metadata: existing.metadata ?? '', + bytecode: '0x' as `0x${string}`, // Clear bytecode - proxy changed + deployedBytecode: undefined, + argsData: '0x' as `0x${string}`, + metadata: '', } as unknown as Parameters[1]) } // else: existing record with same address - do nothing, preserve rocketh's state @@ -569,9 +618,7 @@ async function syncContract( const existingProxyDeployment = env.getOrNull(proxyDeploymentName) if (existingProxyDeployment?.argsData && existingProxyDeployment.argsData !== '0x') { const entry = spec.proxy.addressBook.getEntry(spec.name) - const proxyRockethBlockNumber = existingProxyDeployment.receipt?.blockNumber - ? parseInt(existingProxyDeployment.receipt.blockNumber as string) - : undefined + const proxyRockethBlockNumber = toBlockNumber(existingProxyDeployment.receipt?.blockNumber) const proxyAddressBookBlockNumber = entry.proxyDeployment?.blockNumber // Backfill if: @@ -611,7 +658,7 @@ async function syncContract( let proxyAdminAbi: readonly unknown[] = [] const proxyAdminMetadata = getContractMetadata(spec.addressBookType, proxyAdminDeploymentName) if (proxyAdminMetadata?.artifact) { - const proxyAdminArtifact = loadArtifactFromSource(proxyAdminMetadata.artifact) + const proxyAdminArtifact = tryLoadArtifactFromSource(proxyAdminMetadata.artifact) if (proxyAdminArtifact?.abi) { proxyAdminAbi = proxyAdminArtifact.abi } @@ -625,48 +672,56 @@ async function syncContract( } as unknown as Parameters[1]) } - // Save implementation deployment record - // Pick pending or current - both have same structure (address + deployment metadata) - const pendingImpl = updatedEntry.pendingImplementation - const implAddress = pendingImpl?.address ?? updatedEntry.implementation - const implDeployment = pendingImpl - ? pendingImpl.deployment - : spec.proxy.addressBook.getDeploymentMetadata(spec.name) - + // Save implementation deployment record (if local hash matches stored) if (implAddress) { const storedHash = implDeployment?.bytecodeHash - - // Only sync if stored hash matches local artifact let hashMatches = false - if (storedHash && spec.proxy.artifact) { - const localArtifact = loadArtifactFromSource(spec.proxy.artifact) - if (localArtifact?.deployedBytecode) { - const localHash = computeBytecodeHash(localArtifact.deployedBytecode) - if (storedHash === localHash) { - hashMatches = true - } else { - syncNotes.push('impl outdated') - } - } + + if (storedHash && localHash) { + hashMatches = storedHash === localHash } + // When hash doesn't match, leave the existing rocketh record untouched. + // The old record (with real bytecode from the previous deploy) lets rocketh + // correctly detect the bytecode change and trigger a fresh deployment. + // NOTE: Do NOT clear the record to bytecode '0x' — rocketh's CBOR-stripping + // comparison treats '0x' as NaN length, causing slice(0, NaN) → '' for both + // old and new bytecodes, making them falsely compare as equal. + if (hashMatches) { const implResult = await syncContract(env, client, { name: `${spec.name}_Implementation`, addressBookType: spec.addressBookType, address: implAddress, prerequisite: true, + artifact: spec.proxy.artifact, }) if (!implResult.success) { return implResult } + // Patch implementation record with deployment metadata for accurate + // rocketh comparison. syncContract creates bare records without argsData, + // but rocketh's deploy() compares argsData to decide if redeployment is + // needed. Without the real argsData, rocketh falsely detects a change + // and redeploys implementations that haven't changed. + const implRecordName = `${spec.name}_Implementation` + const implRecord = env.getOrNull(implRecordName) + if (implRecord && implDeployment?.argsData && (!implRecord.argsData || implRecord.argsData === '0x')) { + await env.save(implRecordName, { + address: implRecord.address as `0x${string}`, + abi: implRecord.abi as typeof implRecord.abi & readonly unknown[], + bytecode: (implRecord.bytecode ?? '0x') as `0x${string}`, + deployedBytecode: implRecord.deployedBytecode as `0x${string}` | undefined, + argsData: implDeployment.argsData as `0x${string}`, + metadata: (implRecord as Record).metadata ?? '', + } as unknown as Parameters[1]) + } + // Backfill address book metadata from rocketh if rocketh is newer const rockethImpl = env.getOrNull(`${spec.name}_Implementation`) if (rockethImpl?.argsData && rockethImpl.argsData !== '0x') { - const rockethBlockNumber = rockethImpl.receipt?.blockNumber - ? parseInt(rockethImpl.receipt.blockNumber as string) - : undefined + const rockethBlockNumber = toBlockNumber(rockethImpl.receipt?.blockNumber) const bookBlockNumber = implDeployment?.blockNumber // Backfill if: @@ -681,10 +736,13 @@ async function syncContract( rockethBlockNumber > bookBlockNumber) if (rockethIsNewer) { + // Hash from the artifact (with library resolution) so the stored value stays + // in lockstep with checkShouldSync's artifact-side comparison. Hashing + // rocketh's linked `deployedBytecode` would diverge for library-using impls. const metadata: DeploymentMetadata = { txHash: rockethImpl.transaction?.hash ?? '', argsData: rockethImpl.argsData, - bytecodeHash: rockethImpl.deployedBytecode ? computeBytecodeHash(rockethImpl.deployedBytecode) : '', + bytecodeHash: tryComputeArtifactBytecodeHash(spec.proxy.artifact) ?? '', ...(rockethBlockNumber !== undefined && { blockNumber: rockethBlockNumber }), } // Write to correct location based on pending vs current @@ -742,36 +800,52 @@ async function syncContract( statusNotes.push('re-imported') } + // Decide whether to seed rocketh's record from the local artifact (see + // `shouldSeedRocketh` for the rationale and gate). + const chainIdForVerify = await getTargetChainIdFromEnv(env) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const addressBookForVerify: any = getAddressBookForType(spec.addressBookType, chainIdForVerify) + const seedDecision = shouldSeedRocketh(spec, addressBookForVerify) + if (!existing) { - // No existing record - create from artifact - let abi: readonly unknown[] = [] - let bytecode: `0x${string}` = '0x' - let deployedBytecode: `0x${string}` | undefined - if (spec.artifact) { - const artifact = loadArtifactFromSource(spec.artifact) - if (artifact?.abi) { - abi = artifact.abi - } - if (artifact?.bytecode) { - bytecode = artifact.bytecode as `0x${string}` - } - if (artifact?.deployedBytecode) { - deployedBytecode = artifact.deployedBytecode as `0x${string}` + if (seedDecision.seed) { + // Either no artifact to compare (legacy/external entry) or hash verified — + // safe to seed rocketh from the artifact. + let abi: readonly unknown[] = [] + let bytecode: `0x${string}` = '0x' + let deployedBytecode: `0x${string}` | undefined + if (spec.artifact) { + const artifact = tryLoadArtifactFromSource(spec.artifact) + if (artifact?.abi) { + abi = artifact.abi + } + if (artifact?.bytecode) { + bytecode = artifact.bytecode as `0x${string}` + } + if (artifact?.deployedBytecode) { + deployedBytecode = artifact.deployedBytecode as `0x${string}` + } } + await env.save(spec.name, { + address: spec.address as `0x${string}`, + abi: abi as typeof abi & readonly unknown[], + bytecode, + deployedBytecode, + argsData: (spec.deploymentArgsData ?? '0x') as `0x${string}`, + metadata: '', + } as unknown as Parameters[1]) + } else { + // Cannot verify artifact matches what's on-chain — leave the rocketh + // record absent so the next deployFn detects no prior bytecode and + // deploys fresh. Seeding from a stale or new artifact would mask the + // drift: rocketh would compare new artifact to itself and skip redeploy. + statusNotes.push(`seed skipped (${seedDecision.reason})`) } - await env.save(spec.name, { - address: spec.address as `0x${string}`, - abi: abi as typeof abi & readonly unknown[], - bytecode, - deployedBytecode, - argsData: '0x' as `0x${string}`, - metadata: '', - } as unknown as Parameters[1]) } else if (addressChanged) { // Address changed - update address but preserve existing bytecode let abi: readonly unknown[] = existing.abi as readonly unknown[] if (spec.artifact) { - const artifact = loadArtifactFromSource(spec.artifact) + const artifact = tryLoadArtifactFromSource(spec.artifact) if (artifact?.abi) { abi = artifact.abi } @@ -787,11 +861,100 @@ async function syncContract( } // else: existing record with same address - do nothing, preserve rocketh's state + // Backfill deployment metadata from rocketh → address book (mirrors proxy backfill) + // Only for real registry entries — skip synthetic names (e.g. HorizonStaking_Implementation) + // created by proxy sync as rocketh-only records + const registryMetadata = getContractMetadata(spec.addressBookType, spec.name) + const rockethRecord = env.getOrNull(spec.name) + if (registryMetadata && rockethRecord?.argsData && rockethRecord.argsData !== '0x') { + const chainId = await getTargetChainIdFromEnv(env) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const addressBook: any = getAddressBookForType(spec.addressBookType, chainId) + const entry = addressBook.getEntry(spec.name) + const rockethBlockNumber = toBlockNumber(rockethRecord.receipt?.blockNumber) + const addressBookBlockNumber = entry.deployment?.blockNumber + + const rockethIsNewer = + !entry.deployment?.argsData || + (rockethBlockNumber !== undefined && addressBookBlockNumber === undefined) || + (rockethBlockNumber !== undefined && + addressBookBlockNumber !== undefined && + rockethBlockNumber > addressBookBlockNumber) + + if (rockethIsNewer) { + // Hash from the artifact (with library resolution) so the stored value stays in lockstep + // with checkShouldSync's artifact-side comparison. Hashing rocketh's linked + // `deployedBytecode` would diverge for library-using contracts. + const deploymentMetadata: DeploymentMetadata = { + txHash: rockethRecord.transaction?.hash ?? '', + argsData: rockethRecord.argsData, + bytecodeHash: tryComputeArtifactBytecodeHash(spec.artifact) ?? '', + ...(rockethBlockNumber !== undefined && { blockNumber: rockethBlockNumber }), + } + addressBook.setDeploymentMetadata(spec.name, deploymentMetadata) + statusNotes.push('backfilled metadata') + } + } + // Format status line for non-proxy contracts (two-column format with blank status icon position) const statusSuffix = statusNotes.length > 0 ? ` (${statusNotes.join(', ')})` : '' return { success: true, status: `✓ ${nonProxySyncIcon} ${spec.name} @ ${formatAddress(spec.address)}${statusSuffix}` } } +/** + * Options for sync display filtering + */ +export interface SyncOptions { + /** + * Tags requested in the deploy command (e.g., ['IssuanceAllocator:deploy', 'sync']). + * When set, only contracts matching these tags or with detected changes are displayed. + * Sync still runs for all contracts regardless of filter. + */ + tagFilter?: string[] +} + +/** + * Extract component names from deployment tags. + * + * Strips action suffixes (e.g., 'IssuanceAllocator:deploy' → 'IssuanceAllocator') + * and filters out the special 'sync' tag. + */ +function extractComponentNames(tags: string[]): Set { + const components = new Set() + for (const tag of tags) { + if (tag === SpecialTags.SYNC) continue + components.add(tag.split(':')[0]) + } + return components +} + +/** + * Check whether a sync status line indicates changes were detected. + * + * Icons: ↑ upgraded, ↻ synced/re-imported, ◷ pending, △ code changed + * Parenthetical notes also indicate notable state (but not "(not deployed)"). + */ +function statusHasChanges(status: string): boolean { + if (/[↑↻◷△]/.test(status)) return true + if (status.includes('(') && !status.includes('(not deployed)')) return true + return false +} + +/** + * Determine whether a contract's sync result should be displayed. + */ +function shouldDisplay( + spec: ContractSpec, + result: { success: boolean; status: string }, + filterComponents: Set | null, +): boolean { + if (!filterComponents) return true + if (!result.success) return true + if (statusHasChanges(result.status)) return true + const metadata = getContractMetadata(spec.addressBookType, spec.name) + return !!metadata?.componentTag && filterComponents.has(metadata.componentTag) +} + /** * Sync contract groups with on-chain state * @@ -800,34 +963,234 @@ async function syncContract( * - Import contract addresses into rocketh deployment records * - Validate prerequisites exist on-chain * - Show code changed indicator (△) when local bytecode differs from deployed + * + * When options.tagFilter is set, only contracts matching the requested tags + * or with detected changes are displayed. Sync still runs for all contracts. */ -export async function syncContractGroups(env: Environment, groups: AddressBookGroup[]): Promise { +export async function syncContractGroups( + env: Environment, + groups: AddressBookGroup[], + options?: SyncOptions, +): Promise { const client = graph.getPublicClient(env) const failures: string[] = [] let totalSynced = 0 + // Build component filter from tags (null = no filtering) + const filterComponents = + options?.tagFilter && options.tagFilter.length > 0 ? extractComponentNames(options.tagFilter) : null + const isFiltering = filterComponents !== null && filterComponents.size > 0 + let totalSuppressed = 0 + for (const group of groups) { - env.showMessage(`\n📦 ${group.label}`) + // Buffer results so we can filter display without affecting sync + const results: Array<{ spec: ContractSpec; result: { success: boolean; status: string } }> = [] for (const spec of group.contracts) { const result = await syncContract(env, client, spec) + results.push({ spec, result }) - env.showMessage(` ${result.status}`) if (!result.success) { failures.push(spec.name) } else { totalSynced++ - // For proxies, syncContract also syncs the implementation internally if (spec.proxy) { - totalSynced++ // Count the implementation sync + totalSynced++ } } } + + // Filter which results to display + const visible = isFiltering + ? results.filter(({ spec, result }) => shouldDisplay(spec, result, filterComponents)) + : results + const suppressed = results.length - visible.length + totalSuppressed += suppressed + + if (visible.length > 0) { + env.showMessage(`\n📦 ${group.label}`) + for (const { result } of visible) { + env.showMessage(` ${result.status}`) + } + if (suppressed > 0) { + env.showMessage(` ... ${suppressed} unchanged`) + } + } + } + + if (isFiltering && totalSuppressed > 0) { + env.showMessage(`\n ... ${totalSuppressed} unchanged contracts hidden (--tags sync for full output)`) } return { success: failures.length === 0, totalSynced, failures } } +/** + * Sync a single component from the contract registry with on-chain state. + * + * Resolves the address book, builds a ContractSpec, and runs the same sync + * logic as the full sync script — reading on-chain state to confirm and + * propagate reality into address books and rocketh records. + * + * Components call this immediately before and after mutating actions so the + * action operates on a confirmed-fresh view, without requiring a separate + * global sync to have run first. + */ +export async function syncComponentFromRegistry(env: Environment, contract: RegistryEntry): Promise { + const chainId = await getTargetChainIdFromEnv(env) + const addressBook = getAddressBookForType(contract.addressBook, chainId) + const metadata = getContractMetadata(contract.addressBook, contract.name) + if (!metadata) { + throw new Error(`Contract '${contract.name}' not found in ${contract.addressBook} registry`) + } + + const spec = buildContractSpec(contract.addressBook, contract.name, metadata, addressBook, chainId) + const client = graph.getPublicClient(env) + const result = await syncContract(env, client, spec) + + env.showMessage(` ${result.status}`) + if (!result.success) { + throw new Error(`Sync failed for ${contract.name}: ${result.status}`) + } +} + +/** + * Sync multiple components from the contract registry with on-chain state. + * + * Convenience wrapper around `syncComponentFromRegistry` for scripts that need + * a small set of contracts in sync before they read them — typically the + * contract being acted on plus its direct on-chain prerequisites (Controller, + * shared implementations, etc.). + */ +export async function syncComponentsFromRegistry(env: Environment, contracts: RegistryEntry[]): Promise { + for (const contract of contracts) { + await syncComponentFromRegistry(env, contract) + } +} + +/** + * Run the full address book sync across every deployable contract in every + * address book (Horizon, SubgraphService, Issuance). + * + * This is the implementation behind both the `00_sync.ts` deploy script (run + * via `--tags sync`) and the `deploy:sync` Hardhat task. Orchestration scripts + * that need many contracts in sync before they run (e.g. the GIP-0088 upgrade + * batch builder) call this directly instead of relying on a tag dependency. + * + * On failure, exits the process with code 1 after printing remediation hints. + */ +export async function runFullSync(env: Environment): Promise { + // Get chainId from provider (will be 31337 in fork mode) + const chainIdHex = await env.network.provider.request({ method: 'eth_chainId' }) + const providerChainId = Number(chainIdHex) + + // Auto-detect fork network from anvil if not explicitly set + if (providerChainId === 31337 && !getForkNetwork(env.name)) { + const detected = await autoDetectForkNetwork() + if (detected) { + env.showMessage(`\n🔍 Auto-detected fork network: ${detected}`) + } + } + + // Determine target chain ID for address book lookups + const forkNetwork = getForkNetwork(env.name) + const isForking = isForkMode(env.name) + const forkChainId = getForkTargetChainId(env.name) + const targetChainId = forkChainId ?? providerChainId + + // Check for common misconfiguration: localhost without FORK_NETWORK and not a detectable fork + if (providerChainId === 31337 && !forkNetwork) { + throw new Error( + `Running on localhost (chainId 31337) without FORK_NETWORK set.\n\n` + + `If you're testing against a forked network, set the environment variable:\n` + + ` export FORK_NETWORK=arbitrumSepolia\n` + + ` npx hardhat deploy:sync --network localhost\n\n` + + `Or use ephemeral fork mode:\n` + + ` HARDHAT_FORK=arbitrumSepolia npx hardhat deploy:sync`, + ) + } + + if (forkNetwork) { + const forkStateDir = getForkStateDir(env.name, forkNetwork) + env.showMessage(`\n🔄 Sync: ${forkNetwork} fork (chainId: ${targetChainId})`) + env.showMessage(` Using fork-local address books (${forkStateDir}/)`) + } else { + env.showMessage(`\n🔄 Sync: ${env.name} (chainId: ${providerChainId})`) + } + + // Get address books (automatically uses fork-local copies in fork mode) + const horizonAddressBook = graph.getHorizonAddressBook(targetChainId) + const ssAddressBook = graph.getSubgraphServiceAddressBook(targetChainId) + + const groups: AddressBookGroup[] = [] + + // --- Horizon contracts --- + const horizonContracts: ContractSpec[] = getDeployableContracts('horizon').map((name) => { + const metadata = getContractMetadata('horizon', name) + if (!metadata) throw new Error(`Contract ${name} not found in horizon registry`) + return buildContractSpec('horizon', name, metadata, horizonAddressBook, targetChainId) + }) + groups.push({ label: 'Horizon', contracts: horizonContracts, addressBook: horizonAddressBook }) + + // --- SubgraphService contracts --- + const ssContracts: ContractSpec[] = getDeployableContracts('subgraph-service').map((name) => { + const metadata = getContractMetadata('subgraph-service', name) + if (!metadata) throw new Error(`Contract ${name} not found in subgraph-service registry`) + return buildContractSpec('subgraph-service', name, metadata, ssAddressBook, targetChainId) + }) + groups.push({ label: 'SubgraphService', contracts: ssContracts, addressBook: ssAddressBook }) + + // --- Issuance contracts --- + const issuanceBookPath = getIssuanceAddressBookPath() + const issuanceAddressBook = existsSync(issuanceBookPath) ? graph.getIssuanceAddressBook(targetChainId) : null + + if (issuanceAddressBook) { + const issuanceContracts: ContractSpec[] = getDeployableContracts('issuance').map((name) => { + const metadata = getContractMetadata('issuance', name) + if (!metadata) throw new Error(`Contract ${name} not found in issuance registry`) + return buildContractSpec('issuance', name, metadata, issuanceAddressBook, targetChainId) + }) + if (issuanceContracts.length > 0) { + groups.push({ label: 'Issuance', contracts: issuanceContracts, addressBook: issuanceAddressBook }) + } + } + + // Parse --tags from process.argv to filter sync display when invoked via + // `hardhat deploy --tags ...` (does nothing for the standalone deploy:sync task) + const tagsIndex = process.argv.indexOf('--tags') + const requestedTags = + tagsIndex !== -1 && tagsIndex < process.argv.length - 1 ? process.argv[tagsIndex + 1].split(',') : [] + + const syncOptions: SyncOptions = requestedTags.length > 0 ? { tagFilter: requestedTags } : {} + + const result = await syncContractGroups(env, groups, syncOptions) + + if (!result.success) { + env.showMessage(`\n❌ Sync failed: address book does not match chain state.\n`) + env.showMessage(`The following contracts are in address book but have no code on-chain:`) + env.showMessage(` ${result.failures.join(', ')}\n`) + if (isForking) { + env.showMessage(`This is likely because the fork was restarted.\n`) + env.showMessage(`To fix, reset fork state and re-run:`) + env.showMessage(` npx hardhat deploy:reset-fork --network localhost`) + } else { + env.showMessage(`Possible causes:`) + env.showMessage(` 1. Address book has incorrect addresses for this network`) + env.showMessage(` 2. Running against wrong network`) + } + process.exit(1) + } + + env.showMessage(`\n✅ Sync complete: ${result.totalSynced} contracts synced\n`) +} + +/** Filter deployable contracts from a registry namespace. */ +function getDeployableContracts(addressBook: AddressBookType): string[] { + return getContractsByAddressBook(addressBook) + .filter(([_, metadata]) => metadata.deployable !== false) + .map(([name]) => name) +} + /** * Contract status result (read-only, no sync operations) */ @@ -838,6 +1201,64 @@ export interface ContractStatusResult { exists: boolean /** Optional warnings (e.g., address book stale) */ warnings?: string[] + /** Proxy admin ownership state (only for proxied contracts) */ + proxyAdminOwner?: ProxyAdminOwner + /** Proxy admin address (only for proxied contracts) */ + proxyAdminAddress?: string + /** Proxy admin owner address (only for proxied contracts with on-chain query) */ + proxyAdminOwnerAddress?: string + /** Whether local compiled bytecode differs from deployed bytecode */ + codeChanged?: boolean + /** Whether a pending implementation upgrade exists */ + hasPendingImplementation?: boolean +} + +/** + * Options for querying proxy admin ownership during status checks + */ +export interface ProxyAdminOwnershipContext { + /** Governor address (from Controller) — required */ + governor: string + /** Deployer address (from named accounts) — optional, used for labelling */ + deployer?: string +} + +/** + * Query ProxyAdmin ownership and classify as governor/deployer/unknown + * + * The 🔑 warning icon is shown for anything NOT governor-owned. + * Deployer detection is best-effort (only when deployer address is known). + */ +async function queryProxyAdminOwnership( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + client: any, + proxyAdminAddress: string, + ctx: ProxyAdminOwnershipContext, +): Promise<{ owner: ProxyAdminOwner; ownerAddress: string }> { + try { + const ownerAddress = (await client.readContract({ + address: proxyAdminAddress as `0x${string}`, + abi: [ + { + inputs: [], + name: 'owner', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'owner', + })) as string + + if (ownerAddress.toLowerCase() === ctx.governor.toLowerCase()) { + return { owner: 'governor', ownerAddress } + } else if (ctx.deployer && ownerAddress.toLowerCase() === ctx.deployer.toLowerCase()) { + return { owner: 'deployer', ownerAddress } + } + return { owner: 'other', ownerAddress } + } catch { + return { owner: 'unknown', ownerAddress: '' } + } } /** @@ -845,12 +1266,14 @@ export interface ContractStatusResult { * * Returns a formatted status line similar to sync output: * - ✓ = ok, △ = code changed, ◷ = pending upgrade, ○ = not deployed, ❌ = error + * - 🔑 = ProxyAdmin still owned by deployer (not yet transferred to governor) * * @param client - Viem public client * @param addressBookType - Which address book this contract belongs to * @param addressBook - Address book instance * @param contractName - Name of the contract in the registry * @param metadata - Contract metadata from registry (optional, will look up if not provided) + * @param ownershipCtx - Governor/deployer context for proxy admin ownership checks */ export async function getContractStatusLine( // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -860,6 +1283,7 @@ export async function getContractStatusLine( addressBook: any, contractName: string, metadata?: ContractMetadata, + ownershipCtx?: ProxyAdminOwnershipContext, ): Promise { const meta = metadata ?? getContractMetadata(addressBookType, contractName) const entryName = getAddressBookEntryName(addressBookType, contractName) @@ -875,6 +1299,17 @@ export async function getContractStatusLine( return { line: `✓ ${contractName} @ ${formatAddress(entry.address)}`, exists: true } } + // If no client available, show address book status without on-chain verification + if (!client) { + if (meta?.proxyType && entry.implementation) { + return { + line: `? ${contractName} @ ${formatAddress(entry.address)} → ${formatAddress(entry.implementation)} (no on-chain check)`, + exists: true, + } + } + return { line: `? ${contractName} @ ${formatAddress(entry.address)} (no on-chain check)`, exists: true } + } + // Check if code exists on-chain const code = await client.getCode({ address: entry.address as `0x${string}` }) if (!code || code === '0x') { @@ -904,27 +1339,33 @@ export async function getContractStatusLine( } if (actualImpl) { - // Check if local bytecode differs from deployed (via bytecodeHash) - // If artifact exists but no bytecodeHash stored, assume code changed (untracked state) - let codeChanged = false - if (meta.artifact) { - const deploymentMetadata = addressBook.getDeploymentMetadata(contractName) - const localArtifact = loadArtifactFromSource(meta.artifact) - if (deploymentMetadata?.bytecodeHash && localArtifact?.deployedBytecode) { - const localHash = computeBytecodeHash(localArtifact.deployedBytecode) - codeChanged = localHash !== deploymentMetadata.bytecodeHash - } else if (localArtifact?.deployedBytecode) { - // No stored bytecodeHash but artifact exists - untracked/legacy state - codeChanged = true + // Check code changes: own artifact first, then shared implementation's artifact + let { codeChanged } = checkCodeChanged(meta.artifact, addressBook, entryName) + if (!codeChanged && meta.sharedImplementation) { + const sharedMeta = getContractMetadata(addressBookType, meta.sharedImplementation) + if (sharedMeta?.artifact) { + const sharedCheck = checkCodeChanged(sharedMeta.artifact, addressBook, meta.sharedImplementation) + codeChanged = sharedCheck.codeChanged } } + // Query proxy admin ownership for OZ v5 transparent proxies only + // (old Graph proxies are controller-governed, owner() doesn't exist) + let proxyAdminOwner: ProxyAdminOwner | undefined + let proxyAdminOwnerAddress: string | undefined + if (ownershipCtx && proxyAdminAddress && meta.proxyType !== 'graph') { + const ownership = await queryProxyAdminOwnership(client, proxyAdminAddress, ownershipCtx) + proxyAdminOwner = ownership.owner + proxyAdminOwnerAddress = ownership.ownerAddress + } + const result = formatProxyStatusLine({ name: contractName, proxyAddress: entry.address, implAddress: actualImpl, pendingAddress: entry.pendingImplementation?.address, codeChanged, + proxyAdminOwner, }) // Check if address book is stale (on-chain impl differs from recorded impl) @@ -934,13 +1375,67 @@ export async function getContractStatusLine( warnings.push(`address book stale: recorded impl ${formatAddress(bookImpl)}`) } - return { line: result.line, exists: true, warnings: warnings.length > 0 ? warnings : undefined } + return { + line: result.line, + exists: true, + warnings: warnings.length > 0 ? warnings : undefined, + proxyAdminOwner, + proxyAdminAddress, + proxyAdminOwnerAddress, + codeChanged, + hasPendingImplementation: !!entry.pendingImplementation?.address, + } } } - // Non-proxy contract - use two-column format with blank status icon - return { line: `✓ ${contractName} @ ${formatAddress(entry.address)}`, exists: true } - } catch { - return { line: `⚠ ${contractName}: error reading`, exists: false } + // Non-proxy contract — check for code changes against stored bytecodeHash + const { codeChanged } = meta?.artifact + ? checkCodeChanged(meta.artifact, addressBook, entryName) + : { codeChanged: false } + const icon = codeChanged ? '△' : '✓' + return { line: `${icon} ${contractName} @ ${formatAddress(entry.address)}`, exists: true, codeChanged } + } catch (e) { + const errMsg = e instanceof Error ? e.message.split('\n')[0].slice(0, 120) : String(e).slice(0, 120) + return { line: `⚠ ${contractName}: error reading (${errMsg})`, exists: false } + } +} + +/** + * Check if any deployable proxy across all address books has a pending + * implementation or local code that differs from the deployed version. + * + * Used by status scripts for next-step guidance without duplicating + * address book scanning logic. + */ +export function checkAllProxyStates(targetChainId: number): { anyCodeChanged: boolean; anyPending: boolean } { + const addressBookTypes: AddressBookType[] = ['horizon', 'subgraph-service', 'issuance'] + let anyCodeChanged = false + let anyPending = false + + for (const abType of addressBookTypes) { + const ab: AnyAddressBookOps = getAddressBookForType(abType, targetChainId) + + for (const [name, meta] of getContractsByAddressBook(abType)) { + if (!meta.deployable || !meta.proxyType) continue + if (!ab.entryExists(name)) continue + const entry = ab.getEntry(name) + if (!entry?.address) continue + + if (entry.pendingImplementation?.address) anyPending = true + if (meta.artifact) { + const { codeChanged } = checkCodeChanged(meta.artifact, ab, name) + if (codeChanged) anyCodeChanged = true + } else if (meta.sharedImplementation) { + const sharedMeta = getContractMetadata(abType, meta.sharedImplementation) + if (sharedMeta?.artifact) { + const { codeChanged } = checkCodeChanged(sharedMeta.artifact, ab, meta.sharedImplementation) + if (codeChanged) anyCodeChanged = true + } + } + + if (anyCodeChanged && anyPending) return { anyCodeChanged, anyPending } + } } + + return { anyCodeChanged, anyPending } } diff --git a/packages/deployment/lib/task-utils.ts b/packages/deployment/lib/task-utils.ts new file mode 100644 index 000000000..62826905d --- /dev/null +++ b/packages/deployment/lib/task-utils.ts @@ -0,0 +1,134 @@ +/** + * Shared Task Utilities + * + * Common functions used across Hardhat tasks. Consolidates helpers that were + * previously duplicated across grant-role, revoke-role, reo-tasks, eth-tasks, + * grt-tasks, and check-deployer. + */ + +import { configVariable } from 'hardhat/config' + +import { getAddressBookForType } from './address-book-utils.js' +import { type AddressBookType, CONTRACT_REGISTRY } from './contract-registry.js' + +/** + * Convert network name to env var prefix: arbitrumSepolia → ARBITRUM_SEPOLIA + */ +export function networkToEnvPrefix(networkName: string): string { + return networkName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase() +} + +/** + * Resolve a configuration variable using Hardhat's hook chain (keystore + env fallback) + * + * Tries the Hardhat keystore plugin first, then falls back to environment variables. + * Returns undefined if the variable is not found in either location. + * + * @param hre - Hardhat Runtime Environment + * @param name - Configuration variable name (e.g., 'ARBITRUM_SEPOLIA_DEPLOYER_KEY') + * @returns The resolved value or undefined if not set + */ +export async function resolveConfigVar(hre: unknown, name: string): Promise { + try { + const variable = configVariable(name) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const hooks = (hre as any).hooks + + const value = await hooks.runHandlerChain( + 'configurationVariables', + 'fetchValue', + [variable], + async (_context: unknown, v: { name: string }) => { + const envValue = process.env[v.name] + if (typeof envValue !== 'string') { + throw new Error(`Variable ${v.name} not found`) + } + return envValue + }, + ) + return value + } catch { + return undefined + } +} + +/** + * Get the deployer key name for a network, handling fork mode. + * + * In fork mode (network name is 'fork'), uses the HARDHAT_FORK env var to + * determine the source network. Falls back to 'arbitrumSepolia'. + * + * @param networkName - Network name (e.g., 'fork', 'arbitrumSepolia') + * @returns Key name (e.g., 'ARBITRUM_SEPOLIA_DEPLOYER_KEY') + */ +export function getDeployerKeyName(networkName: string): string { + const effectiveNetwork = networkName === 'fork' ? (process.env.HARDHAT_FORK ?? 'arbitrumSepolia') : networkName + return `${networkToEnvPrefix(effectiveNetwork)}_DEPLOYER_KEY` +} + +/** + * Resolve contract from registry by name + * + * Searches across all address books for a matching contract with roles defined. + * Returns the address book type and role list if found. + */ +export function resolveContractFromRegistry( + contractName: string, +): { addressBook: AddressBookType; roles: readonly string[] } | null { + for (const [book, contracts] of Object.entries(CONTRACT_REGISTRY)) { + const contract = contracts[contractName as keyof typeof contracts] as { roles?: readonly string[] } | undefined + if (contract?.roles) { + return { addressBook: book as AddressBookType, roles: contract.roles } + } + } + return null +} + +/** + * Get contract address from address book + */ +export function getContractAddress(addressBook: AddressBookType, contractName: string, chainId: number): string | null { + const book = getAddressBookForType(addressBook, chainId) + + // Address book type is a union — cast to access entryExists/getEntry with a runtime name + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const anyBook = book as any + if (!anyBook.entryExists(contractName)) { + return null + } + + return anyBook.getEntry(contractName)?.address ?? null +} + +/** + * Format duration in seconds to human-readable string (e.g., "2d 3h 15m") + */ +export function formatDuration(seconds: bigint): string { + const days = seconds / 86400n + const hours = (seconds % 86400n) / 3600n + const mins = (seconds % 3600n) / 60n + + if (days > 0n) { + return `${days}d ${hours}h ${mins}m` + } else if (hours > 0n) { + return `${hours}h ${mins}m` + } else { + return `${mins}m` + } +} + +/** + * Format timestamp to human-readable string (ISO format without milliseconds) + */ +export function formatTimestamp(timestamp: bigint): string { + if (timestamp === 0n) { + return 'never' + } + + const date = new Date(Number(timestamp) * 1000) + return date + .toISOString() + .replace(/\.000Z$/, '') + .replace(/Z$/, '') + .replace('T', ' ') +} diff --git a/packages/deployment/lib/upgrade-implementation.ts b/packages/deployment/lib/upgrade-implementation.ts index 866cfd047..81b5fc814 100644 --- a/packages/deployment/lib/upgrade-implementation.ts +++ b/packages/deployment/lib/upgrade-implementation.ts @@ -1,13 +1,13 @@ import type { Environment } from '@rocketh/core/types' import { encodeFunctionData } from 'viem' -import { getTargetChainIdFromEnv } from './address-book-utils.js' -import type { AnyAddressBookOps } from './address-book-ops.js' +import { getAddressBookForType, getTargetChainIdFromEnv } from './address-book-utils.js' import { GRAPH_PROXY_ADMIN_ABI, OZ_PROXY_ADMIN_ABI } from './abis.js' import { type AddressBookType, type ProxyType, type RegistryEntry } from './contract-registry.js' -import { createGovernanceTxBuilder } from './execute-governance.js' +import { getOnChainImplementation } from './deploy-implementation.js' +import { createGovernanceTxBuilder, saveGovernanceTx } from './execute-governance.js' import { graph } from '../rocketh/deploy.js' -import type { TxMetadata } from './tx-builder.js' +import type { TxBuilder, TxMetadata } from './tx-builder.js' /** * Configuration for upgrading an implementation (manual override mode) @@ -19,10 +19,11 @@ export interface ImplementationUpgradeConfig { /** * Name of the proxy admin entry in address book. - * Examples: 'GraphProxyAdmin', 'GraphIssuanceProxyAdmin' + * Example: 'GraphProxyAdmin' for legacy GraphProxy contracts. * - * Optional for subgraph-service contracts - the proxy admin address - * is read from the contract entry's proxyAdmin field. + * Optional for OZ v5 TransparentUpgradeableProxy contracts (subgraph-service + * and issuance) — the per-proxy admin address is read from the contract + * entry's proxyAdmin field. */ proxyAdminName?: string @@ -30,8 +31,8 @@ export interface ImplementationUpgradeConfig { * Implementation contract name if different from contractName. * Used when a proxy is upgraded to a different contract type. * - * Example: PilotAllocation proxy upgraded to DirectAllocation implementation - * contractName: 'PilotAllocation' + * Example: ReclaimedRewards proxy upgraded to DirectAllocation implementation + * contractName: 'ReclaimedRewards' * implementationName: 'DirectAllocation' * * Default: same as contractName @@ -62,7 +63,7 @@ export interface ImplementationUpgradeOverrides { * Implementation contract name if different from contractName. * Used when a proxy is upgraded to a different contract type. * - * Example: PilotAllocation proxy upgraded to DirectAllocation implementation + * Example: ReclaimedRewards proxy upgraded to DirectAllocation implementation */ implementationName?: string @@ -110,7 +111,7 @@ function createUpgradeConfigFromRegistry( * import { Contracts } from '../../lib/contract-registry.js' * await upgradeImplementation(env, Contracts.horizon.RewardsManager) * await upgradeImplementation(env, Contracts["subgraph-service"].SubgraphService) - * await upgradeImplementation(env, Contracts.issuance.PilotAllocation, { + * await upgradeImplementation(env, Contracts.issuance.ReclaimedRewards, { * implementationName: 'DirectAllocation', // Upgrade to different implementation * }) * ``` @@ -124,41 +125,78 @@ function createUpgradeConfigFromRegistry( * }) * ``` */ -export async function upgradeImplementation( +/** + * Build upgrade TXs for a contract and add them to an existing builder. + * + * Checks the address book for a pendingImplementation. If found, encodes upgrade + * TX(s) and adds them to the provided builder. Returns without exiting. + * + * Use this when building a batch of upgrades (e.g., GIP-level stage scripts). + * For single-contract upgrades that save and exit, use `upgradeImplementation`. + * + * @returns Whether an upgrade was needed (pendingImplementation existed) + */ +export async function buildUpgradeTxs( env: Environment, entryOrConfig: RegistryEntry | ImplementationUpgradeConfig, + builder: TxBuilder, overrides?: ImplementationUpgradeOverrides, -): Promise { - // Handle overloads - convert registry entry to config +): Promise<{ upgraded: boolean }> { const config: ImplementationUpgradeConfig = 'name' in entryOrConfig ? createUpgradeConfigFromRegistry(entryOrConfig, overrides) : entryOrConfig const { contractName, proxyAdminName, proxyType = 'graph', addressBook = 'horizon' } = config - // Use fork-local address book in fork mode, canonical address book otherwise const targetChainId = await getTargetChainIdFromEnv(env) - const addressBookInstance: AnyAddressBookOps = - addressBook === 'subgraph-service' - ? graph.getSubgraphServiceAddressBook(targetChainId) - : addressBook === 'issuance' - ? graph.getIssuanceAddressBook(targetChainId) - : graph.getHorizonAddressBook(targetChainId) + const addressBookInstance = getAddressBookForType(addressBook, targetChainId) // Check for pending implementation const contractEntry = addressBookInstance.getEntry(contractName) if (!contractEntry?.pendingImplementation?.address) { - env.showMessage(`\n✓ No pending ${contractName} implementation to upgrade`) - return { upgraded: false, executed: false } + // No pending implementation stored — check if a shared implementation has changed on-chain + const implName = config.implementationName + if (implName && contractEntry?.address) { + const implDepName = `${implName}_Implementation` + const implDep = env.getOrNull(implDepName) + if (implDep) { + const client = graph.getPublicClient(env) + const onChainImpl = await getOnChainImplementation(client, contractEntry.address, proxyType) + if (onChainImpl.toLowerCase() !== implDep.address.toLowerCase()) { + // Shared implementation changed — auto-set pendingImplementation + const implMetadata = addressBookInstance.getDeploymentMetadata(implDepName) + if (!implMetadata) { + throw new Error( + `${contractName}: deployment metadata missing for ${implDepName}. ` + + `Run the implementation deploy script (or sync) before invoking upgrade.`, + ) + } + addressBookInstance.setPendingImplementationWithMetadata(contractName, implDep.address, implMetadata) + env.showMessage(` ⚠️ ${contractName}: shared implementation changed, setting pending upgrade`) + // Fall through to process the upgrade + } else { + env.showMessage(` ✓ ${contractName}: no pending implementation`) + return { upgraded: false } + } + } else { + env.showMessage(` ✓ ${contractName}: no pending implementation`) + return { upgraded: false } + } + } else { + env.showMessage(` ✓ ${contractName}: no pending implementation`) + return { upgraded: false } + } + } + + // Re-read entry after potential auto-set + const updatedEntry = addressBookInstance.getEntry(contractName) + if (!updatedEntry?.pendingImplementation?.address) { + return { upgraded: false } } // Get proxy admin address - // Priority: 1) Per-proxy ProxyAdmin in entry (OZ v5 / subgraph-service) - // 2) Shared ProxyAdmin by name (legacy horizon pattern) let proxyAdminAddress: string | undefined - if (contractEntry.proxyAdmin) { - // Per-proxy ProxyAdmin stored inline (OZ v5 issuance, subgraph-service) - proxyAdminAddress = contractEntry.proxyAdmin + if (updatedEntry.proxyAdmin) { + proxyAdminAddress = updatedEntry.proxyAdmin } else if (proxyAdminName) { - // Shared ProxyAdmin by name (horizon legacy pattern) proxyAdminAddress = addressBookInstance.getEntry(proxyAdminName)?.address } @@ -169,28 +207,13 @@ export async function upgradeImplementation( ) } - const proxyAddress = contractEntry.address - const pendingImpl = contractEntry.pendingImplementation.address - - env.showMessage(`\n🔧 Upgrading ${contractName}...`) - env.showMessage(` Proxy: ${proxyAddress}`) - env.showMessage(` ProxyAdmin: ${proxyAdminAddress}`) - env.showMessage(` New implementation: ${pendingImpl}`) - - // Generate governance TX with deterministic name (overwrites if exists) - const builder = await createGovernanceTxBuilder(env, `upgrade-${contractName}`, { - name: `${contractName} Upgrade`, - description: `Upgrade ${contractName} proxy to new implementation`, - }) + const proxyAddress = updatedEntry.address + const pendingImpl = updatedEntry.pendingImplementation!.address + const currentImpl = updatedEntry.implementation ?? 'unknown' - // Get current implementation for state change tracking - const currentImpl = contractEntry.implementation ?? 'unknown' + env.showMessage(` + ${contractName}: ${pendingImpl.slice(0, 10)}... (${proxyType} proxy)`) - // Build TX based on proxy type if (proxyType === 'transparent') { - // OpenZeppelin v5 ProxyAdmin uses upgradeAndCall() with empty calldata - // Note: we use empty bytes (0x) because not all contracts implement ERC165, - // so supportsInterface cannot be used as a universal no-op const upgradeData = encodeFunctionData({ abi: OZ_PROXY_ADMIN_ABI, functionName: 'upgradeAndCall', @@ -202,25 +225,15 @@ export async function upgradeImplementation( contractName, decoded: { function: 'upgradeAndCall(address,address,bytes)', - args: { - proxy: proxyAddress, - implementation: pendingImpl, - data: '0x [empty]', - }, + args: { proxy: proxyAddress, implementation: pendingImpl, data: '0x [empty]' }, }, stateChanges: { - [`${contractName} implementation`]: { - current: currentImpl, - new: pendingImpl, - }, + [`${contractName} implementation`]: { current: currentImpl, new: pendingImpl }, }, notes: 'OZ TransparentUpgradeableProxy upgrade via per-proxy ProxyAdmin', } builder.addTx({ to: proxyAdminAddress, value: '0', data: upgradeData }, metadata) } else { - // Graph legacy: upgrade() + acceptProxy(implementation, proxy) - // Note: GraphProxyAdmin.sol requires both implementation and proxy parameters, - // despite IGraphProxyAdmin interface only showing proxy parameter (interface is outdated) const upgradeData = encodeFunctionData({ abi: GRAPH_PROXY_ADMIN_ABI, functionName: 'upgrade', @@ -232,45 +245,75 @@ export async function upgradeImplementation( args: [pendingImpl as `0x${string}`, proxyAddress as `0x${string}`], }) - const upgradeMetadata: TxMetadata = { - toLabel: 'GraphProxyAdmin', - contractName, - decoded: { - function: 'upgrade(address,address)', - args: { - proxy: proxyAddress, - implementation: pendingImpl, + builder.addTx( + { to: proxyAdminAddress, value: '0', data: upgradeData }, + { + toLabel: 'GraphProxyAdmin', + contractName, + decoded: { + function: 'upgrade(address,address)', + args: { proxy: proxyAddress, implementation: pendingImpl }, }, + notes: 'Graph legacy proxy upgrade (step 1/2: set pending implementation)', }, - notes: 'Graph legacy proxy upgrade (step 1/2: set pending implementation)', - } - builder.addTx({ to: proxyAdminAddress, value: '0', data: upgradeData }, upgradeMetadata) - - const acceptMetadata: TxMetadata = { - toLabel: 'GraphProxyAdmin', - contractName, - decoded: { - function: 'acceptProxy(address,address)', - args: { - implementation: pendingImpl, - proxy: proxyAddress, + ) + builder.addTx( + { to: proxyAdminAddress, value: '0', data: acceptData }, + { + toLabel: 'GraphProxyAdmin', + contractName, + decoded: { + function: 'acceptProxy(address,address)', + args: { implementation: pendingImpl, proxy: proxyAddress }, }, - }, - stateChanges: { - [`${contractName} implementation`]: { - current: currentImpl, - new: pendingImpl, + stateChanges: { + [`${contractName} implementation`]: { current: currentImpl, new: pendingImpl }, }, + notes: 'Graph legacy proxy upgrade (step 2/2: accept and activate)', }, - notes: 'Graph legacy proxy upgrade (step 2/2: accept and activate)', - } - builder.addTx({ to: proxyAdminAddress, value: '0', data: acceptData }, acceptMetadata) + ) } - const txFile = builder.saveToFile() - env.showMessage(` ✓ Governance TX saved: ${txFile}`) - env.showMessage(` Run: npx hardhat deploy:execute-governance --network ${env.name}`) + return { upgraded: true } +} + +/** + * Upgrade an implementation via governance TX (registry-driven) + * + * Generates a governance TX batch file for a single contract upgrade, then exits. + * For batch upgrades (multiple contracts in one TX batch), use `buildUpgradeTxs` instead. + * + * @example Registry-driven with Contracts object (recommended): + * ```typescript + * import { Contracts } from '../../lib/contract-registry.js' + * await upgradeImplementation(env, Contracts.horizon.RewardsManager) + * await upgradeImplementation(env, Contracts["subgraph-service"].SubgraphService) + * await upgradeImplementation(env, Contracts.issuance.ReclaimedRewards, { + * implementationName: 'DirectAllocation', // Upgrade to different implementation + * }) + * ``` + */ +export async function upgradeImplementation( + env: Environment, + entryOrConfig: RegistryEntry | ImplementationUpgradeConfig, + overrides?: ImplementationUpgradeOverrides, +): Promise { + const config: ImplementationUpgradeConfig = + 'name' in entryOrConfig ? createUpgradeConfigFromRegistry(entryOrConfig, overrides) : entryOrConfig + + const builder = await createGovernanceTxBuilder(env, `upgrade-${config.contractName}`, { + name: `${config.contractName} Upgrade`, + description: `Upgrade ${config.contractName} proxy to new implementation`, + }) + + env.showMessage(`\n🔧 Upgrading ${config.contractName}...`) + const { upgraded } = await buildUpgradeTxs(env, entryOrConfig, builder, overrides) + + if (!upgraded) { + env.showMessage(`\n✓ No pending ${config.contractName} implementation to upgrade`) + return { upgraded: false, executed: false } + } - // Exit to prevent subsequent deployment steps until governance TX is executed - process.exit(1) + saveGovernanceTx(env, builder, `${config.contractName} upgrade`) + return { upgraded: true, executed: false } } diff --git a/packages/deployment/package.json b/packages/deployment/package.json index fc4a55ad2..9cd1d0e5f 100644 --- a/packages/deployment/package.json +++ b/packages/deployment/package.json @@ -4,9 +4,11 @@ "description": "Unified deployment for Graph Protocol contracts", "private": true, "scripts": { - "build": "pnpm build:deps", + "build": "pnpm build:deps && pnpm build:self", + "build:self": "pnpm generate:abis", "build:deps": "pnpm --filter @graphprotocol/deployment^... build", "build:clean": "pnpm --filter @graphprotocol/contracts clean && pnpm build:deps", + "generate:abis": "tsx scripts/generate-abis.ts", "deploy": "pnpm build:clean && hardhat deploy", "deploy:sync": "hardhat deploy --tags sync", "deploy:status": "hardhat deploy:deployment-status", @@ -21,6 +23,7 @@ "dependencies": { "@graphprotocol/contracts": "workspace:*", "@graphprotocol/horizon": "workspace:*", + "@graphprotocol/interfaces": "workspace:*", "@graphprotocol/issuance": "workspace:*", "@graphprotocol/subgraph-service": "workspace:*", "@graphprotocol/toolshed": "workspace:*", @@ -48,6 +51,7 @@ "@types/node": "^20.0.0", "chai": "^4.3.0", "hardhat-deploy": "2.0.0-next.61", + "json5": "^2.2.3", "mocha": "^10.7.0", "rocketh": "^0.17.13", "tsx": "^4.19.0", diff --git a/packages/deployment/rocketh/config.ts b/packages/deployment/rocketh/config.ts index 44bcb4fd6..e0ef1b47b 100644 --- a/packages/deployment/rocketh/config.ts +++ b/packages/deployment/rocketh/config.ts @@ -17,8 +17,14 @@ export const accounts = { deployer: { default: 0, }, - // Note: Governor address is queried from Controller contract via Controller.getGovernor() - // See lib/controller-utils.ts for helper functions + // Governor — second mnemonic account on local/test networks. + // On mainnet, governance is a multisig (not available via mnemonic). + // The on-chain source of truth is Controller.getGovernor() — see lib/controller-utils.ts. + // This named account exists so rocketh registers a signer, allowing deploy + // scripts to send TXs as governor via tx(). + governor: { + default: 1, + }, } as const satisfies UserConfig['accounts'] // Network-specific data (can be extended as needed) @@ -33,6 +39,14 @@ const hardhatLocalChain: ChainInfo = { testnet: true, } +const graphLocalNetworkChain: ChainInfo = { + id: 1337, + name: 'Graph Local Network', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { default: { http: ['http://chain:8545'] } }, + testnet: true, +} + const arbitrumSepoliaChain: ChainInfo = { id: 421614, name: 'Arbitrum Sepolia', @@ -58,6 +72,7 @@ export const config: UserConfig = { deployments: 'deployments', scripts: ['deploy'], chains: { + 1337: { info: graphLocalNetworkChain }, 31337: { info: hardhatLocalChain }, 421614: { info: arbitrumSepoliaChain }, 42161: { info: arbitrumOneChain }, @@ -68,6 +83,7 @@ export const config: UserConfig = { hardhat: { chain: 31337 }, localhost: { chain: 31337 }, fork: { chain: 31337 }, + localNetwork: { chain: 1337 }, arbitrumSepolia: { chain: 421614 }, arbitrumOne: { chain: 42161 }, }, diff --git a/packages/deployment/rocketh/deploy.ts b/packages/deployment/rocketh/deploy.ts index c3c86f230..e2d4eed0f 100644 --- a/packages/deployment/rocketh/deploy.ts +++ b/packages/deployment/rocketh/deploy.ts @@ -5,7 +5,10 @@ import { deployViaProxy } from '@rocketh/proxy' import { execute, read, tx } from '@rocketh/read-execute' import { createPublicClient, custom } from 'viem' +import type { AnyAddressBookOps } from '../lib/address-book-ops.js' import { + autoDetectForkNetwork, + getAddressBookForType, getForkTargetChainId, getHorizonAddressBook, getIssuanceAddressBook, @@ -13,12 +16,13 @@ import { getTargetChainIdFromEnv, isForkMode, } from '../lib/address-book-utils.js' +import type { RegistryEntry } from '../lib/contract-registry.js' import { accounts, data } from './config.js' /** - * Options for updating issuance address book after deployment + * Options for updating an address book after deployment */ -export interface IssuanceDeploymentUpdate { +export interface DeploymentUpdate { /** Contract name in the address book */ name: string /** Deployed address (proxy address if proxied) */ @@ -29,8 +33,12 @@ export interface IssuanceDeploymentUpdate { implementation?: string /** Proxy type if this is a proxied contract */ proxy?: 'transparent' | 'graph' - /** Implementation deployment metadata (for verification) */ + /** Proxy deployment metadata (for verification of the proxy contract itself) */ + proxyDeployment?: DeploymentMetadata + /** Implementation deployment metadata (for verification of proxied contracts) */ implementationDeployment?: DeploymentMetadata + /** Deployment metadata (for verification of non-proxied contracts) */ + deployment?: DeploymentMetadata } /** @@ -56,6 +64,13 @@ export interface IssuanceDeploymentUpdate { * ``` */ export const graph = { + /** + * Auto-detect fork network by querying anvil. + * Call at the top of any task that needs fork awareness. + * No-op if FORK_NETWORK is already set or node isn't an anvil fork. + */ + autoDetect: () => autoDetectForkNetwork(), + /** * Get a viem public client for on-chain queries */ @@ -90,38 +105,61 @@ export const graph = { */ getIssuanceAddressBook: (chainId?: number) => getIssuanceAddressBook(chainId), + /** + * Update horizon address book after deploying a contract. + * Supports both standalone and proxied contracts. + */ + updateHorizonAddressBook: async (env: Environment, update: DeploymentUpdate) => { + const chainId = await getTargetChainIdFromEnv(env) + await applyDeploymentUpdate(getHorizonAddressBook(chainId), update) + }, + + /** + * Update subgraph-service address book after deploying a contract. + * Supports both standalone and proxied contracts. + */ + updateSubgraphServiceAddressBook: async (env: Environment, update: DeploymentUpdate) => { + const chainId = await getTargetChainIdFromEnv(env) + await applyDeploymentUpdate(getSubgraphServiceAddressBook(chainId), update) + }, + /** * Update issuance address book after deploying a contract. * Call this after rocketh's deployViaProxy or deploy to sync the address book. - * - * @param env - Rocketh environment (used to get chain ID from provider) - * @param update - Deployment update details */ - updateIssuanceAddressBook: async (env: Environment, update: IssuanceDeploymentUpdate) => { + updateIssuanceAddressBook: async (env: Environment, update: DeploymentUpdate) => { const chainId = await getTargetChainIdFromEnv(env) - const addressBook = getIssuanceAddressBook(chainId) - - if (update.proxy) { - addressBook.setProxy( - update.name as Parameters[0], - update.address, - update.implementation!, - update.proxyAdmin!, - update.proxy, - ) - // Store implementation deployment metadata for verification - if (update.implementationDeployment) { - addressBook.setImplementationDeploymentMetadata( - update.name as Parameters[0], - update.implementationDeployment, - ) - } - } else { - addressBook.setContract(update.name as Parameters[0], update.address) - } + await applyDeploymentUpdate(getIssuanceAddressBook(chainId), update) + }, + + /** + * Update the address book for a contract, choosing the correct book from + * `contract.addressBook`. Single dispatch point — adding a new address book + * type will surface as a TypeScript error in `getAddressBookForType`. + */ + updateAddressBookForContract: async (env: Environment, contract: RegistryEntry, update: DeploymentUpdate) => { + const chainId = await getTargetChainIdFromEnv(env) + await applyDeploymentUpdate(getAddressBookForType(contract.addressBook, chainId), update) }, } +function applyDeploymentUpdate(addressBook: AnyAddressBookOps, update: DeploymentUpdate): void { + if (update.proxy) { + addressBook.setProxy(update.name, update.address, update.implementation!, update.proxyAdmin!, update.proxy) + if (update.proxyDeployment) { + addressBook.setProxyDeploymentMetadata(update.name, update.proxyDeployment) + } + if (update.implementationDeployment) { + addressBook.setImplementationDeploymentMetadata(update.name, update.implementationDeployment) + } + } else { + addressBook.setContract(update.name, update.address) + if (update.deployment) { + addressBook.setDeploymentMetadata(update.name, update.deployment) + } + } +} + // Re-export rocketh functions for convenience export { deploy, deployViaProxy, execute, read, tx } diff --git a/packages/deployment/scripts/check-bytecode.ts b/packages/deployment/scripts/check-bytecode.ts new file mode 100644 index 000000000..9d9178b2a --- /dev/null +++ b/packages/deployment/scripts/check-bytecode.ts @@ -0,0 +1,54 @@ +import { createPublicClient, http } from 'viem' + +import { loadSubgraphServiceArtifact } from '../lib/artifact-loaders.js' +import { computeBytecodeHash } from '../lib/bytecode-utils.js' +import { graph } from '../rocketh/deploy.js' + +async function main() { + const chainId = 421614 // arbitrumSepolia + + // Get address book + const addressBook = graph.getSubgraphServiceAddressBook(chainId) + const entry = addressBook.getEntry('SubgraphService') + const deploymentMetadata = addressBook.getDeploymentMetadata('SubgraphService') + + console.log('\n📋 SubgraphService Bytecode Analysis\n') + console.log('Proxy address:', entry.address) + console.log('Current implementation:', entry.implementation) + console.log('Pending implementation:', entry.pendingImplementation?.address ?? 'none') + + // Get local artifact + const artifact = loadSubgraphServiceArtifact('SubgraphService') + const localHash = computeBytecodeHash(artifact.deployedBytecode ?? '0x') + console.log('\nLocal artifact bytecode hash:', localHash) + + // Get address book stored hash + console.log('Address book stored hash:', deploymentMetadata?.bytecodeHash ?? '(none)') + + // Get on-chain bytecode + const client = createPublicClient({ + transport: http('https://sepolia-rollup.arbitrum.io/rpc'), + }) + + const onChainBytecode = await client.getCode({ + address: entry.implementation as `0x${string}`, + }) + + if (onChainBytecode && onChainBytecode !== '0x') { + const onChainHash = computeBytecodeHash(onChainBytecode) + console.log('On-chain implementation hash:', onChainHash) + + console.log('\n🔍 Comparison:') + console.log( + 'Local vs Address Book:', + localHash === (deploymentMetadata?.bytecodeHash ?? '') ? '✓ MATCH' : '✗ DIFFERENT', + ) + console.log('Local vs On-chain:', localHash === onChainHash ? '✓ MATCH' : '✗ DIFFERENT') + console.log( + 'Address Book vs On-chain:', + (deploymentMetadata?.bytecodeHash ?? '') === onChainHash ? '✓ MATCH' : '✗ DIFFERENT (or missing)', + ) + } +} + +main().catch(console.error) diff --git a/packages/deployment/scripts/check-rocketh-bytecode.ts b/packages/deployment/scripts/check-rocketh-bytecode.ts new file mode 100644 index 000000000..aff8f394a --- /dev/null +++ b/packages/deployment/scripts/check-rocketh-bytecode.ts @@ -0,0 +1,34 @@ +import { readFileSync } from 'fs' + +import { loadSubgraphServiceArtifact } from '../lib/artifact-loaders.js' +import { computeBytecodeHash } from '../lib/bytecode-utils.js' + +async function main() { + console.log('\n📋 Rocketh vs Local Artifact Comparison\n') + + // Get local artifact + const artifact = loadSubgraphServiceArtifact('SubgraphService') + const localHash = computeBytecodeHash(artifact.deployedBytecode ?? '0x') + console.log('Local artifact hash:', localHash) + + // Check rocketh stored bytecode + try { + const rockethPath = '.rocketh/deployments/arbitrumSepolia/SubgraphService_Implementation.json' + const rockethData = JSON.parse(readFileSync(rockethPath, 'utf-8')) + + if (rockethData.deployedBytecode) { + const rockethHash = computeBytecodeHash(rockethData.deployedBytecode) + console.log('Rocketh stored hash:', rockethHash) + console.log( + '\nComparison:', + localHash === rockethHash ? '✓ MATCH (deploy will skip)' : '✗ DIFFERENT (deploy will redeploy)', + ) + } else { + console.log('Rocketh stored hash: (no deployedBytecode)') + } + } catch { + console.log('Rocketh record:', 'not found') + } +} + +main().catch(console.error) diff --git a/packages/deployment/scripts/debug-deploy-state.ts b/packages/deployment/scripts/debug-deploy-state.ts new file mode 100644 index 000000000..6267734f2 --- /dev/null +++ b/packages/deployment/scripts/debug-deploy-state.ts @@ -0,0 +1,27 @@ +import { loadSubgraphServiceArtifact } from '../lib/artifact-loaders.js' +import { computeBytecodeHash } from '../lib/bytecode-utils.js' + +async function main() { + console.log('\n📋 Investigating Deploy "Unchanged" Message\n') + + // The deploy script checks env.getOrNull('SubgraphService_Implementation') + // But rocketh state is in-memory during deploy runs + // We can't easily check that without running deploy + + // What we CAN check is: + // 1. If sync step would have synced the implementation + // 2. The actual bytecode hashes + + const artifact = loadSubgraphServiceArtifact('SubgraphService') + const localHash = computeBytecodeHash(artifact.deployedBytecode ?? '0x') + + console.log('Local artifact bytecode hash:', localHash) + console.log('\n⚠️ The issue:') + console.log('1. Sync shows "code changed" because address book has different/missing hash') + console.log('2. Deploy says "unchanged" - this suggests rocketh has the implementation') + console.log('3. But local bytecode IS different from on-chain') + console.log('\nThis means deploy will NOT deploy the new implementation!') + console.log('The local changes will be ignored.\n') +} + +main().catch(console.error) diff --git a/packages/deployment/scripts/generate-abis.ts b/packages/deployment/scripts/generate-abis.ts new file mode 100644 index 000000000..f4ac49a14 --- /dev/null +++ b/packages/deployment/scripts/generate-abis.ts @@ -0,0 +1,264 @@ +/** + * ABI Codegen Script + * + * Generates typed `as const` ABI exports from the contract registry. + * Reads interface declarations and artifact sources from the registry, + * resolves them to JSON artifacts, and writes a generated TypeScript file. + * + * Usage: tsx scripts/generate-abis.ts + */ + +import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' +import { createRequire } from 'node:module' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { toFunctionSelector } from 'viem' + +import { CONTRACT_REGISTRY, type ContractMetadata, type InterfaceAbiConfig } from '../lib/contract-registry.js' + +const require = createRequire(import.meta.url) +const __dirname = dirname(fileURLToPath(import.meta.url)) +const OUTPUT_DIR = join(__dirname, '..', 'lib', 'generated') +const OUTPUT_FILE = join(OUTPUT_DIR, 'abis.ts') + +// --------------------------------------------------------------------------- +// Utility ABIs — not tied to any registry entry +// --------------------------------------------------------------------------- + +const UTILITY_ABIS: Array<{ name: string; artifactPath: string }> = [ + { + name: 'IERC165_ABI', + artifactPath: '@graphprotocol/interfaces/artifacts/@openzeppelin/contracts/introspection/IERC165.sol/IERC165.json', + }, + { + name: 'ISSUANCE_TARGET_ABI', + artifactPath: + '@graphprotocol/interfaces/artifacts/contracts/issuance/allocate/IIssuanceTarget.sol/IIssuanceTarget.json', + }, + { + name: 'OZ_PROXY_ADMIN_ABI', + artifactPath: + '@graphprotocol/horizon/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json', + }, +] + +// Alias re-exports (source export name → alias export name) +const ABI_ALIASES: Array<{ source: string; alias: string }> = [ + { source: 'ISSUANCE_ALLOCATOR_ABI', alias: 'SET_TARGET_ALLOCATION_ABI' }, + { source: 'DIRECT_ALLOCATION_ABI', alias: 'INITIALIZE_GOVERNOR_ABI' }, +] + +// Interface IDs to extract (export name → interface name used in ABI_SOURCES or registry) +// Derived from registry interfaces + utility ABIs +const INTERFACE_IDS: Array<{ name: string; abiExportName: string }> = [ + { name: 'IERC165_INTERFACE_ID', abiExportName: 'IERC165_ABI' }, + { name: 'IISSUANCE_TARGET_INTERFACE_ID', abiExportName: 'ISSUANCE_TARGET_ABI' }, + { name: 'IREWARDS_MANAGER_INTERFACE_ID', abiExportName: 'REWARDS_MANAGER_ABI' }, +] + +// --------------------------------------------------------------------------- +// Interface artifact discovery +// --------------------------------------------------------------------------- + +/** + * Build an index of interface name → artifact path by scanning the + * @graphprotocol/interfaces artifacts directory. + */ +function buildInterfaceIndex(): Map { + const index = new Map() + + // Resolve the interfaces package artifacts root + // Use a known artifact to locate the package, then walk up + const knownArtifact = + require.resolve('@graphprotocol/interfaces/artifacts/contracts/contracts/rewards/IRewardsManager.sol/IRewardsManager.json') + // Walk up to find the 'artifacts' directory + let artifactsRoot = dirname(knownArtifact) + while (!artifactsRoot.endsWith('/artifacts') && artifactsRoot !== '/') { + artifactsRoot = dirname(artifactsRoot) + } + + // Recursively scan for JSON files + function scan(dir: string): void { + for (const entry of readdirSync(dir)) { + const full = join(dir, entry) + if (entry === 'build-info') continue + if (statSync(full).isDirectory()) { + scan(full) + } else if (entry.endsWith('.json') && !entry.endsWith('.dbg.json')) { + // Extract interface name from filename (e.g. IRewardsManager.json → IRewardsManager) + const name = entry.replace('.json', '') + // Store as package-relative path for require.resolve + const relativePath = full.slice(full.indexOf('/artifacts/') + 1) + index.set(name, `@graphprotocol/interfaces/${relativePath}`) + } + } + } + + scan(artifactsRoot) + return index +} + +// --------------------------------------------------------------------------- +// Artifact loading +// --------------------------------------------------------------------------- + +type AbiEntry = Record + +function loadAbiFromArtifact(artifactPath: string): AbiEntry[] { + const resolved = require.resolve(artifactPath) + const artifact = JSON.parse(readFileSync(resolved, 'utf-8')) + return artifact.abi +} + +/** + * Resolve artifact path for a generateAbi entry based on its ArtifactSource. + */ +function resolveContractArtifactPath(artifact: { type: string; path?: string; name?: string }): string { + switch (artifact.type) { + case 'contracts': + return `@graphprotocol/contracts/artifacts/contracts/${artifact.path}/${artifact.name}.sol/${artifact.name}.json` + case 'subgraph-service': { + const baseName = (artifact.name ?? '').includes('/') ? (artifact.name ?? '').split('/').pop()! : artifact.name + return `@graphprotocol/subgraph-service/artifacts/contracts/${artifact.name}.sol/${baseName}.json` + } + case 'horizon': + return `@graphprotocol/horizon/artifacts/${artifact.path}.json` + case 'issuance': + return `@graphprotocol/issuance/artifacts/${artifact.path}.json` + case 'openzeppelin': + return `@openzeppelin/contracts/build/contracts/${artifact.name}.json` + default: + throw new Error(`Unknown artifact type: ${artifact.type}`) + } +} + +// --------------------------------------------------------------------------- +// Interface ID calculation +// --------------------------------------------------------------------------- + +/** + * Calculate ERC-165 interface ID from an ABI. + * The interface ID is XOR of all function selectors. + */ +function calculateInterfaceId(abi: AbiEntry[]): string { + const functions = abi.filter((entry) => entry.type === 'function') + if (functions.length === 0) return '0x00000000' + + let id = BigInt(0) + for (const fn of functions) { + const inputs = (fn.inputs as Array<{ type: string }>) ?? [] + const sig = `${fn.name}(${inputs.map((i) => i.type).join(',')})` + const selector = toFunctionSelector(sig) + id ^= BigInt(selector) + } + + return '0x' + id.toString(16).padStart(8, '0') +} + +// --------------------------------------------------------------------------- +// Code generation +// --------------------------------------------------------------------------- + +function formatAbiEntry(entry: AbiEntry, indent: string): string { + return `${indent}${JSON.stringify(entry)}` +} + +function generateAbiExport(name: string, abi: AbiEntry[]): string { + const entries = abi.map((entry) => formatAbiEntry(entry, ' ')).join(',\n') + return `export const ${name} = [\n${entries},\n] as const\n` +} + +function main(): void { + const verbose = process.argv.includes('--verbose') + + const interfaceIndex = buildInterfaceIndex() + const abiMap = new Map() + const lines: string[] = [ + '/**', + ' * Auto-generated typed ABI exports', + ' *', + ' * DO NOT EDIT — regenerate with: pnpm generate:abis', + ' */', + '', + ] + + // 1. Walk registry for interface ABIs + for (const [bookName, book] of Object.entries(CONTRACT_REGISTRY)) { + for (const [contractName, rawMeta] of Object.entries(book)) { + const meta = rawMeta as ContractMetadata + // Interface ABIs + if (meta.interfaces) { + for (const iface of meta.interfaces as readonly InterfaceAbiConfig[]) { + const artifactPath = interfaceIndex.get(iface.interface) + if (!artifactPath) { + throw new Error( + `Interface "${iface.interface}" not found in @graphprotocol/interfaces artifacts ` + + `(referenced by ${bookName}.${contractName})`, + ) + } + const abi = loadAbiFromArtifact(artifactPath) + abiMap.set(iface.name, abi) + if (verbose) console.log(` ${iface.name} ← ${iface.interface} (${abi.length} entries)`) + } + } + + // Full contract ABI + if (meta.generateAbi && meta.artifact) { + const exportName = meta.generateAbi as string + const artifactPath = resolveContractArtifactPath( + meta.artifact as { type: string; path?: string; name?: string }, + ) + const abi = loadAbiFromArtifact(artifactPath) + abiMap.set(exportName, abi) + if (verbose) console.log(` ${exportName} ← ${contractName} (${abi.length} entries)`) + } + } + } + + // 2. Utility ABIs + for (const util of UTILITY_ABIS) { + const abi = loadAbiFromArtifact(util.artifactPath) + abiMap.set(util.name, abi) + if (verbose) console.log(` ${util.name} ← utility (${abi.length} entries)`) + } + + // 3. Generate ABI exports + for (const [name, abi] of abiMap) { + lines.push(generateAbiExport(name, abi)) + } + + // 4. Alias re-exports + for (const { source, alias } of ABI_ALIASES) { + if (!abiMap.has(source)) { + throw new Error(`Alias source "${source}" not found in generated ABIs`) + } + lines.push(`export { ${source} as ${alias} }\n`) + if (verbose) console.log(` ${alias} → ${source}`) + } + + // 5. Interface IDs + lines.push('// Interface IDs (computed from ABI function selectors)') + for (const { name, abiExportName } of INTERFACE_IDS) { + const abi = abiMap.get(abiExportName) + if (!abi) { + throw new Error(`ABI "${abiExportName}" not found for interface ID "${name}"`) + } + const id = calculateInterfaceId(abi) + lines.push(`export const ${name} = '${id}' as const`) + if (verbose) console.log(` ${name} = ${id}`) + } + lines.push('') + + // Write output + if (!existsSync(OUTPUT_DIR)) { + mkdirSync(OUTPUT_DIR, { recursive: true }) + } + writeFileSync(OUTPUT_FILE, lines.join('\n')) + + console.log( + `Generated ${abiMap.size} ABIs, ${ABI_ALIASES.length} aliases, ${INTERFACE_IDS.length} interface IDs → lib/generated/abis.ts`, + ) +} + +main() diff --git a/packages/deployment/scripts/tag-deployment.sh b/packages/deployment/scripts/tag-deployment.sh index a6c5f8838..3e05e28a9 100755 --- a/packages/deployment/scripts/tag-deployment.sh +++ b/packages/deployment/scripts/tag-deployment.sh @@ -271,8 +271,7 @@ fi # --- Build annotation --- ANNOTATION="network: ${DISPLAY} (${CHAIN_ID}) -deployed-by: ${DEPLOYER} -commit: ${COMMIT_SHA}" +deployed-by: ${DEPLOYER}" if [[ -n "$UPGRADE_NAME" ]]; then ANNOTATION="upgrade: ${UPGRADE_NAME} ${ANNOTATION}" diff --git a/packages/deployment/tasks/check-deployer.ts b/packages/deployment/tasks/check-deployer.ts index d28eba36c..275568ad0 100644 --- a/packages/deployment/tasks/check-deployer.ts +++ b/packages/deployment/tasks/check-deployer.ts @@ -1,47 +1,15 @@ -import { configVariable, task } from 'hardhat/config' +import { task } from 'hardhat/config' import type { NewTaskActionFunction } from 'hardhat/types/tasks' import { createPublicClient, custom, formatEther } from 'viem' import { privateKeyToAccount } from 'viem/accounts' +import { networkToEnvPrefix, resolveConfigVar } from '../lib/task-utils.js' + const BLOCK_EXPLORERS: Record = { 42161: 'https://arbiscan.io/address/', 421614: 'https://sepolia.arbiscan.io/address/', } -/** - * Convert network name to env var prefix: arbitrumSepolia → ARBITRUM_SEPOLIA - */ -function networkToEnvPrefix(networkName: string): string { - return networkName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase() -} - -/** - * Resolve a configuration variable using Hardhat's hook chain (keystore + env fallback) - */ -async function resolveConfigVar(hre: unknown, name: string): Promise { - try { - const variable = configVariable(name) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const hooks = (hre as any).hooks - - const value = await hooks.runHandlerChain( - 'configurationVariables', - 'fetchValue', - [variable], - async (_context: unknown, v: { name: string }) => { - const envValue = process.env[v.name] - if (typeof envValue !== 'string') { - throw new Error(`Variable ${v.name} not found`) - } - return envValue - }, - ) - return value - } catch { - return undefined - } -} - interface TaskArgs { // No arguments for this task } diff --git a/packages/deployment/tasks/deployment-status.ts b/packages/deployment/tasks/deployment-status.ts index 8b5994f0d..373c0d9d4 100644 --- a/packages/deployment/tasks/deployment-status.ts +++ b/packages/deployment/tasks/deployment-status.ts @@ -1,27 +1,20 @@ import { task } from 'hardhat/config' import { ArgumentType } from 'hardhat/types/arguments' import type { NewTaskActionFunction } from 'hardhat/types/tasks' -import { createPublicClient, custom, type PublicClient } from 'viem' +import { createPublicClient, custom, http, type PublicClient } from 'viem' -import { - IISSUANCE_TARGET_INTERFACE_ID, - IREWARDS_MANAGER_INTERFACE_ID, - ISSUANCE_ALLOCATOR_ABI, - REWARDS_ELIGIBILITY_ORACLE_ABI, - REWARDS_MANAGER_ABI, -} from '../lib/abis.js' -import type { AddressBookOps } from '../lib/address-book-ops.js' -import { - checkIssuanceAllocatorActivation, - checkOperatorRole, - getReclaimAddress, - RECLAIM_CONTRACT_NAMES, - RECLAIM_REASONS, - type ReclaimReasonKey, - supportsInterface, -} from '../lib/contract-checks.js' +import { CONTROLLER_ABI } from '../lib/abis.js' +import { autoDetectForkNetwork } from '../lib/address-book-utils.js' +import { formatAddress } from '../lib/contract-checks.js' import { type AddressBookType, getContractsByAddressBook } from '../lib/contract-registry.js' -import { getContractStatusLine } from '../lib/sync-utils.js' +import { + getIssuanceAllocatorChecks, + getReclaimAddressChecks, + getRewardsEligibilityOracleChecks, + getRewardsManagerChecks, + type IntegrationCheck, +} from '../lib/status-detail.js' +import { getContractStatusLine, type ProxyAdminOwnershipContext } from '../lib/sync-utils.js' import { graph } from '../rocketh/deploy.js' /** Get deployable contract names for an address book (requires explicit deployable: true) */ @@ -31,14 +24,97 @@ function getDeployableContracts(addressBook: AddressBookType): string[] { .map(([name]) => name) } -/** Integration check result */ -interface IntegrationCheck { - ok: boolean | null // null = not applicable / not deployed - label: string +/** + * Get non-deployable contract names for an address book. + * + * Includes prerequisites (`prerequisite: true`), address-only entries + * (`addressOnly: true`) and pure registry placeholders (`{}`). The status task + * surfaces these as context — they're contracts the deployment depends on but + * doesn't manage. Entries not present in the on-chain address book are filtered + * out at print time so the listing only shows what actually exists for the + * network. + */ +function getPrerequisiteContracts(addressBook: AddressBookType): string[] { + return getContractsByAddressBook(addressBook) + .filter(([_, meta]) => meta.deployable !== true) + .map(([name]) => name) +} + +function printCheck(check: IntegrationCheck): void { + const icon = check.ok === null ? '○' : check.ok ? '✓' : '✗' + console.log(` ${icon} ${check.label}`) +} + +function printWarnings(warnings: string[] | undefined): void { + if (!warnings) return + for (const warning of warnings) { + console.log(` ⚠ ${warning}`) + } +} + +/** Print proxy admin detail in verbose/component mode */ +function printProxyAdminDetail(result: { + proxyAdminOwner?: string + proxyAdminAddress?: string + proxyAdminOwnerAddress?: string +}): void { + if (!result.proxyAdminAddress) return + const ownerLabel = + result.proxyAdminOwner === 'governor' + ? 'governor ✓' + : result.proxyAdminOwner === 'deployer' + ? 'deployer ⚠' + : 'not governor ⚠' + const ownerAddr = result.proxyAdminOwnerAddress ? ` ${result.proxyAdminOwnerAddress}` : '' + console.log(` ProxyAdmin: ${result.proxyAdminAddress}`) + console.log(` ProxyAdmin owner:${ownerAddr} (${ownerLabel})`) +} + +/** + * Print prerequisite contracts (non-deployable registry entries) in a dim format. + * + * Shown after the deployable contracts in each address book section. Skips + * entries that aren't present in the address book — placeholders that are in + * the registry for type completeness but aren't configured for the network are + * silent rather than printed as `(not deployed)`. + * + * In default mode each entry is one line: `· Name @ 0x1234...5678`. In + * verbose mode the full `getContractStatusLine` output is shown so users can + * drill into proxy detail for prerequisites that have it. + */ +async function printPrerequisites( + client: PublicClient | undefined, + addressBookType: AddressBookType, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + addressBook: any, + matchesComponent: (name: string) => boolean, + verbose: boolean, + ownershipCtx: ProxyAdminOwnershipContext | undefined, +): Promise { + const names = getPrerequisiteContracts(addressBookType).filter(matchesComponent) + // Filter to entries actually present in the address book — placeholders that + // aren't configured for this network shouldn't add noise. + const present = names.filter((name) => addressBook.entryExists(name)) + if (present.length === 0) return + + for (const name of present) { + if (verbose) { + const result = await getContractStatusLine(client, addressBookType, addressBook, name, undefined, ownershipCtx) + console.log(` · ${result.line}`) + printWarnings(result.warnings) + printProxyAdminDetail(result) + } else { + const entry = addressBook.getEntry(name) + const addr = entry?.address ? formatAddress(entry.address) : '(no address)' + console.log(` · ${name} @ ${addr}`) + } + } } interface TaskArgs { package: string + verbose: boolean + component: string } const action: NewTaskActionFunction = async (taskArgs, hre) => { @@ -47,25 +123,82 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { const conn = await (hre as any).network.connect() const networkName = conn.networkName const packageFilter = taskArgs.package.toLowerCase() + const verbose = taskArgs.verbose + const componentFilter = taskArgs.component?.toLowerCase() || '' + const showDetail = verbose || !!componentFilter + + // Get configured chain ID from network config (always available) + const configuredChainId = conn.networkConfig?.chainId as number | undefined + + // Default RPC URLs for read-only access (no accounts needed) + const DEFAULT_RPC_URLS: Record = { + arbitrumOne: 'https://arb1.arbitrum.io/rpc', + arbitrumSepolia: 'https://sepolia-rollup.arbitrum.io/rpc', + } + + // Get RPC URL: prefer env var, then default + const envRpcUrl = + networkName === 'arbitrumSepolia' + ? process.env.ARBITRUM_SEPOLIA_RPC + : networkName === 'arbitrumOne' + ? process.env.ARBITRUM_ONE_RPC + : undefined + const rpcUrl = envRpcUrl || DEFAULT_RPC_URLS[networkName] // Get viem public client for on-chain checks + // Use direct HTTP transport to RPC URL (bypasses Hardhat's account resolution) let client: PublicClient | undefined let actualChainId: number | undefined - try { - if (conn.provider) { + let providerError: string | undefined + + if (rpcUrl) { + // Create read-only client directly to RPC (no accounts needed) + try { client = createPublicClient({ - transport: custom(conn.provider), + transport: http(rpcUrl), }) as PublicClient actualChainId = await client.getChainId() + } catch (e) { + client = undefined + const errMsg = e instanceof Error ? e.message : String(e) + providerError = errMsg.split('\n')[0] + } + } else { + // No RPC URL available - try Hardhat's provider (may fail if accounts not configured) + try { + if (conn.provider) { + client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + actualChainId = await client.getChainId() + } + } catch (e) { + // Provider failed - disable on-chain checks + client = undefined + + // Extract error message (may be nested in viem error or cause chain) + let errMsg = e instanceof Error ? e.message : String(e) + const cause = e instanceof Error ? (e as Error & { cause?: Error }).cause : undefined + if (cause?.message) { + errMsg = cause.message + } + + providerError = errMsg.split('\n')[0] + } + } + + // Auto-detect fork network from anvil if on localhost without FORK_NETWORK + if (configuredChainId === 31337 && !process.env.FORK_NETWORK && !process.env.HARDHAT_FORK) { + const detected = await autoDetectForkNetwork() + if (detected) { + console.log(`🔍 Auto-detected fork network: ${detected}`) } - } catch { - // Provider not available } - // Determine target chain ID: use actual chain ID when not in fork mode + // Determine target chain ID: use fork target, then configured, then actual, then fallback const forkChainId = graph.getForkTargetChainId() const isForkMode = forkChainId !== null - const targetChainId = forkChainId ?? actualChainId ?? 31337 + const targetChainId = forkChainId ?? configuredChainId ?? actualChainId ?? 31337 // Show status header with chain info if (isForkMode) { @@ -75,7 +208,13 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { console.log(`⚠️ Warning: Connected chain (${actualChainId}) differs from target (${targetChainId})`) console.log(` Address book lookups use chainId ${targetChainId}\n`) } else { - console.log(`\n🔍 Status: ${networkName} (chainId: ${actualChainId ?? targetChainId})\n`) + console.log(`\n🔍 Status: ${networkName} (chainId: ${targetChainId})\n`) + } + + // Show provider warning if we couldn't connect (but continue with address book lookups) + if (providerError) { + console.log(`⚠️ Provider unavailable: ${providerError}`) + console.log(` On-chain checks disabled. Set the missing variable or use --network hardhat for local testing.\n`) } // Get address books @@ -83,324 +222,180 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { const subgraphServiceAddressBook = graph.getSubgraphServiceAddressBook(targetChainId) const issuanceAddressBook = graph.getIssuanceAddressBook(targetChainId) - // Horizon contracts (deploy targets only) - if (packageFilter === 'all' || packageFilter === 'horizon') { - console.log('📦 Horizon') - for (const name of getDeployableContracts('horizon')) { - const result = await getContractStatusLine(client, 'horizon', horizonAddressBook, name) - console.log(` ${result.line}`) - printWarnings(result.warnings) - - // Integration checks for RewardsManager (only if deployed) - if (name === 'RewardsManager' && client && result.exists) { - const checks = await getRewardsManagerChecks(client, horizonAddressBook) - for (const check of checks) { - printCheck(check) + // Resolve governor/deployer for proxy admin ownership checks + let ownershipCtx: ProxyAdminOwnershipContext | undefined + if (client) { + try { + const controllerAddress = horizonAddressBook.entryExists('Controller') + ? horizonAddressBook.getEntry('Controller')?.address + : null + if (controllerAddress) { + const governor = (await client.readContract({ + address: controllerAddress as `0x${string}`, + abi: CONTROLLER_ABI, + functionName: 'getGovernor', + })) as string + + if (governor) { + // Deployer is best-effort: available when provider has accounts (fork/local) + let deployer: string | undefined + try { + const accounts = (await conn.provider?.request({ method: 'eth_accounts' })) as string[] | undefined + if (accounts && accounts.length > 0) { + deployer = accounts[0] + } + } catch { + // No accounts available (read-only provider) — that's fine + } + ownershipCtx = { governor, deployer } } } + } catch { + // Controller not available — skip ownership checks } } - // SubgraphService contracts - if (packageFilter === 'all' || packageFilter === 'subgraph-service') { - console.log('\n📦 SubgraphService') - for (const name of getDeployableContracts('subgraph-service')) { - const result = await getContractStatusLine(client, 'subgraph-service', subgraphServiceAddressBook, name) - console.log(` ${result.line}`) - printWarnings(result.warnings) + // Helper to check if a contract name matches the component filter + const matchesComponent = (name: string) => !componentFilter || name.toLowerCase().includes(componentFilter) + + // Show ownership context in verbose mode + if (verbose && ownershipCtx) { + console.log(` Governor: ${ownershipCtx.governor}`) + if (ownershipCtx.deployer) { + console.log(` Deployer: ${ownershipCtx.deployer}`) } + console.log() } - // Issuance contracts - if (packageFilter === 'all' || packageFilter === 'issuance') { - console.log('\n📦 Issuance') - for (const name of getDeployableContracts('issuance')) { - const result = await getContractStatusLine(client, 'issuance', issuanceAddressBook, name) - console.log(` ${result.line}`) - printWarnings(result.warnings) - - // Integration checks for IssuanceAllocator (only if deployed) - if (name === 'IssuanceAllocator' && client && result.exists) { - const checks = await getIssuanceAllocatorChecks(client, horizonAddressBook, issuanceAddressBook) - for (const check of checks) { - printCheck(check) - } - } - - // Integration checks for RewardsEligibilityOracle (only if deployed) - if (name === 'RewardsEligibilityOracle' && client && result.exists) { - const checks = await getRewardsEligibilityOracleChecks(client, horizonAddressBook, issuanceAddressBook) - for (const check of checks) { - printCheck(check) + // Horizon contracts (deploy targets + prerequisites) + if (packageFilter === 'all' || packageFilter === 'horizon') { + const contracts = getDeployableContracts('horizon').filter(matchesComponent) + if (contracts.length > 0 || showDetail) { + console.log('📦 Horizon') + for (const name of contracts) { + const result = await getContractStatusLine(client, 'horizon', horizonAddressBook, name, undefined, ownershipCtx) + console.log(` ${result.line}`) + printWarnings(result.warnings) + + if (showDetail) { + printProxyAdminDetail(result) + + // Integration checks for RewardsManager (only if deployed) + if (name === 'RewardsManager' && client && result.exists) { + const checks = await getRewardsManagerChecks(client, horizonAddressBook, targetChainId) + for (const check of checks) { + printCheck(check) + } + } } } - - // Integration checks for reclaim addresses (only if deployed) - if (name.startsWith('ReclaimedRewardsFor') && client && result.exists) { - const checks = await getReclaimAddressChecks(client, horizonAddressBook, issuanceAddressBook, name) - for (const check of checks) { - printCheck(check) - } + if (showDetail) { + await printPrerequisites(client, 'horizon', horizonAddressBook, matchesComponent, verbose, ownershipCtx) } } } - console.log() -} - -function printCheck(check: IntegrationCheck): void { - const icon = check.ok === null ? '○' : check.ok ? '✓' : '✗' - console.log(` ${icon} ${check.label}`) -} - -function printWarnings(warnings: string[] | undefined): void { - if (!warnings) return - for (const warning of warnings) { - console.log(` ⚠ ${warning}`) - } -} - -async function getRewardsManagerChecks(client: PublicClient, horizonBook: AddressBookOps): Promise { - const checks: IntegrationCheck[] = [] - const rmAddress = horizonBook.entryExists('RewardsManager') ? horizonBook.getEntry('RewardsManager')?.address : null - - if (!rmAddress) return checks - - // Check IRewardsManager support (latest interface version) - const supportsRewardsManager = await supportsInterface(client, rmAddress, IREWARDS_MANAGER_INTERFACE_ID) - checks.push({ ok: supportsRewardsManager, label: `implements IRewardsManager (${IREWARDS_MANAGER_INTERFACE_ID})` }) - - // Check IIssuanceTarget support (required for issuance integration) - const supportsIssuanceTarget = await supportsInterface(client, rmAddress, IISSUANCE_TARGET_INTERFACE_ID) - checks.push({ ok: supportsIssuanceTarget, label: `implements IIssuanceTarget (${IISSUANCE_TARGET_INTERFACE_ID})` }) - - return checks -} - -async function getIssuanceAllocatorChecks( - client: PublicClient, - horizonBook: AddressBookOps, - issuanceBook: AddressBookOps, -): Promise { - const checks: IntegrationCheck[] = [] - - const iaAddress = issuanceBook.entryExists('IssuanceAllocator') - ? issuanceBook.getEntry('IssuanceAllocator')?.address - : null - const rmAddress = horizonBook.entryExists('RewardsManager') ? horizonBook.getEntry('RewardsManager')?.address : null - const gtAddress = horizonBook.entryExists('L2GraphToken') ? horizonBook.getEntry('L2GraphToken')?.address : null - - if (!iaAddress || !rmAddress || !gtAddress) return checks - - // RM must implement IIssuanceTarget for IA integration - const rmSupportsTarget = await supportsInterface(client, rmAddress, IISSUANCE_TARGET_INTERFACE_ID) - checks.push({ ok: rmSupportsTarget, label: `RM implements IIssuanceTarget (${IISSUANCE_TARGET_INTERFACE_ID})` }) - - // Only check activation if RM supports IIssuanceTarget (has been upgraded) - if (rmSupportsTarget) { - const activation = await checkIssuanceAllocatorActivation(client, iaAddress, rmAddress, gtAddress) - checks.push({ ok: activation.iaIntegrated, label: 'RM.issuanceAllocator == this' }) - checks.push({ ok: activation.iaMinter, label: 'GraphToken.MINTER_ROLE granted' }) - } else { - // RM not upgraded yet - can't check activation - checks.push({ ok: null, label: 'RM.issuanceAllocator == this (RM not upgraded)' }) - checks.push({ ok: null, label: 'GraphToken.MINTER_ROLE granted (RM not upgraded)' }) - } - - // Check default target configured - try { - const defaultTarget = (await client.readContract({ - address: iaAddress as `0x${string}`, - abi: ISSUANCE_ALLOCATOR_ABI, - functionName: 'getDefaultTarget', - })) as string - const hasDefaultTarget = defaultTarget !== '0x0000000000000000000000000000000000000000' - checks.push({ ok: hasDefaultTarget, label: 'defaultTarget configured' }) - } catch { - // Function not available - } - - return checks -} - -async function getRewardsEligibilityOracleChecks( - client: PublicClient, - horizonBook: AddressBookOps, - issuanceBook: AddressBookOps, -): Promise { - const checks: IntegrationCheck[] = [] - - const reoAddress = issuanceBook.entryExists('RewardsEligibilityOracle') - ? issuanceBook.getEntry('RewardsEligibilityOracle')?.address - : null - const rmAddress = horizonBook.entryExists('RewardsManager') ? horizonBook.getEntry('RewardsManager')?.address : null - const controllerAddress = horizonBook.entryExists('Controller') ? horizonBook.getEntry('Controller')?.address : null - - if (!reoAddress || !rmAddress) return checks - - // Get governor and pause guardian from Controller for role checks - let governor: string | null = null - let pauseGuardian: string | null = null - if (controllerAddress) { - try { - governor = (await client.readContract({ - address: controllerAddress as `0x${string}`, - abi: [ - { - inputs: [], - name: 'getGovernor', - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - ], - functionName: 'getGovernor', - })) as string - } catch { - // Controller doesn't have getGovernor - } - try { - pauseGuardian = (await client.readContract({ - address: controllerAddress as `0x${string}`, - abi: [ - { - inputs: [], - name: 'pauseGuardian', - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - ], - functionName: 'pauseGuardian', - })) as string - } catch { - // Controller doesn't have pauseGuardian - } - } - - // Check access control roles - try { - const governorRole = (await client.readContract({ - address: reoAddress as `0x${string}`, - abi: REWARDS_ELIGIBILITY_ORACLE_ABI, - functionName: 'GOVERNOR_ROLE', - })) as `0x${string}` - - if (governor) { - const governorHasRole = (await client.readContract({ - address: reoAddress as `0x${string}`, - abi: REWARDS_ELIGIBILITY_ORACLE_ABI, - functionName: 'hasRole', - args: [governorRole, governor as `0x${string}`], - })) as boolean - checks.push({ ok: governorHasRole, label: 'governor has GOVERNOR_ROLE' }) + // SubgraphService contracts + if (packageFilter === 'all' || packageFilter === 'subgraph-service') { + const contracts = getDeployableContracts('subgraph-service').filter(matchesComponent) + if (contracts.length > 0 || showDetail) { + console.log('\n📦 SubgraphService') + for (const name of contracts) { + const result = await getContractStatusLine( + client, + 'subgraph-service', + subgraphServiceAddressBook, + name, + undefined, + ownershipCtx, + ) + console.log(` ${result.line}`) + printWarnings(result.warnings) + if (showDetail) { + printProxyAdminDetail(result) + } + } + if (showDetail) { + await printPrerequisites( + client, + 'subgraph-service', + subgraphServiceAddressBook, + matchesComponent, + verbose, + ownershipCtx, + ) + } } - } catch { - // Role check not available } - // Check PAUSE_ROLE - try { - const pauseRole = (await client.readContract({ - address: reoAddress as `0x${string}`, - abi: REWARDS_ELIGIBILITY_ORACLE_ABI, - functionName: 'PAUSE_ROLE', - })) as `0x${string}` - - if (pauseGuardian) { - const pauseGuardianHasRole = (await client.readContract({ - address: reoAddress as `0x${string}`, - abi: REWARDS_ELIGIBILITY_ORACLE_ABI, - functionName: 'hasRole', - args: [pauseRole, pauseGuardian as `0x${string}`], - })) as boolean - checks.push({ ok: pauseGuardianHasRole, label: 'pause guardian has PAUSE_ROLE' }) + // Issuance contracts + if (packageFilter === 'all' || packageFilter === 'issuance') { + const contracts = getDeployableContracts('issuance').filter(matchesComponent) + if (contracts.length > 0 || showDetail) { + console.log('\n📦 Issuance') + for (const name of contracts) { + const result = await getContractStatusLine( + client, + 'issuance', + issuanceAddressBook, + name, + undefined, + ownershipCtx, + ) + console.log(` ${result.line}`) + printWarnings(result.warnings) + + if (showDetail) { + printProxyAdminDetail(result) + + // Integration checks for IssuanceAllocator (only if deployed) + if (name === 'IssuanceAllocator' && client && result.exists) { + const checks = await getIssuanceAllocatorChecks(client, horizonAddressBook, issuanceAddressBook) + for (const check of checks) { + printCheck(check) + } + } + + // Integration checks for REO instances (only if deployed) + if ( + (name === 'RewardsEligibilityOracleA' || name === 'RewardsEligibilityOracleB') && + client && + result.exists + ) { + const checks = await getRewardsEligibilityOracleChecks( + client, + horizonAddressBook, + issuanceAddressBook, + name, + ) + for (const check of checks) { + printCheck(check) + } + } + + // Integration checks for reclaim address (only if deployed) + if (name === 'ReclaimedRewards' && client && result.exists) { + const checks = await getReclaimAddressChecks(client, horizonAddressBook, issuanceAddressBook) + for (const check of checks) { + printCheck(check) + } + } + } + } + if (showDetail) { + await printPrerequisites(client, 'issuance', issuanceAddressBook, matchesComponent, verbose, ownershipCtx) + } } - } catch { - // Role check not available - } - - // Check OPERATOR_ROLE using shared function (single source of truth) - const networkOperator = issuanceBook.entryExists('NetworkOperator') - ? (issuanceBook.getEntry('NetworkOperator')?.address ?? null) - : null - - try { - const operatorCheck = await checkOperatorRole(client, reoAddress, networkOperator) - // For status check: NetworkOperator not configured is always a configuration failure - // (even if role assignment is technically correct with 0 holders) - const statusOk = networkOperator === null ? false : operatorCheck.ok - checks.push({ ok: statusOk, label: operatorCheck.message }) - } catch { - checks.push({ ok: null, label: 'OPERATOR_ROLE (check failed)' }) - } - - // Check if configured in RM - try { - const currentREO = (await client.readContract({ - address: rmAddress as `0x${string}`, - abi: REWARDS_MANAGER_ABI, - functionName: 'getProviderEligibilityOracle', - })) as string - const configured = currentREO.toLowerCase() === reoAddress.toLowerCase() - checks.push({ ok: configured, label: 'RM.providerEligibilityOracle == this' }) - } catch { - // Function not available on old RM } - // Check if validation is enabled - try { - const enabled = (await client.readContract({ - address: reoAddress as `0x${string}`, - abi: REWARDS_ELIGIBILITY_ORACLE_ABI, - functionName: 'getEligibilityValidation', - })) as boolean - checks.push({ ok: enabled, label: 'eligibility validation enabled' }) - } catch { - // Function not available + // Legend for icons (shown when proxy admin warnings are present or in verbose mode) + if (verbose) { + console.log( + '\n Legend: ✓ ok △ code changed ◷ pending upgrade ↑ upgraded ↻ synced 🔑 ProxyAdmin not on governor', + ) } - // Check last oracle update time (indicates if active) - try { - const lastUpdate = (await client.readContract({ - address: reoAddress as `0x${string}`, - abi: REWARDS_ELIGIBILITY_ORACLE_ABI, - functionName: 'getLastOracleUpdateTime', - })) as bigint - const hasUpdates = lastUpdate > 0n - checks.push({ ok: hasUpdates, label: 'oracle has processed updates' }) - } catch { - // Function not available - } - - return checks -} - -async function getReclaimAddressChecks( - client: PublicClient, - horizonBook: AddressBookOps, - issuanceBook: AddressBookOps, - contractName: string, -): Promise { - const checks: IntegrationCheck[] = [] - - const rmAddress = horizonBook.entryExists('RewardsManager') ? horizonBook.getEntry('RewardsManager')?.address : null - const contractAddress = issuanceBook.entryExists(contractName) ? issuanceBook.getEntry(contractName)?.address : null - - if (!rmAddress || !contractAddress) return checks - - // Find the reclaim reason for this contract - const reclaimKey = Object.entries(RECLAIM_CONTRACT_NAMES).find(([_, name]) => name === contractName)?.[0] as - | ReclaimReasonKey - | undefined - if (!reclaimKey) return checks - - const reason = RECLAIM_REASONS[reclaimKey] - const actualAddress = await getReclaimAddress(client, rmAddress, reason) - const configured = actualAddress?.toLowerCase() === contractAddress.toLowerCase() - checks.push({ ok: configured, label: 'configured in RM.reclaimAddresses' }) - - return checks + console.log() } const deployStatusTask = task('deploy:status', 'Show deployment and integration status') @@ -410,6 +405,18 @@ const deployStatusTask = task('deploy:status', 'Show deployment and integration type: ArgumentType.STRING, defaultValue: 'all', }) + .addOption({ + name: 'verbose', + description: 'Show full detail including proxy admin ownership, addresses, and legend', + type: ArgumentType.FLAG, + defaultValue: false, + }) + .addOption({ + name: 'component', + description: 'Filter to contracts matching this name (case-insensitive substring match)', + type: ArgumentType.STRING, + defaultValue: '', + }) .setAction(async () => ({ default: action })) .build() diff --git a/packages/deployment/tasks/eth-tasks.ts b/packages/deployment/tasks/eth-tasks.ts new file mode 100644 index 000000000..7033fe8c4 --- /dev/null +++ b/packages/deployment/tasks/eth-tasks.ts @@ -0,0 +1,208 @@ +import { task } from 'hardhat/config' +import { ArgumentType } from 'hardhat/types/arguments' +import type { NewTaskActionFunction } from 'hardhat/types/tasks' +import { createPublicClient, createWalletClient, custom, formatEther, parseEther, type PublicClient } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +import { getDeployerKeyName, resolveConfigVar } from '../lib/task-utils.js' + +// -- Task Types -- + +interface CheckKeyArgs { + key: string +} + +interface FundArgs { + to: string + amount: string +} + +interface BalanceArgs { + account: string +} + +// -- Task Actions -- + +/** + * Verify a keystore variable holds the private key for an expected address + */ +const checkKeyAction: NewTaskActionFunction = async (taskArgs, hre) => { + if (!taskArgs.key) { + console.error('\nError: --key is required') + console.error('Usage: npx hardhat eth:check-key --key ARBITRUM_ONE_ORACLE_KEY') + return + } + + const keyValue = await resolveConfigVar(hre, taskArgs.key) + + if (!keyValue) { + console.error(`\nError: Key "${taskArgs.key}" not found in keystore or environment.`) + console.error(`Set via keystore: npx hardhat keystore set ${taskArgs.key}`) + console.error(`Or environment: export ${taskArgs.key}=0x...`) + return + } + + const account = privateKeyToAccount(keyValue as `0x${string}`) + + console.log(`\nKey Check`) + console.log(` Variable: ${taskArgs.key}`) + console.log(` Address: ${account.address}`) + console.log() +} + +/** + * Query ETH balance for an address + */ +const balanceAction: NewTaskActionFunction = async (taskArgs, hre) => { + if (!taskArgs.account) { + console.error('\nError: --account is required') + console.error('Usage: npx hardhat eth:balance --account 0x... --network arbitrumOne') + return + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const chainId = await client.getChainId() + const account = taskArgs.account as `0x${string}` + const balance = await client.getBalance({ address: account }) + + console.log(`\nETH Balance`) + console.log(` Account: ${account}`) + console.log(` Network: ${networkName} (chainId: ${chainId})`) + console.log(` Balance: ${formatEther(balance)} ETH`) + console.log() +} + +/** + * Send ETH from deployer to an address + */ +const fundAction: NewTaskActionFunction = async (taskArgs, hre) => { + if (!taskArgs.to || !taskArgs.amount) { + console.error('\nError: --to and --amount are required') + console.error('Usage: npx hardhat eth:fund --to 0x... --amount 0.01 --network arbitrumOne') + return + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const chainId = await client.getChainId() + + // Get deployer key + const keyName = getDeployerKeyName(networkName) + const deployerKey = await resolveConfigVar(hre, keyName) + + if (!deployerKey) { + console.error('\nError: No deployer key configured.') + console.error(`Set via keystore: npx hardhat keystore set ${keyName}`) + console.error(`Or environment: export ${keyName}=0x...`) + return + } + + const account = privateKeyToAccount(deployerKey as `0x${string}`) + const to = taskArgs.to as `0x${string}` + const value = parseEther(taskArgs.amount) + + // Check deployer balance + const balance = await client.getBalance({ address: account.address }) + + if (balance < value) { + console.error(`\nError: Insufficient balance`) + console.error(` Deployer balance: ${formatEther(balance)} ETH`) + console.error(` Requested: ${taskArgs.amount} ETH`) + return + } + + console.log(`\nSending ETH`) + console.log(` From: ${account.address}`) + console.log(` To: ${to}`) + console.log(` Amount: ${taskArgs.amount} ETH`) + console.log(` Network: ${networkName} (chainId: ${chainId})`) + + const walletClient = createWalletClient({ + account, + transport: custom(conn.provider), + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const hash = await (walletClient as any).sendTransaction({ to, value }) + console.log(` TX: ${hash}`) + + const receipt = await client.waitForTransactionReceipt({ hash }) + if (receipt.status === 'success') { + const newBalance = await client.getBalance({ address: to }) + console.log(`\n Sent successfully!`) + console.log(` Recipient balance: ${formatEther(newBalance)} ETH\n`) + } else { + console.error(`\n Transaction failed\n`) + } +} + +// -- Task Definitions -- + +/** + * Verify a keystore/env variable holds the key for an expected address + * + * Examples: + * npx hardhat eth:check-key --key ARBITRUM_ONE_ORACLE_KEY + */ +export const ethCheckKeyTask = task('eth:check-key', 'Derive and display address from a keystore variable') + .addOption({ + name: 'key', + description: 'Keystore variable name (e.g. ARBITRUM_ONE_ORACLE_KEY)', + type: ArgumentType.STRING, + defaultValue: '', + }) + .setAction(async () => ({ default: checkKeyAction })) + .build() + +/** + * Query ETH balance for an address + * + * Examples: + * npx hardhat eth:balance --account 0x1234... --network arbitrumOne + */ +export const ethBalanceTask = task('eth:balance', 'Query ETH balance for an address') + .addOption({ + name: 'account', + description: 'Address to query balance for', + type: ArgumentType.STRING, + defaultValue: '', + }) + .setAction(async () => ({ default: balanceAction })) + .build() + +/** + * Send ETH from deployer to an address + * + * Uses the deployer key from the Hardhat keystore or environment. + * + * Examples: + * npx hardhat eth:fund --to 0x1234... --amount 0.01 --network arbitrumOne + */ +export const ethFundTask = task('eth:fund', 'Send ETH from deployer to an address') + .addOption({ + name: 'to', + description: 'Recipient address', + type: ArgumentType.STRING, + defaultValue: '', + }) + .addOption({ + name: 'amount', + description: 'Amount of ETH to send (e.g. 0.01)', + type: ArgumentType.STRING, + defaultValue: '', + }) + .setAction(async () => ({ default: fundAction })) + .build() + +export default [ethCheckKeyTask, ethBalanceTask, ethFundTask] diff --git a/packages/deployment/tasks/execute-governance.ts b/packages/deployment/tasks/execute-governance.ts index ea405265d..2fb205a74 100644 --- a/packages/deployment/tasks/execute-governance.ts +++ b/packages/deployment/tasks/execute-governance.ts @@ -1,50 +1,11 @@ import fs from 'fs' -import { configVariable, task } from 'hardhat/config' +import { task } from 'hardhat/config' import type { NewTaskActionFunction } from 'hardhat/types/tasks' import path from 'path' +import { autoDetectForkNetwork } from '../lib/address-book-utils.js' import { executeGovernanceTxs } from '../lib/execute-governance.js' - -/** - * Convert network name to env var prefix: arbitrumSepolia → ARBITRUM_SEPOLIA - */ -function networkToEnvPrefix(networkName: string): string { - return networkName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase() -} - -/** - * Resolve a configuration variable using Hardhat's hook chain (keystore + env fallback) - * - * Uses hre.hooks.runHandlerChain to go through the configurationVariables fetchValue - * hook chain, which includes the keystore plugin. - */ -async function resolveConfigVar(hre: unknown, name: string): Promise { - try { - const variable = configVariable(name) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const hooks = (hre as any).hooks - - // Call the configurationVariables fetchValue hook chain - // Falls back to env var if not in keystore - const value = await hooks.runHandlerChain( - 'configurationVariables', - 'fetchValue', - [variable], - // Default handler: read from environment variable - async (_context: unknown, v: { name: string }) => { - const envValue = process.env[v.name] - if (typeof envValue !== 'string') { - throw new Error(`Environment variable ${v.name} not found`) - } - return envValue - }, - ) - return value - } catch { - // Key not configured in keystore or env - return undefined - } -} +import { networkToEnvPrefix, resolveConfigVar } from '../lib/task-utils.js' /** * Resolve governor key for a network. @@ -79,17 +40,17 @@ interface TaskArgs { * npx hardhat keystore set ARBITRUM_SEPOLIA_GOVERNOR_KEY * npx hardhat deploy:execute-governance --network arbitrumSepolia * - * For fork testing: - * FORK_NETWORK=arbitrumSepolia npx hardhat deploy:execute-governance --network fork + * For fork testing (auto-detects fork network from anvil): + * npx hardhat deploy:execute-governance --network fork */ const action: NewTaskActionFunction = async (_taskArgs, hre) => { + // Auto-detect fork network from anvil before checking + await autoDetectForkNetwork() + // HH v3: Connect to network to get network connection // eslint-disable-next-line @typescript-eslint/no-explicit-any const conn = await (hre as any).network.connect() - // Get governor key: try network-specific first, fall back to generic - const governorPrivateKey = await resolveGovernorKey(hre, conn.networkName) - // Create minimal Environment-like object for executeGovernanceTxs const env = { name: conn.networkName, @@ -112,8 +73,11 @@ const action: NewTaskActionFunction = async (_taskArgs, hre) => { }, } + // Lazy resolver for governor key - only called when actually needed (non-fork EOA mode) + const resolveKey = () => resolveGovernorKey(hre, conn.networkName) + // eslint-disable-next-line @typescript-eslint/no-explicit-any - await executeGovernanceTxs(env as any, { governorPrivateKey }) + await executeGovernanceTxs(env as any, { resolveGovernorKey: resolveKey }) } const executeGovernanceTask = task( diff --git a/packages/deployment/tasks/grant-role.ts b/packages/deployment/tasks/grant-role.ts index daea22f3a..df28572e7 100644 --- a/packages/deployment/tasks/grant-role.ts +++ b/packages/deployment/tasks/grant-role.ts @@ -1,4 +1,4 @@ -import { configVariable, task } from 'hardhat/config' +import { task } from 'hardhat/config' import { ArgumentType } from 'hardhat/types/arguments' import type { NewTaskActionFunction } from 'hardhat/types/tasks' import { @@ -19,8 +19,13 @@ import { getRoleHash, hasAdminRole, } from '../lib/contract-checks.js' -import { type AddressBookType, CONTRACT_REGISTRY } from '../lib/contract-registry.js' import { createGovernanceTxBuilder } from '../lib/execute-governance.js' +import { + getContractAddress, + getDeployerKeyName, + resolveConfigVar, + resolveContractFromRegistry, +} from '../lib/task-utils.js' import { graph } from '../rocketh/deploy.js' interface TaskArgs { @@ -30,73 +35,6 @@ interface TaskArgs { account: string } -/** - * Convert network name to env var prefix: arbitrumSepolia → ARBITRUM_SEPOLIA - */ -function networkToEnvPrefix(networkName: string): string { - return networkName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase() -} - -/** - * Resolve a configuration variable using Hardhat's hook chain (keystore + env fallback) - */ -async function resolveConfigVar(hre: unknown, name: string): Promise { - try { - const variable = configVariable(name) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const hooks = (hre as any).hooks - - const value = await hooks.runHandlerChain( - 'configurationVariables', - 'fetchValue', - [variable], - async (_context: unknown, v: { name: string }) => { - const envValue = process.env[v.name] - if (typeof envValue !== 'string') { - throw new Error(`Variable ${v.name} not found`) - } - return envValue - }, - ) - return value - } catch { - return undefined - } -} - -/** - * Resolve contract from registry by name - */ -function resolveContractFromRegistry( - contractName: string, -): { addressBook: AddressBookType; roles: readonly string[] } | null { - for (const [book, contracts] of Object.entries(CONTRACT_REGISTRY)) { - const contract = contracts[contractName as keyof typeof contracts] as { roles?: readonly string[] } | undefined - if (contract?.roles) { - return { addressBook: book as AddressBookType, roles: contract.roles } - } - } - return null -} - -/** - * Get contract address from address book - */ -function getContractAddress(addressBook: AddressBookType, contractName: string, chainId: number): string | null { - const book = - addressBook === 'issuance' - ? graph.getIssuanceAddressBook(chainId) - : addressBook === 'horizon' - ? graph.getHorizonAddressBook(chainId) - : graph.getSubgraphServiceAddressBook(chainId) - - if (!book.entryExists(contractName)) { - return null - } - - return book.getEntry(contractName)?.address ?? null -} - const action: NewTaskActionFunction = async (taskArgs, hre) => { const contractName = taskArgs.contract || undefined const addressArg = taskArgs.address || undefined @@ -128,6 +66,7 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { }) as PublicClient const actualChainId = await client.getChainId() + await graph.autoDetect() const forkChainId = graph.getForkTargetChainId() const targetChainId = forkChainId ?? actualChainId @@ -184,7 +123,7 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { console.log(` Admin holders: ${adminInfo.adminMembers.length > 0 ? adminInfo.adminMembers.join(', ') : '(none)'}`) // Get deployer account (from keystore or env var) - const keyName = `${networkToEnvPrefix(networkName === 'fork' ? (process.env.HARDHAT_FORK ?? 'arbitrumSepolia') : networkName)}_DEPLOYER_KEY` + const keyName = getDeployerKeyName(networkName) const deployerKey = await resolveConfigVar(hre, keyName) let deployer: string | undefined @@ -206,7 +145,8 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { console.log(`\n Deployer has ${adminInfo.adminRoleName ?? 'admin role'}, executing directly...`) // Execute directly - const hash = await walletClient.writeContract({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const hash = await (walletClient as any).writeContract({ address: contractAddress as `0x${string}`, abi: ACCESS_CONTROL_ENUMERABLE_ABI, functionName: 'grantRole', @@ -266,12 +206,12 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { * Grant a role to an account on a BaseUpgradeable contract * * Examples: - * npx hardhat roles:grant --contract RewardsEligibilityOracle --role ORACLE_ROLE --account 0x... --network arbitrumSepolia + * npx hardhat roles:grant --contract RewardsEligibilityOracleA --role ORACLE_ROLE --account 0x... --network arbitrumSepolia */ const grantRoleTask = task('roles:grant', 'Grant a role to an account') .addOption({ name: 'contract', - description: 'Contract name from registry (e.g., RewardsEligibilityOracle)', + description: 'Contract name from registry (e.g., RewardsEligibilityOracleA)', type: ArgumentType.STRING, defaultValue: '', }) diff --git a/packages/deployment/tasks/grt-tasks.ts b/packages/deployment/tasks/grt-tasks.ts new file mode 100644 index 000000000..1a8ffa099 --- /dev/null +++ b/packages/deployment/tasks/grt-tasks.ts @@ -0,0 +1,449 @@ +import { task } from 'hardhat/config' +import { ArgumentType } from 'hardhat/types/arguments' +import type { NewTaskActionFunction } from 'hardhat/types/tasks' +import { createPublicClient, createWalletClient, custom, formatEther, parseEther, type PublicClient } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +import { GRAPH_TOKEN_ABI } from '../lib/abis.js' +import { getDeployerKeyName, resolveConfigVar } from '../lib/task-utils.js' +import { graph } from '../rocketh/deploy.js' + +// governor() is on the Governed base contract, not in IGraphToken +const GOVERNED_ABI = [ + { + inputs: [], + name: 'governor', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +] as const + +/** + * Get L2GraphToken address from horizon address book + */ +function getGraphTokenAddress(chainId: number): string | null { + const book = graph.getHorizonAddressBook(chainId) + if (!book.entryExists('L2GraphToken')) { + return null + } + return book.getEntry('L2GraphToken')?.address ?? null +} + +// -- Task Types -- + +interface EmptyArgs { + // No arguments +} + +interface BalanceArgs { + account: string +} + +interface TransferArgs { + to: string + amount: string +} + +interface MintArgs { + to: string + amount: string +} + +// -- Task Actions -- + +/** + * Query GRT balance for an address + */ +const balanceAction: NewTaskActionFunction = async (taskArgs, hre) => { + if (!taskArgs.account) { + console.error('\nError: --account is required') + console.error('Usage: npx hardhat grt:balance --account 0x... --network arbitrumSepolia') + return + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const actualChainId = await client.getChainId() + await graph.autoDetect() + const forkChainId = graph.getForkTargetChainId() + const targetChainId = forkChainId ?? actualChainId + + const tokenAddress = getGraphTokenAddress(targetChainId) + if (!tokenAddress) { + console.error(`\nError: L2GraphToken not found in address book for chain ${targetChainId}`) + return + } + + const account = taskArgs.account as `0x${string}` + + const balance = (await client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'balanceOf', + args: [account], + })) as bigint + + console.log(`\nGRT Balance`) + console.log(` Account: ${account}`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + console.log(` Token: ${tokenAddress}`) + console.log(` Balance: ${formatEther(balance)} GRT`) + console.log() +} + +/** + * Transfer GRT from deployer to an address + */ +const transferAction: NewTaskActionFunction = async (taskArgs, hre) => { + if (!taskArgs.to || !taskArgs.amount) { + console.error('\nError: --to and --amount are required') + console.error('Usage: npx hardhat grt:transfer --to 0x... --amount 10000 --network arbitrumSepolia') + return + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const actualChainId = await client.getChainId() + await graph.autoDetect() + const forkChainId = graph.getForkTargetChainId() + const targetChainId = forkChainId ?? actualChainId + + const tokenAddress = getGraphTokenAddress(targetChainId) + if (!tokenAddress) { + console.error(`\nError: L2GraphToken not found in address book for chain ${targetChainId}`) + return + } + + // Get deployer key + const keyName = getDeployerKeyName(networkName) + const deployerKey = await resolveConfigVar(hre, keyName) + + if (!deployerKey) { + console.error('\nError: No deployer key configured.') + console.error(`Set via keystore: npx hardhat keystore set ${keyName}`) + console.error(`Or environment: export ${keyName}=0x...`) + return + } + + const account = privateKeyToAccount(deployerKey as `0x${string}`) + const to = taskArgs.to as `0x${string}` + const amount = parseEther(taskArgs.amount) + + // Check deployer balance + const balance = (await client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'balanceOf', + args: [account.address], + })) as bigint + + if (balance < amount) { + console.error(`\nError: Insufficient balance`) + console.error(` Deployer balance: ${formatEther(balance)} GRT`) + console.error(` Requested: ${taskArgs.amount} GRT`) + return + } + + console.log(`\nTransferring GRT`) + console.log(` From: ${account.address}`) + console.log(` To: ${to}`) + console.log(` Amount: ${taskArgs.amount} GRT`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + console.log(` Token: ${tokenAddress}`) + + const walletClient = createWalletClient({ + account, + transport: custom(conn.provider), + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const hash = await (walletClient as any).writeContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'transfer', + args: [to, amount], + }) + + console.log(` TX: ${hash}`) + + const receipt = await client.waitForTransactionReceipt({ hash }) + if (receipt.status === 'success') { + const newBalance = (await client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'balanceOf', + args: [to], + })) as bigint + + console.log(`\n Transferred successfully!`) + console.log(` Recipient balance: ${formatEther(newBalance)} GRT\n`) + } else { + console.error(`\n Transaction failed\n`) + } +} + +/** + * Mint GRT to an address (requires deployer to be a minter) + */ +const mintAction: NewTaskActionFunction = async (taskArgs, hre) => { + if (!taskArgs.to || !taskArgs.amount) { + console.error('\nError: --to and --amount are required') + console.error('Usage: npx hardhat grt:mint --to 0x... --amount 10000 --network arbitrumSepolia') + return + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const actualChainId = await client.getChainId() + await graph.autoDetect() + const forkChainId = graph.getForkTargetChainId() + const targetChainId = forkChainId ?? actualChainId + + const tokenAddress = getGraphTokenAddress(targetChainId) + if (!tokenAddress) { + console.error(`\nError: L2GraphToken not found in address book for chain ${targetChainId}`) + return + } + + // Get deployer key + const keyName = getDeployerKeyName(networkName) + const deployerKey = await resolveConfigVar(hre, keyName) + + if (!deployerKey) { + console.error('\nError: No deployer key configured.') + console.error(`Set via keystore: npx hardhat keystore set ${keyName}`) + console.error(`Or environment: export ${keyName}=0x...`) + return + } + + const account = privateKeyToAccount(deployerKey as `0x${string}`) + const to = taskArgs.to as `0x${string}` + const amount = parseEther(taskArgs.amount) + + // Check deployer is a minter + const isMinter = (await client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'isMinter', + args: [account.address], + })) as boolean + + if (!isMinter) { + console.error(`\nError: Deployer ${account.address} is not a minter on GraphToken`) + console.error('The deployer must be added as a minter by the governor first.') + return + } + + console.log(`\nMinting GRT`) + console.log(` To: ${to}`) + console.log(` Amount: ${taskArgs.amount} GRT`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + console.log(` Token: ${tokenAddress}`) + console.log(` Minter: ${account.address}`) + + const walletClient = createWalletClient({ + account, + transport: custom(conn.provider), + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const hash = await (walletClient as any).writeContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'mint', + args: [to, amount], + }) + + console.log(` TX: ${hash}`) + + const receipt = await client.waitForTransactionReceipt({ hash }) + if (receipt.status === 'success') { + // Read new balance + const newBalance = (await client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'balanceOf', + args: [to], + })) as bigint + + console.log(`\n Minted successfully!`) + console.log(` New balance: ${formatEther(newBalance)} GRT\n`) + } else { + console.error(`\n Transaction failed\n`) + } +} + +/** + * Show GRT token status: governor, deployer minter check, total supply + */ +const statusAction: NewTaskActionFunction = async (_taskArgs, hre) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const actualChainId = await client.getChainId() + await graph.autoDetect() + const forkChainId = graph.getForkTargetChainId() + const targetChainId = forkChainId ?? actualChainId + + const tokenAddress = getGraphTokenAddress(targetChainId) + if (!tokenAddress) { + console.error(`\nError: L2GraphToken not found in address book for chain ${targetChainId}`) + return + } + + // Read token info in parallel + const [governor, totalSupply] = await Promise.all([ + client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GOVERNED_ABI, + functionName: 'governor', + }) as Promise, + client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'totalSupply', + }) as Promise, + ]) + + console.log(`\nGRT Token Status`) + console.log(` Token: ${tokenAddress}`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + console.log(` Total supply: ${formatEther(totalSupply)} GRT`) + console.log(` Governor: ${governor}`) + + // Check if governor is a minter + const governorIsMinter = (await client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'isMinter', + args: [governor as `0x${string}`], + })) as boolean + console.log(` Governor is minter: ${governorIsMinter ? 'yes' : 'no'}`) + + // Check deployer if key is available + const keyName = getDeployerKeyName(networkName) + const deployerKey = await resolveConfigVar(hre, keyName) + + if (deployerKey) { + const deployer = privateKeyToAccount(deployerKey as `0x${string}`) + const deployerIsMinter = (await client.readContract({ + address: tokenAddress as `0x${string}`, + abi: GRAPH_TOKEN_ABI, + functionName: 'isMinter', + args: [deployer.address], + })) as boolean + + console.log(`\n Deployer: ${deployer.address}`) + console.log(` Deployer is minter: ${deployerIsMinter ? 'yes' : 'no'}`) + console.log(` Deployer is governor: ${deployer.address.toLowerCase() === governor.toLowerCase() ? 'yes' : 'no'}`) + + if (!deployerIsMinter) { + console.log(`\n To add deployer as minter, the governor must call:`) + console.log(` addMinter(${deployer.address})`) + } + } else { + console.log(`\n Deployer key not configured (${keyName})`) + } + + console.log() +} + +// -- Task Definitions -- + +/** + * Show GRT token status: governor, deployer minter status, total supply + * + * Examples: + * npx hardhat grt:status --network arbitrumSepolia + */ +export const grtStatusTask = task('grt:status', 'Show GRT token status (governor, minter, supply)') + .setAction(async () => ({ default: statusAction })) + .build() + +/** + * Query GRT balance for an address + * + * Examples: + * npx hardhat grt:balance --account 0x1234... --network arbitrumSepolia + */ +export const grtBalanceTask = task('grt:balance', 'Query GRT balance for an address') + .addOption({ + name: 'account', + description: 'Address to query balance for', + type: ArgumentType.STRING, + defaultValue: '', + }) + .setAction(async () => ({ default: balanceAction })) + .build() + +/** + * Transfer testnet GRT from deployer to an address + * + * Uses the deployer's existing balance. No minter role needed. + * + * Examples: + * npx hardhat grt:transfer --to 0x1234... --amount 10000 --network arbitrumSepolia + */ +export const grtTransferTask = task('grt:transfer', 'Transfer GRT from deployer to an address') + .addOption({ + name: 'to', + description: 'Recipient address', + type: ArgumentType.STRING, + defaultValue: '', + }) + .addOption({ + name: 'amount', + description: 'Amount of GRT to transfer (in whole tokens, e.g. 10000)', + type: ArgumentType.STRING, + defaultValue: '', + }) + .setAction(async () => ({ default: transferAction })) + .build() + +/** + * Mint testnet GRT to an address + * + * Requires deployer to be a minter on the GraphToken contract. + * The deployer/governor is a minter by default after deployment. + * + * Examples: + * npx hardhat grt:mint --to 0x1234... --amount 10000 --network arbitrumSepolia + */ +export const grtMintTask = task('grt:mint', 'Mint testnet GRT to an address') + .addOption({ + name: 'to', + description: 'Recipient address', + type: ArgumentType.STRING, + defaultValue: '', + }) + .addOption({ + name: 'amount', + description: 'Amount of GRT to mint (in whole tokens, e.g. 10000)', + type: ArgumentType.STRING, + defaultValue: '', + }) + .setAction(async () => ({ default: mintAction })) + .build() + +export default [grtStatusTask, grtBalanceTask, grtTransferTask, grtMintTask] diff --git a/packages/deployment/tasks/list-pending-implementations.ts b/packages/deployment/tasks/list-pending-implementations.ts index 3d85f50a4..76b0d4553 100644 --- a/packages/deployment/tasks/list-pending-implementations.ts +++ b/packages/deployment/tasks/list-pending-implementations.ts @@ -5,6 +5,7 @@ import type { NewTaskActionFunction } from 'hardhat/types/tasks' import type { AddressBookEntry, AddressBookOps } from '../lib/address-book-ops.js' import { + autoDetectForkNetwork, getForkTargetChainId, getHorizonAddressBook, getIssuanceAddressBook, @@ -33,6 +34,9 @@ const action: NewTaskActionFunction = async (_taskArgs, hre) => { const conn = await (hre as any).network.connect() const networkName = conn.networkName + // Auto-detect fork network from anvil before checking + await autoDetectForkNetwork() + // Get target chain ID (fork mode or provider) const forkChainId = getForkTargetChainId() let targetChainId: number diff --git a/packages/deployment/tasks/list-roles.ts b/packages/deployment/tasks/list-roles.ts index 1f0a8a4ac..46af75366 100644 --- a/packages/deployment/tasks/list-roles.ts +++ b/packages/deployment/tasks/list-roles.ts @@ -4,12 +4,8 @@ import type { NewTaskActionFunction } from 'hardhat/types/tasks' import { createPublicClient, custom, type PublicClient } from 'viem' import { enumerateContractRoles, type RoleInfo } from '../lib/contract-checks.js' -import { - type AddressBookType, - CONTRACT_REGISTRY, - Contracts, - type IssuanceContractName, -} from '../lib/contract-registry.js' +import { Contracts, type IssuanceContractName } from '../lib/contract-registry.js' +import { getContractAddress, resolveContractFromRegistry } from '../lib/task-utils.js' import { graph } from '../rocketh/deploy.js' interface TaskArgs { @@ -52,43 +48,6 @@ function printRoleInfo(role: RoleInfo, knownRoles: RoleInfo[]): void { } } -/** - * Resolve contract from registry by name - * - * Searches across all address books for a matching contract name. - * Returns the contract metadata and address book type if found. - */ -function resolveContractFromRegistry( - contractName: string, -): { addressBook: AddressBookType; roles: readonly string[] } | null { - // Search issuance first (most likely for this use case) - for (const [book, contracts] of Object.entries(CONTRACT_REGISTRY)) { - const contract = contracts[contractName as keyof typeof contracts] as { roles?: readonly string[] } | undefined - if (contract?.roles) { - return { addressBook: book as AddressBookType, roles: contract.roles } - } - } - return null -} - -/** - * Get contract address from address book - */ -function getContractAddress(addressBook: AddressBookType, contractName: string, chainId: number): string | null { - const book = - addressBook === 'issuance' - ? graph.getIssuanceAddressBook(chainId) - : addressBook === 'horizon' - ? graph.getHorizonAddressBook(chainId) - : graph.getSubgraphServiceAddressBook(chainId) - - if (!book.entryExists(contractName)) { - return null - } - - return book.getEntry(contractName)?.address ?? null -} - const action: NewTaskActionFunction = async (taskArgs, hre) => { // Empty strings treated as not provided const contractName = taskArgs.contract || undefined @@ -97,7 +56,7 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { // Validate: must provide either --contract or --address if (!contractName && !address) { console.error('\nError: Must provide either --contract or --address') - console.error(' --contract Contract name from registry (e.g., RewardsEligibilityOracle)') + console.error(' --contract Contract name from registry (e.g., RewardsEligibilityOracleA)') console.error(' --address Contract address (requires known role list)\n') return } @@ -115,6 +74,7 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { const actualChainId = await client.getChainId() // Determine target chain ID (handle fork mode) + await graph.autoDetect() const forkChainId = graph.getForkTargetChainId() const targetChainId = forkChainId ?? actualChainId @@ -189,13 +149,13 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { * List all role holders for a BaseUpgradeable contract * * Examples: - * npx hardhat roles:list --contract RewardsEligibilityOracle --network arbitrumSepolia + * npx hardhat roles:list --contract RewardsEligibilityOracleA --network arbitrumSepolia * npx hardhat roles:list --address 0x62c2... --network arbitrumSepolia */ const listRolesTask = task('roles:list', 'List all role holders for a contract') .addOption({ name: 'contract', - description: 'Contract name from registry (e.g., RewardsEligibilityOracle)', + description: 'Contract name from registry (e.g., RewardsEligibilityOracleA)', type: ArgumentType.STRING, defaultValue: '', }) diff --git a/packages/deployment/tasks/reo-tasks.ts b/packages/deployment/tasks/reo-tasks.ts new file mode 100644 index 000000000..4489ee3ce --- /dev/null +++ b/packages/deployment/tasks/reo-tasks.ts @@ -0,0 +1,597 @@ +import { task } from 'hardhat/config' +import type { NewTaskActionFunction } from 'hardhat/types/tasks' +import { + createPublicClient, + createWalletClient, + custom, + encodeFunctionData, + type PublicClient, + type WalletClient, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +import { PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, REWARDS_ELIGIBILITY_ORACLE_ABI } from '../lib/abis.js' +import { accountHasRole, enumerateContractRoles, getRoleHash } from '../lib/contract-checks.js' +import { createGovernanceTxBuilder } from '../lib/execute-governance.js' +import { formatDuration, formatTimestamp, getDeployerKeyName, resolveConfigVar } from '../lib/task-utils.js' +import { graph } from '../rocketh/deploy.js' + +// -- Types -- + +type REOInstance = 'A' | 'B' | 'Mock' + +const VALID_INSTANCES: REOInstance[] = ['A', 'B', 'Mock'] + +interface TaskArgs { + instance: string +} + +/** + * Get address book entry name for an REO instance + */ +function reoEntryName(instance: REOInstance): string { + return `RewardsEligibilityOracle${instance}` +} + +/** + * Get REO address from issuance address book for a specific instance + */ +function getREOAddress(chainId: number, instance: REOInstance): string | null { + const book = graph.getIssuanceAddressBook(chainId) + const name = reoEntryName(instance) as Parameters[0] + if (!book.entryExists(name)) { + return null + } + return book.getEntry(name)?.address ?? null +} + +/** + * Parse and validate --instance flag. Returns null if invalid. + * Accepts case-insensitive input: "a", "A", "b", "B", "mock", "Mock" + */ +function parseInstance(raw: string): REOInstance | null { + const lower = raw.toLowerCase() + const mapping: Record = { a: 'A', b: 'B', mock: 'Mock' } + return mapping[lower] ?? null +} + +// -- Enable/Disable Shared Logic -- + +interface SetValidationArgs { + enabled: boolean + instance: REOInstance + hre: unknown +} + +async function setEligibilityValidation({ enabled, instance, hre }: SetValidationArgs): Promise { + const action = enabled ? 'Enable' : 'Disable' + const actionLower = enabled ? 'enable' : 'disable' + + // Connect to network + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + // Create viem client + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const actualChainId = await client.getChainId() + await graph.autoDetect() + const forkChainId = graph.getForkTargetChainId() + const targetChainId = forkChainId ?? actualChainId + + // Get REO address + const reoAddress = getREOAddress(targetChainId, instance) + if (!reoAddress) { + console.error(`\nError: ${reoEntryName(instance)} not found in address book for chain ${targetChainId}`) + return + } + + // Check current state + const currentState = (await client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getEligibilityValidation', + })) as boolean + + if (currentState === enabled) { + console.log(`\n✓ [${instance}] Eligibility validation already ${actionLower}d`) + console.log(' No action needed.\n') + return + } + + // Get OPERATOR_ROLE hash + const operatorRoleHash = await getRoleHash(client, reoAddress, 'OPERATOR_ROLE') + if (!operatorRoleHash) { + console.error('\nError: Could not read OPERATOR_ROLE from contract') + return + } + + console.log(`\n🔧 ${action} Eligibility Validation [Instance ${instance}]`) + console.log(` Contract: ${reoAddress}`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + console.log(` Current: ${currentState ? 'enabled' : 'disabled'}`) + console.log(` Target: ${enabled ? 'enabled' : 'disabled'}`) + + // Get deployer account (from keystore or env var) + const keyName = getDeployerKeyName(networkName) + const deployerKey = await resolveConfigVar(hre, keyName) + + let deployer: string | undefined + let walletClient: WalletClient | undefined + + if (deployerKey) { + const account = privateKeyToAccount(deployerKey as `0x${string}`) + deployer = account.address + walletClient = createWalletClient({ + account, + transport: custom(conn.provider), + }) + } + + // Check if deployer has OPERATOR_ROLE + const canExecuteDirectly = deployer ? await accountHasRole(client, reoAddress, operatorRoleHash, deployer) : false + + if (canExecuteDirectly && walletClient && deployer) { + console.log(`\n Deployer has OPERATOR_ROLE, executing directly...`) + + // Execute directly + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const hash = await (walletClient as any).writeContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'setEligibilityValidation', + args: [enabled], + }) + + console.log(` TX: ${hash}`) + + // Wait for confirmation + const receipt = await client.waitForTransactionReceipt({ hash }) + if (receipt.status === 'success') { + console.log(`\n✓ [${instance}] Eligibility validation ${actionLower}d successfully\n`) + } else { + console.error(`\n✗ Transaction failed\n`) + } + } else { + // Generate governance TX + console.log(`\n Requires OPERATOR_ROLE to ${actionLower}`) + console.log(' Generating governance TX...') + + // Create a minimal environment for the TxBuilder + const env = { + name: networkName, + network: { provider: conn.provider }, + showMessage: console.log, + } + + const txName = `reo-${instance.toLowerCase()}-${actionLower}-validation` + const builder = await createGovernanceTxBuilder(env as Parameters[0], txName, { + name: `${action} REO ${instance} Validation`, + description: `${action} eligibility validation on ${reoEntryName(instance)}`, + }) + + // Encode the setEligibilityValidation call + const data = encodeFunctionData({ + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'setEligibilityValidation', + args: [enabled], + }) + + builder.addTx({ + to: reoAddress, + data, + value: '0', + }) + + const txFile = builder.saveToFile() + console.log(`\n✓ Governance TX saved: ${txFile}`) + console.log('\nNext steps:') + console.log(' • Fork testing: npx hardhat deploy:execute-governance --network fork') + console.log(' • Safe multisig: Upload JSON to Transaction Builder') + console.log('') + } +} + +// -- Status for a single instance -- + +async function showInstanceStatus( + client: PublicClient, + reoAddress: string, + instance: REOInstance, + networkName: string, + targetChainId: number, +): Promise { + // Mock has a simplified status (no roles, no validation toggle, no oracle) + if (instance === 'Mock') { + console.log(`\n📊 RewardsEligibilityOracle Mock Status`) + console.log(` Address: ${reoAddress}`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + console.log(` Type: MockRewardsEligibilityOracle (testnet, indexers self-manage eligibility)`) + console.log() + return + } + + console.log(`\n📊 RewardsEligibilityOracle ${instance} Status`) + console.log(` Address: ${reoAddress}`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + + // Read all status values + const [validationEnabled, eligibilityPeriod, oracleUpdateTimeout, lastOracleUpdateTime] = await Promise.all([ + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getEligibilityValidation', + }) as Promise, + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getEligibilityPeriod', + }) as Promise, + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getOracleUpdateTimeout', + }) as Promise, + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getLastOracleUpdateTime', + }) as Promise, + ]) + + // Calculate derived states + const now = BigInt(Math.floor(Date.now() / 1000)) + const timeSinceLastUpdate = lastOracleUpdateTime > 0n ? now - lastOracleUpdateTime : null + const timeoutExceeded = timeSinceLastUpdate !== null && timeSinceLastUpdate > oracleUpdateTimeout + const effectivelyDisabled = !validationEnabled || timeoutExceeded + + // Configuration section + console.log(`\n🔧 Configuration`) + console.log(` Validation enabled: ${validationEnabled ? '✓ yes' : '✗ no'}`) + console.log(` Eligibility period: ${formatDuration(eligibilityPeriod)} (${eligibilityPeriod} seconds)`) + console.log(` Oracle timeout: ${formatDuration(oracleUpdateTimeout)} (${oracleUpdateTimeout} seconds)`) + + // Oracle activity section + console.log(`\n📡 Oracle Activity`) + console.log(` Last update: ${formatTimestamp(lastOracleUpdateTime)}`) + if (timeSinceLastUpdate === null) { + console.log(` ⚠️ No oracle updates yet`) + } else if (timeoutExceeded) { + console.log(` ⚠️ Timeout exceeded! All indexers treated as eligible (fail-safe active)`) + } + + // Effective state section + console.log(`\n🎯 Effective State`) + if (effectivelyDisabled) { + console.log(` Status: ✗ DISABLED (all indexers eligible)`) + if (!validationEnabled) { + console.log(` Reason: Validation toggle is off`) + } else if (timeoutExceeded) { + console.log(` Reason: Oracle timeout exceeded (fail-safe)`) + } + } else { + console.log(` Status: ✓ ACTIVE (enforcing eligibility)`) + } + + // Check if RewardsManager is configured to use this REO instance + const horizonBook = graph.getHorizonAddressBook(targetChainId) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rmAddress = (horizonBook as any).entryExists('RewardsManager') + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (horizonBook as any).getEntry('RewardsManager')?.address + : null + + if (rmAddress) { + try { + const configuredOracle = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + functionName: 'getProviderEligibilityOracle', + })) as string + + const isConfigured = configuredOracle.toLowerCase() === reoAddress.toLowerCase() + if (isConfigured) { + console.log(` RewardsManager: ✓ using this instance`) + } else if (configuredOracle === '0x0000000000000000000000000000000000000000') { + console.log(` RewardsManager: ✗ no oracle configured`) + } else { + console.log(` RewardsManager: ✗ using different oracle (${configuredOracle})`) + } + } catch { + console.log(` RewardsManager: ? not upgraded yet (getProviderEligibilityOracle not available)`) + } + } + + // Role holders section + console.log(`\n🔐 Role Holders`) + const knownRoles = ['GOVERNOR_ROLE', 'PAUSE_ROLE', 'OPERATOR_ROLE', 'ORACLE_ROLE'] + const result = await enumerateContractRoles(client, reoAddress, knownRoles) + + for (const role of result.roles) { + const memberList = role.members.length > 0 ? role.members.join(', ') : '(none)' + console.log(` ${role.name} (${role.memberCount}): ${memberList}`) + } + + if (result.failedRoles.length > 0) { + console.log(` ⚠️ Failed to read: ${result.failedRoles.join(', ')}`) + } + + console.log() +} + +// -- Indexer listing for a single instance -- + +async function showInstanceIndexers( + client: PublicClient, + reoAddress: string, + instance: REOInstance, + networkName: string, + targetChainId: number, +): Promise { + console.log(`\n📋 RewardsEligibilityOracle ${instance} — Tracked Indexers`) + console.log(` Address: ${reoAddress}`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + + // Get indexer count and eligibility period in parallel + const [indexerCount, eligibilityPeriod, validationEnabled] = await Promise.all([ + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getIndexerCount', + }) as Promise, + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getEligibilityPeriod', + }) as Promise, + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getEligibilityValidation', + }) as Promise, + ]) + + console.log(` Validation: ${validationEnabled ? 'enabled' : 'disabled'}`) + console.log(` Eligibility period: ${formatDuration(eligibilityPeriod)}`) + console.log(` Tracked indexers: ${indexerCount}`) + + if (indexerCount === 0n) { + console.log('\n No indexers tracked.\n') + return + } + + // Fetch all indexer addresses + const indexers = (await client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getIndexers', + })) as `0x${string}`[] + + // Batch-read eligibility and renewal time for each indexer + const details = await Promise.all( + indexers.map(async (indexer) => { + const [eligible, renewalTime] = await Promise.all([ + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'isEligible', + args: [indexer], + }) as Promise, + client.readContract({ + address: reoAddress as `0x${string}`, + abi: REWARDS_ELIGIBILITY_ORACLE_ABI, + functionName: 'getEligibilityRenewalTime', + args: [indexer], + }) as Promise, + ]) + return { indexer, eligible, renewalTime } + }), + ) + + // Sort by renewal time (most recent first), then by address within each group + details.sort((a, b) => { + if (a.renewalTime !== b.renewalTime) { + return a.renewalTime < b.renewalTime ? 1 : -1 + } + return a.indexer.toLowerCase() < b.indexer.toLowerCase() ? -1 : 1 + }) + + // Display results grouped by renewal time with blank lines between groups + let lastRenewalTime: bigint | null = null + for (const { indexer, eligible, renewalTime } of details) { + if (lastRenewalTime !== null && renewalTime !== lastRenewalTime) { + console.log('') + } + lastRenewalTime = renewalTime + const status = eligible ? '✓' : '✗' + console.log(` ${status} ${indexer} renewed ${formatTimestamp(renewalTime)}`) + } + + // Summary + const eligibleCount = details.filter((d) => d.eligible).length + console.log(`\n Summary: ${eligibleCount}/${details.length} eligible\n`) +} + +// -- Task Actions -- + +const enableAction: NewTaskActionFunction = async (taskArgs, hre) => { + const instance = parseInstance(taskArgs.instance) + if (!instance) { + console.error(`\nError: --instance is required (a, b, or mock)`) + return + } + if (instance === 'Mock') { + console.error(`\nError: Mock REO has no validation toggle — it's always active`) + return + } + await setEligibilityValidation({ enabled: true, instance, hre }) +} + +const disableAction: NewTaskActionFunction = async (taskArgs, hre) => { + const instance = parseInstance(taskArgs.instance) + if (!instance) { + console.error(`\nError: --instance is required (a, b, or mock)`) + return + } + if (instance === 'Mock') { + console.error(`\nError: Mock REO has no validation toggle — it's always active`) + return + } + await setEligibilityValidation({ enabled: false, instance, hre }) +} + +const indexersAction: NewTaskActionFunction = async (taskArgs, hre) => { + // Connect to network + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + // Create viem client + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const actualChainId = await client.getChainId() + await graph.autoDetect() + const forkChainId = graph.getForkTargetChainId() + const targetChainId = forkChainId ?? actualChainId + + // Determine which instances to show + const requestedInstance = taskArgs.instance ? parseInstance(taskArgs.instance) : null + const instancesToShow: REOInstance[] = requestedInstance ? [requestedInstance] : VALID_INSTANCES + + let found = false + for (const instance of instancesToShow) { + const reoAddress = getREOAddress(targetChainId, instance) + if (reoAddress) { + found = true + await showInstanceIndexers(client, reoAddress, instance, networkName, targetChainId) + } else if (requestedInstance) { + console.error(`\nError: ${reoEntryName(instance)} not found in address book for chain ${targetChainId}`) + } + } + + if (!found) { + console.error(`\nError: No REO instances found in address book for chain ${targetChainId}`) + } +} + +const statusAction: NewTaskActionFunction = async (taskArgs, hre) => { + // Connect to network + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + // Create viem client + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const actualChainId = await client.getChainId() + await graph.autoDetect() + const forkChainId = graph.getForkTargetChainId() + const targetChainId = forkChainId ?? actualChainId + + // Determine which instances to show + const requestedInstance = taskArgs.instance ? parseInstance(taskArgs.instance) : null + const instancesToShow: REOInstance[] = requestedInstance ? [requestedInstance] : VALID_INSTANCES + + let found = false + for (const instance of instancesToShow) { + const reoAddress = getREOAddress(targetChainId, instance) + if (reoAddress) { + found = true + await showInstanceStatus(client, reoAddress, instance, networkName, targetChainId) + } else if (requestedInstance) { + // Only error if a specific instance was requested and not found + console.error(`\nError: ${reoEntryName(instance)} not found in address book for chain ${targetChainId}`) + } + } + + if (!found) { + console.error(`\nError: No REO instances found in address book for chain ${targetChainId}`) + } +} + +// -- Task Definitions -- + +/** + * Enable eligibility validation on a REO instance + * + * Requires OPERATOR_ROLE. If deployer has the role, executes directly. + * Otherwise generates a governance TX for multisig execution. + * + * Examples: + * npx hardhat reo:enable --instance a --network arbitrumSepolia + */ +export const reoEnableTask = task('reo:enable', 'Enable eligibility validation on a REO instance') + .addOption({ + name: 'instance', + description: 'REO instance (a, b, or mock)', + defaultValue: '', + }) + .setAction(async () => ({ default: enableAction })) + .build() + +/** + * Disable eligibility validation on a REO instance + * + * Requires OPERATOR_ROLE. If deployer has the role, executes directly. + * Otherwise generates a governance TX for multisig execution. + * + * WARNING: When validation is disabled, ALL indexers are treated as eligible. + * + * Examples: + * npx hardhat reo:disable --instance b --network arbitrumSepolia + */ +export const reoDisableTask = task('reo:disable', 'Disable eligibility validation on a REO instance') + .addOption({ + name: 'instance', + description: 'REO instance (a, b, or mock)', + defaultValue: '', + }) + .setAction(async () => ({ default: disableAction })) + .build() + +/** + * Show detailed status of REO instance(s) + * + * Displays configuration, oracle activity, effective state, and role holders. + * If --instance is omitted, shows status for all deployed instances. + * + * Examples: + * npx hardhat reo:status --network arbitrumSepolia # show all + * npx hardhat reo:status --instance a --network arbitrumSepolia # show A only + */ +export const reoStatusTask = task('reo:status', 'Show detailed REO status') + .addOption({ + name: 'instance', + description: 'REO instance (a, b, or mock; omit for all)', + defaultValue: '', + }) + .setAction(async () => ({ default: statusAction })) + .build() + +/** + * List tracked indexers with eligibility info + * + * Shows each indexer's eligibility status, renewal time, and expiry. + * If --instance is omitted, shows indexers for all deployed instances. + * + * Examples: + * npx hardhat reo:indexers --network arbitrumSepolia # show all + * npx hardhat reo:indexers --instance a --network arbitrumSepolia # show A only + */ +export const reoIndexersTask = task('reo:indexers', 'List tracked indexers with eligibility info') + .addOption({ + name: 'instance', + description: 'REO instance (a, b, or mock; omit for all)', + defaultValue: '', + }) + .setAction(async () => ({ default: indexersAction })) + .build() + +export default [reoEnableTask, reoDisableTask, reoStatusTask, reoIndexersTask] diff --git a/packages/deployment/tasks/reset-fork.ts b/packages/deployment/tasks/reset-fork.ts index f64335c3d..ff683423d 100644 --- a/packages/deployment/tasks/reset-fork.ts +++ b/packages/deployment/tasks/reset-fork.ts @@ -4,7 +4,7 @@ import path from 'node:path' import { task } from 'hardhat/config' import type { NewTaskActionFunction } from 'hardhat/types/tasks' -import { getForkNetwork, getForkStateDir } from '../lib/address-book-utils.js' +import { autoDetectForkNetwork, getForkNetwork, getForkStateDir } from '../lib/address-book-utils.js' interface TaskArgs { // No arguments for this task @@ -27,6 +27,8 @@ const action: NewTaskActionFunction = async (_taskArgs, hre) => { const conn = await (hre as any).network.connect() const networkName = conn.networkName + // Auto-detect fork network from anvil before checking + await autoDetectForkNetwork() const forkNetwork = getForkNetwork() if (!forkNetwork) { diff --git a/packages/deployment/tasks/revoke-role.ts b/packages/deployment/tasks/revoke-role.ts index 029d23336..10f239508 100644 --- a/packages/deployment/tasks/revoke-role.ts +++ b/packages/deployment/tasks/revoke-role.ts @@ -1,4 +1,4 @@ -import { configVariable, task } from 'hardhat/config' +import { task } from 'hardhat/config' import { ArgumentType } from 'hardhat/types/arguments' import type { NewTaskActionFunction } from 'hardhat/types/tasks' import { @@ -19,8 +19,13 @@ import { getRoleHash, hasAdminRole, } from '../lib/contract-checks.js' -import { type AddressBookType, CONTRACT_REGISTRY } from '../lib/contract-registry.js' import { createGovernanceTxBuilder } from '../lib/execute-governance.js' +import { + getContractAddress, + getDeployerKeyName, + resolveConfigVar, + resolveContractFromRegistry, +} from '../lib/task-utils.js' import { graph } from '../rocketh/deploy.js' interface TaskArgs { @@ -30,73 +35,6 @@ interface TaskArgs { account: string } -/** - * Convert network name to env var prefix: arbitrumSepolia → ARBITRUM_SEPOLIA - */ -function networkToEnvPrefix(networkName: string): string { - return networkName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase() -} - -/** - * Resolve a configuration variable using Hardhat's hook chain (keystore + env fallback) - */ -async function resolveConfigVar(hre: unknown, name: string): Promise { - try { - const variable = configVariable(name) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const hooks = (hre as any).hooks - - const value = await hooks.runHandlerChain( - 'configurationVariables', - 'fetchValue', - [variable], - async (_context: unknown, v: { name: string }) => { - const envValue = process.env[v.name] - if (typeof envValue !== 'string') { - throw new Error(`Variable ${v.name} not found`) - } - return envValue - }, - ) - return value - } catch { - return undefined - } -} - -/** - * Resolve contract from registry by name - */ -function resolveContractFromRegistry( - contractName: string, -): { addressBook: AddressBookType; roles: readonly string[] } | null { - for (const [book, contracts] of Object.entries(CONTRACT_REGISTRY)) { - const contract = contracts[contractName as keyof typeof contracts] as { roles?: readonly string[] } | undefined - if (contract?.roles) { - return { addressBook: book as AddressBookType, roles: contract.roles } - } - } - return null -} - -/** - * Get contract address from address book - */ -function getContractAddress(addressBook: AddressBookType, contractName: string, chainId: number): string | null { - const book = - addressBook === 'issuance' - ? graph.getIssuanceAddressBook(chainId) - : addressBook === 'horizon' - ? graph.getHorizonAddressBook(chainId) - : graph.getSubgraphServiceAddressBook(chainId) - - if (!book.entryExists(contractName)) { - return null - } - - return book.getEntry(contractName)?.address ?? null -} - const action: NewTaskActionFunction = async (taskArgs, hre) => { const contractName = taskArgs.contract || undefined const addressArg = taskArgs.address || undefined @@ -128,6 +66,7 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { }) as PublicClient const actualChainId = await client.getChainId() + await graph.autoDetect() const forkChainId = graph.getForkTargetChainId() const targetChainId = forkChainId ?? actualChainId @@ -184,7 +123,7 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { console.log(` Admin holders: ${adminInfo.adminMembers.length > 0 ? adminInfo.adminMembers.join(', ') : '(none)'}`) // Get deployer account - const keyName = `${networkToEnvPrefix(networkName === 'fork' ? (process.env.HARDHAT_FORK ?? 'arbitrumSepolia') : networkName)}_DEPLOYER_KEY` + const keyName = getDeployerKeyName(networkName) const deployerKey = await resolveConfigVar(hre, keyName) let deployer: string | undefined @@ -206,7 +145,8 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { console.log(`\n Deployer has ${adminInfo.adminRoleName ?? 'admin role'}, executing directly...`) // Execute directly - const hash = await walletClient.writeContract({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const hash = await (walletClient as any).writeContract({ address: contractAddress as `0x${string}`, abi: ACCESS_CONTROL_ENUMERABLE_ABI, functionName: 'revokeRole', @@ -266,12 +206,12 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { * Revoke a role from an account on a BaseUpgradeable contract * * Examples: - * npx hardhat roles:revoke --contract RewardsEligibilityOracle --role ORACLE_ROLE --account 0x... --network arbitrumSepolia + * npx hardhat roles:revoke --contract RewardsEligibilityOracleA --role ORACLE_ROLE --account 0x... --network arbitrumSepolia */ const revokeRoleTask = task('roles:revoke', 'Revoke a role from an account') .addOption({ name: 'contract', - description: 'Contract name from registry (e.g., RewardsEligibilityOracle)', + description: 'Contract name from registry (e.g., RewardsEligibilityOracleA)', type: ArgumentType.STRING, defaultValue: '', }) diff --git a/packages/deployment/tasks/ss-tasks.ts b/packages/deployment/tasks/ss-tasks.ts new file mode 100644 index 000000000..6479fa681 --- /dev/null +++ b/packages/deployment/tasks/ss-tasks.ts @@ -0,0 +1,306 @@ +import { task } from 'hardhat/config' +import type { NewTaskActionFunction } from 'hardhat/types/tasks' +import { createPublicClient, custom, type PublicClient } from 'viem' + +// Minimal ABI for RewardsManager public storage variable (not in the IRewardsManager interface) +const REWARDS_MANAGER_SIGNAL_ABI = [ + { + inputs: [], + name: 'minimumSubgraphSignal', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +] as const +import { formatGRT } from '../lib/format.js' +import { formatDuration } from '../lib/task-utils.js' +import { graph } from '../rocketh/deploy.js' + +// -- ABIs -- + +// Minimal ABI for SubgraphService view functions +const SUBGRAPH_SERVICE_ABI = [ + { + inputs: [], + name: 'getProvisionTokensRange', + outputs: [{ type: 'uint256' }, { type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getDelegationRatio', + outputs: [{ type: 'uint32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'stakeToFeesRatio', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'curationFeesCut', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'maxPOIStaleness', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getThawingPeriodRange', + outputs: [{ type: 'uint64' }, { type: 'uint64' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifierCutRange', + outputs: [{ type: 'uint32' }, { type: 'uint32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getDisputeManager', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getGraphTallyCollector', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getCuration', + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBlockClosingAllocationWithActiveAgreement', + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, +] as const + +// -- Helpers -- + +const PPM = 1_000_000 + +function formatPPM(value: bigint | number): string { + const pct = (Number(value) / PPM) * 100 + return `${pct}% (${value} PPM)` +} + +// -- Task Action -- + +const statusAction: NewTaskActionFunction = async (_taskArgs, hre) => { + // Connect to network + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const conn = await (hre as any).network.connect() + const networkName = conn.networkName + + const client = createPublicClient({ + transport: custom(conn.provider), + }) as PublicClient + + const actualChainId = await client.getChainId() + await graph.autoDetect() + const forkChainId = graph.getForkTargetChainId() + const targetChainId = forkChainId ?? actualChainId + + // Get SubgraphService address + const ssBook = graph.getSubgraphServiceAddressBook(targetChainId) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const ssAddress = (ssBook as any).entryExists('SubgraphService') + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (ssBook as any).getEntry('SubgraphService')?.address + : null + + if (!ssAddress) { + console.error(`\nError: SubgraphService not found in address book for chain ${targetChainId}`) + return + } + + // Get RewardsManager address + const horizonBook = graph.getHorizonAddressBook(targetChainId) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rmAddress = (horizonBook as any).entryExists('RewardsManager') + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (horizonBook as any).getEntry('RewardsManager')?.address + : null + + console.log(`\n📊 SubgraphService Status`) + console.log(` Address: ${ssAddress}`) + console.log(` Network: ${networkName} (chainId: ${targetChainId})`) + + // Batch-read all SubgraphService parameters + const [ + provisionRange, + delegationRatio, + stakeToFees, + curationCut, + poiStaleness, + thawingRange, + verifierCutRange, + disputeManager, + tallyCollector, + curation, + ] = await Promise.all([ + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'getProvisionTokensRange', + }) as Promise<[bigint, bigint]>, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'getDelegationRatio', + }) as Promise, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'stakeToFeesRatio', + }) as Promise, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'curationFeesCut', + }) as Promise, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'maxPOIStaleness', + }) as Promise, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'getThawingPeriodRange', + }) as Promise, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'getVerifierCutRange', + }) as Promise, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'getDisputeManager', + }) as Promise, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'getGraphTallyCollector', + }) as Promise, + client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'getCuration', + }) as Promise, + ]) + + // Try newer functions that may not be on current deployment + let blockClosingWithAgreement: boolean | null = null + try { + blockClosingWithAgreement = (await client.readContract({ + address: ssAddress as `0x${string}`, + abi: SUBGRAPH_SERVICE_ABI, + functionName: 'getBlockClosingAllocationWithActiveAgreement', + })) as boolean + } catch { + // Not available on current implementation + } + + // Display SubgraphService parameters + console.log(`\n🔧 Provision Parameters`) + console.log(` Min provision tokens: ${formatGRT(provisionRange[0])}`) + if (provisionRange[1] < 2n ** 256n - 1n) { + console.log(` Max provision tokens: ${formatGRT(provisionRange[1])}`) + } else { + console.log(` Max provision tokens: unlimited`) + } + console.log(` Delegation ratio: ${delegationRatio}x`) + + console.log(`\n📐 Thawing & Verifier Ranges`) + if (thawingRange[0] === thawingRange[1]) { + console.log(` Thawing period: ${formatDuration(thawingRange[0])} (fixed)`) + } else { + console.log(` Thawing period: ${formatDuration(thawingRange[0])} – ${formatDuration(thawingRange[1])}`) + } + console.log(` Verifier cut: ${formatPPM(verifierCutRange[0])} – ${formatPPM(verifierCutRange[1])}`) + + console.log(`\n💰 Fee Parameters`) + console.log(` Stake to fees ratio: ${stakeToFees}`) + console.log(` Curation fees cut: ${formatPPM(curationCut)}`) + + console.log(`\n⏱️ Staleness`) + console.log(` Max POI staleness: ${formatDuration(poiStaleness)} (${poiStaleness} seconds)`) + + if (blockClosingWithAgreement !== null) { + console.log(`\n🔒 Agreement Guards`) + console.log(` Block closing allocation with active agreement: ${blockClosingWithAgreement ? 'yes' : 'no'}`) + } + + console.log(`\n🔗 Linked Contracts`) + console.log(` DisputeManager: ${disputeManager}`) + console.log(` GraphTallyCollector: ${tallyCollector}`) + console.log(` Curation: ${curation}`) + + // RewardsManager parameters + if (rmAddress) { + console.log(`\n📊 RewardsManager`) + console.log(` Address: ${rmAddress}`) + + try { + const minimumSignal = (await client.readContract({ + address: rmAddress as `0x${string}`, + abi: REWARDS_MANAGER_SIGNAL_ABI, + functionName: 'minimumSubgraphSignal', + })) as bigint + + if (minimumSignal === 0n) { + console.log(` Minimum subgraph signal: 0 (disabled)`) + } else { + console.log(` Minimum subgraph signal: ${formatGRT(minimumSignal)}`) + } + } catch { + console.log(` Minimum subgraph signal: ? (not readable)`) + } + } + + console.log() +} + +// -- Task Definition -- + +/** + * Show SubgraphService configuration parameters + * + * Displays provision requirements, fee parameters, staleness thresholds, + * and linked contract addresses. + * + * Examples: + * npx hardhat ss:status --network arbitrumOne + * npx hardhat ss:status --network arbitrumSepolia + */ +export const ssStatusTask = task('ss:status', 'Show SubgraphService configuration parameters') + .setAction(async () => ({ default: statusAction })) + .build() + +export default [ssStatusTask] diff --git a/packages/deployment/tasks/sync.ts b/packages/deployment/tasks/sync.ts new file mode 100644 index 000000000..563cad8ad --- /dev/null +++ b/packages/deployment/tasks/sync.ts @@ -0,0 +1,37 @@ +import { task } from 'hardhat/config' +import type { NewTaskActionFunction } from 'hardhat/types/tasks' + +interface TaskArgs { + // No arguments for this task +} + +/** + * Explicit global address book sync. + * + * Runs the full sync (00_sync.ts) over every contract in every address book, + * reconciling on-chain implementation state with the recorded address books and + * rocketh deployment records. Use this when: + * + * - You want a full overview of address book state + * - Governance executed a TX batch out-of-band and address books need to catch up + * - A fork was reset and rocketh records need to be rebuilt + * + * Per-component actions sync the contracts they touch immediately before and + * after their work, so this task is no longer required as a prerequisite for + * normal `--tags Component,verb` invocations. + * + * Usage: + * npx hardhat deploy:sync --network arbitrumOne + * npx hardhat deploy:sync --network localhost (auto-detects fork network) + */ +const action: NewTaskActionFunction = async (_taskArgs, hre) => { + // Sync is read-only, so suppress the gas-price confirmation prompt that the + // rocketh deploy task shows by default. + await hre.tasks.getTask('deploy').run({ tags: 'sync', skipPrompts: true }) +} + +const syncTask = task('deploy:sync', 'Sync address books and deployment records with on-chain state') + .setAction(async () => ({ default: action })) + .build() + +export default syncTask diff --git a/packages/deployment/tasks/verify-contract.ts b/packages/deployment/tasks/verify-contract.ts index 793f921f3..e6195008e 100644 --- a/packages/deployment/tasks/verify-contract.ts +++ b/packages/deployment/tasks/verify-contract.ts @@ -1,6 +1,6 @@ import { spawn } from 'child_process' import fs from 'fs' -import { configVariable, task } from 'hardhat/config' +import { task } from 'hardhat/config' import { ArgumentType } from 'hardhat/types/arguments' import type { NewTaskActionFunction } from 'hardhat/types/tasks' import os from 'os' @@ -8,7 +8,7 @@ import path from 'path' import { decodeAbiParameters } from 'viem' import type { AnyAddressBookOps } from '../lib/address-book-ops.js' -import { computeBytecodeHash } from '../lib/bytecode-utils.js' +import { getAddressBookForType } from '../lib/address-book-utils.js' import { type AddressBookType, type ArtifactSource, @@ -16,9 +16,9 @@ import { getContractMetadata, getContractsByAddressBook, } from '../lib/contract-registry.js' -import { loadArtifactFromSource } from '../lib/deploy-implementation.js' -import { verifyOZProxy } from '../lib/oz-proxy-verify.js' -import { graph } from '../rocketh/deploy.js' +import { computeArtifactBytecodeHash, loadArtifactFromSource } from '../lib/deploy-implementation.js' +import { checkEtherscanVerified, verifyOZProxy } from '../lib/oz-proxy-verify.js' +import { resolveConfigVar } from '../lib/task-utils.js' const ADDRESS_BOOK_TYPES: AddressBookType[] = ['horizon', 'subgraph-service', 'issuance'] @@ -31,6 +31,8 @@ function getPackageDir(artifactSource: ArtifactSource): string { return 'packages/contracts' case 'subgraph-service': return 'packages/subgraph-service' + case 'horizon': + return 'packages/horizon' case 'issuance': return 'packages/issuance' case 'openzeppelin': @@ -50,6 +52,14 @@ function getFullyQualifiedContractName(artifactSource: ArtifactSource): string { case 'subgraph-service': // e.g., contracts/SubgraphService.sol:SubgraphService return `contracts/${artifactSource.name}.sol:${artifactSource.name}` + case 'horizon': { + // path is like 'contracts/staking/HorizonStaking.sol/HorizonStaking' + // Need to convert to 'contracts/staking/HorizonStaking.sol:HorizonStaking' + const parts = artifactSource.path.split('/') + const contractName = parts.pop()! + const solPath = parts.join('/') + return `${solPath}:${contractName}` + } case 'issuance': { // path is like 'contracts/allocate/IssuanceAllocator.sol/IssuanceAllocator' // Need to convert to 'contracts/allocate/IssuanceAllocator.sol:IssuanceAllocator' @@ -74,8 +84,8 @@ function findContractAddressBook( for (const addressBook of ADDRESS_BOOK_TYPES) { const metadata = getContractMetadata(addressBook, contractName) - // Only consider entries that are deployable and have an artifact source - if (metadata?.deployable && metadata.artifact) { + // Consider entries that are deployable with an artifact, or proxy-only contracts (shared impl) + if (metadata?.deployable && (metadata.artifact || metadata.proxyType)) { matches.push({ addressBook, metadata }) } } @@ -107,7 +117,7 @@ function getAllDeployableContracts(): Array<{ for (const addressBook of ADDRESS_BOOK_TYPES) { for (const [name, metadata] of getContractsByAddressBook(addressBook)) { - if (metadata.deployable && metadata.artifact) { + if (metadata.deployable && (metadata.artifact || metadata.proxyType)) { contracts.push({ name, addressBook, metadata }) } } @@ -116,32 +126,7 @@ function getAllDeployableContracts(): Array<{ return contracts } -/** - * Resolve a configuration variable using Hardhat's hook chain (keystore + env fallback) - */ -async function resolveConfigVar(hre: unknown, name: string): Promise { - try { - const variable = configVariable(name) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const hooks = (hre as any).hooks - - const value = await hooks.runHandlerChain( - 'configurationVariables', - 'fetchValue', - [variable], - async (_context: unknown, v: { name: string }) => { - const envValue = process.env[v.name] - if (typeof envValue !== 'string') { - throw new Error(`Environment variable ${v.name} not found`) - } - return envValue - }, - ) - return value - } catch { - return undefined - } -} +// resolveConfigVar imported from shared task-utils /** * Check if a package uses Hardhat v3 (which has different verify CLI options) @@ -307,20 +292,6 @@ async function runVerify( }) } -/** - * Get address book for a given type and chainId - */ -function getAddressBook(addressBookType: AddressBookType, chainId: number): AnyAddressBookOps { - switch (addressBookType) { - case 'horizon': - return graph.getHorizonAddressBook(chainId) - case 'subgraph-service': - return graph.getSubgraphServiceAddressBook(chainId) - case 'issuance': - return graph.getIssuanceAddressBook(chainId) - } -} - /** * Check if local artifact bytecode matches stored bytecodeHash * @@ -347,8 +318,9 @@ function checkBytecodeMatch( return { matches: false, reason: 'no deployment metadata (not deployed by this system)' } } - // Compare local artifact bytecodeHash with stored hash - const localBytecodeHash = computeBytecodeHash(artifact.deployedBytecode) + // Compare local artifact bytecodeHash with stored hash. Uses the same artifact-side + // hashing as deploy-time so library-using contracts round-trip correctly. + const localBytecodeHash = computeArtifactBytecodeHash(metadata.artifact!) if (localBytecodeHash !== deploymentMetadata.bytecodeHash) { return { matches: false, @@ -382,7 +354,7 @@ async function verifySingleContract( proxyOnly: boolean, implOnly: boolean, ): Promise { - const addressBook = getAddressBook(addressBookType, chainId) + const addressBook = getAddressBookForType(addressBookType, chainId) // Check if deployed if (!addressBook.entryExists(contractName)) { @@ -393,24 +365,23 @@ async function verifySingleContract( const isProxied = Boolean(metadata.proxyType) const implAddress = isProxied ? entry.implementation : entry.address + // Proxy-only contracts (shared implementation, no artifact) — only verify the proxy + // Implementation verification is handled by the shared _Implementation entry + const hasArtifact = Boolean(metadata.artifact) + // Check bytecode matches for implementation (using stored bytecodeHash) - if (implAddress) { + // This is a warning, not a blocker — Etherscan is the ultimate arbiter + let bytecodeMatches = true + if (hasArtifact && implAddress) { const bytecodeCheck = checkBytecodeMatch(contractName, metadata, addressBook) if (!bytecodeCheck.matches) { - return { - contract: contractName, - addressBook: addressBookType, - status: 'skipped', - reason: bytecodeCheck.reason, - } + bytecodeMatches = false + console.log(` ⚠️ ${bytecodeCheck.reason}`) } } - const packageDir = getPackageDir(metadata.artifact!) - const isHHv3 = isHardhatV3Package(metadata.artifact!) - const artifact = loadArtifactFromSource(metadata.artifact!) - const fullyQualifiedName = getFullyQualifiedContractName(metadata.artifact!) let implResult: { success: boolean; url?: string } = { success: true } + let verificationFailed = false // Get constructor args from deployment metadata const deploymentMetadata = addressBook.getDeploymentMetadata?.(contractName) @@ -423,75 +394,106 @@ async function verifySingleContract( if (entry.proxyDeployment?.verified) { console.log(` ✓ Proxy already verified: ${entry.proxyDeployment.verified}`) } else { - // Get proxy constructor args from address book (stored separately from implementation args) - const proxyArgsData = entry.proxyDeployment?.argsData - if (!proxyArgsData) { - console.log(` ⏭️ Proxy verification skipped (no constructor args in address book)`) + // Check Etherscan before submitting — avoids redundant submissions + const existingUrl = await checkEtherscanVerified(entry.address, apiKey, chainId) + if (existingUrl) { + console.log(` ✓ Proxy already verified: ${existingUrl}`) + addressBook.setVerified(contractName, existingUrl) } else { - console.log(` 📋 Verifying OZ TransparentUpgradeableProxy at: ${entry.address}`) - console.log(` 📦 Source: @openzeppelin/contracts v5.4.0 (from node_modules)`) + // Get proxy constructor args from address book (stored separately from implementation args) + const proxyArgsData = entry.proxyDeployment?.argsData + if (!proxyArgsData) { + console.log(` ⏭️ Proxy verification skipped (no constructor args in address book)`) + } else { + console.log(` 📋 Verifying OZ TransparentUpgradeableProxy at: ${entry.address}`) + console.log(` 📦 Source: @openzeppelin/contracts v5.4.0 (from node_modules)`) - const proxyResult = await verifyOZProxy(entry.address, proxyArgsData, apiKey, chainId) + const proxyResult = await verifyOZProxy(entry.address, proxyArgsData, apiKey, chainId) - if (proxyResult.success && proxyResult.url) { - console.log(` ✅ Proxy verification complete`) - // Record verification URL in address book (setVerified sets proxyDeployment.verified for proxied contracts) - addressBook.setVerified(contractName, proxyResult.url) - } else if (proxyResult.success) { - console.log(` ✅ Proxy verification complete (${proxyResult.message || 'no URL returned'})`) - } else { - console.log(` ⚠️ Proxy verification failed: ${proxyResult.message || 'unknown error'}`) + if (proxyResult.success && proxyResult.url) { + console.log(` ✅ Proxy verification complete`) + addressBook.setVerified(contractName, proxyResult.url) + } else if (proxyResult.success) { + console.log(` ✅ Proxy verification complete (${proxyResult.message || 'no URL returned'})`) + } else { + console.log(` ⚠️ Proxy verification failed: ${proxyResult.message || 'unknown error'}`) + verificationFailed = true + } } } } } // Verify implementation (if proxied and not proxy-only, or if not proxied) - if ((isProxied && !proxyOnly) || !isProxied) { + // Skip for proxy-only contracts with no artifact (shared implementation verified separately) + if (!hasArtifact) { + if (!proxyOnly) { + console.log(` ⏭️ Implementation verification skipped (shared implementation)`) + } + } else if ((isProxied && !proxyOnly) || !isProxied) { + const packageDir = getPackageDir(metadata.artifact!) + const isHHv3 = isHardhatV3Package(metadata.artifact!) + const artifact = loadArtifactFromSource(metadata.artifact!) + const fullyQualifiedName = getFullyQualifiedContractName(metadata.artifact!) + if (!implAddress) { console.log(' ⚠️ No implementation address found, skipping') } else { - // Skip if already verified + // Skip if already verified (local record) const implVerified = isProxied ? entry.implementationDeployment?.verified : entry.deployment?.verified if (implVerified) { const label = isProxied ? 'Implementation' : 'Contract' console.log(` ✓ ${label} already verified: ${implVerified}`) } else { - const label = isProxied ? 'implementation' : 'contract' - console.log(` 📋 Verifying ${label} at: ${implAddress}`) - // Pass constructor args for implementation contracts - // Use fullyQualifiedName to ensure hardhat uses current build artifacts - implResult = await runVerify( - packageDir, - networkName, - implAddress, - apiKey, - constructorArgsData, - artifact, - isHHv3, - fullyQualifiedName, - ) - if (implResult.success && implResult.url) { - console.log(` ✅ ${label.charAt(0).toUpperCase() + label.slice(1)} verification complete`) - // Record verification URL in address book + // Check Etherscan before attempting local verify — catches contracts + // verified out-of-band or where previous attempts failed locally + const existingImplUrl = await checkEtherscanVerified(implAddress, apiKey, chainId) + if (existingImplUrl) { + const label = isProxied ? 'Implementation' : 'Contract' + console.log(` ✓ ${label} already verified: ${existingImplUrl}`) if (isProxied) { - addressBook.setImplementationVerified(contractName, implResult.url) + addressBook.setImplementationVerified(contractName, existingImplUrl) } else { - addressBook.setVerified(contractName, implResult.url) + addressBook.setVerified(contractName, existingImplUrl) } - } else if (implResult.success) { - console.log(` ✅ ${label.charAt(0).toUpperCase() + label.slice(1)} verification complete`) + } else if (!bytecodeMatches) { + // Bytecode mismatch and not verified on Etherscan — skip + const label = isProxied ? 'Implementation' : 'Contract' + console.log(` ⏭️ ${label} verification skipped (bytecode mismatch)`) } else { - console.log( - ` ⚠️ ${label.charAt(0).toUpperCase() + label.slice(1)} verification failed (may already be verified)`, + const label = isProxied ? 'implementation' : 'contract' + console.log(` 📋 Verifying ${label} at: ${implAddress}`) + implResult = await runVerify( + packageDir, + networkName, + implAddress, + apiKey, + constructorArgsData, + artifact, + isHHv3, + fullyQualifiedName, ) + if (implResult.success && implResult.url) { + console.log(` ✅ ${label.charAt(0).toUpperCase() + label.slice(1)} verification complete`) + if (isProxied) { + addressBook.setImplementationVerified(contractName, implResult.url) + } else { + addressBook.setVerified(contractName, implResult.url) + } + } else if (implResult.success) { + console.log(` ✅ ${label.charAt(0).toUpperCase() + label.slice(1)} verification complete`) + } else { + console.log( + ` ⚠️ ${label.charAt(0).toUpperCase() + label.slice(1)} verification failed (may already be verified)`, + ) + verificationFailed = true + } } } } } - // Both failing or already verified is still "success" for the workflow - return { contract: contractName, addressBook: addressBookType, status: 'verified' } + return { contract: contractName, addressBook: addressBookType, status: verificationFailed ? 'failed' : 'verified' } } interface TaskArgs { @@ -534,7 +536,11 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { // Get API key from keystore const apiKey = await resolveConfigVar(hre, 'ARBISCAN_API_KEY') if (!apiKey) { - throw new Error('ARBISCAN_API_KEY not found. Set it in keystore:\n npx hardhat keystore set ARBISCAN_API_KEY') + throw new Error( + 'No Arbiscan API key configured.\n' + + 'Set via keystore: npx hardhat keystore set ARBISCAN_API_KEY\n' + + 'Or environment: export ARBISCAN_API_KEY=...', + ) } // Determine contracts to verify @@ -548,7 +554,7 @@ const action: NewTaskActionFunction = async (taskArgs, hre) => { if (explicitAddressBook) { addressBookType = explicitAddressBook as AddressBookType const foundMetadata = getContractMetadata(addressBookType, contract) - if (!foundMetadata?.deployable || !foundMetadata.artifact) { + if (!foundMetadata?.deployable || (!foundMetadata.artifact && !foundMetadata.proxyType)) { throw new Error(`Contract ${contract} not found as deployable in ${addressBookType} registry`) } metadata = foundMetadata diff --git a/packages/deployment/test/bytecode-comparison.test.ts b/packages/deployment/test/bytecode-comparison.test.ts index 394cf57e4..8e0ebef27 100644 --- a/packages/deployment/test/bytecode-comparison.test.ts +++ b/packages/deployment/test/bytecode-comparison.test.ts @@ -1,6 +1,11 @@ import { expect } from 'chai' -import { computeBytecodeHash, stripMetadata } from '../lib/bytecode-utils.js' +import { + computeBytecodeHash, + type LibraryArtifactResolver, + type LinkReferences, + stripMetadata, +} from '../lib/bytecode-utils.js' import { loadContractsArtifact } from '../lib/deploy-implementation.js' /** @@ -102,6 +107,55 @@ describe('Bytecode Utilities', function () { expect(hash).to.be.a('string') expect(hash).to.match(/^0x[a-f0-9]{64}$/) }) + + it('should handle bytecode with unlinked library placeholders', function () { + // Library placeholders are deterministic (keccak256 of "path:name")) and + // included as-is in the hash — they're part of the artifact identity + const placeholder = '__$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$__' + const code = '0x' + BASE_CODE + '73' + placeholder + METADATA_A + const hash = computeBytecodeHash(code) + expect(hash).to.be.a('string') + expect(hash).to.match(/^0x[a-f0-9]{64}$/) + }) + + it('should detect code changes around library placeholders', function () { + const placeholder = '__$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$__' + const codeA = '0x' + BASE_CODE + '73' + placeholder + METADATA_A + const codeB = '0x' + BASE_CODE + '6001' + '73' + placeholder + METADATA_A + expect(computeBytecodeHash(codeA)).to.not.equal(computeBytecodeHash(codeB)) + }) + + it('should resolve library placeholders with resolver', async function () { + const { keccak256: k, toUtf8Bytes: u } = await import('ethers') + const libPath = 'contracts/libs/MyLib.sol' + const libName = 'MyLib' + const placeholderHash = k(u(`${libPath}:${libName}`)).slice(2, 36) + const placeholder = `__$${placeholderHash}$__` + // Use placeholder in middle with enough valid hex around it, plus metadata suffix + const code = '0x' + BASE_CODE + '73' + placeholder + BASE_CODE + METADATA_A + + const linkRefs: LinkReferences = { + [libPath]: { [libName]: [{ length: 20, start: 0 }] }, + } + const libBytecodeA = '0x6001600201' + const libBytecodeB = '0x6001600301' // different library code + + const resolver: LibraryArtifactResolver = () => ({ + deployedBytecode: libBytecodeA, + }) + const resolverB: LibraryArtifactResolver = () => ({ + deployedBytecode: libBytecodeB, + }) + + const hashA = computeBytecodeHash(code, linkRefs, resolver) + const hashB = computeBytecodeHash(code, linkRefs, resolverB) + const hashNoResolver = computeBytecodeHash(code) + + // Different library code should produce different hashes + expect(hashA).to.not.equal(hashB) + // With resolver should differ from without (zero-filled) + expect(hashA).to.not.equal(hashNoResolver) + }) }) }) diff --git a/packages/deployment/test/chain-id-resolution.test.ts b/packages/deployment/test/chain-id-resolution.test.ts index 356f653d8..3e5948580 100644 --- a/packages/deployment/test/chain-id-resolution.test.ts +++ b/packages/deployment/test/chain-id-resolution.test.ts @@ -1,16 +1,18 @@ import type { Environment } from '@rocketh/core/types' import { expect } from 'chai' -import { getForkTargetChainId, getTargetChainIdFromEnv } from '../lib/address-book-utils.js' +import { getForkNetwork, getForkTargetChainId, getTargetChainIdFromEnv, isForkMode } from '../lib/address-book-utils.js' describe('Chain ID Resolution', function () { // Store original env vars to restore after tests let originalHardhatFork: string | undefined let originalForkNetwork: string | undefined + let originalHardhatNetwork: string | undefined beforeEach(function () { originalHardhatFork = process.env.HARDHAT_FORK originalForkNetwork = process.env.FORK_NETWORK + originalHardhatNetwork = process.env.HARDHAT_NETWORK }) afterEach(function () { @@ -25,6 +27,11 @@ describe('Chain ID Resolution', function () { } else { process.env.FORK_NETWORK = originalForkNetwork } + if (originalHardhatNetwork === undefined) { + delete process.env.HARDHAT_NETWORK + } else { + process.env.HARDHAT_NETWORK = originalHardhatNetwork + } }) describe('getForkTargetChainId', function () { @@ -81,6 +88,116 @@ describe('Chain ID Resolution', function () { expect(() => getForkTargetChainId()).to.throw('Unknown fork network: unknownNetwork') }) + + it('should return null when FORK_NETWORK is set but network is a real network', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'arbitrumSepolia' + + const result = getForkTargetChainId() + expect(result).to.be.null + }) + + it('should return chain ID when FORK_NETWORK is set and network is localhost', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'localhost' + + const result = getForkTargetChainId() + expect(result).to.equal(421614) + }) + + it('should return chain ID when explicit networkName is localhost', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'arbitrumSepolia' // would normally prevent fork mode + + // Explicit networkName overrides HARDHAT_NETWORK + const result = getForkTargetChainId('localhost') + expect(result).to.equal(421614) + }) + + it('should return null when explicit networkName is a real network', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + delete process.env.HARDHAT_NETWORK + + const result = getForkTargetChainId('arbitrumSepolia') + expect(result).to.be.null + }) + }) + + describe('isForkMode (network-aware)', function () { + it('should return false when no fork env vars are set', function () { + delete process.env.HARDHAT_FORK + delete process.env.FORK_NETWORK + + expect(isForkMode()).to.be.false + }) + + it('should return true when FORK_NETWORK is set and HARDHAT_NETWORK is localhost', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'localhost' + + expect(isForkMode()).to.be.true + }) + + it('should return true when FORK_NETWORK is set and HARDHAT_NETWORK is fork', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'fork' + + expect(isForkMode()).to.be.true + }) + + it('should return false when FORK_NETWORK is set but HARDHAT_NETWORK is a real network', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'arbitrumSepolia' + + expect(isForkMode()).to.be.false + }) + + it('should return false when FORK_NETWORK is set but HARDHAT_NETWORK is arbitrumOne', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'arbitrumOne' + + expect(isForkMode()).to.be.false + }) + + it('should use explicit networkName over HARDHAT_NETWORK', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'arbitrumSepolia' // real network + + // Explicit networkName says localhost - should be fork mode + expect(isForkMode('localhost')).to.be.true + }) + + it('should return false with explicit real networkName even if HARDHAT_NETWORK is local', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'localhost' + + // Explicit networkName says real network - should not be fork mode + expect(isForkMode('arbitrumSepolia')).to.be.false + }) + + it('should return true when FORK_NETWORK is set and no network context available', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + delete process.env.HARDHAT_NETWORK + + // No context - preserves existing behavior (trusts env var) + expect(isForkMode()).to.be.true + }) + }) + + describe('getForkNetwork (network-aware)', function () { + it('should return null on real networks even if FORK_NETWORK is set', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'arbitrumSepolia' + + expect(getForkNetwork()).to.be.null + }) + + it('should return fork network name on localhost', function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + process.env.HARDHAT_NETWORK = 'localhost' + + expect(getForkNetwork()).to.equal('arbitrumSepolia') + }) }) describe('getTargetChainIdFromEnv', function () { @@ -89,6 +206,7 @@ describe('Chain ID Resolution', function () { // Mock environment - provider won't be called in fork mode const mockEnv = { + name: 'localhost', network: { provider: { request: () => { @@ -108,6 +226,7 @@ describe('Chain ID Resolution', function () { // Mock environment with provider returning 421614 const mockEnv = { + name: 'arbitrumSepolia', network: { provider: { request: async ({ method }: { method: string }) => { @@ -130,6 +249,7 @@ describe('Chain ID Resolution', function () { // Test Arbitrum One (42161 = 0xA4B1) const mockEnvArb = { + name: 'arbitrumOne', network: { provider: { request: async () => '0xa4b1', // 42161 in hex @@ -140,17 +260,18 @@ describe('Chain ID Resolution', function () { const resultArb = await getTargetChainIdFromEnv(mockEnvArb) expect(resultArb).to.equal(42161) - // Test localhost (31337 = 0x7A69) - const mockEnvLocal = { + // Test Ethereum mainnet (1 = 0x1) + const mockEnvMainnet = { + name: 'mainnet', network: { provider: { - request: async () => '0x7a69', // 31337 in hex + request: async () => '0x1', // 1 in hex }, }, } as unknown as Environment - const resultLocal = await getTargetChainIdFromEnv(mockEnvLocal) - expect(resultLocal).to.equal(31337) + const resultMainnet = await getTargetChainIdFromEnv(mockEnvMainnet) + expect(resultMainnet).to.equal(1) }) it('should prefer fork chain ID over provider chain ID when forking', async function () { @@ -158,6 +279,7 @@ describe('Chain ID Resolution', function () { // Mock provider returning 31337 (local hardhat node) const mockEnv = { + name: 'localhost', network: { provider: { request: async () => '0x7a69', // 31337 in hex @@ -169,6 +291,24 @@ describe('Chain ID Resolution', function () { // Should return fork target (42161), not provider chain ID (31337) expect(result).to.equal(42161) }) + + it('should return provider chain ID on real network even if FORK_NETWORK is set', async function () { + process.env.FORK_NETWORK = 'arbitrumSepolia' + + // Running on arbitrumOne - FORK_NETWORK should be ignored + const mockEnv = { + name: 'arbitrumOne', + network: { + provider: { + request: async () => '0xa4b1', // 42161 in hex + }, + }, + } as unknown as Environment + + const result = await getTargetChainIdFromEnv(mockEnv) + // Should return provider chain ID (42161), not fork target (421614) + expect(result).to.equal(42161) + }) }) describe('Integration: Fork mode detection', function () { @@ -178,6 +318,7 @@ describe('Chain ID Resolution', function () { delete process.env.FORK_NETWORK const mockEnvNonFork = { + name: 'arbitrumSepolia', network: { provider: { request: async () => '0x66eee', // 421614 @@ -186,7 +327,7 @@ describe('Chain ID Resolution', function () { } as unknown as Environment const nonForkChainId = await getTargetChainIdFromEnv(mockEnvNonFork) - const forkChainId1 = getForkTargetChainId() + const forkChainId1 = getForkTargetChainId('arbitrumSepolia') expect(forkChainId1).to.be.null expect(nonForkChainId).to.equal(421614) @@ -195,6 +336,7 @@ describe('Chain ID Resolution', function () { process.env.FORK_NETWORK = 'arbitrumSepolia' const mockEnvFork = { + name: 'localhost', network: { provider: { request: async () => '0x7a69', // 31337 (local node) @@ -203,7 +345,7 @@ describe('Chain ID Resolution', function () { } as unknown as Environment const forkModeChainId = await getTargetChainIdFromEnv(mockEnvFork) - const forkChainId2 = getForkTargetChainId() + const forkChainId2 = getForkTargetChainId('localhost') expect(forkChainId2).to.equal(421614) expect(forkModeChainId).to.equal(421614) // Fork target, not 31337 diff --git a/packages/deployment/test/config-reconciliation.test.ts b/packages/deployment/test/config-reconciliation.test.ts new file mode 100644 index 000000000..2ea3fd131 --- /dev/null +++ b/packages/deployment/test/config-reconciliation.test.ts @@ -0,0 +1,231 @@ +import { expect } from 'chai' +import { HDNodeWallet } from 'ethers' +import fs from 'fs' +import JSON5 from 'json5' +import path from 'path' +import { fileURLToPath } from 'url' + +/** + * Deployment config reconciliation + * + * Catches drift between the per-network Ignition config files in + * `packages/horizon/ignition/configs/` and `packages/subgraph-service/ignition/configs/`. + * + * Four checks: + * + * 1. Cross-package sibling agreement. For each `(prefix, network)` pair where both + * horizon and subgraph-service have a config file (e.g. both `migrate.arbitrumOne.json5`), + * every overlapping non-empty `$global` field must match. Catches the failure mode where + * one package is updated but the sibling drifts. + * + * 2. localNetwork all-files `$global` agreement. For localNetwork specifically (one stack, + * one governor) every `$global` field meaningfully declared in more than one of the four + * `{horizon,subgraph-service}/{migrate,protocol}.localNetwork.json5` files must match + * across all of them. Stricter than #1 — catches same-package cross-prefix drift. + * + * 3. localNetwork same-package cross-prefix sub-object agreement. For localNetwork, each + * package's per-contract config blocks (e.g. `"DisputeManager": { ... }`) must agree + * leaf-by-leaf between `migrate` and `protocol`. Catches drift in things like + * `eip712Name`/`eip712Version` (which would silently break signature verification) and + * `disputePeriod`/`disputeDeposit` parameters. Restricted to localNetwork because for + * other networks (notably `default`) migrate and protocol are intentionally different + * templates with different parameter values. + * + * 4. localNetwork mnemonic-index correctness. Lines like + * "governor": "0x70997970…", // index 1 + * must have an address that derives from the hardhat default mnemonic at the stated + * BIP44 index. Catches copy-paste mistakes where someone updates the value but not the + * comment, or vice versa. + */ + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +const HARDHAT_DEFAULT_MNEMONIC = 'test test test test test test test test test test test junk' +const PACKAGES_DIR = path.resolve(__dirname, '../..') +const PACKAGES = ['horizon', 'subgraph-service'] as const +const CONFIG_FILE_RE = /^(migrate|protocol)\.(.+)\.json5$/ + +type ConfigPrefix = 'migrate' | 'protocol' + +interface ConfigFile { + package: string + network: string + prefix: ConfigPrefix + filePath: string + globalFields: Record + subObjects: Record> + rawText: string +} + +function discoverConfigs(): ConfigFile[] { + const out: ConfigFile[] = [] + for (const pkg of PACKAGES) { + const dir = path.join(PACKAGES_DIR, pkg, 'ignition/configs') + if (!fs.existsSync(dir)) continue + for (const file of fs.readdirSync(dir)) { + const m = CONFIG_FILE_RE.exec(file) + if (!m) continue + const filePath = path.join(dir, file) + const rawText = fs.readFileSync(filePath, 'utf8') + const parsed = JSON5.parse>(rawText) + const globalFields = (parsed.$global ?? {}) as Record + const subObjects: Record> = {} + for (const [k, v] of Object.entries(parsed)) { + if (k === '$global') continue + if (typeof v === 'object' && v !== null && !Array.isArray(v)) { + subObjects[k] = v as Record + } + } + out.push({ + package: pkg, + network: m[2], + prefix: m[1] as ConfigPrefix, + filePath, + globalFields, + subObjects, + rawText, + }) + } + } + return out +} + +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + +function isMeaningful(value: unknown): boolean { + if (value === '' || value === null || value === undefined) return false + if (typeof value === 'string' && value.toLowerCase() === ZERO_ADDRESS) return false + return true +} + +function deriveHardhatAddress(index: number): string { + return HDNodeWallet.fromPhrase(HARDHAT_DEFAULT_MNEMONIC, undefined, `m/44'/60'/0'/0/${index}`).address +} + +function groupByPrefixAndNetwork(configs: ConfigFile[]): Map { + const out = new Map() + for (const c of configs) { + const key = `${c.prefix}.${c.network}` + if (!out.has(key)) out.set(key, []) + out.get(key)!.push(c) + } + return out +} + +describe('Deployment Config Reconciliation', () => { + const configs = discoverConfigs() + const grouped = groupByPrefixAndNetwork(configs) + + describe('Cross-package sibling agreement', () => { + for (const [key, files] of grouped) { + if (files.length < 2) continue + + it(`${key}.json5: overlapping $global fields agree across packages`, () => { + const overlap = new Set() + for (const field of Object.keys(files[0].globalFields)) { + if (files.every((f) => isMeaningful(f.globalFields[field]))) overlap.add(field) + } + + const mismatches: string[] = [] + for (const field of overlap) { + const distinct = new Set(files.map((f) => JSON.stringify(f.globalFields[field]))) + if (distinct.size > 1) { + const summary = files.map((f) => ` ${f.package}: ${JSON.stringify(f.globalFields[field])}`).join('\n') + mismatches.push(` ${field}:\n${summary}`) + } + } + + expect(mismatches, `Cross-package mismatches in ${key}.json5:\n${mismatches.join('\n')}`).to.have.lengthOf(0) + }) + } + }) + + describe('localNetwork all-files agreement', () => { + const localNetworkFiles = configs.filter((c) => c.network === 'localNetwork') + + if (localNetworkFiles.length >= 2) { + it('localNetwork: $global identity fields agree across all (package, prefix) files', () => { + const allFields = new Set() + for (const f of localNetworkFiles) { + for (const [k, v] of Object.entries(f.globalFields)) { + if (isMeaningful(v)) allFields.add(k) + } + } + + const mismatches: string[] = [] + for (const field of allFields) { + const present = localNetworkFiles.filter((f) => isMeaningful(f.globalFields[field])) + if (present.length < 2) continue + const distinct = new Set(present.map((f) => JSON.stringify(f.globalFields[field]))) + if (distinct.size > 1) { + const summary = present + .map((f) => ` ${f.package}/${f.prefix}.localNetwork.json5: ${JSON.stringify(f.globalFields[field])}`) + .join('\n') + mismatches.push(` ${field}:\n${summary}`) + } + } + + expect( + mismatches, + `localNetwork identity-field mismatches across files:\n${mismatches.join('\n')}`, + ).to.have.lengthOf(0) + }) + } + }) + + describe('localNetwork same-package cross-prefix sub-object agreement', () => { + // localNetwork-only: one stack, so per-contract config in protocol and migrate must agree. + // For other networks (e.g. `default`), migrate and protocol are different templates with + // intentionally different parameter values. + for (const pkg of PACKAGES) { + const migrate = configs.find((c) => c.package === pkg && c.network === 'localNetwork' && c.prefix === 'migrate') + const protocol = configs.find((c) => c.package === pkg && c.network === 'localNetwork' && c.prefix === 'protocol') + if (!migrate || !protocol) continue + + it(`${pkg}/localNetwork: per-contract sub-object leaves agree across migrate and protocol`, () => { + const sharedKeys = Object.keys(migrate.subObjects).filter((k) => k in protocol.subObjects) + + const mismatches: string[] = [] + for (const subKey of sharedKeys) { + const m = migrate.subObjects[subKey] + const p = protocol.subObjects[subKey] + for (const leaf of new Set([...Object.keys(m), ...Object.keys(p)])) { + if (!(leaf in m) || !(leaf in p)) continue // declared in only one side + if (JSON.stringify(m[leaf]) !== JSON.stringify(p[leaf])) { + mismatches.push( + ` ${subKey}.${leaf}: migrate=${JSON.stringify(m[leaf])} protocol=${JSON.stringify(p[leaf])}`, + ) + } + } + } + + expect( + mismatches, + `Sub-object leaf mismatches in ${pkg}/localNetwork:\n${mismatches.join('\n')}`, + ).to.have.lengthOf(0) + }) + } + }) + + describe('localNetwork mnemonic-index comments', () => { + const indexCommentRe = /"(0x[a-fA-F0-9]{40})"\s*,?\s*\/\/\s*index\s+(\d+)/g + + for (const cfg of configs) { + if (cfg.network !== 'localNetwork') continue + + it(`${cfg.package}/${path.basename(cfg.filePath)}: addresses match // index N comments`, () => { + const errors: string[] = [] + for (const match of cfg.rawText.matchAll(indexCommentRe)) { + const [, address, indexStr] = match + const index = Number.parseInt(indexStr, 10) + const expected = deriveHardhatAddress(index) + if (address.toLowerCase() !== expected.toLowerCase()) { + errors.push(`address ${address} marked "// index ${index}" should be ${expected}`) + } + } + expect(errors, errors.join('\n')).to.have.lengthOf(0) + }) + } + }) +}) diff --git a/packages/deployment/test/deployment-metadata.test.ts b/packages/deployment/test/deployment-metadata.test.ts index 2661ccda4..c3347d0f2 100644 --- a/packages/deployment/test/deployment-metadata.test.ts +++ b/packages/deployment/test/deployment-metadata.test.ts @@ -2,7 +2,8 @@ import { expect } from 'chai' import { AddressBookOps } from '../lib/address-book-ops.js' import { computeBytecodeHash } from '../lib/bytecode-utils.js' -import { checkShouldSync, createDeploymentMetadata, reconstructDeploymentRecord } from '../lib/sync-utils.js' +import { buildDeploymentMetadata } from '../lib/deployment-metadata.js' +import { checkShouldSync, reconstructDeploymentRecord } from '../lib/sync-utils.js' /** * Deployment Metadata Tests @@ -303,30 +304,68 @@ describe('Sync Change Detection', () => { }) }) - describe('createDeploymentMetadata', () => { + describe('buildDeploymentMetadata', () => { + const bytecode = '0x608060405234801561001057600080fd5b50' + it('creates metadata with all required fields', () => { - const bytecode = '0x608060405234801561001057600080fd5b50' const expectedHash = computeBytecodeHash(bytecode) + const metadata = buildDeploymentMetadata( + { transaction: { hash: '0xtxhash' }, argsData: '0xargsdata' }, + expectedHash, + { blockNumber: 12345678, timestamp: '2024-01-15T10:30:00Z' }, + ) + + expect(metadata).to.exist + expect(metadata!.txHash).to.equal('0xtxhash') + expect(metadata!.argsData).to.equal('0xargsdata') + expect(metadata!.bytecodeHash).to.equal(expectedHash) + expect(metadata!.blockNumber).to.equal(12345678) + expect(metadata!.timestamp).to.equal('2024-01-15T10:30:00Z') + }) + + it('creates metadata without optional fields', () => { + const metadata = buildDeploymentMetadata( + { transaction: { hash: '0xtxhash' }, argsData: '0xargsdata' }, + computeBytecodeHash(bytecode), + ) + + expect(metadata).to.exist + expect(metadata!.txHash).to.equal('0xtxhash') + expect(metadata!.argsData).to.equal('0xargsdata') + expect(metadata!.bytecodeHash).to.be.a('string') + expect(metadata!.blockNumber).to.be.undefined + expect(metadata!.timestamp).to.be.undefined + }) - const metadata = createDeploymentMetadata('0xtxhash', '0xargsdata', bytecode, 12345678, '2024-01-15T10:30:00Z') + it('prefers explicit blockNumber over receipt.blockNumber', () => { + const metadata = buildDeploymentMetadata( + { transaction: { hash: '0xtxhash' }, argsData: '0xargsdata', receipt: { blockNumber: '0x1' } }, + computeBytecodeHash(bytecode), + { blockNumber: 12345678 }, + ) - expect(metadata.txHash).to.equal('0xtxhash') - expect(metadata.argsData).to.equal('0xargsdata') - expect(metadata.bytecodeHash).to.equal(expectedHash) - expect(metadata.blockNumber).to.equal(12345678) - expect(metadata.timestamp).to.equal('2024-01-15T10:30:00Z') + expect(metadata!.blockNumber).to.equal(12345678) }) - it('creates metadata without optional fields', () => { - const bytecode = '0x608060405234801561001057600080fd5b50' + it('extracts blockNumber from a hex-string receipt', () => { + const metadata = buildDeploymentMetadata( + { transaction: { hash: '0xtxhash' }, argsData: '0xargsdata', receipt: { blockNumber: '0x2a' } }, + computeBytecodeHash(bytecode), + ) - const metadata = createDeploymentMetadata('0xtxhash', '0xargsdata', bytecode) + expect(metadata!.blockNumber).to.equal(42) + }) + + it('returns undefined when txHash is missing', () => { + const metadata = buildDeploymentMetadata({ argsData: '0xargsdata' }, computeBytecodeHash(bytecode)) + + expect(metadata).to.be.undefined + }) + + it('returns undefined when argsData is missing', () => { + const metadata = buildDeploymentMetadata({ transaction: { hash: '0xtxhash' } }, computeBytecodeHash(bytecode)) - expect(metadata.txHash).to.equal('0xtxhash') - expect(metadata.argsData).to.equal('0xargsdata') - expect(metadata.bytecodeHash).to.be.a('string') - expect(metadata.blockNumber).to.be.undefined - expect(metadata.timestamp).to.be.undefined + expect(metadata).to.be.undefined }) }) }) diff --git a/packages/deployment/test/should-seed-rocketh.test.ts b/packages/deployment/test/should-seed-rocketh.test.ts new file mode 100644 index 000000000..a982d02e0 --- /dev/null +++ b/packages/deployment/test/should-seed-rocketh.test.ts @@ -0,0 +1,126 @@ +import { expect } from 'chai' + +import { getLibraryResolver, loadDirectAllocationArtifact } from '../lib/artifact-loaders.js' +import { computeBytecodeHash } from '../lib/bytecode-utils.js' +import { Contracts } from '../lib/contract-registry.js' +import { type ContractSpec, shouldSeedRocketh } from '../lib/sync-utils.js' + +/** + * shouldSeedRocketh — gate that decides whether sync should write rocketh's + * deployment record from the local artifact. + * + * The gate exists to prevent a silent failure mode: seeding rocketh from a + * stale local artifact masks rocketh's bytecode-change detection on the next + * deployFn call (it ends up comparing the new artifact to itself), so the + * impl never gets redeployed and dependent proxies never receive a pending + * implementation. Concretely, this caused shared-impl proxies (DefaultAllocation, + * ReclaimedRewards) to get stuck on stale code with no upgrade triggered. + * + * The rules below are the truth table that pins the gate against future + * regressions of any of those failure modes. + */ + +const sharedImpl = Contracts.issuance.DirectAllocation_Implementation + +function specForSharedImpl(overrides: Partial = {}): ContractSpec { + return { + name: sharedImpl.name, + addressBookType: 'issuance', + address: '0x0000000000000000000000000000000000000aaa', + prerequisite: false, + artifact: sharedImpl.artifact, + ...overrides, + } +} + +function localArtifactHash(): string { + const artifact = loadDirectAllocationArtifact() + return computeBytecodeHash( + artifact.deployedBytecode ?? '0x', + artifact.deployedLinkReferences, + getLibraryResolver('issuance'), + ) +} + +describe('shouldSeedRocketh', () => { + it('seeds when name is unregistered (proxy-recursion synthetic name passthrough)', () => { + // Regression: my first attempt of this gate broke RewardsManager sync because + // the proxy path recurses with `${name}_Implementation` synthetic names that + // aren't real registry entries. The gate must let those fall through. + const spec = specForSharedImpl({ name: 'RewardsManager_Implementation' }) + const result = shouldSeedRocketh(spec, {}) + expect(result.seed).to.be.true + expect(result.reason).to.match(/unregistered/) + }) + + it('seeds when contract is a prerequisite (e.g. L2GraphToken passthrough)', () => { + // Regression: prerequisites are deployed externally and never run through + // deployFn, so dedup-masking doesn't apply. They still need an env record + // for downstream reads. Skipping the seed broke L2GraphToken. + const spec = specForSharedImpl({ prerequisite: true }) + const result = shouldSeedRocketh(spec, {}) + expect(result.seed).to.be.true + expect(result.reason).to.match(/prerequisite/) + }) + + it('seeds when no artifact is configured (legacy entries with no comparison possible)', () => { + const spec = specForSharedImpl({ artifact: undefined }) + const result = shouldSeedRocketh(spec, {}) + expect(result.seed).to.be.true + expect(result.reason).to.match(/no artifact/) + }) + + it('seeds when address-book has no entry (nothing to mask)', () => { + const spec = specForSharedImpl() + const addressBook = { entryExists: () => false } + const result = shouldSeedRocketh(spec, addressBook) + expect(result.seed).to.be.true + expect(result.reason).to.match(/no entry/) + }) + + it('seeds when entry exists but has no stored bytecodeHash', () => { + const spec = specForSharedImpl() + const addressBook = { + entryExists: () => true, + getDeploymentMetadata: () => undefined, + } + const result = shouldSeedRocketh(spec, addressBook) + expect(result.seed).to.be.true + expect(result.reason).to.match(/no hash/) + }) + + it('seeds when stored hash matches local artifact hash (artifact verified)', () => { + const spec = specForSharedImpl() + const addressBook = { + entryExists: () => true, + getDeploymentMetadata: () => ({ + bytecodeHash: localArtifactHash(), + txHash: '', + argsData: '0x', + }), + } + const result = shouldSeedRocketh(spec, addressBook) + expect(result.seed).to.be.true + expect(result.reason).to.match(/verified/) + }) + + it('skips seed when stored hash does not match local artifact hash', () => { + // The core bug. Without this skip, sync seeds rocketh with the local + // artifact bytecode; rocketh then sees its own seeded bytecode == artifact + // and reports newlyDeployed=false on the next deployFn — masking the drift + // and stranding any proxy that depends on this impl with code-changed but + // no pendingImplementation. + const spec = specForSharedImpl() + const addressBook = { + entryExists: () => true, + getDeploymentMetadata: () => ({ + bytecodeHash: '0xstalehashfromearlierdeployment', + txHash: '', + argsData: '0x', + }), + } + const result = shouldSeedRocketh(spec, addressBook) + expect(result.seed).to.be.false + expect(result.reason).to.match(/unverified/) + }) +}) diff --git a/packages/deployment/tsconfig.json b/packages/deployment/tsconfig.json index 75fbe69b6..b97d405ac 100644 --- a/packages/deployment/tsconfig.json +++ b/packages/deployment/tsconfig.json @@ -5,6 +5,6 @@ "rootDir": ".", "composite": true }, - "include": ["lib/**/*", "tasks/**/*", "governance/**/*", "deploy/**/*", "rocketh/**/*", "hardhat.config.ts"], + "include": ["lib/**/*", "tasks/**/*", "deploy/**/*", "rocketh/**/*", "types/**/*", "hardhat.config.ts"], "exclude": ["node_modules", "dist", "artifacts", "cache", "test"] } diff --git a/packages/deployment/types/rocketh.d.ts b/packages/deployment/types/rocketh.d.ts new file mode 100644 index 000000000..af44ad34a --- /dev/null +++ b/packages/deployment/types/rocketh.d.ts @@ -0,0 +1,24 @@ +// Type augmentation: rocketh's skip() support is enabled via pnpm patch (patches/rocketh@0.17.13.patch). +// Deploy scripts also have early-return guards as a safety net. +import type { + UnknownDeployments, + UnresolvedNetworkSpecificData, + UnresolvedUnknownNamedAccounts, +} from '@rocketh/core/types' + +declare module '@rocketh/core/types' { + interface DeployScriptModule< + // eslint-disable-next-line @typescript-eslint/no-unused-vars + NamedAccounts extends UnresolvedUnknownNamedAccounts = UnresolvedUnknownNamedAccounts, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Data extends UnresolvedNetworkSpecificData = UnresolvedNetworkSpecificData, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ArgumentsTypes = undefined, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Deployments extends UnknownDeployments = UnknownDeployments, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Extra extends Record = Record, + > { + skip?: () => Promise + } +} diff --git a/packages/horizon/.solhint.json b/packages/horizon/.solhint.json index d30847305..780d82f39 100644 --- a/packages/horizon/.solhint.json +++ b/packages/horizon/.solhint.json @@ -1,3 +1,3 @@ { - "extends": ["solhint:recommended", "./../../.solhint.json"] + "extends": "./../../.solhint.json" } diff --git a/packages/horizon/addresses.json b/packages/horizon/addresses.json index a7c8437bd..f386a84a3 100644 --- a/packages/horizon/addresses.json +++ b/packages/horizon/addresses.json @@ -71,7 +71,18 @@ "address": "0x4b5D3Da463F7E076bb7CDF5030960bf123245681", "proxy": "transparent", "proxyAdmin": "0x36dFE73C38e0340C8925BA6a68aE706b74340156", - "implementation": "0x36a194135E41a556ad6F4Dbad6b7F8F0e884ba1d" + "implementation": "0x25cf4a6ccd1f829d346cfda69112cd66639aaaa8", + "implementationDeployment": { + "txHash": "0x38c2d58d65e7ba66779cc2c45a9348d6ecb8ecedf80703be5769a3259311db02", + "argsData": "0x0000000000000000000000009db3ee191681f092607035d9bda6e59fbeaca6950000000000000000000000000000000000000000000000000000000000002a30", + "bytecodeHash": "0xc422e0b089ad8479e55a9a768d5bbea929745c83067496ff60a8f47dc2a08d90", + "blockNumber": 258351112, + "timestamp": "2026-04-10T15:30:13.000Z", + "verified": "https://sepolia.arbiscan.io/address/0x25cf4a6ccd1f829d346cfda69112cd66639aaaa8#code" + }, + "proxyDeployment": { + "verified": "https://sepolia.arbiscan.io/address/0x4b5D3Da463F7E076bb7CDF5030960bf123245681#code" + } }, "Controller": { "address": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695" @@ -79,7 +90,18 @@ "L2Curation": { "address": "0xDe761f075200E75485F4358978FB4d1dC8644FD5", "proxy": "graph", - "implementation": "0xbC8F4355f346e47eef8A0DBFF4a58616ACf7DaCA" + "implementation": "0x42e7b4b418672e890b460ca5e83ff47ad5717f02", + "implementationDeployment": { + "txHash": "0x774d6402b982c6a04245715efa92d0d40d47bf06ba95f3e02bc3d2dea3cba409", + "argsData": "0x", + "bytecodeHash": "0xaad16b82ef09b39624235fcc47361da5bd2c6cb0f3926a4aa2d9d11f88a3e238", + "blockNumber": 258322205, + "timestamp": "2026-04-10T13:12:51.000Z", + "verified": "https://sepolia.arbiscan.io/address/0x42e7b4b418672e890b460ca5e83ff47ad5717f02#code" + }, + "proxyDeployment": { + "verified": "https://sepolia.arbiscan.io/address/0xDe761f075200E75485F4358978FB4d1dC8644FD5#code" + } }, "L2GNS": { "address": "0x3133948342F35b8699d8F94aeE064AbB76eDe965", @@ -92,23 +114,34 @@ "RewardsManager": { "address": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79", "proxy": "graph", - "implementation": "0xd681431502e7f9780f14576c17f4459074fc2360", + "implementation": "0xeffc5bb9b46dfbda6f8b0f297d12880674a6717e", "proxyDeployment": { "verified": "https://sepolia.arbiscan.io/address/0x1F49caE7669086c8ba53CC35d1E9f80176d67E79#code" }, "implementationDeployment": { - "txHash": "0x09b9cea7f67a55bf81fc92b08d4bb6c7a34f0471d4d1987ef3d914d76ea3f351", + "txHash": "0x9be5cd5335eec0ae8305d149f13d79ff4015d2327bbeed0a47d444e29fbbfd7a", "argsData": "0x", - "bytecodeHash": "0xee210d0ea0a5e1a46622eb4da78d621523e3efcae872d8a844a69b9677c704ef", - "blockNumber": 240022327, - "timestamp": "2026-02-05T19:03:01.000Z", - "verified": "https://sepolia.arbiscan.io/address/0xd681431502e7f9780f14576c17f4459074fc2360#code" + "bytecodeHash": "0xd0cd3f4b7ce4ce4fe6ea8ee8ecd4e74bb683c64de2696c9e5ad7f74ef4c16f4e", + "blockNumber": 258336594, + "timestamp": "2026-04-10T14:19:51.000Z", + "verified": "https://sepolia.arbiscan.io/address/0xeffc5bb9b46dfbda6f8b0f297d12880674a6717e#code" } }, "HorizonStaking": { "address": "0x865365C425f3A593Ffe698D9c4E6707D14d51e08", "proxy": "graph", - "implementation": "0x2AF6F51e119A79497C3A3FFf012B5889da489764" + "implementation": "0x2333c59d080c5641c804579165641d0162a7249b", + "implementationDeployment": { + "txHash": "0x0cb033e6595517c53daf5e0c736c9a8b49e92e830a894ac05f9fdd81acd6fcfb", + "argsData": "0x0000000000000000000000009db3ee191681f092607035d9bda6e59fbeaca695000000000000000000000000c24a3dac5d06d771f657a48b20ce1a671b78f26b", + "bytecodeHash": "0x4fcc568f70748b19c8a90480ea1521870bac358681074768388098ef263ed559", + "blockNumber": 258348283, + "timestamp": "2026-04-10T15:16:25.000Z", + "verified": "https://sepolia.arbiscan.io/address/0x2333c59d080c5641c804579165641d0162a7249b#code" + }, + "proxyDeployment": { + "verified": "https://sepolia.arbiscan.io/address/0x865365C425f3A593Ffe698D9c4E6707D14d51e08#code" + } }, "GraphTallyCollector": { "address": "0x382863e7B662027117449bd2c49285582bbBd21B" @@ -130,6 +163,27 @@ "address": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb", "proxy": "graph", "implementation": "0x3C2eB5E561f70c0573E5f6c92358e988E32cb5eC" + }, + "RecurringCollector": { + "address": "0x0b18befc60455121ad66ae6e4a647955fcde3900", + "proxy": "transparent", + "proxyAdmin": "0x59d83d4bd5f880c5e635273e4fb12e0a8e827f1d", + "implementation": "0xf4f75d6e1021db1b83b8bccfefa1a0ea06989fa1", + "implementationDeployment": { + "txHash": "0x579640729801f30ddec1e85b7ae6b7b9c51cc2502c1d96a0b698dbb553a1dafa", + "argsData": "0x0000000000000000000000009db3ee191681f092607035d9bda6e59fbeaca6950000000000000000000000000000000000000000000000000000000000007080", + "bytecodeHash": "0xe475513d113bac487d6c2a5504f73e8c1a7962dd611aa981e09cf04ebb0c5486", + "blockNumber": 258348301, + "timestamp": "2026-04-10T15:16:30.000Z", + "verified": "https://sepolia.arbiscan.io/address/0xf4f75d6e1021db1b83b8bccfefa1a0ea06989fa1#code" + }, + "proxyDeployment": { + "txHash": "0x6fb822cdafa22542bed36045d77705c1354770feed50d67ef69ecde1bf668e28", + "argsData": "0x000000000000000000000000763a83af638f1ea6a4033868bc24994f9bd62617000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c44cd88b76000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000012526563757272696e67436f6c6c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "bytecodeHash": "0x6b4ba3015667741610274b7c196ec5d7767235d85865912f7ac680eac3011c54", + "blockNumber": 258322094, + "verified": "https://sepolia.arbiscan.io/address/0x0b18befc60455121ad66ae6e4a647955fcde3900#code" + } } } } diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.todo.md b/packages/horizon/contracts/payments/collectors/RecurringCollector.todo.md new file mode 100644 index 000000000..bbfe4499b --- /dev/null +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.todo.md @@ -0,0 +1,23 @@ +# RecurringCollector.sol — pending updates + +Tracking pending edits noted but deferred to avoid touching contract source until the next maintenance window. When picking up, drop the corresponding entry below as part of the same commit. Line numbers reflect the file at time of writing and will drift. + +## Refactor candidates + +Code-clarity / micro-bytecode wins. Deployed bytecode is **22,558 / 24,576 bytes** (~2 KB free) after `7c12e2f80` made module-level constants `internal`, so none of these are urgent. Pick up opportunistically — e.g. when the adjacent code is already being touched, or in a focused cleanup pass before a future audit round. + +- **Drop `try/catch` around `decodeCollectData`** (`collect()` L213–217). The external-self call + try/catch only converts an ABI-decode panic into `RecurringCollectorInvalidCollectData`. Replace with direct `abi.decode`. Code clarity win + ~150–300 bytes. +- **Hoist callback gas-precheck threshold to a constant.** Three uses at L835, L855, L884 of `(MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD`. Promote to `private constant MIN_GASLEFT_FOR_CALLBACK`. ~30–60 bytes, plus the threshold becomes inspectable. +- **Extract `_invokePayerCallback` helper.** Three near-identical assembly blocks: eligibility staticcall (L834–851, reads return value), `beforeCollection` call (L854–864), `afterCollection` call (L882–897). Probably two helpers (return-value vs. fire-and-forget). Auditor needs to verify assembly preserved + return-value semantics — defer unless the assembly is being touched anyway. ~200–500 bytes. +- **Inline `_getMaxNextClaim(AgreementData storage)` into `_getMaxNextClaimScoped`.** Single call site (L1321, the Accepted branch). Optimizer at `runs: 100` almost certainly already inlines; explicit removal removes a hop for readers. ~50–150 bytes if not already inlined. + +## Offer-keyed terms storage + +Architectural restructure considered — defer to next storage-level pass. Split `AgreementData` so identity + terms live in `offers[hash]` (per-version, immutable) and lifecycle lives in `agreements[id]` (per-agreement, mutates). Decode once at store time; all internal reads go through `offers[hash]`. Collapses the three-way dispatch in `_getMaxNextClaimScoped`, folds `_getMaxNextClaim(_a)` into it, removes the double-write in `_validateAndStoreAgreement` / `_offerNew` (agreement-storage terms + `rcaOffers` blob), simplifies `_validateAndStoreUpdate`. Preserves `offers[hash].data` / `offerType` for API compat. A synthesizing getter keeps `getAgreement(id)`'s return shape stable. Prototype preserved at `archive/indexing-payments-management-unified-terms-storage` (`a9c2737038`) — see "Related archived branches" below. + +## Related archived branches + +Broader RecurringCollector / RAM refactors explored on parallel branches and preserved as tags rather than merged. Useful context when revisiting the architecture: + +- `archive/indexing-payments-management-unified-terms-storage` (`a9c2737038`) — full TRST-L-11 storage refactor: single `terms[hash]` mapping replaces `rcaOffers`/`rcauOffers`, `AgreementData` slimmed from 7 to 5 slots with `pendingTermsHash` pointer, `_storeTerms` as the only validated write gate; the 3-way state dispatch in max-claim collapses into `_activeClaimWindow`/`_maxClaimForTerms`, NEW path shared between `offer(NEW)` and `accept()`. This is the prototype of the "Offer-keyed terms storage" sketch above. Superseded by `-2-light`, which kept the existing storage and addressed TRST-L-11 minimally via per-version semantics in `getAgreementDetails`. +- `archive/indexing-payments-management-collector-led-lifecycle` — full RAM/Collector boundary inversion (audit-fix PR1301 round): payer interacts with the Collector first via a two-phase offer/accept flow, Collector then notifies the data service via `acceptAgreement` / `afterAgreementStateChange` callbacks (with `MAX_CALLBACK_GAS` cap and `PayerCallbackFailed` events); ECDSA signing and `Authorizable` dropped from the Collector. Generic agreement methods moved from `IRecurringCollector` to `IAgreementCollector`; `OfferResult`/`AgreementVersion` unified into a single `AgreementDetails`; `Pair` dropped from RAM's public API. Superseded by `-reduced`, which preserved the data-service-as-orchestrator pattern and instead tightened internals (collector→provider storage hierarchy, stored-hash auth, scoped claims, pausable/upgradeable Collector). diff --git a/packages/horizon/ignition/configs/migrate.arbitrumOne.json5 b/packages/horizon/ignition/configs/migrate.arbitrumOne.json5 index c28f8974c..fe86db939 100644 --- a/packages/horizon/ignition/configs/migrate.arbitrumOne.json5 +++ b/packages/horizon/ignition/configs/migrate.arbitrumOne.json5 @@ -1,54 +1,54 @@ { - "$global": { + $global: { // Accounts already configured in the original Graph Protocol - Arbitrum One values - "governor": "0x8C6de8F8D562f3382417340A6994601eE08D3809", + governor: '0x8C6de8F8D562f3382417340A6994601eE08D3809', // Addresses for contracts deployed in the original Graph Protocol - Arbitrum One values - "graphProxyAdminAddress": "0x2983936aC20202a6555993448E0d5654AC8Ca5fd", - "controllerAddress": "0x0a8491544221dd212964fbb96487467291b2C97e", - "horizonStakingAddress": "0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF03", - "epochManagerAddress": "0x5A843145c43d328B9bB7a4401d94918f131bB281", - "epochManagerImplementationAddress": "0xeEDEdb3660154f439D93bfF612f7902edf07b848", - "graphTokenAddress": "0x9623063377AD1B27544C965cCd7342f7EA7e88C7", - "graphTokenImplementationAddress": "0xaFFCb96181D920FE8C0Af046C49B2c9eC98b28df", - "graphTokenGatewayAddress": "0x65E1a5e8946e7E87d9774f5288f41c30a99fD302", - "graphTokenGatewayImplementationAddress": "0x6f37b2AF8A0Cc74f1bFddf2E9302Cb226710127F", - "rewardsManagerAddress": "0x971B9d3d0Ae3ECa029CAB5eA1fB0F72c85e6a525", - "curationAddress": "0x22d78fb4bc72e191C765807f8891B5e1785C8014", - "gnsAddress": "0xec9A7fb6CbC2E41926127929c2dcE6e9c5D33Bec", - "gnsImplementationAddress": "0x9B81c7C5A21E65b849FD487540B0A82d3b97b2c7", - "subgraphNFTAddress": "0x3FbD54f0cc17b7aE649008dEEA12ed7D2622B23f", + graphProxyAdminAddress: '0x2983936aC20202a6555993448E0d5654AC8Ca5fd', + controllerAddress: '0x0a8491544221dd212964fbb96487467291b2C97e', + horizonStakingAddress: '0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF03', + epochManagerAddress: '0x5A843145c43d328B9bB7a4401d94918f131bB281', + epochManagerImplementationAddress: '0xeEDEdb3660154f439D93bfF612f7902edf07b848', + graphTokenAddress: '0x9623063377AD1B27544C965cCd7342f7EA7e88C7', + graphTokenImplementationAddress: '0xaFFCb96181D920FE8C0Af046C49B2c9eC98b28df', + graphTokenGatewayAddress: '0x65E1a5e8946e7E87d9774f5288f41c30a99fD302', + graphTokenGatewayImplementationAddress: '0x6f37b2AF8A0Cc74f1bFddf2E9302Cb226710127F', + rewardsManagerAddress: '0x971B9d3d0Ae3ECa029CAB5eA1fB0F72c85e6a525', + curationAddress: '0x22d78fb4bc72e191C765807f8891B5e1785C8014', + gnsAddress: '0xec9A7fb6CbC2E41926127929c2dcE6e9c5D33Bec', + gnsImplementationAddress: '0x9B81c7C5A21E65b849FD487540B0A82d3b97b2c7', + subgraphNFTAddress: '0x3FbD54f0cc17b7aE649008dEEA12ed7D2622B23f', // Must be set for step 2 of the migration - "graphPaymentsAddress": "0x7Aae8ae011927BC36Cb4d0d3e81f2E6E30daE06D", - "paymentsEscrowAddress": "0xf6Fcc27aAf1fcD8B254498c9794451d82afC673E", + graphPaymentsAddress: '0x7Aae8ae011927BC36Cb4d0d3e81f2E6E30daE06D', + paymentsEscrowAddress: '0xf6Fcc27aAf1fcD8B254498c9794451d82afC673E', // Must be set for step 3 and 4 of the migration - "subgraphServiceAddress": "0xb2Bb92d0DE618878E438b55D5846cfecD9301105", + subgraphServiceAddress: '0xb2Bb92d0DE618878E438b55D5846cfecD9301105', // Must be set for step 4 of the migration - "horizonStakingImplementationAddress": "0xaA3359434B534dE9964d4e72bE2782b076a1Eb5A", - "curationImplementationAddress": "0xc4Ce508c8fda35C597CC78e3604261110fc4c957", - "rewardsManagerImplementationAddress": "0xBcD7a231eAB1f4667AAbFdb482026f244bfBf101", - "disputeManagerAddress": "0x2FE023a575449AcB698648eD21276293Fa176f96", + horizonStakingImplementationAddress: '0xaA3359434B534dE9964d4e72bE2782b076a1Eb5A', + curationImplementationAddress: '0xc4Ce508c8fda35C597CC78e3604261110fc4c957', + rewardsManagerImplementationAddress: '0xBcD7a231eAB1f4667AAbFdb482026f244bfBf101', + disputeManagerAddress: '0x2FE023a575449AcB698648eD21276293Fa176f96', // Global parameters - "maxThawingPeriod": 2419200 + maxThawingPeriod: 2419200, }, - "GraphPayments": { - "protocolPaymentCut": 10000 + GraphPayments: { + protocolPaymentCut: 10000, }, - "PaymentsEscrow": { - "withdrawEscrowThawingPeriod": 2592000 + PaymentsEscrow: { + withdrawEscrowThawingPeriod: 2592000, }, - "GraphTallyCollector": { - "eip712Name": "GraphTallyCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 2592000 + GraphTallyCollector: { + eip712Name: 'GraphTallyCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 2592000, + }, + RecurringCollector: { + eip712Name: 'RecurringCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 2592000, }, - "RecurringCollector": { - "eip712Name": "RecurringCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 2592000 - } } diff --git a/packages/horizon/ignition/configs/migrate.arbitrumSepolia.json5 b/packages/horizon/ignition/configs/migrate.arbitrumSepolia.json5 index adb2eb86d..9019e9d0a 100644 --- a/packages/horizon/ignition/configs/migrate.arbitrumSepolia.json5 +++ b/packages/horizon/ignition/configs/migrate.arbitrumSepolia.json5 @@ -1,54 +1,54 @@ { - "$global": { + $global: { // Accounts already configured in the original Graph Protocol - "governor": "0x72ee30d43Fb5A90B3FE983156C5d2fBE6F6d07B3", + governor: '0x72ee30d43Fb5A90B3FE983156C5d2fBE6F6d07B3', // Addresses for contracts deployed in the original Graph Protocol - "graphProxyAdminAddress": "0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C", - "controllerAddress": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695", - "horizonStakingAddress": "0x865365C425f3A593Ffe698D9c4E6707D14d51e08", - "epochManagerAddress": "0x88b3C7f37253bAA1A9b95feAd69bD5320585826D", - "epochManagerImplementationAddress": "0x646627fa39ec6f6E757Cb4189bC54c92FFBb71da", - "graphTokenAddress": "0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04", - "graphTokenImplementationAddress": "0x4cf968bA38b43dd10be114daa7959C1b369479e5", - "graphTokenGatewayAddress": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb", - "graphTokenGatewayImplementationAddress": "0x3C2eB5E561f70c0573E5f6c92358e988E32cb5eC", - "rewardsManagerAddress": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79", - "curationAddress": "0xDe761f075200E75485F4358978FB4d1dC8644FD5", - "gnsAddress": "0x3133948342F35b8699d8F94aeE064AbB76eDe965", - "gnsImplementationAddress": "0x00CBF5024d454255577Bf2b0fB6A43328a6828c9", - "subgraphNFTAddress": "0xF21Df5BbA7EB9b54D8F60C560aFb9bA63e6aED1A", + graphProxyAdminAddress: '0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C', + controllerAddress: '0x9DB3ee191681f092607035d9BDA6e59FbEaCa695', + horizonStakingAddress: '0x865365C425f3A593Ffe698D9c4E6707D14d51e08', + epochManagerAddress: '0x88b3C7f37253bAA1A9b95feAd69bD5320585826D', + epochManagerImplementationAddress: '0x646627fa39ec6f6E757Cb4189bC54c92FFBb71da', + graphTokenAddress: '0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04', + graphTokenImplementationAddress: '0x4cf968bA38b43dd10be114daa7959C1b369479e5', + graphTokenGatewayAddress: '0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb', + graphTokenGatewayImplementationAddress: '0x3C2eB5E561f70c0573E5f6c92358e988E32cb5eC', + rewardsManagerAddress: '0x1F49caE7669086c8ba53CC35d1E9f80176d67E79', + curationAddress: '0xDe761f075200E75485F4358978FB4d1dC8644FD5', + gnsAddress: '0x3133948342F35b8699d8F94aeE064AbB76eDe965', + gnsImplementationAddress: '0x00CBF5024d454255577Bf2b0fB6A43328a6828c9', + subgraphNFTAddress: '0xF21Df5BbA7EB9b54D8F60C560aFb9bA63e6aED1A', // Must be set for step 2 of the migration - "graphPaymentsAddress": "0x57E70eC8905E26341d40aF60Dca56cDBA8C166E5", - "paymentsEscrowAddress": "0x4b5D3Da463F7E076bb7CDF5030960bf123245681", + graphPaymentsAddress: '0x57E70eC8905E26341d40aF60Dca56cDBA8C166E5', + paymentsEscrowAddress: '0x4b5D3Da463F7E076bb7CDF5030960bf123245681', // Must be set for step 3 and 4 of the migration - "subgraphServiceAddress": "0xc24A3dAC5d06d771f657A48B20cE1a671B78f26b", + subgraphServiceAddress: '0xc24A3dAC5d06d771f657A48B20cE1a671B78f26b', // Must be set for step 4 of the migration - "horizonStakingImplementationAddress": "0x2AF6F51e119A79497C3A3FFf012B5889da489764", - "curationImplementationAddress": "0xbC8F4355f346e47eef8A0DBFF4a58616ACf7DaCA", - "rewardsManagerImplementationAddress": "0x856843F6409a8b3A0d4aaE67313037FED02bBBFf", - "disputeManagerAddress": "0x96e1b86b2739e8A3d59F40F2532caDF9cE8Da088", - + horizonStakingImplementationAddress: '0x2AF6F51e119A79497C3A3FFf012B5889da489764', + curationImplementationAddress: '0xbC8F4355f346e47eef8A0DBFF4a58616ACf7DaCA', + rewardsManagerImplementationAddress: '0x856843F6409a8b3A0d4aaE67313037FED02bBBFf', + disputeManagerAddress: '0x96e1b86b2739e8A3d59F40F2532caDF9cE8Da088', + // Global parameters - "maxThawingPeriod": 2419200 + maxThawingPeriod: 2419200, + }, + GraphPayments: { + protocolPaymentCut: 10000, }, - "GraphPayments": { - "protocolPaymentCut": 10000 + PaymentsEscrow: { + withdrawEscrowThawingPeriod: 10800, }, - "PaymentsEscrow": { - "withdrawEscrowThawingPeriod": 10800 + GraphTallyCollector: { + eip712Name: 'GraphTallyCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10800, }, - "GraphTallyCollector": { - "eip712Name": "GraphTallyCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10800 + RecurringCollector: { + eip712Name: 'RecurringCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10800, }, - "RecurringCollector": { - "eip712Name": "RecurringCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10800 - } } diff --git a/packages/horizon/ignition/configs/migrate.default.json5 b/packages/horizon/ignition/configs/migrate.default.json5 index b770de7a3..a27b8dd8f 100644 --- a/packages/horizon/ignition/configs/migrate.default.json5 +++ b/packages/horizon/ignition/configs/migrate.default.json5 @@ -1,54 +1,54 @@ { - "$global": { + $global: { // Accounts already configured in the original Graph Protocol - Arbitrum Sepolia values - "governor": "0x72ee30d43Fb5A90B3FE983156C5d2fBE6F6d07B3", + governor: '0x72ee30d43Fb5A90B3FE983156C5d2fBE6F6d07B3', // Addresses for contracts deployed in the original Graph Protocol - Arbitrum Sepolia values - "graphProxyAdminAddress": "0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C", - "controllerAddress": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695", - "horizonStakingAddress": "0x865365C425f3A593Ffe698D9c4E6707D14d51e08", - "epochManagerAddress": "0x88b3C7f37253bAA1A9b95feAd69bD5320585826D", - "epochManagerImplementationAddress": "0x646627fa39ec6f6E757Cb4189bC54c92FFBb71da", - "graphTokenAddress": "0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04", - "graphTokenImplementationAddress": "0x4cf968bA38b43dd10be114daa7959C1b369479e5", - "graphTokenGatewayAddress": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb", - "graphTokenGatewayImplementationAddress": "0x3C2eB5E561f70c0573E5f6c92358e988E32cb5eC", - "rewardsManagerAddress": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79", - "curationAddress": "0xDe761f075200E75485F4358978FB4d1dC8644FD5", - "gnsAddress": "0x3133948342F35b8699d8F94aeE064AbB76eDe965", - "gnsImplementationAddress": "0x00CBF5024d454255577Bf2b0fB6A43328a6828c9", - "subgraphNFTAddress": "0xF21Df5BbA7EB9b54D8F60C560aFb9bA63e6aED1A", + graphProxyAdminAddress: '0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C', + controllerAddress: '0x9DB3ee191681f092607035d9BDA6e59FbEaCa695', + horizonStakingAddress: '0x865365C425f3A593Ffe698D9c4E6707D14d51e08', + epochManagerAddress: '0x88b3C7f37253bAA1A9b95feAd69bD5320585826D', + epochManagerImplementationAddress: '0x646627fa39ec6f6E757Cb4189bC54c92FFBb71da', + graphTokenAddress: '0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04', + graphTokenImplementationAddress: '0x4cf968bA38b43dd10be114daa7959C1b369479e5', + graphTokenGatewayAddress: '0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb', + graphTokenGatewayImplementationAddress: '0x3C2eB5E561f70c0573E5f6c92358e988E32cb5eC', + rewardsManagerAddress: '0x1F49caE7669086c8ba53CC35d1E9f80176d67E79', + curationAddress: '0xDe761f075200E75485F4358978FB4d1dC8644FD5', + gnsAddress: '0x3133948342F35b8699d8F94aeE064AbB76eDe965', + gnsImplementationAddress: '0x00CBF5024d454255577Bf2b0fB6A43328a6828c9', + subgraphNFTAddress: '0xF21Df5BbA7EB9b54D8F60C560aFb9bA63e6aED1A', // Must be set for step 2 of the migration - "graphPaymentsAddress": "", - "paymentsEscrowAddress": "", + graphPaymentsAddress: '', + paymentsEscrowAddress: '', // Must be set for step 3 and 4 of the migration - "subgraphServiceAddress": "", + subgraphServiceAddress: '', // Must be set for step 4 of the migration - "horizonStakingImplementationAddress": "", - "curationImplementationAddress": "", - "rewardsManagerImplementationAddress": "", - "disputeManagerAddress": "", + horizonStakingImplementationAddress: '', + curationImplementationAddress: '', + rewardsManagerImplementationAddress: '', + disputeManagerAddress: '', // Global parameters - "maxThawingPeriod": 2419200 + maxThawingPeriod: 2419200, }, - "GraphPayments": { - "protocolPaymentCut": 10000 + GraphPayments: { + protocolPaymentCut: 10000, }, - "PaymentsEscrow": { - "withdrawEscrowThawingPeriod": 10000 + PaymentsEscrow: { + withdrawEscrowThawingPeriod: 10000, }, - "GraphTallyCollector": { - "eip712Name": "GraphTallyCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 + GraphTallyCollector: { + eip712Name: 'GraphTallyCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, + }, + RecurringCollector: { + eip712Name: 'RecurringCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, }, - "RecurringCollector": { - "eip712Name": "RecurringCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 - } } diff --git a/packages/horizon/ignition/configs/migrate.integration.json5 b/packages/horizon/ignition/configs/migrate.integration.json5 index 5b2f2155f..775e56161 100644 --- a/packages/horizon/ignition/configs/migrate.integration.json5 +++ b/packages/horizon/ignition/configs/migrate.integration.json5 @@ -1,54 +1,54 @@ { - "$global": { + $global: { // Accounts - "governor": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", + governor: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', // Addresses for contracts deployed in the original Graph Protocol - "graphProxyAdminAddress": "0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C", - "controllerAddress": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695", - "horizonStakingAddress": "0x865365C425f3A593Ffe698D9c4E6707D14d51e08", - "epochManagerAddress": "0x88b3C7f37253bAA1A9b95feAd69bD5320585826D", - "epochManagerImplementationAddress": "0x646627fa39ec6f6E757Cb4189bC54c92FFBb71da", - "graphTokenAddress": "0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04", - "graphTokenImplementationAddress": "0x4cf968bA38b43dd10be114daa7959C1b369479e5", - "graphTokenGatewayAddress": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb", - "graphTokenGatewayImplementationAddress": "0x3C2eB5E561f70c0573E5f6c92358e988E32cb5eC", - "rewardsManagerAddress": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79", - "curationAddress": "0xDe761f075200E75485F4358978FB4d1dC8644FD5", - "gnsAddress": "0x3133948342F35b8699d8F94aeE064AbB76eDe965", - "gnsImplementationAddress": "0x00CBF5024d454255577Bf2b0fB6A43328a6828c9", - "subgraphNFTAddress": "0xF21Df5BbA7EB9b54D8F60C560aFb9bA63e6aED1A", + graphProxyAdminAddress: '0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C', + controllerAddress: '0x9DB3ee191681f092607035d9BDA6e59FbEaCa695', + horizonStakingAddress: '0x865365C425f3A593Ffe698D9c4E6707D14d51e08', + epochManagerAddress: '0x88b3C7f37253bAA1A9b95feAd69bD5320585826D', + epochManagerImplementationAddress: '0x646627fa39ec6f6E757Cb4189bC54c92FFBb71da', + graphTokenAddress: '0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04', + graphTokenImplementationAddress: '0x4cf968bA38b43dd10be114daa7959C1b369479e5', + graphTokenGatewayAddress: '0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb', + graphTokenGatewayImplementationAddress: '0x3C2eB5E561f70c0573E5f6c92358e988E32cb5eC', + rewardsManagerAddress: '0x1F49caE7669086c8ba53CC35d1E9f80176d67E79', + curationAddress: '0xDe761f075200E75485F4358978FB4d1dC8644FD5', + gnsAddress: '0x3133948342F35b8699d8F94aeE064AbB76eDe965', + gnsImplementationAddress: '0x00CBF5024d454255577Bf2b0fB6A43328a6828c9', + subgraphNFTAddress: '0xF21Df5BbA7EB9b54D8F60C560aFb9bA63e6aED1A', // Must be set for step 2 of the migration - "graphPaymentsAddress": "", - "paymentsEscrowAddress": "", + graphPaymentsAddress: '', + paymentsEscrowAddress: '', // Must be set for step 3 and 4 of the migration - "subgraphServiceAddress": "", + subgraphServiceAddress: '', // Must be set for step 4 of the migration - "horizonStakingImplementationAddress": "", - "curationImplementationAddress": "", - "rewardsManagerImplementationAddress": "", - "disputeManagerAddress": "", + horizonStakingImplementationAddress: '', + curationImplementationAddress: '', + rewardsManagerImplementationAddress: '', + disputeManagerAddress: '', // Global parameters - "maxThawingPeriod": 2419200 + maxThawingPeriod: 2419200, }, - "GraphPayments": { - "protocolPaymentCut": 10000 + GraphPayments: { + protocolPaymentCut: 10000, }, - "PaymentsEscrow": { - "withdrawEscrowThawingPeriod": 10000 + PaymentsEscrow: { + withdrawEscrowThawingPeriod: 10000, }, - "GraphTallyCollector": { - "eip712Name": "GraphTallyCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 + GraphTallyCollector: { + eip712Name: 'GraphTallyCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, + }, + RecurringCollector: { + eip712Name: 'RecurringCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, }, - "RecurringCollector": { - "eip712Name": "RecurringCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 - } } diff --git a/packages/horizon/ignition/configs/migrate.localNetwork.json5 b/packages/horizon/ignition/configs/migrate.localNetwork.json5 index 8b052634d..68e9166c7 100644 --- a/packages/horizon/ignition/configs/migrate.localNetwork.json5 +++ b/packages/horizon/ignition/configs/migrate.localNetwork.json5 @@ -1,54 +1,54 @@ { - "$global": { + $global: { // Accounts already configured in the original Graph Protocol - Local Network values - "governor": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + governor: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Addresses for contracts deployed in the original Graph Protocol - Local Network values - "graphProxyAdminAddress": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "controllerAddress": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - "horizonStakingAddress": "0xc5a5C42992dECbae36851359345FE25997F5C42d", - "epochManagerAddress": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - "epochManagerImplementationAddress": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", - "graphTokenAddress": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", - "graphTokenImplementationAddress": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", - "graphTokenGatewayAddress": "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690", - "graphTokenGatewayImplementationAddress": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E", - "rewardsManagerAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", - "curationAddress": "0x59b670e9fA9D0A427751Af201D676719a970857b", - "gnsAddress": "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f", - "gnsImplementationAddress": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "subgraphNFTAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + graphProxyAdminAddress: '0x5FbDB2315678afecb367f032d93F642f64180aa3', + controllerAddress: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0', + horizonStakingAddress: '0xc5a5C42992dECbae36851359345FE25997F5C42d', + epochManagerAddress: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9', + epochManagerImplementationAddress: '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9', + graphTokenAddress: '0x3Aa5ebB10DC797CAC828524e59A333d0A371443c', + graphTokenImplementationAddress: '0x3Aa5ebB10DC797CAC828524e59A333d0A371443c', + graphTokenGatewayAddress: '0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690', + graphTokenGatewayImplementationAddress: '0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E', + rewardsManagerAddress: '0x9A676e781A523b5d0C0e43731313A708CB607508', + curationAddress: '0x59b670e9fA9D0A427751Af201D676719a970857b', + gnsAddress: '0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f', + gnsImplementationAddress: '0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44', + subgraphNFTAddress: '0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e', // Must be set for step 2 of the migration - "graphPaymentsAddress": "", - "paymentsEscrowAddress": "", + graphPaymentsAddress: '', + paymentsEscrowAddress: '', // Must be set for step 3 and 4 of the migration - "subgraphServiceAddress": "", + subgraphServiceAddress: '', // Must be set for step 4 of the migration - "horizonStakingImplementationAddress": "", - "curationImplementationAddress": "", - "rewardsManagerImplementationAddress": "", - "disputeManagerAddress": "", + horizonStakingImplementationAddress: '', + curationImplementationAddress: '', + rewardsManagerImplementationAddress: '', + disputeManagerAddress: '', // Global parameters - "maxThawingPeriod": 2419200 + maxThawingPeriod: 2419200, }, - "GraphPayments": { - "protocolPaymentCut": 10000 + GraphPayments: { + protocolPaymentCut: 10000, }, - "PaymentsEscrow": { - "withdrawEscrowThawingPeriod": 10000 + PaymentsEscrow: { + withdrawEscrowThawingPeriod: 10000, }, - "GraphTallyCollector": { - "eip712Name": "GraphTallyCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 + GraphTallyCollector: { + eip712Name: 'GraphTallyCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, + }, + RecurringCollector: { + eip712Name: 'RecurringCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, }, - "RecurringCollector": { - "eip712Name": "RecurringCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 - } } diff --git a/packages/horizon/ignition/configs/protocol.default.json5 b/packages/horizon/ignition/configs/protocol.default.json5 index 817758796..d93d53143 100644 --- a/packages/horizon/ignition/configs/protocol.default.json5 +++ b/packages/horizon/ignition/configs/protocol.default.json5 @@ -1,45 +1,44 @@ { - "$global": { + $global: { // Accounts for new deployment - derived from hardhat default mnemonic - "pauseGuardian": "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d", // index 3 - "subgraphAvailabilityOracle": "0xd03ea8624C8C5987235048901fB614fDcA89b117", // index 4 + governor: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', // index 1 + pauseGuardian: '0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d', // index 3 + subgraphAvailabilityOracle: '0xd03ea8624C8C5987235048901fB614fDcA89b117', // index 4 // Placeholder address for a standalone Horizon deployment, see README.md for more details - "subgraphServiceAddress": "0x0000000000000000000000000000000000000000", - "disputeManagerAddress": "0x0000000000000000000000000000000000000001", + subgraphServiceAddress: '0x0000000000000000000000000000000000000000', + disputeManagerAddress: '0x0000000000000000000000000000000000000001', // Global parameters - "maxThawingPeriod": 2419200 + maxThawingPeriod: 2419200, }, - "GraphPayments": { - "protocolPaymentCut": 10000 + GraphPayments: { + protocolPaymentCut: 10000, }, - "PaymentsEscrow": { - "withdrawEscrowThawingPeriod": 10000 + PaymentsEscrow: { + withdrawEscrowThawingPeriod: 10000, }, - "GraphTallyCollector": { - "eip712Name": "GraphTallyCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 + GraphTallyCollector: { + eip712Name: 'GraphTallyCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, }, - "RecurringCollector": { - "eip712Name": "RecurringCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 + RecurringCollector: { + eip712Name: 'RecurringCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, }, - "RewardsManager": { - "issuancePerBlock": "114155251141552511415n" + RewardsManager: { + issuancePerBlock: '114155251141552511415n', }, - "EpochManager": { - "epochLength": 60 + EpochManager: { + epochLength: 60, }, - "L2Curation": { - "curationTaxPercentage": 10000, - "minimumCurationDeposit": 1 + L2Curation: { + curationTaxPercentage: 10000, + minimumCurationDeposit: 1, }, - "L2GraphToken": { - "initialSupply": "10000000000000000000000000000n" + L2GraphToken: { + initialSupply: '10000000000000000000000000000n', }, - - } diff --git a/packages/horizon/ignition/configs/protocol.localNetwork.json5 b/packages/horizon/ignition/configs/protocol.localNetwork.json5 index 2d3c08b39..a4ffcef66 100644 --- a/packages/horizon/ignition/configs/protocol.localNetwork.json5 +++ b/packages/horizon/ignition/configs/protocol.localNetwork.json5 @@ -1,45 +1,44 @@ { - "$global": { + $global: { // Accounts for new deployment - derived from hardhat default mnemonic - "pauseGuardian": "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d", // index 3 - "subgraphAvailabilityOracle": "0xd03ea8624C8C5987235048901fB614fDcA89b117", // index 4 + governor: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // index 1 + pauseGuardian: '0x90F79bf6EB2c4f870365E785982E1f101E93b906', // index 3 + subgraphAvailabilityOracle: '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65', // index 4 // Placeholder address for a standalone Horizon deployment, see README.md for more details - "subgraphServiceAddress": "0x0000000000000000000000000000000000000000", - "disputeManagerAddress": "0x0000000000000000000000000000000000000001", + subgraphServiceAddress: '0x0000000000000000000000000000000000000000', + disputeManagerAddress: '0x0000000000000000000000000000000000000001', // Global parameters - "maxThawingPeriod": 2419200 + maxThawingPeriod: 2419200, }, - "GraphPayments": { - "protocolPaymentCut": 10000 + GraphPayments: { + protocolPaymentCut: 10000, }, - "PaymentsEscrow": { - "withdrawEscrowThawingPeriod": 10000 + PaymentsEscrow: { + withdrawEscrowThawingPeriod: 10000, }, - "GraphTallyCollector": { - "eip712Name": "GraphTallyCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 + GraphTallyCollector: { + eip712Name: 'GraphTallyCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, }, - "RecurringCollector": { - "eip712Name": "RecurringCollector", - "eip712Version": "1", - "revokeSignerThawingPeriod": 10000 + RecurringCollector: { + eip712Name: 'RecurringCollector', + eip712Version: '1', + revokeSignerThawingPeriod: 10000, }, - "RewardsManager": { - "issuancePerBlock": "114155251141552511415n" + RewardsManager: { + issuancePerBlock: '114155251141552511415n', }, - "EpochManager": { - "epochLength": 60, // Note that localNetwork does not auto-mine blocks, so this could be any amount of time in seconds + EpochManager: { + epochLength: 60, // Note that localNetwork does not auto-mine blocks, so this could be any amount of time in seconds }, - "L2Curation": { - "curationTaxPercentage": 10000, - "minimumCurationDeposit": 1 + L2Curation: { + curationTaxPercentage: 10000, + minimumCurationDeposit: 1, }, - "L2GraphToken": { - "initialSupply": "10000000000000000000000000000n" + L2GraphToken: { + initialSupply: '10000000000000000000000000000n', }, - - } diff --git a/packages/horizon/ignition/modules/core/GraphPayments.ts b/packages/horizon/ignition/modules/core/GraphPayments.ts index 4ce5ec3e0..d4b5d1208 100644 --- a/packages/horizon/ignition/modules/core/GraphPayments.ts +++ b/packages/horizon/ignition/modules/core/GraphPayments.ts @@ -10,7 +10,7 @@ export default buildModule('GraphPayments', (m) => { const { Controller } = m.useModule(GraphPeripheryModule) const { GraphPaymentsProxyAdmin, GraphPaymentsProxy } = m.useModule(HorizonProxiesModule) - const governor = m.getAccount(1) + const governor = m.getParameter('governor') const protocolPaymentCut = m.getParameter('protocolPaymentCut') // Deploy GraphPayments implementation - requires periphery and proxies to be registered in the controller diff --git a/packages/horizon/ignition/modules/core/HorizonStaking.ts b/packages/horizon/ignition/modules/core/HorizonStaking.ts index a7bec9076..ec98c1066 100644 --- a/packages/horizon/ignition/modules/core/HorizonStaking.ts +++ b/packages/horizon/ignition/modules/core/HorizonStaking.ts @@ -15,12 +15,16 @@ export default buildModule('HorizonStaking', (m) => { const subgraphServiceAddress = m.getParameter('subgraphServiceAddress') const maxThawingPeriod = m.getParameter('maxThawingPeriod') - // Deploy HorizonStaking implementation - const HorizonStakingImplementation = deployImplementation(m, { - name: 'HorizonStaking', - artifact: HorizonStakingArtifact, - constructorArgs: [Controller, subgraphServiceAddress], - }) + // Deploy HorizonStaking implementation - requires periphery and proxies to be registered in the controller + const HorizonStakingImplementation = deployImplementation( + m, + { + name: 'HorizonStaking', + artifact: HorizonStakingArtifact, + constructorArgs: [Controller, subgraphServiceAddress], + }, + { after: [GraphPeripheryModule, HorizonProxiesModule] }, + ) // Upgrade proxy to implementation contract const HorizonStaking = upgradeGraphProxy(m, GraphProxyAdmin, HorizonStakingProxy, HorizonStakingImplementation, { diff --git a/packages/horizon/ignition/modules/core/PaymentsEscrow.ts b/packages/horizon/ignition/modules/core/PaymentsEscrow.ts index 432e50743..80589c351 100644 --- a/packages/horizon/ignition/modules/core/PaymentsEscrow.ts +++ b/packages/horizon/ignition/modules/core/PaymentsEscrow.ts @@ -10,7 +10,7 @@ export default buildModule('PaymentsEscrow', (m) => { const { Controller } = m.useModule(GraphPeripheryModule) const { PaymentsEscrowProxyAdmin, PaymentsEscrowProxy } = m.useModule(HorizonProxiesModule) - const governor = m.getAccount(1) + const governor = m.getParameter('governor') const withdrawEscrowThawingPeriod = m.getParameter('withdrawEscrowThawingPeriod') // Deploy PaymentsEscrow implementation - requires periphery and proxies to be registered in the controller diff --git a/packages/horizon/ignition/modules/core/RecurringCollector.ts b/packages/horizon/ignition/modules/core/RecurringCollector.ts index c1481aa4f..48d74eba2 100644 --- a/packages/horizon/ignition/modules/core/RecurringCollector.ts +++ b/packages/horizon/ignition/modules/core/RecurringCollector.ts @@ -12,7 +12,7 @@ import HorizonProxiesModule from './HorizonProxies' export default buildModule('RecurringCollector', (m) => { const { Controller } = m.useModule(GraphPeripheryModule) - const governor = m.getAccount(1) + const governor = m.getParameter('governor') const revokeSignerThawingPeriod = m.getParameter('revokeSignerThawingPeriod') const eip712Name = m.getParameter('eip712Name') const eip712Version = m.getParameter('eip712Version') diff --git a/packages/horizon/ignition/modules/deploy.ts b/packages/horizon/ignition/modules/deploy.ts index 428f2e0c7..6fd0d9270 100644 --- a/packages/horizon/ignition/modules/deploy.ts +++ b/packages/horizon/ignition/modules/deploy.ts @@ -36,6 +36,10 @@ export default buildModule('GraphHorizon_Deploy', (m) => { RecurringCollectorImplementation, } = m.useModule(GraphHorizonCoreModule) + // m.getAccount(1) rather than getParameter('governor') — the from: option on + // m.call only accepts an AccountRuntimeValue, so the acceptOwnership signer + // has to come from getAccount. Configs are expected to set the 'governor' + // parameter to this same address (index 1 of the deploying mnemonic). const governor = m.getAccount(1) // BUG?: acceptOwnership should be called after everything in GraphHorizonCoreModule and GraphPeripheryModule is resolved diff --git a/packages/horizon/ignition/modules/periphery/Controller.ts b/packages/horizon/ignition/modules/periphery/Controller.ts index dd6664925..9ed7e4b61 100644 --- a/packages/horizon/ignition/modules/periphery/Controller.ts +++ b/packages/horizon/ignition/modules/periphery/Controller.ts @@ -3,7 +3,7 @@ import { buildModule } from '@nomicfoundation/hardhat-ignition/modules' import { ethers } from 'ethers' export default buildModule('Controller', (m) => { - const governor = m.getAccount(1) + const governor = m.getParameter('governor') const pauseGuardian = m.getParameter('pauseGuardian') const Controller = m.contract('Controller', ControllerArtifact) diff --git a/packages/horizon/ignition/modules/periphery/GNS.ts b/packages/horizon/ignition/modules/periphery/GNS.ts index 71dc69e4b..4195d803e 100644 --- a/packages/horizon/ignition/modules/periphery/GNS.ts +++ b/packages/horizon/ignition/modules/periphery/GNS.ts @@ -19,7 +19,7 @@ export default buildModule('L2GNS', (m) => { const { L2Curation } = m.useModule(CurationModule) const deployer = m.getAccount(0) - const governor = m.getAccount(1) + const governor = m.getParameter('governor') const SubgraphNFTDescriptor = m.contract('SubgraphNFTDescriptor', SubgraphNFTDescriptorArtifact) const SubgraphNFT = m.contract('SubgraphNFT', SubgraphNFTArtifact, [deployer]) diff --git a/packages/horizon/ignition/modules/periphery/GraphProxyAdmin.ts b/packages/horizon/ignition/modules/periphery/GraphProxyAdmin.ts index 82d73ef39..52dd6087d 100644 --- a/packages/horizon/ignition/modules/periphery/GraphProxyAdmin.ts +++ b/packages/horizon/ignition/modules/periphery/GraphProxyAdmin.ts @@ -2,7 +2,7 @@ import GraphProxyAdminArtifact from '@graphprotocol/contracts/artifacts/contract import { buildModule } from '@nomicfoundation/hardhat-ignition/modules' export default buildModule('GraphProxyAdmin', (m) => { - const governor = m.getAccount(1) + const governor = m.getParameter('governor') const GraphProxyAdmin = m.contract('GraphProxyAdmin', GraphProxyAdminArtifact) m.call(GraphProxyAdmin, 'transferOwnership', [governor]) diff --git a/packages/horizon/ignition/modules/periphery/GraphToken.ts b/packages/horizon/ignition/modules/periphery/GraphToken.ts index 73f2f29ad..24d4564cd 100644 --- a/packages/horizon/ignition/modules/periphery/GraphToken.ts +++ b/packages/horizon/ignition/modules/periphery/GraphToken.ts @@ -12,6 +12,10 @@ export default buildModule('L2GraphToken', (m) => { const { L2GraphTokenGateway } = m.useModule(GraphTokenGatewayModule) const deployer = m.getAccount(0) + // m.getAccount(1) rather than getParameter('governor') — the from: option on + // m.call only accepts an AccountRuntimeValue, so the acceptOwnership signer + // has to come from getAccount. Configs are expected to set the 'governor' + // parameter to this same address (index 1 of the deploying mnemonic). const governor = m.getAccount(1) const initialSupply = m.getParameter('initialSupply') diff --git a/packages/interfaces/.solhint.json b/packages/interfaces/.solhint.json index d30847305..780d82f39 100644 --- a/packages/interfaces/.solhint.json +++ b/packages/interfaces/.solhint.json @@ -1,3 +1,3 @@ { - "extends": ["solhint:recommended", "./../../.solhint.json"] + "extends": "./../../.solhint.json" } diff --git a/packages/interfaces/contracts/horizon/internal/IHorizonStakingTypes.todo.md b/packages/interfaces/contracts/horizon/internal/IHorizonStakingTypes.todo.md new file mode 100644 index 000000000..ab7a2caf1 --- /dev/null +++ b/packages/interfaces/contracts/horizon/internal/IHorizonStakingTypes.todo.md @@ -0,0 +1,21 @@ +# IHorizonStakingTypes.sol — pending updates + +Tracking pending edits noted but deferred to avoid touching contract source until the next maintenance window. When picking up, drop the corresponding entry below as part of the same commit. + +## `LegacyAllocationState.Active` NatSpec drift + +The enum docstring at L233 describes the `Active` state as `not Null && tokens > 0`, but the implementation in `_getLegacyAllocationState` (`packages/horizon/contracts/staking/HorizonStaking.sol:1190`) gates on `createdAtEpoch != 0 && closedAtEpoch == 0` — no `tokens > 0` check. + +`createdAtEpoch` is set together with `indexer` at allocation creation, so `createdAtEpoch != 0` is equivalent to `not NULL`. The docstring's `tokens > 0` clause has no implementation counterpart. + +Suggested replacement: + +```solidity + * - Null = indexer == address(0) + * - Active = not Null && closedAtEpoch == 0 + * - Closed = not Null && closedAtEpoch != 0 +``` + +Also tightens `Closed` away from the recursive `Active && closedAtEpoch != 0` framing, which becomes circular once `Active` is restated. + +**Origin:** Inline review nits from Maikol on PR #1331 (2026-05-07, L233 & L234); PR was approved with these threads left open as non-blocking. diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json index 3b774337d..17d2d8994 100644 --- a/packages/interfaces/package.json +++ b/packages/interfaces/package.json @@ -1,6 +1,6 @@ { "name": "@graphprotocol/interfaces", - "version": "0.6.6", + "version": "0.7.2", "publishConfig": { "access": "public" }, diff --git a/packages/interfaces/src/types/horizon.ts b/packages/interfaces/src/types/horizon.ts index c2a09abb6..7bd9ca1db 100644 --- a/packages/interfaces/src/types/horizon.ts +++ b/packages/interfaces/src/types/horizon.ts @@ -10,6 +10,7 @@ import type { IL2GNSToolshed, ILegacyRewardsManager, IPaymentsEscrowToolshed, + IRecurringCollector, IRewardsManagerToolshed, IStaking, ISubgraphNFT, @@ -28,6 +29,7 @@ export { ILegacyRewardsManager as LegacyRewardsManager, IStaking as LegacyStaking, IPaymentsEscrowToolshed as PaymentsEscrow, + IRecurringCollector as RecurringCollector, IRewardsManagerToolshed as RewardsManager, ISubgraphNFT as SubgraphNFT, } diff --git a/packages/issuance/.solhint.json b/packages/issuance/.solhint.json index d30847305..780d82f39 100644 --- a/packages/issuance/.solhint.json +++ b/packages/issuance/.solhint.json @@ -1,3 +1,3 @@ { - "extends": ["solhint:recommended", "./../../.solhint.json"] + "extends": "./../../.solhint.json" } diff --git a/packages/issuance/README.md b/packages/issuance/README.md index c6def2743..f6c4e4856 100644 --- a/packages/issuance/README.md +++ b/packages/issuance/README.md @@ -10,7 +10,7 @@ The issuance contracts handle token issuance mechanisms for The Graph protocol. - **[IssuanceAllocator](contracts/allocate/IssuanceAllocator.md)** - Central distribution hub for token issuance, allocating tokens to different protocol components based on configured rates - **[RewardsEligibilityOracle](contracts/eligibility/RewardsEligibilityOracle.md)** - Oracle-based eligibility system for indexer rewards with time-based expiration -- **DirectAllocation** - Simple target contract implementation for receiving and distributing allocated tokens (deployed as PilotAllocation and other instances) +- **DirectAllocation** - Simple target contract implementation for receiving and distributing allocated tokens (deployed as ReclaimedRewards) - **[RecurringAgreementManager](contracts/agreement/RecurringAgreementManager.md)** - Funds PaymentsEscrow deposits for RCAs using issuance tokens, tracking max-next-claim per agreement per indexer ## Development diff --git a/packages/issuance/addresses.json b/packages/issuance/addresses.json index ad38aec4e..24d2a2893 100644 --- a/packages/issuance/addresses.json +++ b/packages/issuance/addresses.json @@ -32,33 +32,140 @@ "address": "0x6ba849fbd33257162552578b2a432d30784f2f80", "proxy": "transparent", "proxyAdmin": "0xfd76b74d4da4ef5b9c2379b9c8dbd79575b0fdda", - "implementation": "0x24901750b48ad049b914f13e1855dc71ecf8397a", + "implementation": "0xd6f2acf352f655b72cc32a056edf7ca97ec3e9e4", "implementationDeployment": { - "txHash": "0xc2bdcd2b9d40f9932f231e04bae0a8248745ee1a3514851e5e25ee17ef5f1fa7", + "txHash": "0xbf484964670ce105ce4de7f97d3617dbccede17d6ab806174c49fa36c1483950", "argsData": "0x000000000000000000000000f8c05dcf59e8b28bfd5eed176c562bebcfc7ac04", "bytecodeHash": "0x8ff7d1a6e22cf7f074c4688d9c84394ee151531de3f219ceabf66f0386201412", - "blockNumber": 250569158 + "blockNumber": 258351189, + "timestamp": "2026-04-10T15:30:34.000Z", + "verified": "https://sepolia.arbiscan.io/address/0xd6f2acf352f655b72cc32a056edf7ca97ec3e9e4#code" }, "proxyDeployment": { "txHash": "0xcf2995a0f7142be957a71da0bc3f63e93939d7442dcab8f549e7765585464ce1", "argsData": "0x00000000000000000000000024901750b48ad049b914f13e1855dc71ecf8397a00000000000000000000000072ee30d43fb5a90b3fe983156c5d2fbe6f6d07b300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024c4d66de800000000000000000000000072ee30d43fb5a90b3fe983156c5d2fbe6f6d07b300000000000000000000000000000000000000000000000000000000", "bytecodeHash": "0x6b4ba3015667741610274b7c196ec5d7767235d85865912f7ac680eac3011c54", - "blockNumber": 250569166 + "blockNumber": 250569166, + "verified": "https://sepolia.arbiscan.io/address/0x6ba849fbd33257162552578b2a432d30784f2f80#code" } }, - "RewardsEligibilityOracleMock": { - "address": "0x5FB23365F8cf643D5f1459E9793EfF7254522400" - }, "IssuanceAllocator": { "address": "0x76a0d75651d4db83f74ac502b86a0ae4e19ac38b", "proxy": "transparent", "proxyAdmin": "0x9a3e5bd36a72a6306c63dce573a8100992479bfa", - "implementation": "0x50782d395e32300f57f6446951cf6734ae22c68d", + "implementation": "0x96baa229e1a0bdb750330617876cb9f40d9c2632", + "implementationDeployment": { + "txHash": "0x2175ca7acce3d792681391f98458190c7a1983d9222856ec28663a13df98577a", + "argsData": "0x000000000000000000000000f8c05dcf59e8b28bfd5eed176c562bebcfc7ac04", + "bytecodeHash": "0xf16079c15a15d3ae077bfadf40e4865fe5c73bb213486831e129b725a8554092", + "blockNumber": 258351131, + "timestamp": "2026-04-10T15:30:19.000Z", + "verified": "https://sepolia.arbiscan.io/address/0x96baa229e1a0bdb750330617876cb9f40d9c2632#code" + }, + "proxyDeployment": { + "txHash": "0xd633a88e947568883f1f38be269f63f061d764550ebae189402b11a376f7e973", + "argsData": "0x00000000000000000000000050782d395e32300f57f6446951cf6734ae22c68d00000000000000000000000072ee30d43fb5a90b3fe983156c5d2fbe6f6d07b300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024c4d66de800000000000000000000000072ee30d43fb5a90b3fe983156c5d2fbe6f6d07b300000000000000000000000000000000000000000000000000000000", + "bytecodeHash": "0x6b4ba3015667741610274b7c196ec5d7767235d85865912f7ac680eac3011c54", + "blockNumber": 250574013, + "verified": "https://sepolia.arbiscan.io/address/0x76a0d75651d4db83f74ac502b86a0ae4e19ac38b#code" + } + }, + "DefaultAllocation": { + "address": "0xa0eab4367d753314840c09313a5c6d27174bd541", + "proxy": "transparent", + "proxyAdmin": "0x6b09a6fcef85b1df540c922af2c9b64847ff8ae6", + "implementation": "0xd5de0951759b8306226fa370a9ecca40a31aa2d3", + "proxyDeployment": { + "txHash": "0xde2ebe4a22d0b6473736cea55f335bb5debfc6086a4de4f7261d6b3d0ff6952a", + "argsData": "0x000000000000000000000000d5de0951759b8306226fa370a9ecca40a31aa2d3000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000", + "bytecodeHash": "0x6b4ba3015667741610274b7c196ec5d7767235d85865912f7ac680eac3011c54", + "blockNumber": 258322247, + "verified": "https://sepolia.arbiscan.io/address/0xa0eab4367d753314840c09313a5c6d27174bd541#code" + } + }, + "ReclaimedRewards": { + "address": "0xe01bb1bba83d3d5b823877d85bc3ba9fd7835c6d", + "proxy": "transparent", + "proxyAdmin": "0xb2201d01a41c1afc76fa9e598f3c57b5733dc7dc", + "implementation": "0xd5de0951759b8306226fa370a9ecca40a31aa2d3", + "proxyDeployment": { + "txHash": "0x26a5e9dbce77a2b88906321c51505ae4ea8b570b5d86d161fa68753553eb23ee", + "argsData": "0x000000000000000000000000d5de0951759b8306226fa370a9ecca40a31aa2d3000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000", + "bytecodeHash": "0x6b4ba3015667741610274b7c196ec5d7767235d85865912f7ac680eac3011c54", + "blockNumber": 258322261, + "verified": "https://sepolia.arbiscan.io/address/0xe01bb1bba83d3d5b823877d85bc3ba9fd7835c6d#code" + } + }, + "RecurringAgreementManager": { + "address": "0x590dbbbdb1b6261e39bcc1fe88bffc21c847a68e", + "proxy": "transparent", + "proxyAdmin": "0xc80b101a601d38b3f72e22c613fdafb594d82f2e", + "implementation": "0xcea9350703c07dc1a92516f472d4769092e26e21", "implementationDeployment": { - "txHash": "0x4cf7787b81d88786893c7aca5da193d9041c3272995f3c1cdd202d87919e47e6", + "txHash": "0xd182846b059c7441dd76172a343d51185184a74a5a834e546a493429ef8096b1", + "argsData": "0x000000000000000000000000f8c05dcf59e8b28bfd5eed176c562bebcfc7ac040000000000000000000000004b5d3da463f7e076bb7cdf5030960bf123245681", + "bytecodeHash": "0x8d7d7208240cb7032d538818a2879ac2b6102267d80258c943469b16d7794d3f", + "blockNumber": 258351168, + "timestamp": "2026-04-10T15:30:28.000Z", + "verified": "https://sepolia.arbiscan.io/address/0xcea9350703c07dc1a92516f472d4769092e26e21#code" + }, + "proxyDeployment": { + "txHash": "0x8b6a8b30950715a5be3c95159949a2dc2bf7368684022a8f305eb711c5667e85", + "argsData": "0x0000000000000000000000002b114f3a63715224c1b5722f17fd84b6417a794a000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000", + "bytecodeHash": "0x6b4ba3015667741610274b7c196ec5d7767235d85865912f7ac680eac3011c54", + "blockNumber": 258322276, + "verified": "https://sepolia.arbiscan.io/address/0x590dbbbdb1b6261e39bcc1fe88bffc21c847a68e#code" + } + }, + "RewardsEligibilityOracleB": { + "address": "0xcc70eae4001b36029fecb285ba6e8bbfd753e3da", + "proxy": "transparent", + "proxyAdmin": "0x6bbf45ff96b1acfbb04645c42783d8115c4befde", + "implementation": "0x35150110d11199e746fc1529f1647f162fb6c785", + "implementationDeployment": { + "txHash": "0xc60d3032c9c4825115e4d7432784dcf69e2c59557bae4607165a416b59792a35", + "argsData": "0x000000000000000000000000f8c05dcf59e8b28bfd5eed176c562bebcfc7ac04", + "bytecodeHash": "0x8ff7d1a6e22cf7f074c4688d9c84394ee151531de3f219ceabf66f0386201412", + "blockNumber": 258351208, + "timestamp": "2026-04-10T15:30:40.000Z", + "verified": "https://sepolia.arbiscan.io/address/0x35150110d11199e746fc1529f1647f162fb6c785#code" + }, + "proxyDeployment": { + "txHash": "0xccaf5e98ca9b1112ef332119c5ad2830d4b7d951d1ba4319f6fb3538dff4eff1", + "argsData": "0x000000000000000000000000b23e0463b930523ff34b32b28f32ff0484a8e0dc000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000", + "bytecodeHash": "0x6b4ba3015667741610274b7c196ec5d7767235d85865912f7ac680eac3011c54", + "blockNumber": 258322322, + "verified": "https://sepolia.arbiscan.io/address/0xcc70eae4001b36029fecb285ba6e8bbfd753e3da#code" + } + }, + "RewardsEligibilityOracleMock": { + "address": "0x69b0f3c6a19beaf1ba59405f7179e188c64b4e06", + "proxy": "transparent", + "proxyAdmin": "0xca303d77c53c1e8aaec32d1a81e5a359ea2bb308", + "implementation": "0xa9336216cd501c554c76f1dcd85b90e84ebbf972", + "implementationDeployment": { + "txHash": "0x7c12fea73aac7421b49f41508ef87f9c542e7fa7001152850a57d63797e94109", + "argsData": "0x000000000000000000000000f8c05dcf59e8b28bfd5eed176c562bebcfc7ac04", + "bytecodeHash": "0x7048d139b92b2e2638c66eca026737eddd064e85ebf20e0438bdef81232ea320", + "blockNumber": 258351227, + "timestamp": "2026-04-10T15:30:46.000Z", + "verified": "https://sepolia.arbiscan.io/address/0xa9336216cd501c554c76f1dcd85b90e84ebbf972#code" + }, + "proxyDeployment": { + "txHash": "0xff444e8f13200eed55e7499b4cbdf4d4363f3e6db2c9913bc19ee5b0abbf75ec", + "argsData": "0x0000000000000000000000009e67aff526f1446455cc3e154c813100048c0ee5000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000ade6b8eb69a49b56929c1d4f4b428d791861db6f00000000000000000000000000000000000000000000000000000000", + "bytecodeHash": "0x6b4ba3015667741610274b7c196ec5d7767235d85865912f7ac680eac3011c54", + "blockNumber": 258322343, + "verified": "https://sepolia.arbiscan.io/address/0x69b0f3c6a19beaf1ba59405f7179e188c64b4e06#code" + } + }, + "DirectAllocation_Implementation": { + "address": "0xd5de0951759b8306226fa370a9ecca40a31aa2d3", + "deployment": { + "txHash": "0x8e52d51f893a7fe465e475c20ade5911a3cdb7a8e7b2b625564f49d8ee93bb67", "argsData": "0x000000000000000000000000f8c05dcf59e8b28bfd5eed176c562bebcfc7ac04", - "bytecodeHash": "0x94b490cdb340cdf9f601e618fdb7e21608969ba1a0dee05a3b017efa4ad36ad0", - "blockNumber": 250574005 + "bytecodeHash": "0xf11b102de39fbe66879f57214393c7ff7438e050f77802e2c08c71a000002003", + "blockNumber": 258322238 } } } diff --git a/packages/issuance/audits/Graph_PR1242_1243_v02.pdf b/packages/issuance/audits/2025-11-17_Graph_PR1242_1243_v02.pdf similarity index 100% rename from packages/issuance/audits/Graph_PR1242_1243_v02.pdf rename to packages/issuance/audits/2025-11-17_Graph_PR1242_1243_v02.pdf diff --git a/packages/issuance/audits/Graph_EligibilityOracle_v02.pdf b/packages/issuance/audits/2025-12-13_Graph_EligibilityOracle_v02.pdf similarity index 100% rename from packages/issuance/audits/Graph_EligibilityOracle_v02.pdf rename to packages/issuance/audits/2025-12-13_Graph_EligibilityOracle_v02.pdf diff --git a/packages/issuance/audits/Graph_IssuanceAllocator_v03.pdf b/packages/issuance/audits/2025-12-28_Graph_IssuanceAllocator_v03.pdf similarity index 100% rename from packages/issuance/audits/Graph_IssuanceAllocator_v03.pdf rename to packages/issuance/audits/2025-12-28_Graph_IssuanceAllocator_v03.pdf diff --git a/packages/issuance/audits/Graph_PR1279_v02.pdf b/packages/issuance/audits/2026-02-15_Graph_PR1279_v02.pdf similarity index 100% rename from packages/issuance/audits/Graph_PR1279_v02.pdf rename to packages/issuance/audits/2026-02-15_Graph_PR1279_v02.pdf diff --git a/packages/issuance/audits/PR1301/Graph_PR1334_v05.pdf b/packages/issuance/audits/2026-05-09_Graph_PR1334_v05.pdf similarity index 100% rename from packages/issuance/audits/PR1301/Graph_PR1334_v05.pdf rename to packages/issuance/audits/2026-05-09_Graph_PR1334_v05.pdf diff --git a/packages/issuance/audits/PR1301/Graph_PR1301_v01.pdf b/packages/issuance/audits/PR1301/Graph_PR1301_v01.pdf deleted file mode 100644 index 8f14dd018..000000000 Binary files a/packages/issuance/audits/PR1301/Graph_PR1301_v01.pdf and /dev/null differ diff --git a/packages/issuance/audits/PR1301/Graph_PR1301_v02.pdf b/packages/issuance/audits/PR1301/Graph_PR1301_v02.pdf deleted file mode 100644 index e9512ec7e..000000000 Binary files a/packages/issuance/audits/PR1301/Graph_PR1301_v02.pdf and /dev/null differ diff --git a/packages/issuance/audits/PR1301/Graph_PR1325_v03.pdf b/packages/issuance/audits/PR1301/Graph_PR1325_v03.pdf deleted file mode 100644 index e210c6ce5..000000000 Binary files a/packages/issuance/audits/PR1301/Graph_PR1325_v03.pdf and /dev/null differ diff --git a/packages/issuance/audits/PR1301/Graph_PR1331_v04.pdf b/packages/issuance/audits/PR1301/Graph_PR1331_v04.pdf deleted file mode 100644 index 68967caaf..000000000 Binary files a/packages/issuance/audits/PR1301/Graph_PR1331_v04.pdf and /dev/null differ diff --git a/packages/issuance/audits/PR1301/README.md b/packages/issuance/audits/PR1301/README.md deleted file mode 100644 index 70a0aa8b5..000000000 --- a/packages/issuance/audits/PR1301/README.md +++ /dev/null @@ -1,192 +0,0 @@ -# Trust Security Audit - PR #1301 / #1312 / #1325 / #1331 / #1334 - -**Auditor:** Trust Security -**Period:** 2026-03-03 to 2026-03-19 -**Commit:** 7405c9d5f73bce04734efb3f609b76d95ffb520e -**Fix review commit:** 0bbb476f37f85d042927e84d8764fa58eb020ccf -**2nd fix review commit:** f44fc5a4c74fa5190fd2892ae15a083b79f715f3 -**3rd fix review commit:** bbec75e04aa14c34d771681528b9a655be1f8249 (post-rebase) -**4th fix review commit:** 8d148f39a89e4f569456e098eb4d68f8a8d967e3 -**Report:** [Graph_PR1334_v05.pdf](Graph_PR1334_v05.pdf) - -> **SHA note.** The first three commits cited above are pre-rebase SHAs on -> `indexing-payments-management-audit-fix-2-light`, which was rebased onto -> current `main` as `indexing-payments-management-audit-fixed-rebased`. The pre-rebase tip is -> preserved at tag `indexing-payments-management-audit-pre-rebase` -> (`a3c73f87e`). The 3rd fix review commit is post-rebase only (it -> records the SHA mapping itself). Old → new mapping is at the bottom of -> this file. - -## Findings Summary - -| ID | Title | Severity | Status | -| ------------------------- | -------------------------------------------------------- | -------- | ------------ | -| [TRST-H-1](TRST-H-1.md) | Malicious payer gas siphoning via 63/64 rule | High | Fixed | -| [TRST-H-2](TRST-H-2.md) | Invalid supportsInterface() returndata escapes try/catch | High | Fixed | -| [TRST-H-3](TRST-H-3.md) | Stale escrow snapshot causes perpetual revert loop | High | Fixed | -| [TRST-H-4](TRST-H-4.md) | EOA payer can block collection via EIP-7702 | High | Fixed | -| [TRST-M-1](TRST-M-1.md) | Micro-thaw griefing via permissionless depositTo() | Medium | Fixed | -| [TRST-M-2](TRST-M-2.md) | tempJit fallback in beforeCollection() unreachable | Medium | Fixed | -| [TRST-M-3](TRST-M-3.md) | Instant escrow mode degradation via agreement offer | Medium | Acknowledged | -| [TRST-M-4](TRST-M-4.md) | Returndata bombing via payer callbacks | Medium | Fixed | -| [TRST-L-1](TRST-L-1.md) | Insufficient gas for afterCollection callback | Low | Fixed | -| [TRST-L-2](TRST-L-2.md) | Pending update over-reserves escrow | Low | Fixed | -| [TRST-L-3](TRST-L-3.md) | Unsafe approveAgreement behavior during pause | Low | Fixed | -| [TRST-L-4](TRST-L-4.md) | Pair tracking removal blocked by 1 wei donation | Low | Acknowledged | -| [TRST-L-5](TRST-L-5.md) | \_computeMaxFirstClaim overestimates near deadline | Low | Fixed | -| [TRST-L-6](TRST-L-6.md) | cancel() order sensitivity leaves RCAU offer unreachable | Low | Fixed | -| [TRST-L-7](TRST-L-7.md) | EOA payer signatures cannot be revoked before deadline | Low | Fixed | -| [TRST-L-8](TRST-L-8.md) | Callback gas precheck does not account for overhead | Low | Fixed | -| [TRST-L-9](TRST-L-9.md) | EIP-7702 payer code change enables callback gas griefing | Low | Fixed | -| [TRST-L-10](TRST-L-10.md) | Inaccurate state flags in getAgreementDetails() | Low | Fixed | - -## Client-Reported Findings - -| ID | Title | -| ------------------------- | --------------------------------------------------------------------------- | -| [TRST-CL-1](TRST-CL-1.md) | RecurringCollector may underreport required claim causing under-reservation | -| [TRST-CL-2](TRST-CL-2.md) | IssuanceAllocator may skip minting due to hypothetical governance action | - -## Recommendations - -| ID | Title | -| ------------------------- | --------------------------------------------------------------- | -| [TRST-R-1](TRST-R-1.md) | Avoid redeployment of RewardsEligibilityOracle | -| [TRST-R-2](TRST-R-2.md) | Improve stale documentation | -| [TRST-R-3](TRST-R-3.md) | Incorporate defensive coding best practices | -| [TRST-R-4](TRST-R-4.md) | Document critical assumptions in the RAM | -| [TRST-R-5](TRST-R-5.md) | Ambiguous return value in getAgreementOfferAt() | -| [TRST-R-6](TRST-R-6.md) | Dead code guard in \_validateAndStoreUpdate() | -| [TRST-R-7](TRST-R-7.md) | Remove consumed offers in accept() and update() | -| [TRST-R-8](TRST-R-8.md) | Align pause documentation with callback behavior in the RAM | -| [TRST-R-9](TRST-R-9.md) | \_isAuthorized() override trusts itself for any authorizer | -| [TRST-R-10](TRST-R-10.md) | Document role-change semantics for existing agreements | -| [TRST-R-11](TRST-R-11.md) | Remove or implement unused state flags in IAgreementCollector | -| [TRST-R-12](TRST-R-12.md) | Document ACCEPTED state returned for cancelled agreements | -| [TRST-R-13](TRST-R-13.md) | Document reclaim reason change for stale allocation force-close | -| [TRST-R-14](TRST-R-14.md) | Avoid magic numbers in production code | - -## Centralization Risks - -| ID | Title | -| ------------------------- | --------------------------------------------------------------- | -| [TRST-CR-1](TRST-CR-1.md) | RAM Governor has unilateral control over payment infrastructure | -| [TRST-CR-2](TRST-CR-2.md) | Operator role controls agreement lifecycle and escrow mode | -| [TRST-CR-3](TRST-CR-3.md) | Single RAM instance manages all agreement escrow | - -## Systemic Risks - -| ID | Title | -| ------------------------- | -------------------------------------------------------------- | -| [TRST-SR-1](TRST-SR-1.md) | JIT mode provider payment race condition | -| [TRST-SR-2](TRST-SR-2.md) | Escrow thawing period creates prolonged fund immobility | -| [TRST-SR-3](TRST-SR-3.md) | Issuance distribution dependency for RAM solvency | -| [TRST-SR-4](TRST-SR-4.md) | Try/catch callback pattern silently degrades state consistency | - -## Notes on findings dropped between v02 and v03 - -- v02 **TRST-M-5** (Perpetual thaw griefing via micro deposits) was withdrawn in v03; the underlying concern is treated as a sub-vector of TRST-M-1, addressed by `minResidualEscrowFactor`. -- v02 **TRST-L-6** (Update offer cleanup bypassed via planted offer) was withdrawn in v03; the agreement.payer / per-version persistence refactor done for v03 TRST-L-6 / TRST-L-10 supersedes the original cleanup concern. - -## Pre-rebase → post-rebase SHA mapping - -`indexing-payments-management-audit-fix-2-light` was rebased onto current `main` as `indexing-payments-management-audit-fixed-rebased`. The first-parent linear chain was re-signed (new SHAs); side-branch commits reachable through the two internal merges kept their original SHAs and signatures. - -Verify audit-side content was preserved byte-identically: - -```bash -ORIG=indexing-payments-management-audit-pre-rebase # tag at a3c73f87e -REB=2292e6ae8 # rebased tip before this commit -diff <(git diff "$ORIG" "$REB") <(git diff ddee12b11 main) -# expected: empty -``` - -| old | new | subject | -| ----------- | ----------- | --------------------------------------------------------------------------------------------------- | -| `5c51f0d6a` | `ad157562f` | chore: use ^0.8.27 caret pragma and bump solc to 0.8.34 | -| `bf6d4cb70` | `9b2fddc51` | Merge commit '0e469beeba0ec433e313be8c9129bcf99acdaac6' into indexing-payments-management-audit | -| `28edcd7a3` | `2a9d80b3e` | Merge commit 'd9f053a7d96a8a4d81415303ae1d537f836f887c' into indexing-payments-management-audit | -| `f4451f189` | `927cd08cf` | feat: add back legacy allocation id collision check | -| `fd962344c` | `9edb765ae` | chore: restore pragma | -| `fa9951427` | `79af54585` | fix: cap maxSecondsPerCollection instead of reverting | -| `c6836a716` | `20abcac15` | fix: enforce temporal validation on zero-token collections and remove zero-POI special case | -| `8efaec97d` | `8776d4931` | feat: add adjustThaw to PaymentsEscrow | -| `3f1578cdc` | `5ea7b1e19` | refactor: rename IRewardsEligibility to IProviderEligibility | -| `d20bc844d` | `d31a2c95c` | feat: contract approver model for RecurringCollector accept/update | -| `ec7236086` | `2bcc8ec88` | feat: IDataServiceAgreements interface and SubgraphService integration | -| `89def3d34` | `7d7927a10` | feat: enumerable indexer tracking for REO and issuance constructor cleanup | -| `a23ad681e` | `15bd6ceca` | feat: RecurringAgreementManager with lifecycle, escrow funding, and agreement updates | -| `8673c34c0` | `8ca9cc1e1` | fix(rewards): reorder subtraction in \_updateSubgraphRewards to avoid underflow | -| `506601ff8` | `2b5bce3e8` | fix(test): set subgraphService in snapshot inversion tests | -| `32bd36134` | `0d9e8fe44` | fix(test): exclude named test users from fuzz-generated indexer addresses | -| `0f4f48693` | `cd2f1b468` | feat: add issuance distribution integration to RAM | -| `86a5d6e2b` | `89ba6e873` | docs: clarify two-layer token capping semantics in collection flow | -| `7405c9d5f` | `50e984da9` | docs: add payments trust model | -| `9ae7643eb` | `6a9e37209` | test: add cross-package testing harness with callback gas measurements | -| `efc51160f` | `251e16f28` | docs(audit): add PR1301 audit report and findings | -| `956d983aa` | `f5617ef98` | feat(RAM): threshold-based escrow basis degradation (TRST-M-2, TRST-M-3) | -| `e1d73c109` | `435a281f7` | fix(RAM): refresh escrow snapshot in \_updateEscrow (TRST-H-3) | -| `e1a3c5ade` | `4fc2be9fd` | fix(RAM): add minimum thaw fraction to prevent dust-thaw griefing (TRST-M-1) | -| `56322cc50` | `e3bec768d` | feat(RM): add revert control for ineligible indexers | -| `3b617b47b` | `3f9c68c12` | docs(audit): acknowledge audit findings (TRST-CR-1/3, L-4, R-1, SR-1/2/3) | -| `df93851fb` | `df7f08123` | feat: resize allocations to zero instead of force-closing | -| `b1246562b` | `4c61ad9d6` | feat: revert closing allocations with active indexing agreement | -| `40c910464` | `a93ee9285` | fix(collector): reject agreements with overflow-prone token/duration terms | -| `83e25156a` | `74aaa3417` | feat(collector): offer storage, stored-hash auth, scoped claims and cancel (TRST-L-2, L-5) | -| `38b090c2b` | `68c8a39f4` | fix(collector): harden payer callbacks, add opt-in eligibility gate (TRST-H-1, H-2, H-4, L-1, SR-4) | -| `5b4100543` | `9d16819a7` | fix: compiler stack overflow | -| `608346eb2` | `f56be5f8e` | refactor(RAM): replace set-based range views with indexed accessors | -| `0b22a1408` | `fc8e14124` | feat(RAM): add emergency role control and eligibility oracle escape hatch | -| `77fc87f78` | `22eab2737` | refactor(RAM): convert offerAgreement and cancelAgreement to IAgreementCollector pass-throughs | -| `64bc0f0ed` | `87a0b899c` | refactor(RAM): remove offerAgreementUpdate, revokeAgreementUpdate, and revokeOffer | -| `daf0b47ed` | `2e75ecc92` | refactor(RAM): restructure storage into collector → provider hierarchy | -| `9ec2c072e` | `67af760a7` | feat(collector): make RecurringCollector upgradeable | -| `bbe019588` | `be5cead8c` | feat(collector): add pause mechanism to RecurringCollector (TRST-L-3) | -| `0bbb476f3` | `70b8fa1d7` | fix(subgraph-service): remove VALID_PROVISION and REGISTERED from cancelIndexingAgreement | -| `df9a8464e` | `b289023b9` | docs: update audit extracts for PR1301 v02 report | -| `cb6c45c1a` | `a882efc15` | fix(collector): add gas overhead buffer to callback prechecks (TRST-L-9) | -| `3ce581315` | `8a22a87ae` | fix(collector): cap returndata copy in payer callbacks (TRST-M-4) | -| `8e50abda2` | `c7f3c4b79` | docs: add response to TRST-L-10 EIP-7702 callback dispatch (won't fix) | -| `6a0ac799d` | `a7a730558` | feat(RAM): drop pair tracking below residual escrow threshold (TRST-M-1, TRST-M-5) | -| `f96a7316c` | `c1ba8b5ba` | docs: add responses to TRST-L-6, TRST-R-7 (both won't fix) | -| `35447e703` | `466599894` | docs(audit): acknowledge TRST-R-3 cancelAgreement defensive check | -| `2dd23720f` | `018a31524` | fix(collector): remove dead oldHash guard (TRST-R-6) | -| `c1ef1cb68` | `7a2ceeafc` | fix(collector): non-zero offer types, reserve OFFER_TYPE_NONE=0 sentinel (TRST-R-5) | -| `36217930d` | `98acdddfe` | refactor(interfaces): drop unused state and offer-option flags, tighten flag NatSpec (TRST-R-11) | -| `f32e55024` | `bebf0dbff` | docs(audit): acknowledge trust-boundary correction in TRST-H-4 | -| `d2fd36444` | `65a63fa91` | docs(audit): acknowledge reclaim-reason change in TRST-R-13 | -| `b61d4415f` | `eb511caa0` | docs(ram): document collector replay-protection assumption (TRST-R-4) | -| `02710154d` | `f2441eabc` | docs(ram): document non-retroactive role-change semantics (TRST-R-10) | -| `1ee49f232` | `ad829d054` | docs(ram): align pause-escalation prose with whenNotPaused scope (TRST-R-8) | -| `9396dbd12` | `c2c15eb74` | docs(collector): note self-authorization auth-check obligation (TRST-R-9) | -| `1e5a6b33a` | `229ede684` | fix(subgraph-service): validate update terms against RCAU rate, not stale agreement rate | -| `8be1aa0c8` | `35ba8639a` | refactor(collector): preparatory helpers, signatures, and version constants | -| `cfaf39b21` | `9ad649837` | refactor(collector): drop unreachable agreementId-zero check | -| `35748ff47` | `cb36d5717` | refactor(collector): extract \_requireValidTerms from duplicated validation | -| `0ad0be4fd` | `25fd07088` | refactor(collector): split accept logic out of \_validateAndStoreAgreement | -| `bfe77547d` | `4fdbdab9b` | refactor(collector): split update apply out of \_validateAndStoreUpdate | -| `594d19b07` | `08634a331` | feat(subgraph-service): idempotent accept/update with allocation rebinding | -| `885555e91` | `51963df39` | refactor(collector): hoist solhint-disable, idiomatic deadline comparisons | -| `572853b01` | `c4c52dea4` | fix(collector): validate offer terms against deadline, not block.timestamp | -| `b6adbf16b` | `523171e84` | refactor(collector): extract \_getAgreementDetails/\_versionHashAt helpers | -| `8b48437be` | `41caee8fc` | fix(collector): persistent agreement.payer for independent cancellation (TRST-L-7) | -| `769b252e5` | `de4997923` | feat(collector): idempotent accept/update/cancel-on-nothing | -| `f96b4ea65` | `600951db2` | feat(collector): add OfferCancelled event for SCOPE_PENDING cancellations | -| `c1dfc34af` | `ef0c92e47` | feat(collector): per-version semantics in getAgreementDetails (TRST-L-11) | -| `33d2cede2` | `f2361e8b4` | feat(collector): compose cancel/settled flags in getAgreementDetails (TRST-R-12) | -| `fe13b1128` | `262d9cc51` | feat(collector): add SCOPE_SIGNED to cancel() for EOA offer revocation (TRST-L-8) | -| `b13d9106c` | `258a0f32b` | feat(issuance): expose getIssuanceAllocator on IIssuanceTarget | -| `e4cd9e026` | `d28e30c10` | fix(collector): validate full terms at offer time | -| `6772545f1` | `1434b1249` | fix(collector): respect deadlines in scoped claim cap | -| `067168e4d` | `740192335` | refactor(collector): collapse redundant state guard in \_getMaxNextClaim | -| `757da4174` | `950b23b13` | fix(collector): use dedicated error for invalid offer type in offer() | -| `87ee8b6df` | `7c12e2f80` | chore(collector): make module-level constants internal to free EIP-170 headroom | -| `f44fc5a4c` | `a8f46322a` | feat(collector): add CONDITION_AGREEMENT_OWNER for ERC-165-validated callback opt-in | -| `5b07b5833` | `11292cf12` | docs(audits): drop withdrawn TRST findings, retitle and park remaining lows for v03 | -| `68cd77e5f` | `3af1424fc` | docs(audits): rename parked v03 lows from TRST-L-{old}-{new}.md to final paths | -| `f09c2e3ac` | `7911e1de9` | docs(audits): incorporate Trust Security PR1325 v03 fix-review | -| `9b30707b0` | `80c5010cf` | docs(collector): clarify cancel() signer-vs-payer caller for SCOPE_SIGNED (TRST-L-7) | -| `c21d7f88f` | `8d1653753` | test(collector): assert callbacks receive MAX_PAYER_CALLBACK_GAS with safety margin (TRST-L-8) | -| `75cae7ceb` | `66dec495f` | docs(collector): document EIP-7702 trust assumption for CONDITION_ELIGIBILITY_CHECK (TRST-L-9) | -| `c8a5c2150` | `93e7d1cbb` | refactor(ram): use VERSION_CURRENT instead of magic 0 in getAgreementDetails (TRST-R-14) | -| `a3c73f87e` | `2292e6ae8` | chore(ci): fix flaky CI tests and silence block-timestamp lint | diff --git a/packages/issuance/audits/PR1301/TRST-CL-1.md b/packages/issuance/audits/PR1301/TRST-CL-1.md deleted file mode 100644 index 94f92f1c9..000000000 --- a/packages/issuance/audits/PR1301/TRST-CL-1.md +++ /dev/null @@ -1,17 +0,0 @@ -# TRST-CL-1: RecurringCollector may underreport required claim causing under-reservation in the RAM - -- **Category:** Logical flaws -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -In the refactored codebase, `_getMaxNextClaimScoped()` returns the next worst-case collection as a `max()` of PENDING and ACTIVE scopes. In the PENDING calculation, the collection time window always starts at `block.timestamp`. However, new agreements after `update()` take effect retroactively, so the time window may be larger (since last collection date). - -The end impact is an understated next claim, which may cause the RAM to under-reserve for the particular allocation. No loss of funds is possible, although a delay in payment may arise. - -## Mitigation Review - -Issue has been addressed surgically. The function now accounts for the last collection time in case the agreement has already been accepted. - ---- diff --git a/packages/issuance/audits/PR1301/TRST-CL-2.md b/packages/issuance/audits/PR1301/TRST-CL-2.md deleted file mode 100644 index d3bbe3235..000000000 --- a/packages/issuance/audits/PR1301/TRST-CL-2.md +++ /dev/null @@ -1,17 +0,0 @@ -# TRST-CL-2: IssuanceAllocator may skip minting due to hypothetical governance action - -- **Category:** Logical flaws -- **Source:** IssuanceAllocator.sol -- **Status:** Fixed - -## Description - -The `_advanceSelfMintingBlock()` implementation optimizes the minting happy-path, and if there is no accumulating minting quota and the contract isn't paused, it does not increment the allocator. This is generally correct because under the presumed `_distributeIssuance()` call path, the happy path would lead to minting through `_performNormalDistribution()`, so accumulator should not be touched. However, there are hypothetical paths to the advance logic through the overloaded `distributePendingIssuance()`. In case these are used and the conditions for happy-path are met, the offset will not be updated, yet `_performNormalDistribution()` will not be called as part of the flow, causing under-minting of GRT. - -The scenario is considered pathological because the governance-controlled distribution functions are unnecessary in the happy path, since `distributeIssuance()` achieves the same, and the contracts are past any sort of recovery mode at that point. - -## Mitigation Review - -The implementation has been simplified: the optimized path no longer exists and the accumulator invariant always holds. - ---- diff --git a/packages/issuance/audits/PR1301/TRST-CR-1.md b/packages/issuance/audits/PR1301/TRST-CR-1.md deleted file mode 100644 index 65827afaa..000000000 --- a/packages/issuance/audits/PR1301/TRST-CR-1.md +++ /dev/null @@ -1,19 +0,0 @@ -# TRST-CR-1: RAM Governor has unilateral control over payment infrastructure - -- **Severity:** Centralization Risk - -## Description - -The RecurringAgreementManager's `GOVERNOR_ROLE` has broad unilateral authority over critical payment infrastructure: - -- Controls which data services can participate (`DATA_SERVICE_ROLE` grants) -- Controls which collectors are trusted (`COLLECTOR_ROLE` grants) -- Can set the issuance allocator address, redirecting the token flow that funds all escrow -- Can set the provider eligibility oracle, which gates who can receive payments -- Can pause the entire contract, halting all agreement management - -A compromised or malicious governor could revoke a data service's role (preventing new agreements), change the issuance allocator to a contract that withholds funds, or set a malicious eligibility oracle that blocks specific providers from collecting. These actions affect all agreements managed by the RAM, not just future ones. - ---- - -Accepted centralization tradeoff. The governor must have these powers for effective protocol operation. Expected to be a multisig or governance contract in production. diff --git a/packages/issuance/audits/PR1301/TRST-CR-2.md b/packages/issuance/audits/PR1301/TRST-CR-2.md deleted file mode 100644 index 3331459bb..000000000 --- a/packages/issuance/audits/PR1301/TRST-CR-2.md +++ /dev/null @@ -1,17 +0,0 @@ -# TRST-CR-2: Operator role controls agreement lifecycle and escrow mode - -- **Severity:** Centralization Risk - -## Description - -The `OPERATOR_ROLE` (admin of `AGREEMENT_MANAGER_ROLE`) controls the operational layer of the RAM: - -- Grants `AGREEMENT_MANAGER_ROLE`, which authorizes offering, updating, revoking, and canceling agreements -- Can change the `escrowBasis` (Full/OnDemand/JIT), instantly affecting escrow behavior for all existing agreements -- Can set `tempJit`, overriding the escrow mode to JIT for all pairs - -An operator switching from Full to JIT mode instantly removes proactive escrow guarantees for all providers. Providers who accepted agreements under the assumption of Full escrow backing may find their payment security degraded without notice or consent. The escrow mode change is a storage write with no timelock or multi-sig requirement. - ---- - -Accepted. The operator is a trusted role managing agreement lifecycle and escrow parameters on behalf of the protocol. Escrow parameter changes are visible on-chain via events. diff --git a/packages/issuance/audits/PR1301/TRST-CR-3.md b/packages/issuance/audits/PR1301/TRST-CR-3.md deleted file mode 100644 index 42097257c..000000000 --- a/packages/issuance/audits/PR1301/TRST-CR-3.md +++ /dev/null @@ -1,15 +0,0 @@ -# TRST-CR-3: Single RAM instance manages all agreement escrow - -- **Severity:** Centralization Risk - -## Description - -The RecurringAgreementManager is a single contract instance that manages escrow for all agreements across all (collector, provider) pairs. The `totalEscrowDeficit` is a global aggregate, and the escrow mode (Full/OnDemand/JIT) applies uniformly to all pairs. - -This means operational decisions or issues affecting one pair can cascade to all others. For example, a single large agreement that becomes insolvent increases `totalEscrowDeficit`, potentially degrading the escrow mode from Full to OnDemand for every other pair. Similarly, a stale snapshot on one pair (TRST-H-3) affects the global deficit calculation. - -There is no isolation between pairs beyond the per-pair `sumMaxNextClaim` tracking. The RAM does not support per-pair escrow mode configuration or per-pair balance ringfencing. - ---- - -Accepted design tradeoff. The shared pool optimizes capital efficiency — per-pair isolation would significantly increase complexity, gas costs, and operational overhead. The snap-refresh fix (TRST-H-3) and minThawFraction (TRST-M-1) reduce cascading effects. diff --git a/packages/issuance/audits/PR1301/TRST-H-1.md b/packages/issuance/audits/PR1301/TRST-H-1.md deleted file mode 100644 index 7c15cd250..000000000 --- a/packages/issuance/audits/PR1301/TRST-H-1.md +++ /dev/null @@ -1,30 +0,0 @@ -# TRST-H-1: Malicious payer gas siphoning via 63/64 rule in collection callbacks leads to collection bypass - -- **Severity:** High -- **Category:** Gas-related issues -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -In `RecurringCollector._collect()`, the `beforeCollection()` and `afterCollection()` callbacks to contract payers are wrapped in try/catch blocks (lines 380, 416). A malicious contract payer can exploit the EVM's 63/64 gas forwarding rule to consume nearly all available gas in these callbacks. - -The attack works as follows: the malicious payer's `beforeCollection()` implementation consumes 63/64 of the gas forwarded to it, either returning successfully or reverting, but regardless leaving only 1/64 of the original gas for the remainder of `_collect()`. The core payment logic (`PaymentsEscrow.collect()` at line 384) and event emissions then execute with a fraction of the expected gas. The `afterCollection()` callback then consumes another 63/64 of what remains. - -Realistically, after both callbacks siphon gas, there will not be enough gas left to complete the `PaymentsEscrow.collect()` call and the subsequent event emissions, causing the entire `collect()` transaction to revert. The security model for Payer as a smart contract does not account for requiring such gas expenditure, which can also be obfuscated away. This gives the malicious payer effective veto power over all collections against their agreements. - -## Recommended Mitigation - -Enforce a minimum gas reservation before each callback. Before calling `beforeCollection()`, check that `gasleft()` is sufficient and forward only a bounded amount of gas using the `{gas: maxCallbackGas}` syntax, retaining enough gas for the core payment logic. Apply the same pattern to `afterCollection()`. This caps the gas available to the payer's callbacks regardless of their implementation, ensuring the critical `PaymentsEscrow.collect()` call always has enough gas to complete. - -## Team Response - -Fixed. - -## Mitigation Review - -Issue has been fixed as suggested. - ---- - -Fixed. Added `MAX_PAYER_CALLBACK_GAS` constant (1,500,000 gas) in `RecurringCollector._collect()`. All external calls to payer contracts (`isEligible`, `beforeCollection`, `afterCollection`) now use gas-capped low-level `call`/`staticcall`, preventing gas siphoning via the 63/64 forwarding rule. A `gasleft()` guard before the callback block reverts with `RecurringCollectorInsufficientCallbackGas` when insufficient gas remains, ensuring core payment logic always has enough gas to complete. diff --git a/packages/issuance/audits/PR1301/TRST-H-2.md b/packages/issuance/audits/PR1301/TRST-H-2.md deleted file mode 100644 index 3f8eea841..000000000 --- a/packages/issuance/audits/PR1301/TRST-H-2.md +++ /dev/null @@ -1,30 +0,0 @@ -# TRST-H-2: Invalid supportsInterface() returndata escapes try/catch leading to collection bypass - -- **Severity:** High -- **Category:** Logical flaws -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -In `RecurringCollector._collect()` (lines 368-378), the provider eligibility check calls `IERC165(agreement.payer).supportsInterface()` inside a try/catch block. The try clause expects a `(bool supported)` return value. If the external call succeeds at the EVM level (does not revert) but returns malformed data - such as fewer than 32 bytes of returndata or data that cannot be ABI-decoded as a bool - the Solidity ABI decoder reverts on the caller side when attempting to decode the return value. - -This ABI decoding revert occurs in the calling contract's execution context, not in the external call itself. Solidity's try/catch mechanism only catches reverts originating from the external call (callee-side reverts). Caller-side decoding failures escape the catch block and propagate as an unhandled revert, causing the entire `_collect()` transaction to fail. - -A malicious contract payer can exploit this by implementing a `supportsInterface()` function that returns success with empty returndata, a single byte, or any non-standard encoding. This permanently blocks all collections against agreements with that payer, since the `code.length > 0` check always routes through the vulnerable path. As before, the security model does not account for this bypass path to be validated against. - -## Recommended Mitigation - -Avoid receiving and decoding values from untrusted contract calls. This can be done manually by reading returndata at the assembly level. - -## Team Response - -Fixed. - -## Mitigation Review - -Fixed. The affected code has been refactored, addressing the issue. - ---- - -Fixed. Replaced the `supportsInterface` → `isEligible` two-step with a single direct `isEligible` low-level `staticcall` with gas cap. Returndata is validated for length (>= 32 bytes) and decoded as `uint256`. Only an explicit return of `0` blocks collection; reverts, short returndata, and malformed responses are treated as "no opinion" (collection proceeds), with a `PayerCallbackFailed` event emitted for observability. diff --git a/packages/issuance/audits/PR1301/TRST-H-3.md b/packages/issuance/audits/PR1301/TRST-H-3.md deleted file mode 100644 index 66bddea4d..000000000 --- a/packages/issuance/audits/PR1301/TRST-H-3.md +++ /dev/null @@ -1,32 +0,0 @@ -# TRST-H-3: Stale escrow snapshot causes a perpetual revert loop - -- **Severity:** High -- **Category:** Logical flaws -- **Source:** RecurringAgreementManager.sol -- **Status:** Fixed - -## Description - -The RecurringAgreementManager (RAM) maintains an `escrowSnap` per (collector, provider) pair - a cached view of the escrow balance used to compute `totalEscrowDeficit`. This snap is only updated at the end of `_updateEscrow()` via `_setEscrowSnap()`. When `afterCollection()` is called by the RecurringCollector after a payment collection, the escrow balance has already been reduced by the collected amount, but `escrowSnap` still reflects the pre-collection value. - -The stale-high snap causes `_escrowMinMax()` to understate the deficit. In Full escrow mode, when the RAM's free token balance is low, this leads to an incorrect decision to deposit into escrow. The deposit attempt reverts due to insufficient ERC20 balance, and the entire `afterCollection()` call fails. Since RecurringCollector wraps `afterCollection()` in try/catch (line 416), the revert is silently swallowed - but the snap never gets updated, making it permanently stale. - -This is self-reinforcing: every subsequent `afterCollection()`, `reconcileAgreement()`, and `reconcileCollectorProvider()` call for the affected pair follows the same code path and reverts for the same reason. There is no manual recovery path. The escrow accounting diverges from reality for the affected pair, and `totalEscrowDeficit` is globally understated, potentially causing other pairs to incorrectly enter Full mode and over-deposit. - -The state only self-heals when the RAM receives enough tokens (e.g., from issuance distribution) to cover the phantom deposit, at which point the deposit succeeds but sends tokens to escrow unnecessarily. - -## Recommended Mitigation - -Read the fresh escrow balance inside `_escrowMinMax()` when computing the deficit, rather than relying on the cached `escrowSnap` derived from `totalEscrowDeficit`. This makes the function self-correcting: even if a prior `afterCollection()` failed, the next call sees the true balance and makes the correct deposit/thaw decision. This approach fixes the root cause rather than masking the symptom with a balance guard. - -## Team Response - -Fixed. - -## Mitigation Review - -The new code has a `_setEscrowSnap()` call before `_escrowMinMax()`, ensuring the snapshot is updated and fixing the root cause. - ---- - -Now refreshing the cached `escrowSnap` at the start of `_updateEscrow()` so that `_escrowMinMax()` uses updated `totalEscrowDeficit`. diff --git a/packages/issuance/audits/PR1301/TRST-H-4.md b/packages/issuance/audits/PR1301/TRST-H-4.md deleted file mode 100644 index d9fa550bc..000000000 --- a/packages/issuance/audits/PR1301/TRST-H-4.md +++ /dev/null @@ -1,32 +0,0 @@ -# TRST-H-4: EOA payer can block collection by acquiring code via EIP-7702 - -- **Severity:** High -- **Category:** Type confusion -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -In `RecurringCollector._collect()` (lines 368-378), the provider eligibility gate is applied when `agreement.payer.code.length > 0`. This gate was designed as an opt-in mechanism for contract payers to control which providers can collect. However, with EIP-7702 (live on both Ethereum mainnet and Arbitrum), an EOA can set a code delegation to an arbitrary contract address. - -An EOA payer who originally signed an agreement via the ECDSA path can later acquire code using an EIP-7702 delegation transaction. This causes the `code.length > 0` branch to activate during collection. By delegating to a contract that implements `supportsInterface()` returning true for `IProviderEligibility` and `isEligible()` returning false, the payer triggers the `require()` on line 373. - -The `require()` is inside the try block's success handler. In Solidity, reverts in the success handler are NOT caught by the catch block - they propagate up and revert the entire transaction. This gives the payer complete, toggleable control over whether collections succeed. The payer can enable the delegation to block collections, disable it to sign new agreements, and re-enable it before collection attempts - all at negligible gas cost. - -The payer can then thaw and withdraw their escrowed funds after the thawing period, effectively receiving services for free. This bypasses the assumed security model where a provider can trust the escrow balance for an EOA payer to ensure collection will succeed. - -## Recommended Mitigation - -Record whether the payer had code at agreement acceptance time by adding a bool flag to the agreement struct (e.g., `payerIsContract`). Only apply the `IProviderEligibility` gate when the payer was a contract at acceptance. This preserves the eligibility feature for legitimate contract payers while closing the EOA-to-contract vector introduced by EIP-7702. - -## Team Response - -Fixed. - -## Mitigation Review - -Fixed under the assumption that a provider setting `CONDITION_ELIGIBILITY_CHECK` to true must trust the payer contract. The statement in the fix comment that "An EOA cannot pass this check, so an EOA cannot create an agreement with eligibility gating enabled" is inaccurate, because an EOA can always change its code back and forth via EIP-7702 to pass interface checks. The correct security boundary is that the provider trusts the payer contract when opting into eligibility, not that the payer cannot be an EOA. - ---- - -Agreed; the security boundary is that a provider opts into `CONDITION_ELIGIBILITY_CHECK` to trust the payer contract. diff --git a/packages/issuance/audits/PR1301/TRST-L-1.md b/packages/issuance/audits/PR1301/TRST-L-1.md deleted file mode 100644 index ed4cd9f11..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-1.md +++ /dev/null @@ -1,30 +0,0 @@ -# TRST-L-1: Insufficient gas for afterCollection callback leaves escrow state outdated - -- **Severity:** Low -- **Category:** Time sensitivity flaw -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -In `RecurringCollector._collect()`, after a successful escrow collection, the function notifies contract payers via a try/catch call to `afterCollection()` (line 416). The caller (originating at data provider) controls the gas forwarded to the `collect()` transaction. By providing just enough gas for the core collection to succeed but not enough for the `afterCollection()` callback, the external call will revert due to an out-of-gas error, which is silently caught by the catch block. - -For the RecurringAgreementManager (RAM), `afterCollection()` triggers `_reconcileAndUpdateEscrow()`, which reconciles the agreement's `maxNextClaim` against on-chain state and updates the escrow snapshot via `_setEscrowSnap()`. When this callback is skipped, the `escrowSnap` remains at its pre-collection value, overstating the actual escrow balance. This stale snapshot causes `totalEscrowDeficit` to be understated, which can lead to incorrect escrow mode decisions in `_escrowMinMax()` for subsequent operations on the affected (collector, provider) pair. - -The state will self-correct on the next successful call to `_updateEscrow()` for the same pair (e.g., via `reconcileAgreement()` or a subsequent collection with sufficient gas), so the impact is temporary. However, during the stale window, escrow rebalancing decisions may be suboptimal. - -## Recommended Mitigation - -Enforce a minimum gas forwarding requirement for the `afterCollection()` callback. This can be done by checking `gasleft()` before the `afterCollection()` call and reverting if insufficient gas remains for the callback to execute meaningfully. - -## Team Response - -Fixed. - -## Mitigation Review - -Fixed as suggested. - ---- - -A `gasleft()` guard before each payer callback (`isEligible`, `beforeCollection`, `afterCollection`) reverts the entire collection when insufficient gas remains. Callbacks use low-level `call`/`staticcall` with gas cap (`MAX_PAYER_CALLBACK_GAS`); failures emit `PayerCallbackFailed` for observability but do not block collection. diff --git a/packages/issuance/audits/PR1301/TRST-L-10.md b/packages/issuance/audits/PR1301/TRST-L-10.md deleted file mode 100644 index 73be05ec4..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-10.md +++ /dev/null @@ -1,39 +0,0 @@ -# TRST-L-10: Inaccurate state flags returned by getAgreementDetails() and \_offerUpdate() - -- **Severity:** Low -- **Category:** Logical flaws -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -The `IAgreementCollector` interface defines state bit flags including `ACCEPTED` and `UPDATE`, with the documented convention that `UPDATE` is ORed into the state returned by `getAgreementDetails()` for pending versions (index 1). Two deviations from the specification were observed. - -First, in `_offerUpdate()` (lines 417 to 455), when an update is offered against an already accepted agreement, the returned `AgreementDetails` sets state to `REGISTERED | UPDATE` without ORing `ACCEPTED`. Callers that inspect the returned state to determine whether the agreement is already live will misread the underlying agreement as not accepted. - -Second, in `getAgreementDetails()` (lines 500 to 528), the `UPDATE` bit is never ORed into the returned state for the pending version path. The interface documentation promises this behavior for pending versions, but the implementation returns `REGISTERED` or `ACCEPTED` without regard to whether an RCAU offer is pending. - -Neither deviation changes on-chain accounting, but integrators relying on the declared state semantics will receive misleading data. - -## Recommended Mitigation - -In `_offerUpdate()`, OR the `ACCEPTED` bit into state when the underlying agreement is in the Accepted state. In `getAgreementDetails()`, OR the `UPDATE` bit into the returned state when a pending RCAU offer exists for the agreement. - -## Team Response - -`getAgreementDetails()` previously ignored the `index` parameter and returned only `ACCEPTED` for any agreement past `NotAccepted`, regardless of whether a pending RCAU also existed. It now honors `index` as a generic version selector with two named aliases: - -- `VERSION_CURRENT = 0` — the active version. For an accepted agreement, returns agreement fields + `activeTermsHash` with `REGISTERED | ACCEPTED`, plus `UPDATE` when the active terms came from an update. Pre-acceptance, returns the stored RCA offer with `REGISTERED`. Identity (`payer`, `dataService`, `serviceProvider`) is read from agreement storage in both cases; these fields are now persisted in `offer()`. -- `VERSION_NEXT = 1` — the next queued version: a pending RCAU awaiting acceptance. Returns `REGISTERED | UPDATE` when present; empty once accepted (at which point it has moved to `VERSION_CURRENT`). - -`getAgreementOfferAt()` mirrors the same per-version semantics: `VERSION_CURRENT` returns the offer that produced `activeTermsHash` (RCA pre-update or RCAU post-update); `VERSION_NEXT` returns the pending RCAU when distinct from the active hash. - -`offer()` and `getAgreementDetails()` share a state composer keyed by version index, so both surfaces report identical flags. Flags split into per-version (`REGISTERED`, `ACCEPTED`, `UPDATE`, `SETTLED`) and per-agreement (`NOTICE_GIVEN`, `BY_PAYER`, `BY_PROVIDER`) groups. `ACCEPTED` is set only when the queried version equals `activeTermsHash`; `SETTLED` is scoped to the version's own claim (active or pending) so a non-zero claim on one version does not suppress `SETTLED` on the other. - -After `update()` promotes an RCAU to active, those bytes live in the RCAU slot. A subsequent `offer(OFFER_TYPE_UPDATE)` with a different hash overwrites that slot and the active RCAU's bytes, therefore they cannot be returned by `getAgreementOfferAt(id, VERSION_CURRENT)`. Resolving this without a hash-keyed terms store would require a third storage slot or a flexible-type slot, both judged disproportionate to the observability concern. - -## Mitigation Review - -The latest changes implement a more consistent viewing interface for the contracts, addressing the issues identified. - ---- diff --git a/packages/issuance/audits/PR1301/TRST-L-2.md b/packages/issuance/audits/PR1301/TRST-L-2.md deleted file mode 100644 index f3eee05c5..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-2.md +++ /dev/null @@ -1,30 +0,0 @@ -# TRST-L-2: Pending update over-reserves escrow with unrealistically conservative calculation - -- **Severity:** Low -- **Category:** Arithmetic issues -- **Source:** RecurringAgreementManager.sol -- **Status:** Fixed - -## Description - -In `offerAgreementUpdate()` (line 328), the pending update's `maxNextClaim` is computed via `_computeMaxFirstClaim()` using the full `maxSecondsPerCollection` window and the new `maxInitialTokens`. This amount is added to `sumMaxNextClaim` alongside the existing (non-pending) `maxNextClaim`, making both slots additive. - -This is overly conservative because only one set of terms is ever active at a time. While the update is pending, the RAM reserves escrow for both the current agreement terms and the proposed updated terms simultaneously. The correct calculation should take the maximum of the two rates multiplied by `maxSecondsPerCollection` plus the new `maxInitialTokens`, and add the old `maxInitialTokens` only if the initial collection has not yet occurred. - -The over-reservation reduces the effective capacity of the RAM, ties up capital that could serve other agreements, and in Full mode can trigger escrow mode degradation by inflating `totalEscrowDeficit`. Once the update is accepted or revoked, the excess is released, but during the pending window the impact on escrow accounting is significant for high-value agreements. Additionally, the over-reservation will trigger an unnecessary thaw as soon as the agreement update completes, since escrow will exceed the corrected target. - -## Recommended Mitigation - -The `pendingMaxNextClaim` should be computed as stated above, then reduced by the current `maxNextClaim` so that the total deficit is accurate. This reflects the reality that only one set of terms is active at any time, and the worst-case scenario where `collect()` is called before and after the agreement update. - -## Team Response - -Fixed. - -## Mitigation Review - -Refactored so that at any point, the accurate worst-case collection is reflected. - ---- - -Fixed. RAM now delegates all max-claim estimates to the collector via `IAgreementCollector.getMaxNextClaim(agreementId)`, which returns `max(active, pending)` — only the larger of current or pending terms is reserved, not both additively. The RC's `_getMaxNextClaimScoped` computes active and pending claims independently and returns the maximum, ensuring per-agreement escrow contribution reflects the worst-case single-term scenario. diff --git a/packages/issuance/audits/PR1301/TRST-L-3.md b/packages/issuance/audits/PR1301/TRST-L-3.md deleted file mode 100644 index 92a21e7e4..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-3.md +++ /dev/null @@ -1,32 +0,0 @@ -# TRST-L-3: Unsafe behavior of approveAgreement during pause - -- **Severity:** Low -- **Category:** Access control issues -- **Source:** RecurringAgreementManager.sol -- **Status:** Fixed - -## Description - -The `approveAgreement()` function (line 226) is a view function with no `whenNotPaused` modifier. During a pause, it continues to return the magic selector for authorized hashes, allowing the RecurringCollector to accept new agreements or apply updates even while the RAM is paused. - -A pause is typically an emergency measure intended to halt all state-changing operations. Allowing agreement acceptance during pause undermines this intent, as the accepted agreement creates obligations (escrow reservations, `maxNextClaim` tracking) that the paused RAM cannot manage. - -Similarly, `beforeCollection()` and `afterCollection()` do not check pause state. While blocking these during pause could prevent providers from collecting earned payments, allowing them could pose a security risk if the pause was triggered due to a discovered vulnerability in the escrow management logic. - -## Recommended Mitigation - -Add a pause check to `approveAgreement()` that returns `bytes4(0)` when the contract is paused, preventing new agreement acceptances and updates during emergency pauses. For `beforeCollection()` and `afterCollection()`, evaluate the trade-off: blocking them protects against exploitation of escrow logic bugs during pause, while allowing them ensures providers can still collect earned payments. Consider allowing collection callbacks only in a restricted mode during pause. - -## Team Response - -Fixed. - -## Mitigation Review - -Fixed. Underlying code has been refactored, addressing the issue. - ---- - -Fixed. RecurringCollector now has a pause mechanism with `whenNotPaused` modifier gating `accept`, `update`, `collect`, `cancel`, and `offer`. Pause guardians are managed by the governor via `setPauseGuardian`. This provides a middle layer between the RAM-level pause (agreement lifecycle only) and the Controller-level nuclear pause (all escrow operations protocol-wide). - -The `approveAgreement` callback has been removed entirely — stored-hash authorization replaced callback-based approval, so the pause-bypass vector no longer exists. Collection callbacks (`beforeCollection`, `afterCollection`) are wrapped in try/catch and cannot block collection regardless of pause state. diff --git a/packages/issuance/audits/PR1301/TRST-L-4.md b/packages/issuance/audits/PR1301/TRST-L-4.md deleted file mode 100644 index 4df8bbef9..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-4.md +++ /dev/null @@ -1,26 +0,0 @@ -# TRST-L-4: Pair tracking removal blocked by 1 wei escrow donation - -- **Severity:** Low -- **Category:** Donation attacks -- **Source:** RecurringAgreementManager.sol -- **Status:** Acknowledged - -## Description - -When the last agreement for a (collector, provider) pair is deleted, `_reconcilePairTracking()` is intended to remove the pair from the tracking sets (`collectorProviders`, `collectors`) and clean up the escrow state. However, an attacker can prevent this cleanup by depositing 1 wei of GRT into the pair's escrow account via `PaymentsEscrow.deposit()` just before the reconciliation occurs. - -The donation increases the escrow balance, which in turn updates the `escrowSnap` to a non-zero value during `_updateEscrow()`. The `_reconcilePairTracking()` function checks whether the `escrowSnap` is zero to determine if the pair can be safely removed. With the 1 wei donation, this check passes (snap != 0), and the pair is retained in the tracking sets even though it has no active agreements. - -This leaves orphaned entries in the `collectorProviders` and `collectors` tracking sets, preventing clean removal of the collector from the RAM's accounting. - -## Recommended Mitigation - -In `_reconcilePairTracking()`, base the removal decision on `pairAgreementCount` reaching zero rather than on `escrowSnap` being zero. If no agreements remain for a pair, remove it from tracking regardless of the escrow balance. Any residual escrow balance (from donations or rounding) can be handled by initiating a thaw before removal. - -## Team Response - -Accepted limitation. Orphaned tracking entries do not affect correctness or funds safety. - ---- - -Accepted limitation. Orphaned tracking entries do not affect correctness or funds safety. The proposed fix (removing pairs regardless of escrow balance) would sacrifice discoverability of unreclaimed escrow. Residual balances are handled through offline reconciliation. diff --git a/packages/issuance/audits/PR1301/TRST-L-5.md b/packages/issuance/audits/PR1301/TRST-L-5.md deleted file mode 100644 index 2533503e0..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-5.md +++ /dev/null @@ -1,30 +0,0 @@ -# TRST-L-5: The \_computeMaxFirstClaim function overestimates when deadline is before full collection window - -- **Severity:** Low -- **Category:** Logical flaw -- **Source:** RecurringAgreementManager.sol -- **Status:** Fixed - -## Description - -In `_computeMaxFirstClaim()` (line 645), the maximum first claim is computed as: `maxOngoingTokensPerSecond * maxSecondsPerCollection + maxInitialTokens`. This uses the full `maxSecondsPerCollection` window regardless of how much time actually remains until the agreement's `endsAt` deadline. - -In contrast, RecurringCollector's `getMaxNextClaim()` correctly accounts for the remaining time until the deadline, capping the collection window when the deadline is closer than `maxSecondsPerCollection`. The RAM's overestimate means `sumMaxNextClaim` is inflated for agreements near their end date, causing the RAM to reserve more escrow than the RecurringCollector would ever allow to be collected. - -The excess reservation is wasteful but not directly exploitable, as the collector enforces the actual cap during collection. However, it reduces the RAM's effective capacity and can contribute to unnecessary escrow mode degradation. - -## Recommended Mitigation - -Align `_computeMaxFirstClaim()` with the RecurringCollector's `getMaxNextClaim()` logic by accounting for the remaining time until the agreement's `endsAt`. Compute the collection window as `min(maxSecondsPerCollection, endsAt - lastCollectionAt)` when determining the maximum possible claim. This requires passing the `endsAt` parameter to the function. - -## Team Response - -Fixed. - -## Mitigation Review - -Fixed. The RecurringCollector now calculates the effective window correctly. - ---- - -RAM delegates to `IRecurringCollector.getMaxNextClaim(agreementId)` for all `maxNextClaim` calculations. The RC's `_maxClaimForTerms` correctly caps the collection window by remaining time until `endsAt`, eliminating the overestimate. diff --git a/packages/issuance/audits/PR1301/TRST-L-6.md b/packages/issuance/audits/PR1301/TRST-L-6.md deleted file mode 100644 index 7ce2cd7ed..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-6.md +++ /dev/null @@ -1,26 +0,0 @@ -# TRST-L-6: The cancel() function order sensitivity leaves RCAU offer unreachable - -- **Severity:** Low -- **Category:** Time-sensitivity issues -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -When a payer has both a pending RCA offer and a pending RCAU offer for the same `agreementId` and neither has been accepted, the order of cancellations matters. The `cancel()` overload that takes a terms hash delegates authorization to `_requirePayer()` (lines 480-497), which first checks the accepted agreement's payer and then the stored `rcaOffers` entry's payer. It does not fall back to `rcauOffers`. - -If the payer first cancels the RCA offer under `SCOPE_PENDING`, the entry in `rcaOffers` is deleted. A subsequent attempt to cancel the RCAU offer then fails: `_requirePayer()` finds no accepted agreement and no RCA offer, and reverts with `RecurringCollectorAgreementNotFound`. The orphaned RCAU offer remains in storage and unreachable by the payer. If the same parameters are later re-used to offer a new RCA, the orphaned RCAU is associated with it. The `updateNonce` check prevents immediate acceptance of the stale RCAU, but the payer has lost the ability to clean up state they own. - -## Recommended Mitigation - -Extend `_requirePayer()` to also check `rcauOffers` for a payer match when neither an accepted agreement nor an RCA offer is present. Alternatively, enforce symmetric cleanup so that deleting an RCA offer under `SCOPE_PENDING` also deletes any `rcauOffers` entry with the same `agreementId`. - -## Team Response - -Resolved by persisting `agreement.payer` from the first `offer()` instead of waiting until `accept()`. `_requirePayer` is replaced by an inline `agreement.payer` check at the `cancel()` call site, reading the persisted address directly without falling back through `rcaOffers`. `_offerUpdate` likewise reads `agreement.payer` instead of decoding the stored RCA bytes on every update offer. As a consequence, cancelling a pre-acceptance RCA offer and cancelling a pending RCAU offer are fully independent operations that may be performed in either order — neither path leaves the other unreachable, because the persistent `agreement.payer` continues to authorize the surviving offer. - -## Mitigation Review - -The restructuring of agreement data ensures no offer is orphaned and unreachable. - ---- diff --git a/packages/issuance/audits/PR1301/TRST-L-7.md b/packages/issuance/audits/PR1301/TRST-L-7.md deleted file mode 100644 index 4b66346bf..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-7.md +++ /dev/null @@ -1,28 +0,0 @@ -# TRST-L-7: EOA payer signatures cannot be revoked before deadline - -- **Severity:** Low -- **Category:** Functionality flaws -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -Payers approve agreements through two paths: an ECDSA signature consumed by `accept()` or `update()`, and a stored offer placed by a contract payer via `offer()` and consumed against the stored hash. Contract payers can revoke a pending offer by calling `cancel()` with `SCOPE_PENDING`, which deletes the matching entry from `rcaOffers` or `rcauOffers`. - -EOA payers have no equivalent revocation path. Once an RCA or RCAU has been signed, the signature is accepted by the collector at any time before the `deadline` field expires. A payer that wishes to cancel a signature-based offer before the deadline (for example, to renegotiate terms) has no mechanism to do so. The only remaining option to ensure no duplicate agreement risk is to wait out the deadline (and hope their unintended offer is not matched), or to revoke the signer via the Authorizable thawing and revocation flow, which affects all agreements authorized by that signer rather than an individual offer. - -## Recommended Mitigation - -Expose a `cancelSignature(bytes32 hash)` entry point that records the hash as invalidated on-chain, and have `_requireAuthorization()` reject any hash that has been invalidated. Alternatively, use a per-signer nonce that the payer can bump to invalidate all outstanding signatures for that signer. - -## Team Response - -Added `SCOPE_SIGNED` flag to `cancel()`, giving EOA signers an on-chain revocation path like contract payers already have via `SCOPE_PENDING`. The signer calls `cancel(agreementId, termsHash, SCOPE_SIGNED)` which records `cancelledOffers[msg.sender][termsHash] = agreementId`. When `accept()` or `update()` later processes a signature, `_requireAuthorization` recovers the signer via ECDSA and rejects if the stored agreementId matches. Self-authenticating (keyed by signer address), idempotent, reversible (calling again with `bytes16(0)` undoes the cancellation), and combinable with other scopes. Also made `cancel` no-op when nothing exists on-chain instead of reverting. - -## Mitigation Review - -The introduced alternative cancel path is sufficient. It should be clarified that whenever a payer is represented by a signer, `cancel()` should be called by the signer, not payer. - ---- - -Clarified in the `IAgreementCollector.cancel()` NatSpec: each scope's required caller is named explicitly (`SCOPE_SIGNED` → the ECDSA signer, `SCOPE_PENDING` / `SCOPE_ACTIVE` → the payer), and the combined-call case is noted as only useful when an EOA signs for itself as payer. Implementation `@dev` kept brief; per-scope caller behavior pulled in via `@inheritdoc`. diff --git a/packages/issuance/audits/PR1301/TRST-L-8.md b/packages/issuance/audits/PR1301/TRST-L-8.md deleted file mode 100644 index d2d5e62e8..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-8.md +++ /dev/null @@ -1,37 +0,0 @@ -# TRST-L-8: Callback gas precheck does not account for intermediate overhead - -- **Severity:** Low -- **Category:** Gas-related issues -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -Both `_preCollectCallbacks()` and `_postCollectCallback()` guard each payer callback with a precheck of the form `if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63) revert`. The intent is to ensure that `MAX_PAYER_CALLBACK_GAS` remains available to the callee after applying the EIP-150 63/64 rule. - -However, the precheck is performed before the CALL or STATICCALL opcode itself, and additional gas is consumed between the comparison and the opcode: local Solidity operations, stack and memory setup, calldata encoding, and the fixed cost of the CALL or STATICCALL instruction. The actual gas forwarded to the callee can fall below `MAX_PAYER_CALLBACK_GAS`. An honest callee may perform incorrect logic under the assumption of available gas. One can refer to Optimism's CrossDomainMessenger, which adds explicit buffer constants (`RELAY_GAS_CHECK_BUFFER` and `RELAY_CALL_OVERHEAD`) for this exact reason. - -## Recommended Mitigation - -Add explicit buffer constants to the precheck so that the comparison accounts for the CALL/STATICCALL cost and the intervening Solidity overhead. Size the buffer so that at least `MAX_PAYER_CALLBACK_GAS` is forwarded to the callee when the check passes. - -## Team Response - -Added `CALLBACK_GAS_OVERHEAD = 3_000` constant. All three prechecks now use: - -```solidity -if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) - revert RecurringCollectorInsufficientCallbackGas(); -``` - -Sized to cover the worst-case pre-opcode cost. The eligibility STATICCALL is the first access to the payer account on the collect path, so the EIP-2929 cold-account access cost (2_600) dominates; the remaining headroom covers `abi.encodeCall` and stack/memory setup. Subsequent `beforeCollection` / `afterCollection` calls hit the payer warm (100 gas access), so the buffer is generous there. - -Follows the Optimism buffer-constant pattern as suggested. - -## Mitigation Review - -Issue has been addressed as suggested. Worst-case scenario tests should be introduced to ensure the defined overhead constant is sufficient in future builds. - ---- - -Boundary regression tests added on both sides of the precheck: warm path in Foundry, cold path in Hardhat (foundry/REVM does not apply EIP-2929 cold-access cost in this config, so a separate Hardhat test exercises real cold access). Sabotage-verified. diff --git a/packages/issuance/audits/PR1301/TRST-L-9.md b/packages/issuance/audits/PR1301/TRST-L-9.md deleted file mode 100644 index 1b9ba60d5..000000000 --- a/packages/issuance/audits/PR1301/TRST-L-9.md +++ /dev/null @@ -1,33 +0,0 @@ -# TRST-L-9: EIP-7702 payer code change enables callback gas griefing after acceptance - -- **Severity:** Low -- **Category:** Type confusion -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -Under EIP-7702, which is live on Ethereum mainnet and Arbitrum, an EOA can install arbitrary code via a delegation transaction. `_preCollectCallbacks()` and `_postCollectCallback()` dispatch the `beforeCollection()` and `afterCollection()` callbacks only when `payer.code.length != 0`. A payer who accepted an agreement as an EOA can later acquire code, and have the callbacks dispatched against delegated code that the service provider never considered at acceptance time. - -The callbacks are low level calls with a `MAX_PAYER_CALLBACK_GAS` budget, and they are vulnerable to the returndata bombing vector described in TRST-M-4, on top of the baseline call costs. Service providers estimate gas for `collect()` under the assumption that the payer is an EOA with no callbacks. If the payer is a contract at collection time, the provider's gas estimate may be insufficient and the transaction will revert with griefed gas. This is a distinct attack surface from TRST-H-4, which targeted the eligibility gate rather than the callback path. - -## Recommended Mitigation - -Use the introduced `CONDITION_ELIGIBILITY_CHECK` flag in place of the live `code.length` check in `_preCollectCallbacks()` and `_postCollectCallback()`. This freezes the contract-versus-EOA determination to the state the service provider observed at acceptance. - -## Team Response - -Reusing `CONDITION_ELIGIBILITY_CHECK` for callback dispatch avoided because the eligibility checking is a different concern with different trust assumptions. An agreement can legitimately have one without the other. - -Introduced `CONDITION_AGREEMENT_OWNER` flag that mirrors the eligibility pattern: - -- `_requirePayerInterfaceSupport` validates `IERC165(payer).supportsInterface(type(IAgreementOwner).interfaceId)` if the flag is set, alongside the existing eligibility check. -- `_preCollectCallbacks` and `_postCollectCallback` dispatch on `agreement.conditions & CONDITION_AGREEMENT_OWNER`, replacing the `payer.code.length` check. - -## Mitigation Review - -The root cause has been addressed. It is recommended to document that turning `CONDITION_ELIGIBILITY_CHECK` on an EOA is considered fully trusting it as it can upgrade to a contract that reverts the eligibility check. - ---- - -Expanded the NatSpec on `CONDITION_ELIGIBILITY_CHECK` in `RecurringCollector.sol`: the flag trusts the payer to apply correct eligibility logic in `isEligible()`. The acceptance-time interface check excludes a pure EOA, but a payer that did pass (contract, or EOA with a 7702 delegation at that moment) can later answer dishonestly and block collection. diff --git a/packages/issuance/audits/PR1301/TRST-M-1.md b/packages/issuance/audits/PR1301/TRST-M-1.md deleted file mode 100644 index bff9801e3..000000000 --- a/packages/issuance/audits/PR1301/TRST-M-1.md +++ /dev/null @@ -1,42 +0,0 @@ - - -# TRST-M-1: Micro-thaw griefing via permissionless depositTo() and reconcileAgreement() - -- **Severity:** Medium -- **Category:** Griefing attacks -- **Source:** RecurringAgreementManager.sol -- **Status:** Fixed - -## Description - -Three independently benign features combine into a griefing vector: - -1. `PaymentsEscrow.depositTo()` has no access control - anyone can deposit any amount for any (payer, collector, receiver) tuple. -2. `reconcileAgreement()` is permissionless - anyone can trigger a reconciliation which calls `_updateEscrow()`. -3. `PaymentsEscrow.adjustThaw()` with `evenIfTimerReset=false` is a no-op when increasing the thaw amount would reset the thawing timer. - -An attacker deposits 1 wei into an escrow account via `depositTo()`, then calls `reconcileAgreement()`. The reconciliation detects escrow is 1 wei above target and initiates a thaw of 1 wei via `adjustThaw()`. This starts the thawing timer. When the RAM later needs to thaw a larger amount (e.g., after an agreement ends or is updated), it calls `adjustThaw()` with `evenIfTimerReset=false`, which becomes a no-op because increasing the thaw would reset the timer. - -In cases where thaws are needed to mobilize funds from one escrow pair to another - for example, to fund a new agreement or agreement update for a different provider - this griefing prevents the rebalancing. New agreements or updates that require escrow from the blocked pair's thawed funds could fail to be properly funded, causing escrow mode degradation or preventing the offers entirely. - -## Recommended Mitigation - -Add a minimum thaw threshold in `_updateEscrow()`. Amounts below the threshold should be ignored rather than initiating a thaw. This prevents an attacker from starting a thaw timer with a dust amount. If they do perform the attack, they will donate a non-negligible amount in exchange for the one-round block. - -## Team Response - -Fixed. - -## Mitigation Review - -The griefing path remains reachable. Before any agreement is offered, a 1 wei donation to the (collector, provider) escrow account, followed by a permissionless call to `_reconcilePairTracking()` reaches `_updateEscrow()` with min and max at zero, and the thaw threshold is also at zero. Any positive excess passes the `thawThreshold <= excess` check, causing an `adjustThaw(thawTarget = 1)`. The same sequence also occurs after the final collection of an agreement, when `sumMaxNextClaim` transitions to zero via `afterCollection()` -> `_reconcileAndUpdateEscrow()` -> `_reconcileAgreement()`. There should be a nominal, non-negligible minimum thaw amount on top of the fraction check, applied in both `_reconcileProviderEscrow()` and `_withdrawAndRebalance()`. When `escrowBasis` is JustInTime, override the nominal skip so that dust can still be thawed out for solvency. - -## Team Response - -The zero-threshold path when `sumMaxNextClaim = 0` is acknowledged. Timer resets do not occur (`evenIfTimerReset=false` rejects increases), so the vector is limited to postponing pair tracking cleanup via repeated dust deposits. Added `minResidualEscrowFactor` (uint8, default 50, threshold = 2^value ≈ 0.001 GRT for default): pairs with no agreements and escrow below threshold are dropped from tracking. Untracked pairs can still have escrow drained via blind thaw/withdraw on `reconcileProvider`. - -## Mitigation Review - -The main finding is considered acknowledged. Introduction of `minResidualEscrowFactor` is used only in maintaining of view-related bookkeeping. No new concerns have been introduced. - ---- diff --git a/packages/issuance/audits/PR1301/TRST-M-2.md b/packages/issuance/audits/PR1301/TRST-M-2.md deleted file mode 100644 index df5ca47c6..000000000 --- a/packages/issuance/audits/PR1301/TRST-M-2.md +++ /dev/null @@ -1,32 +0,0 @@ -# TRST-M-2: The tempJit fallback in beforeCollection() is unreachable in practice - -- **Severity:** Medium -- **Category:** Logical flaw -- **Source:** RecurringAgreementManager.sol -- **Status:** Fixed - -## Description - -In `beforeCollection()` (line 236), when the escrow balance is insufficient for an upcoming collection, the function attempts a JIT (Just-In-Time) top-up by setting `$.tempJit = true` before returning. The `tempJit` flag forces `_escrowMinMax()` to return JustInTime mode, freeing escrow from other pairs to fund this collection. - -However, the JIT path is only entered when the escrow is insufficient to cover `tokensToCollect`. In the `RecurringCollector._collect()` flow, `beforeCollection()` is called before `PaymentsEscrow.collect()`. If `beforeCollection()` cannot top up the escrow (because the RAM lacks free balance and the `deficit >= balanceOf()` guard fails), it returns without action. The subsequent `PaymentsEscrow.collect()` then attempts to collect `tokensToCollect` from an escrow that is still insufficient, causing the entire `collect()` transaction to revert. - -This means `tempJit` is never set in the scenario where it would be most needed: when escrow is short and the collection will fail regardless. An admin cannot rely on `tempJit` being triggered automatically during the RecurringCollector collection flow and would need to manually set JIT mode to achieve the intended fallback behavior. This would cause a delay the first time the issue is encountered where presumably there is no reason for admin to intervene. - -## Recommended Mitigation - -The original intention cannot be truly fulfilled without major redesign of multiple contracts. It is in practice more advisable to take the scenario into account and introduce an off-chain monitoring bot which would set the `tempJit` when needed. - -## Team Response - -Fixed. - -## Mitigation Review - -The new setup is schematically sound. Admin intervention to trigger JustInTime may still be required to satisfy requests when the system is in OnDemand but insufficient liquidity is being thawed or minted into the contract. - ---- - -The `tempJit` mechanism has been replaced with threshold-based basis degradation. - -`_escrowMinMax()` now uses `minOnDemandBasisThreshold` and `minFullBasisMargin` parameters to automatically limit the effective escrow basis based on the ratio of spare balance to `sumMaxNextClaimAll`. This does not rely on a callback to activate and provides automatic, configurable transition boundaries. diff --git a/packages/issuance/audits/PR1301/TRST-M-3.md b/packages/issuance/audits/PR1301/TRST-M-3.md deleted file mode 100644 index 7654bbe6c..000000000 --- a/packages/issuance/audits/PR1301/TRST-M-3.md +++ /dev/null @@ -1,28 +0,0 @@ -# TRST-M-3: Instant escrow mode degradation from Full to OnDemand via agreement offer - -- **Severity:** Medium -- **Category:** Logical flaw -- **Source:** RecurringAgreementManager.sol -- **Status:** Acknowledged - -## Description - -Neither `offerAgreement()` nor `offerAgreementUpdate()` verify that the RAM has sufficient token balance to fund the new escrow obligation without degrading the escrow mode. An operator can offer an agreement whose `maxNextClaim`, when added to the existing `sumMaxNextClaim`, causes `totalEscrowDeficit` to exceed the RAM's balance. This instantly degrades the escrow mode from Full to OnDemand for ALL (collector, provider) pairs. - -The degradation occurs because `_escrowMinMax()` checks: `totalEscrowDeficit < balanceOf(address(this))`. When the new agreement pushes the deficit above the balance, this condition becomes false, and `min` drops to 0 for every pair - meaning no proactive deposits are made for any agreement, not just the new one. Existing providers who had fully-escrowed agreements silently lose their escrow guarantees. - -Whether intentional or by misfortune, this behavior can be triggered instantly by a single offer. If this degradation is desirable in some cases, it should only occur by explicit intention, not as a side effect of a routine operation. - -## Recommended Mitigation - -Add a separate configuration flag (e.g., `allowModeDegradation`) that must be explicitly set by the admin to permit offers that would degrade the escrow mode. When the flag is false, `offerAgreement()` and `offerAgreementUpdate()` should revert if the new obligation would push `totalEscrowDeficit` above the current balance. This ensures mode degradation is always a conscious decision. - -## Team Response - -Acknowledged. The risk is documented, including the operator caution about pre-offer headroom checks. - ---- - -Acknowledged. The risk is documented in [RecurringAgreementManager.md — Automatic Degradation](../../contracts/agreement/RecurringAgreementManager.md#automatic-degradation), including the operator caution about pre-offer headroom checks. - -An on-chain guard was prototyped but added ~2.7KB to the contract, exceeding the Spurious Dragon 24576-byte limit. The operator (AGREEMENT_MANAGER_ROLE holder) is a trusted role expected to verify escrow headroom before offering agreements. diff --git a/packages/issuance/audits/PR1301/TRST-M-4.md b/packages/issuance/audits/PR1301/TRST-M-4.md deleted file mode 100644 index 128055ad9..000000000 --- a/packages/issuance/audits/PR1301/TRST-M-4.md +++ /dev/null @@ -1,33 +0,0 @@ -# TRST-M-4: Returndata bombing via payer callbacks in \_preCollectCallbacks and \_postCollectCallback - -- **Severity:** Medium -- **Category:** Gas-related issues -- **Source:** RecurringCollector.sol -- **Status:** Fixed - -## Description - -All three payer callbacks reachable from `_collect()` (the eligibility staticcall in `_preCollectCallbacks()` at line 633, the `beforeCollection()` call in the same function at line 646, and the `afterCollection()` call in `_postCollectCallback()` at line 666) use Solidity's default low-level call pattern, which copies the full returndata buffer into the caller's memory. Note that RETURNDATACOPY is emitted even when the returned bytes are discarded via the `(bool ok, )` tuple pattern. - -With a forwarded budget of `MAX_PAYER_CALLBACK_GAS` (1,500,000) per callback, a malicious payer can expand callee memory and return roughly 850 KB of data. The caller's RETURNDATACOPY and the associated memory expansion then consume approximately 1,500,000 gas in the `_collect()` frame for each callback. Across the three callbacks, a single `collect()` call can be forced to burn about 4,500,000 gas beyond the nominal callback budget. - -The impact is an inflated collection cost that is not reflected in off-chain gas estimates. This is gas griefing rather than a collection block, and gas costs remain manageable. - -## Recommended Mitigation - -Replace the affected high-level call sites with inline assembly that performs the call and bounds the amount of returndata copied. For the eligibility check, copy at most 32 bytes into scratch memory and read the result. For `beforeCollection()` and `afterCollection()`, copy zero bytes since the return value is unused. - -## Team Response - -Replaced all three call sites with inline assembly that bounds returndata copy: - -- **Eligibility staticcall**: copies at most 32 bytes into scratch space (0x00), reads the `uint256` result from there. -- **beforeCollection / afterCollection**: copy 0 bytes (`retSize=0`), only the `bool success` from the CALL opcode is used. - -This prevents a malicious payer from forcing RETURNDATACOPY of ~850 KB per callback. - -## Mitigation Review - -Issue has been addressed as suggested. - ---- diff --git a/packages/issuance/audits/PR1301/TRST-R-1.md b/packages/issuance/audits/PR1301/TRST-R-1.md deleted file mode 100644 index 5f1457f71..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-1.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-1: Avoid redeployment of the RewardsEligibilityOracle by restructuring storage - -- **Severity:** Recommendation - -## Description - -The modified RewardsEligibilityOracle has two new state variables, as well as moving `eligibilityValidationEnabled` from the original slot to the end of the structure. Due to the relocation, an upgrade is needed, meaning all previous eligibility state will be lost. It is possible to only append storage slots to the original structure, and avoid a hard redeployment flow, by leveraging the upgradeability of the oracle. - ---- - -Acknowledged. The oracle is not yet deployed to production so the storage restructuring does not lose live state. The current layout preserves clean append-only expansion for future upgrades. diff --git a/packages/issuance/audits/PR1301/TRST-R-10.md b/packages/issuance/audits/PR1301/TRST-R-10.md deleted file mode 100644 index e1d200ba7..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-10.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-10: Document role-change semantics for existing agreements - -- **Severity:** Recommendation - -## Description - -Changes to `DATA_SERVICE_ROLE` and `COLLECTOR_ROLE` on the RecurringAgreementManager do not affect agreements that have already been offered or accepted through the previously authorized addresses. This is by design (revoking a role should not invalidate settled obligations), but the behavior is not documented. Record this invariant in the RAM documentation so that operators and integrators understand the effect of role changes. - ---- - -Documented in the `RecurringAgreementManager` contract header: role changes are not retroactive — revoking `COLLECTOR_ROLE` or `DATA_SERVICE_ROLE` does not invalidate tracked agreements, which continue to reconcile to orderly settlement. Role checks gate only new `offerAgreement` calls and discovery inside `_reconcileAgreement`. diff --git a/packages/issuance/audits/PR1301/TRST-R-11.md b/packages/issuance/audits/PR1301/TRST-R-11.md deleted file mode 100644 index f8169c789..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-11.md +++ /dev/null @@ -1,15 +0,0 @@ -# TRST-R-11: Remove or implement unused state flags in IAgreementCollector - -- **Severity:** Recommendation - -## Description - -`IAgreementCollector` defines state flag constants that are not currently used in the RecurringCollector implementation, including `NOTICE_GIVEN`, `SETTLED`, `BY_PAYER`, `BY_PROVIDER`, `BY_DATA_SERVICE`, `AUTO_UPDATE`, and `AUTO_UPDATED`. Unused public interface constants are a source of confusion for integrators, who may code against documented semantics that the implementation does not honor. Either remove the unused flags from the interface, or implement the behaviors they describe in the collector. - ---- - -Removed unused flags: `AUTO_UPDATE`, `AUTO_UPDATED`, `BY_DATA_SERVICE`, `WITH_NOTICE` and `IF_NOT_ACCEPTED` are dropped from the interface. - -NatSpec updated for remaining flags with new semantics. - -In RecurringCollector `NOTICE_GIVEN`, `SETTLED`, `BY_PAYER`, `BY_PROVIDER` are now set by `getAgreementDetails` to describe cancel origin and collectability (see TRST-R-12 fix). diff --git a/packages/issuance/audits/PR1301/TRST-R-12.md b/packages/issuance/audits/PR1301/TRST-R-12.md deleted file mode 100644 index 834cb66e8..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-12.md +++ /dev/null @@ -1,17 +0,0 @@ -# TRST-R-12: Document ACCEPTED state returned for cancelled agreements - -- **Severity:** Recommendation - -## Description - -In `getAgreementDetails()`, any agreement whose state is not `AgreementState.NotAccepted` is reported with state flag `ACCEPTED`. This includes agreements that have been cancelled (`CanceledByPayer` or `CanceledByServiceProvider`). Integrators inspecting the returned state cannot distinguish cancelled agreements from live ones without reading separate storage. Document this behavior in the interface, or extend the state bitmask with a `CANCELED` flag and return it for the non-active terminal states. - ---- - -Reusing the existing interface flags instead of adding a `CANCELED` flag. `getAgreementDetails` now composes cancel and collectability information: - -- `NOTICE_GIVEN` — set on cancelled agreements (collection window truncated). -- `BY_PAYER` / `BY_PROVIDER` — paired with `NOTICE_GIVEN` to identify the cancel origin. -- `SETTLED` — per-version: set when nothing is claimable under the queried version's terms (active claim for `VERSION_CURRENT`, pending claim for `VERSION_NEXT`). - -`ACCEPTED` is also narrowed: it is now only set on the active-slot version (`VERSION_CURRENT`) of agreements past `NotAccepted`, so pending updates (`VERSION_NEXT`) no longer report `ACCEPTED`. Integrators distinguish cancelled-vs-live by `NOTICE_GIVEN`, and stop-collecting-now via `SETTLED`. See the TRST-R-11 fix for the accompanying flag cleanup. diff --git a/packages/issuance/audits/PR1301/TRST-R-13.md b/packages/issuance/audits/PR1301/TRST-R-13.md deleted file mode 100644 index cefb73ec0..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-13.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-13: Document reclaim reason change for stale allocation force-close - -- **Severity:** Recommendation - -## Description - -Before the PR's refactor, `forceCloseStaleAllocation()` closed the allocation via `_closeAllocation()` and caused a reclaim with reason `CLOSE_ALLOCATION`. Post refactor, the force close path goes through `_resizeAllocation(allocationId, 0, ...)`, which triggers a reclaim with reason `STALE_POI` instead. The reclaim still occurs, but the reason code exposed to reclaim address configuration changes. Document this change so that operators are able to prepare accordingly and have funding paths line up with intention. - ---- - -Noted. The previous `CLOSE_ALLOCATION` reclaim behavior for this path has not shipped to production, so there is no live operator configuration to migrate. `STALE_POI` is the correct reason for the post-refactor semantics (the allocation is stale; it stays open as stakeless rather than closing). diff --git a/packages/issuance/audits/PR1301/TRST-R-14.md b/packages/issuance/audits/PR1301/TRST-R-14.md deleted file mode 100644 index ef6db21c2..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-14.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-14: Avoid magic numbers in production code - -- **Severity:** Recommendation - -## Description - -In the RecurringAgreementManager, `_getAgreementProvider()` calls `getAgreementDetails()` passing `0` as `index`. While in previous this was not used on the RecurringCollector, it is now handled as `VERSION_CURRENT` for correct logic of the collector. Consider using the constant from `IAgreementCollector` for futureproofing of the contract and clarifying the intention. - ---- - -Imported `VERSION_CURRENT` from `IAgreementCollector` and substituted it for the literal `0` at the single call site in `_getAgreementProvider`. diff --git a/packages/issuance/audits/PR1301/TRST-R-2.md b/packages/issuance/audits/PR1301/TRST-R-2.md deleted file mode 100644 index a9a30ff54..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-2.md +++ /dev/null @@ -1,14 +0,0 @@ -# TRST-R-2: Improve stale documentation - -- **Severity:** Recommendation - -## Description - -The functions below are mentioned in various documentation files but do not exist in the current codebase: - -- `acceptUnsignedIndexingAgreement()` -- `removeAgreement()` - ---- - -Updated documentation to remove references to `acceptUnsignedIndexingAgreement()` and `removeAgreement()`. diff --git a/packages/issuance/audits/PR1301/TRST-R-3.md b/packages/issuance/audits/PR1301/TRST-R-3.md deleted file mode 100644 index 0e012a072..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-3.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-3: Incorporate defensive coding best practices - -- **Severity:** Recommendation - -## Description - -In the RAM's `cancelAgreement()` function, the agreement state is required to not be not accepted. However, the logic could be more specific and require the agreement to be Accepted - rejecting previously cancelled agreements. There is no impact because corresponding checks in the RecurringCollector would deny such cancels, but it remains as a best practice. - ---- - -Fixed. The RAM's `cancelAgreement()` was refactored into a pass-through to `collector.cancel()`, which requires `agreement.state == AgreementState.Accepted` before proceeding. The defensive guard now lives in the single authoritative location for agreement state. diff --git a/packages/issuance/audits/PR1301/TRST-R-4.md b/packages/issuance/audits/PR1301/TRST-R-4.md deleted file mode 100644 index 7947adbdc..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-4.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-4: Document critical assumptions in the RAM - -- **Severity:** Recommendation - -## Description - -The `approveAgreement()` view checks if the agreement hash is valid, however it offers no replay protection for repeated agreement approvals. This attack vector is only stopped at the RecurringCollector as it checks the agreement does not exist and maintains unidirectional transitions from the agreement Accepted state. For future collectors this may not be the case, necessitating clear documentation of the assumption. - ---- - -Documented in the `RecurringAgreementManager` contract header (collector-trust section): collectors own agreement uniqueness, replay protection, and state transitions; RAM does not re-check them. diff --git a/packages/issuance/audits/PR1301/TRST-R-5.md b/packages/issuance/audits/PR1301/TRST-R-5.md deleted file mode 100644 index 0db3ff607..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-5.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-5: Ambiguous return value in getAgreementOfferAt() - -- **Severity:** Recommendation - -## Description - -`getAgreementOfferAt()` returns `(uint8 offerType, bytes memory offerData)`. The offer type constant `OFFER_TYPE_NEW` is defined as 0, which is also the default Solidity return value when no stored offer exists for the given `agreementId` and index. A caller receiving `offerType == 0` cannot distinguish between a stored new-type offer existing and no offer existing. Consider redefining offer type constants with 1-indexed values, or adding an explicit `bool found` return parameter. - ---- - -Using non-zero offer type constants as suggested: `OFFER_TYPE_NEW = 1`, `OFFER_TYPE_UPDATE = 2`. The zero value is declared explicitly as `OFFER_TYPE_NONE` so the "no stored offer" sentinel is part of the interface rather than a NatSpec-only convention. diff --git a/packages/issuance/audits/PR1301/TRST-R-6.md b/packages/issuance/audits/PR1301/TRST-R-6.md deleted file mode 100644 index 46215cc6b..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-6.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-6: Dead code guard in \_validateAndStoreUpdate() - -- **Severity:** Recommendation - -## Description - -In `_validateAndStoreUpdate()` (line 855), the guard `if (oldHash != bytes32(0))` is unreachable as a false branch. Only agreements in the Accepted state may be updated, and every accepted agreement has a non-zero `activeTermsHash` written during `accept()` or a prior `update()`. The guard can be removed or converted into an invariant comment documenting this assumption. - ---- - -Fixed. Removed the dead `if (oldHash != bytes32(0))` guard. Also dropped the unreachable `else if` for `rcauOffers` cleanup — `oldHash` can only survive in `rcaOffers` (from `accept()`), since `update()` always overwrites `rcauOffers` with the new RCAU hash before this point. diff --git a/packages/issuance/audits/PR1301/TRST-R-7.md b/packages/issuance/audits/PR1301/TRST-R-7.md deleted file mode 100644 index 65f7ae98c..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-7.md +++ /dev/null @@ -1,13 +0,0 @@ -# TRST-R-7: Remove consumed offers in accept() and update() - -- **Severity:** Recommendation - -## Description - -After `accept()` or `update()` consumes a stored offer, the corresponding entry in `rcaOffers` or `rcauOffers` becomes stale. Currently only `_validateAndStoreUpdate()` cleans up the previously active offer by looking up the old `activeTermsHash`; the offer whose terms were just accepted is not deleted. This is a storage hygiene concern: stale offer entries remain in storage indefinitely until explicitly replaced or matched by a future update. Consider deleting the consumed offer entry inside `accept()` and `update()` after it has been applied. - ---- - -Keeping consumed offers in storage is by design — offer data (including metadata, nonce, deadline) remains accessible on-chain via `getAgreementOfferAt()` until the terms are obsolete. Stale entries are cleaned up by `_validateAndStoreUpdate()` on the next update, overwritten by a new `offer()`, or removed by `cancel()`. Eagerly deleting on consumption would lose data that callers may still want to inspect. - -(Cleanup handling will be improved in combination with the response to TRST-L-11.) diff --git a/packages/issuance/audits/PR1301/TRST-R-8.md b/packages/issuance/audits/PR1301/TRST-R-8.md deleted file mode 100644 index 821e84823..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-8.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-8: Align pause documentation with callback behavior in the RAM - -- **Severity:** Recommendation - -## Description - -The RecurringAgreementManager documentation header states that pausing the contract "stops all permissionless escrow management". In practice, the `whenNotPaused` modifier also applies to `beforeCollection()` and `afterCollection()`, so pause also halts the callback path used during `collect()`. Update the documentation to reflect that callbacks are affected, or narrow the modifier application so that behavior matches the prose. - ---- - -Updated in the `RecurringAgreementManager` contract header: pause is described as blocking permissionless state changes "including collection callbacks and reconciliation", with a cross-reference to the existing cross-contract note describing the resulting escrow-accounting drift. diff --git a/packages/issuance/audits/PR1301/TRST-R-9.md b/packages/issuance/audits/PR1301/TRST-R-9.md deleted file mode 100644 index efa601a43..000000000 --- a/packages/issuance/audits/PR1301/TRST-R-9.md +++ /dev/null @@ -1,11 +0,0 @@ -# TRST-R-9: \_isAuthorized() override in RecurringCollector trusts itself for any authorizer - -- **Severity:** Recommendation - -## Description - -The `_isAuthorized(address authorizer, address signer)` override in RecurringCollector returns true whenever `signer == address(this)`, regardless of `authorizer`. This enables RecurringCollector to call `dataService.cancelIndexingAgreementByPayer()` on the payer's behalf. The semantics are safe in the current integration with SubgraphService, but they widen the trust surface: any future consumer that relies on `RecurringCollector.isAuthorized()` for access control will grant access when the signer is the collector itself. Consider tightening the override to scope trust to specific callers, or explicitly document the integration contract so it is not misapplied by future consumers. - ---- - -Added a `@custom:security` note at the `RecurringCollector` contract header: self-authorization requires the collector itself to perform the appropriate authorization check before any external call. diff --git a/packages/issuance/audits/PR1301/TRST-SR-1.md b/packages/issuance/audits/PR1301/TRST-SR-1.md deleted file mode 100644 index 1902b2ffd..000000000 --- a/packages/issuance/audits/PR1301/TRST-SR-1.md +++ /dev/null @@ -1,15 +0,0 @@ -# TRST-SR-1: JIT mode provider payment race condition - -- **Severity:** Systemic Risk - -## Description - -When the RecurringAgreementManager operates in JustInTime (JIT) escrow mode, escrow is not proactively funded for any (collector, provider) pair. Instead, funds are deposited into escrow only during the `beforeCollection()` callback, moments before `PaymentsEscrow.collect()` executes. Since the RAM holds a shared pool of GRT that backs all agreements, multiple providers collecting around the same time are effectively racing for the same pool of tokens. - -If the RAM's balance is sufficient to cover any single collection but not all concurrent collections, the provider whose data service submits the `collect()` transaction first will succeed, while subsequent providers' collections will revert because the RAM's balance has been depleted by the first collection's JIT deposit. This creates a first-come-first-served dynamic where providers must compete on transaction ordering to receive payment. - -This race condition is inherent to the JIT mode design and cannot be fully eliminated without proactive escrow funding. In extreme cases, a well-resourced provider could use priority gas auctions or private mempools to consistently front-run other providers' collections, creating an unfair payment advantage unrelated to service quality. - ---- - -Known architectural tradeoff. Full mode eliminates this entirely; OnDemand reduces its likelihood. JIT provides best-effort payment guarantees and is the fallback when the RAM's balance cannot sustain proactive escrow funding. diff --git a/packages/issuance/audits/PR1301/TRST-SR-2.md b/packages/issuance/audits/PR1301/TRST-SR-2.md deleted file mode 100644 index 5ad078675..000000000 --- a/packages/issuance/audits/PR1301/TRST-SR-2.md +++ /dev/null @@ -1,15 +0,0 @@ -# TRST-SR-2: Escrow thawing period creates prolonged fund immobility - -- **Severity:** Systemic Risk - -## Description - -The PaymentsEscrow thawing period (configurable up to `MAX_WAIT_PERIOD`, 90 days) creates a window during which escrowed funds are immobile. When the RAM needs to rebalance escrow across providers - for example, after an agreement ends and funds should be redirected to a new agreement - the thawing delay prevents immediate reallocation. During this window, the RAM effectively has reduced capacity. - -If multiple agreements end in a short period or the escrow mode degrades from Full to OnDemand, the RAM may enter a state where substantial funds are locked in thawing and unavailable for either existing or new obligations. This is compounded by the micro-thaw griefing vector (TRST-M-1), which can extend the immobility period by blocking thaw increases. - -The thawing period is a protocol-level parameter set on PaymentsEscrow and is outside the RAM's control. Changes to this parameter affect all users of the escrow system, not just the RAM. - ---- - -The thawing period protects providers from instant escrow drainage after service delivery. The minThawFraction fix (TRST-M-1) reduces griefing amplification and the snap-refresh fix (TRST-H-3) ensures accurate deficit tracking during rebalancing. The fundamental constraint is a protocol-level design decision outside the RAM's scope. diff --git a/packages/issuance/audits/PR1301/TRST-SR-3.md b/packages/issuance/audits/PR1301/TRST-SR-3.md deleted file mode 100644 index 91a3a71fc..000000000 --- a/packages/issuance/audits/PR1301/TRST-SR-3.md +++ /dev/null @@ -1,15 +0,0 @@ -# TRST-SR-3: Issuance distribution dependency for RAM solvency - -- **Severity:** Systemic Risk - -## Description - -The RAM relies on periodic issuance distribution (via the issuance allocator) to receive GRT tokens for funding escrow obligations. If the issuance system experiences delays, governance disputes, or contract upgrades that temporarily halt distributions, the RAM's free balance depletes as collections drain escrow without replenishment. - -Once the free balance reaches zero, the RAM cannot fund JIT top-ups in `beforeCollection()`, cannot proactively deposit in Full mode for new agreements, and existing escrow accounts gradually drain with each collection. Prolonged issuance interruption could cascade into escrow mode degradation (Full -> OnDemand -> JIT), ultimately affecting all providers' payment reliability. - -This is an external dependency that the RAM admin cannot mitigate beyond maintaining a buffer balance. - ---- - -Acknowledged. The RAM maintains a buffer balance and the escrow degradation mechanism (Full → OnDemand → JIT) provides graceful fallback. Issuance interruptions are visible on-chain, allowing operators to respond before provider payments are affected. diff --git a/packages/issuance/audits/PR1301/TRST-SR-4.md b/packages/issuance/audits/PR1301/TRST-SR-4.md deleted file mode 100644 index e9502f2ec..000000000 --- a/packages/issuance/audits/PR1301/TRST-SR-4.md +++ /dev/null @@ -1,21 +0,0 @@ -# TRST-SR-4: Try/catch callback pattern silently degrades state consistency - -- **Severity:** Systemic Risk - -## Description - -The RecurringCollector wraps all payer callbacks (`beforeCollection()`, `afterCollection()`) in try/catch blocks. While this design prevents malicious or buggy payer contracts from blocking collection, it means that any revert in these callbacks is silently discarded. The collection proceeds as if the callback succeeded, but the payer's internal state (escrow snapshots, deficit tracking, reconciliation) may not have been updated. - -This creates a systemic tension: the try/catch is necessary for liveness (ensuring providers can collect), but it trades state consistency for availability. Over time, if callbacks fail repeatedly (due to gas issues, contract bugs, or the stale snapshot issue in TRST-H-3), the divergence between the RAM's internal accounting and the actual escrow state can compound silently with no on-chain signal. - -There is no event emitted when a callback fails, making it difficult for off-chain monitoring to detect and respond to these silent failures. - -## Team Response - -TBD - ---- - -Non-reverting callbacks are intentional — collector liveness takes priority over payer state updates. Callbacks now use low-level `call`/`staticcall` with gas caps instead of try/catch. The snap-refresh fix (TRST-H-3) ensures the next successful `_reconcileProviderEscrow` call self-corrects any divergence. Permissionless `reconcileAgreement` and `reconcileProvider` provide external recovery paths. - -Failed callbacks emit `PayerCallbackFailed(agreementId, payer, stage)` with a `PayerCallbackStage` enum (`EligibilityCheck`, `BeforeCollection`, `AfterCollection`), giving off-chain monitoring a signal to detect failures and trigger reconciliation. diff --git a/packages/issuance/contracts/allocate/IssuanceAllocator.todo.md b/packages/issuance/contracts/allocate/IssuanceAllocator.todo.md new file mode 100644 index 000000000..d1e1ccea9 --- /dev/null +++ b/packages/issuance/contracts/allocate/IssuanceAllocator.todo.md @@ -0,0 +1,14 @@ +# IssuanceAllocator.sol — pending updates + +Tracking pending edits noted but deferred to avoid touching contract source until the next maintenance window. + +## NatSpec drift after the Self-Minting Accumulation fix + +PR #1334 made `_advanceSelfMintingBlock` accumulate `selfMintingOffset` unconditionally and collapsed `_distributeIssuance` into a single `_distributePendingIssuance` path. Two docstrings still describe the pre-fix behaviour: + +- `_advanceSelfMintingBlock` (~L364–371): drop the "When paused, accumulates…" framing. Lead with the unconditional invariant; keep the pause case as the state where accumulation persists past the transaction. +- `distributeIssuance` "Pause behavior" block (~L346–351): drop the "Normal distribution if no accumulated self-minting, otherwise retroactive" split. Every unpaused distribution flows through `_distributePendingIssuance` using current rates over the undistributed range with `selfMintingOffset` as the budget bound. + +## Duplicate `_advanceSelfMintingBlock` call in `_distributeIssuance` + +`_distributeIssuance` (~L417) and `_distributePendingIssuance` (~L453) both call `_advanceSelfMintingBlock()`. Second call is a no-op in the steady-state path but still costs a function-call + SLOAD + compare. Drop the call in `_distributeIssuance` or split `_distributePendingIssuance`. diff --git a/packages/issuance/contracts/common/BaseUpgradeable.todo.md b/packages/issuance/contracts/common/BaseUpgradeable.todo.md new file mode 100644 index 000000000..6cbfa38b4 --- /dev/null +++ b/packages/issuance/contracts/common/BaseUpgradeable.todo.md @@ -0,0 +1,11 @@ +# BaseUpgradeable.sol — pending updates + +Tracking pending edits noted but deferred to avoid touching contract source until the next maintenance window. When picking up, drop the corresponding entry below as part of the same commit. + +## Unused `MILLION` constant + +`uint256 public constant MILLION = 1_000_000;` (around L37) has no in-repo references. The only mentions of `MILLION` anywhere under `packages/` are this declaration and its own docstring example. + +It's `public`, so removing it is technically an ABI change on every `BaseUpgradeable` descendant (each currently exposes a `MILLION()` getter). + +- **Delete.** Drop the constant and its docstring. Confirm no external caller depends on the getter — a quick GitHub code search across consumers (indexer-rs, eligibility-oracle-node, etc.) should be enough; ABI hash will change on any contract inheriting BaseUpgradeable. diff --git a/packages/issuance/contracts/eligibility/mocks/MockRewardsEligibilityOracle.sol b/packages/issuance/contracts/eligibility/mocks/MockRewardsEligibilityOracle.sol new file mode 100644 index 000000000..7d06ba768 --- /dev/null +++ b/packages/issuance/contracts/eligibility/mocks/MockRewardsEligibilityOracle.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.27; + +import { BaseUpgradeable } from "../../common/BaseUpgradeable.sol"; +import { IGraphToken } from "../../common/IGraphToken.sol"; + +/// @title MockRewardsEligibilityOracle +/// @author The Graph Contributors +/// @notice Testnet REO replacement. Indexers control their own eligibility. +/// @dev Everyone starts eligible. Call setEligible(false) to become ineligible. +/// Upgradeable via OZ TransparentUpgradeableProxy for deployment consistency. +contract MockRewardsEligibilityOracle is BaseUpgradeable { + mapping(address indexer => bool isIneligible) private ineligible; + + /// @notice Emitted when an indexer changes their eligibility. + /// @param indexer The indexer address. + /// @param eligible Whether the indexer is now eligible. + event EligibilitySet(address indexed indexer, bool indexed eligible); + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(IGraphToken graphToken) BaseUpgradeable(graphToken) {} + + /// @notice Initialize the contract. + /// @param governor Address that will have the GOVERNOR_ROLE. + function initialize(address governor) external initializer { + __BaseUpgradeable_init(governor); + } + + /// @notice Toggle the caller's eligibility. + /// @param eligible True to be eligible, false to opt out. + function setEligible(bool eligible) external { + ineligible[msg.sender] = !eligible; + emit EligibilitySet(msg.sender, eligible); + } + + /// @notice Check whether an indexer is eligible for rewards. + /// @dev Called by RewardsManager to check eligibility. + /// @param indexer The indexer address to check. + /// @return True if the indexer is eligible. + function isEligible(address indexer) external view returns (bool) { + return !ineligible[indexer]; + } + + /// @notice ERC165 interface detection. + /// @dev Supports IProviderEligibility (0x66e305fd) and inherited interfaces. + /// @param interfaceId The interface identifier to check. + /// @return True if the interface is supported. + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return interfaceId == 0x66e305fd || super.supportsInterface(interfaceId); + } +} diff --git a/packages/issuance/docs/testing/reo/BaselineTestPlan.md b/packages/issuance/docs/testing/reo/BaselineTestPlan.md new file mode 100644 index 000000000..7c4377e6a --- /dev/null +++ b/packages/issuance/docs/testing/reo/BaselineTestPlan.md @@ -0,0 +1,811 @@ +# Indexer Baseline Test Plan: Post-Upgrade Verification + +> **Navigation**: [← Back to REO Testing](README.md) + +This test plan validates that indexers can perform standard operational cycles on The Graph Network after a protocol upgrade. It is upgrade-agnostic and covers the core indexer workflows that must function correctly regardless of what changed. + +Each test includes CLI commands, GraphQL verification queries against the network subgraph, and pass/fail criteria. + +> All GraphQL queries run against the network subgraph. All addresses must be **lowercase**. + +--- + +## Prerequisites + +- ETH and GRT on the target network (testnet or mainnet) +- Indexer stack running (graph-node, indexer-agent, indexer-service, tap-agent) +- Minimum indexer stake met (100k GRT on testnet) +- Access to Explorer UI and network subgraph + +### Recommended log verbosity for troubleshooting + +``` +tap-agent: RUST_LOG=info,indexer_tap_agent=trace +indexer-service: RUST_LOG=info,indexer_service_rs=trace +indexer-agent: INDEXER_AGENT_LOG_LEVEL=trace +``` + +--- + +## Test Sequence Overview + +The tests are organized into 7 cycles. Cycles 1-6 cover individual operations; Cycle 7 ties them together in an end-to-end workflow. + +| Cycle | Area | Tests | +| ----- | ------------------------------ | --------- | +| 1 | Indexer Setup and Registration | 1.1 - 1.3 | +| 2 | Stake Management | 2.1 - 2.2 | +| 3 | Provision Management | 3.1 - 3.4 | +| 4 | Allocation Management | 4.1 - 4.5 | +| 5 | Query Serving and Revenue | 5.1 - 5.4 | +| 6 | Network Health | 6.1 - 6.3 | +| 7 | End-to-End Workflow | 7.1 | + +--- + +## Cycle 1: Indexer Setup and Registration + +### 1.1 Setup indexer via Explorer + +**Objective**: Stake GRT and set delegation parameters through Explorer UI. + +**Steps**: + +1. Navigate to Explorer +2. Stake GRT to your indexer address +3. Set delegation parameters (query fee cut, indexing reward cut) +4. Wait for transaction confirmation + +**Verification Query**: + +```graphql +{ + indexers(where: { id: "INDEXER_ADDRESS" }) { + id + createdAt + stakedTokens + queryFeeCut + indexingRewardCut + } +} +``` + +**Pass Criteria**: + +- Indexer entity exists with correct `stakedTokens` +- `queryFeeCut` and `indexingRewardCut` reflect configured values +- Transaction visible in Explorer history + +--- + +### 1.2 Register indexer URL and GEO coordinates + +**Objective**: Verify indexer metadata registration via the indexer agent. + +**Steps**: + +1. Configure `indexer-agent` with URL and GEO coordinates +2. Start or restart the agent +3. Confirm the agent logs show successful registration + +**Verification Query**: + +```graphql +{ + indexers(where: { id: "INDEXER_ADDRESS" }) { + id + url + geoHash + } +} +``` + +**Pass Criteria**: + +- `url` matches configured value +- `geoHash` is populated +- Agent logs show `Successfully registered indexer` + +--- + +### 1.3 Validate Subgraph Service provision and registration + +**Objective**: Confirm the indexer agent automatically creates a provision and registers with SubgraphService. + +**Steps**: + +1. Ensure indexer has sufficient unallocated stake +2. Start indexer agent +3. Monitor logs for provision creation and registration + +**Verification Query**: + +```graphql +{ + provisions(where: { indexer_: { id: "INDEXER_ADDRESS" } }) { + id + indexer { + id + url + geoHash + } + tokensProvisioned + tokensAllocated + tokensThawing + thawingPeriod + maxVerifierCut + dataService { + id + } + } +} +``` + +**Pass Criteria**: + +- Provision exists for SubgraphService +- `url` and `geoHash` populated in indexer registration +- `tokensProvisioned` is non-zero +- Agent logs show `Successfully provisioned to the Subgraph Service` and `Successfully registered indexer` + +--- + +## Cycle 2: Stake Management + +### 2.1 Add stake via Explorer + +**Objective**: Verify indexers can increase their stake. + +**Steps**: + +1. Navigate to Explorer +2. Add stake to your indexer +3. Wait for transaction confirmation + +**Verification Query**: + +```graphql +{ + indexers(where: { id: "INDEXER_ADDRESS" }) { + id + stakedTokens + allocatedTokens + availableStake + } +} +``` + +**Pass Criteria**: + +- `stakedTokens` increases by the added amount +- Transaction visible in Explorer history + +--- + +### 2.2 Unstake tokens and withdraw after thawing + +**Objective**: Verify the unstake and thawing period workflow. + +**Steps**: + +1. Unstake tokens via Explorer +2. Note the thawing period end time +3. Wait for thawing period to complete +4. Withdraw thawed tokens + +**Verification Query**: + +```graphql +{ + indexers(where: { id: "INDEXER_ADDRESS" }) { + id + stakedTokens + availableStake + } + thawRequests(where: { indexer_: { id: "INDEXER_ADDRESS" } }) { + id + tokens + thawingUntil + type + } +} +``` + +**Pass Criteria**: + +- Thaw request appears with correct token amount +- After thawing period, tokens withdraw successfully +- `stakedTokens` decreases by withdrawn amount + +--- + +## Cycle 3: Provision Management + +### 3.1 View current provision + +**Objective**: Check current Subgraph Service provision status. + +**Command**: + +```bash +graph indexer provisions get +``` + +**Verification Query**: + +```graphql +{ + provisions(where: { indexer_: { id: "INDEXER_ADDRESS" } }) { + id + tokensProvisioned + tokensThawing + tokensAllocated + thawingPeriod + maxVerifierCut + } +} +``` + +**Pass Criteria**: + +- CLI output matches subgraph data +- `tokensProvisioned` shows provisioned stake + +--- + +### 3.2 Add stake to provision + +**Objective**: Increase provision without creating a new one. + +**Command**: + +```bash +graph indexer provisions add +``` + +**Verification Query**: + +```graphql +{ + provisions(where: { indexer_: { id: "INDEXER_ADDRESS" } }) { + id + tokensProvisioned + tokensAllocated + indexer { + stakedTokens + availableStake + } + } +} +``` + +**Pass Criteria**: + +- `tokensProvisioned` increases by the added amount +- `availableStake` decreases correspondingly + +--- + +### 3.3 Thaw stake from provision + +**Objective**: Initiate thawing process to remove stake from provision. + +**Command**: + +```bash +graph indexer provisions thaw +``` + +**Verification Query**: + +```graphql +{ + provisions(where: { indexer_: { id: "INDEXER_ADDRESS" } }) { + id + tokensProvisioned + tokensThawing + } + thawRequests(where: { indexer_: { id: "INDEXER_ADDRESS" }, type: Provision }) { + id + tokens + thawingUntil + } +} +``` + +**Pass Criteria**: + +- `tokensThawing` increases by the thawed amount +- Thaw request created with future `thawingUntil` timestamp + +--- + +### 3.4 Remove thawed stake from provision + +**Objective**: Complete the provision reduction after thawing period. + +**Command**: + +```bash +graph indexer provisions remove +``` + +**Verification Query**: + +```graphql +{ + provisions(where: { indexer_: { id: "INDEXER_ADDRESS" } }) { + id + tokensProvisioned + tokensThawing + } + indexers(where: { id: "INDEXER_ADDRESS" }) { + availableStake + } +} +``` + +**Pass Criteria**: + +- `tokensThawing` decreases to 0 +- `tokensProvisioned` decreases by the removed amount +- `availableStake` increases correspondingly + +--- + +## Cycle 4: Allocation Management + +### 4.1 Find subgraph deployments with rewards + +**Objective**: Identify eligible deployments for allocation. + +**Query**: + +```graphql +{ + subgraphDeployments(where: { deniedAt: 0, signalledTokens_not: 0, indexingRewardAmount_not: 0 }) { + ipfsHash + stakedTokens + signalledTokens + indexingRewardAmount + manifest { + network + } + } +} +``` + +**Action**: Filter results by chains your graph-node can index. + +--- + +### 4.2 Create allocation manually + +**Objective**: Open an allocation for a specific deployment. + +**Command**: + +```bash +graph indexer allocations create +``` + +**Verification Query**: + +```graphql +{ + allocations(where: { indexer_: { id: "INDEXER_ADDRESS" }, status: "Active" }) { + id + allocatedTokens + createdAtEpoch + subgraphDeployment { + ipfsHash + } + } +} +``` + +**Pass Criteria**: + +- Allocation appears with status `Active` +- `allocatedTokens` matches specified amount +- `createdAtEpoch` is current epoch + +--- + +### 4.3 Create allocation via actions queue + +**Objective**: Test the actions queue workflow for allocation management. + +**Commands**: + +```bash +graph indexer actions queue allocate +graph indexer actions execute approve +``` + +**Verification**: Same as 4.2. + +**Pass Criteria**: + +- Action queued successfully +- After approval, allocation appears with status `Active` + +--- + +### 4.4 Create allocation via deployment rules + +**Objective**: Test automated allocation management through rules. + +**Command**: + +```bash +graph indexer rules set allocationAmount allocationLifetime +``` + +**Verification**: Same as 4.2. + +**Pass Criteria**: + +- Indexer agent picks up the rule and creates the allocation automatically +- Set `allocationLifetime` to a small value for quicker testing + +--- + +### 4.5 Reallocate a deployment + +**Objective**: Close and recreate allocation in one operation. + +**Command**: + +```bash +graph indexer allocations reallocate +``` + +**Verification Query**: + +```graphql +{ + allocations( + where: { indexer_: { id: "INDEXER_ADDRESS" }, subgraphDeployment_: { ipfsHash: "DEPLOYMENT_IPFS_HASH" } } + ) { + id + status + allocatedTokens + createdAtEpoch + closedAtEpoch + } +} +``` + +**Pass Criteria**: + +- Old allocation shows status `Closed` +- New allocation created with status `Active` +- New `allocatedTokens` matches specified amount + +--- + +## Cycle 5: Query Serving and Revenue Collection + +> **Cross-reference**: Allocations opened in Cycles 4-5 may also serve as setup for [ReoTestPlan Cycle 6](./ReoTestPlan.md#cycle-6-integration-with-rewards), which tests reward denial/recovery with mature allocations. If running both plans, keep extra allocations open for the REO reward integration tests. + +### 5.1 Send test queries + +**Objective**: Verify the indexer serves queries through the gateway. + +**Script** (save as `query_test.sh`): + +```bash +#!/bin/bash +subgraph_id=${1} +count=${2:-25} +api_key=${3:-"$GRAPH_API_KEY"} +gateway=${4:-"https://gateway.thegraph.com"} + +for ((i=0; i 50 +``` + +**Verification**: + +1. Queries return valid JSON with block data +2. Check indexer-service logs for query processing +3. Check database for TAP receipts: + +```sql +SELECT COUNT(*) FROM tap_horizon_receipts +WHERE allocation_id = ''; +``` + +**Pass Criteria**: + +- Queries succeed with 200 responses +- TAP receipts generated in database + +--- + +### 5.2 Close allocation and collect indexing rewards + +**Objective**: Verify rewards collection on allocation closure. + +**Prerequisites**: Allocation must be several epochs old. Check first: + +```graphql +{ + graphNetworks { + currentEpoch + } + allocations(where: { indexer_: { id: "INDEXER_ADDRESS" }, status: "Active" }) { + id + allocatedTokens + createdAtEpoch + } +} +``` + +**Command**: + +```bash +graph indexer allocations close +``` + +**Verification Query**: + +```graphql +{ + allocations(where: { id: "ALLOCATION_ID" }) { + id + status + allocatedTokens + indexingRewards + closedAtEpoch + } +} +``` + +**Pass Criteria**: + +- Status changes to `Closed` +- `indexingRewards` is non-zero (for deployments with rewards) +- `closedAtEpoch` is current epoch + +--- + +### 5.3 Verify query fee collection + +**Objective**: Confirm query fees collected after allocation closure. + +> Query fee collection happens asynchronously after closure and may take minutes to hours. + +**Verification Query**: + +```graphql +{ + allocations(where: { indexer_: { id: "INDEXER_ADDRESS" }, status: "Closed" }) { + id + queryFeesCollected + closedAtEpoch + } +} +``` + +**Pass Criteria**: + +- `queryFeesCollected` is non-zero for allocations that served queries + +--- + +### 5.4 Close allocation with explicit POI + +**Objective**: Test POI override and reward eligibility. + +**Prerequisites**: Allocation is several epochs old. + +**Command**: + +```bash +graph indexer allocations close --poi +``` + +**Verification Query**: + +```graphql +{ + allocations(where: { id: "ALLOCATION_ID" }) { + id + status + indexingRewards + poi + } +} +``` + +**Pass Criteria**: + +- `indexingRewards` is non-zero +- `poi` matches the submitted value + +--- + +## Cycle 6: Network Health + +### 6.1 Monitor indexer health + +**Objective**: Verify indexer appears healthy in the network. + +**Query**: + +```graphql +{ + indexers(where: { id: "INDEXER_ADDRESS" }) { + id + url + geoHash + stakedTokens + allocatedTokens + availableStake + delegatedTokens + queryFeesCollected + rewardsEarned + allocations(where: { status: "Active" }) { + id + subgraphDeployment { + ipfsHash + } + } + } +} +``` + +**Pass Criteria**: + +- All expected fields populated +- Active allocations visible +- Accumulated rewards and fees present + +--- + +### 6.2 Check epoch progression + +**Objective**: Verify the network is progressing normally. + +**Query**: + +```graphql +{ + graphNetworks { + id + currentEpoch + totalTokensStaked + totalTokensAllocated + totalQueryFees + totalIndexingRewards + } +} +``` + +**Pass Criteria**: + +- `currentEpoch` increments at the expected rate +- Network totals accumulate over time + +--- + +### 6.3 Verify no unexpected errors in logs + +**Objective**: Confirm clean operation across all indexer components. + +**Steps**: + +1. Review indexer-agent logs for unexpected errors or reverts +2. Review indexer-service logs for query handling issues +3. Review tap-agent logs for receipt/RAV issues +4. Review graph-node logs for indexing errors + +**Pass Criteria**: + +- No unexpected `ERROR` level log entries +- No transaction reverts +- No stuck or looping operations + +--- + +## Cycle 7: End-to-End Workflow + +### 7.1 Full operational cycle + +Run these operations in sequence to validate a complete indexer lifecycle: + +| Step | Operation | Reference | +| ---- | ---------------------------------- | --------- | +| 1 | Check provision status | 3.1 | +| 2 | Find a rewarded deployment | 4.1 | +| 3 | Create allocation | 4.2 | +| 4 | Send test queries (50-100) | 5.1 | +| 5 | Wait 2-3 epochs | - | +| 6 | Close allocation | 5.2 | +| 7 | Verify indexing rewards (non-zero) | 5.2 | +| 8 | Verify query fees collected | 5.3 | +| 9 | Repeat with a different deployment | 4.2 | + +**Pass Criteria**: All individual pass criteria met across the full sequence. + +--- + +## Post-Upgrade Validation Checklist + +### Core functionality + +- [ ] Indexer stack components compatible with upgraded contracts +- [ ] Existing allocations continue to function +- [ ] New allocations can be created +- [ ] Query serving works through gateway +- [ ] Indexing rewards collected correctly +- [ ] Query fees collected correctly +- [ ] Provision management operations succeed + +### Network health + +- [ ] Network subgraph indexes the upgrade correctly +- [ ] Epoch progression continues normally +- [ ] Explorer displays correct data +- [ ] No unexpected reverts or errors in logs + +### Upgrade-specific (fill in per upgrade) + +- [ ] Contract address changes updated in indexer configuration +- [ ] New protocol parameters match expected values +- [ ] Schema changes (if any) reflected correctly +- [ ] _[Add upgrade-specific items here]_ + +--- + +## Troubleshooting + +**Allocation creation fails**: + +- Check `availableStake` is sufficient +- Verify graph-node is syncing the target deployment +- Ensure provision has enough tokens + +**Query fees not collected**: + +- Wait longer (can take several hours) +- Check TAP receipts in database +- Verify queries actually hit your indexer (check service logs) + +**Zero indexing rewards**: + +- Confirm allocation was open for the required number of epochs +- Verify POI was submitted correctly +- Confirm deployment has rewards enabled (`indexingRewardAmount_not: 0`) + +--- + +## Network Configuration Reference + +- [Arbitrum Sepolia (testnet)](TestnetDetails.md) +- [Arbitrum One (mainnet)](MainnetDetails.md) + +--- + +## Related Documentation + +- [← Back to REO Testing](README.md) + +--- + +_Extracted from Horizon upgrade test plans._ diff --git a/packages/issuance/docs/testing/reo/IndexerTestGuide.md b/packages/issuance/docs/testing/reo/IndexerTestGuide.md new file mode 100644 index 000000000..6b1423a36 --- /dev/null +++ b/packages/issuance/docs/testing/reo/IndexerTestGuide.md @@ -0,0 +1,542 @@ +# Indexer Eligibility Test Plan + +> **Navigation**: [← Back to REO Testing](README.md) | [BaselineTestPlan](BaselineTestPlan.md) | [ReoTestPlan](ReoTestPlan.md) + +Tests for indexers to verify correct eligibility handling on Arbitrum Sepolia. This is a focused subset of [ReoTestPlan.md](ReoTestPlan.md), covering per-indexer eligibility flows (renew, expire, recover). The full ReoTestPlan covers additional areas: deployment verification, oracle operations, timeout fail-open, emergency operations, and UI verification. + +Each indexer controls their own eligibility via the ORACLE_ROLE granted to their address. + +Each test includes CLI commands, verification queries against the network subgraph, and pass/fail criteria. + +> All GraphQL queries run against the network subgraph. All addresses must be **lowercase**. + +--- + +## Prerequisites + +- Completed [BaselineTestPlan](BaselineTestPlan.md) Cycles 1-4 (indexer staked, provisioned, can allocate) +- `cast` (Foundry) installed for contract interaction +- Indexer private key available for signing transactions + +### Environment Configuration (set by coordinator) + +- **Eligibility validation**: enabled +- **Eligibility period**: short (e.g. 10-15 minutes) +- **Oracle timeout**: very high (no fail-open during testing) +- **ORACLE_ROLE**: granted to each participating indexer + +### Environment Variables + +```bash +export RPC="https://sepolia-rollup.arbitrum.io/rpc" +export INDEXER= # lowercase +export INDEXER_KEY= + +# Contract addresses (Arbitrum Sepolia) +export REO=0x62c2305739cc75f19a3a6d52387ceb3690d99a99 +export MOCK_REO=0x5FB23365F8cf643D5f1459E9793EfF7254522400 +export REWARDS_MANAGER=0x1f49cae7669086c8ba53cc35d1e9f80176d67e79 +``` + +### Mock REO Option + +A `MockRewardsEligibilityOracle` is deployed at `0x5FB23365F8cf643D5f1459E9793EfF7254522400`. When RewardsManager is pointed at the mock (by the coordinator), you can directly toggle your eligibility without oracle roles, renewal periods, or timeout logic: + +```bash +# Check your eligibility +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC + +# Toggle ineligible (signed by your indexer key) +cast send $MOCK_REO "setEligible(bool)" false --rpc-url $RPC --private-key $INDEXER_KEY + +# Toggle eligible again +cast send $MOCK_REO "setEligible(bool)" true --rpc-url $RPC --private-key $INDEXER_KEY +``` + +If the coordinator has pointed RewardsManager at the mock, you can use Sets 2m-4m below instead of Sets 2-4 for faster testing. Ask the coordinator which REO is active: + +```bash +cast call $REWARDS_MANAGER "getRewardsEligibilityOracle()(address)" --rpc-url $RPC +``` + +### Verify Environment + +```bash +# Validation must be enabled +cast call $REO "getEligibilityValidation()(bool)" --rpc-url $RPC +# Expected: true + +# Confirm you have ORACLE_ROLE +ORACLE_ROLE=$(cast keccak "ORACLE_ROLE") +cast call $REO "hasRole(bytes32,address)(bool)" $ORACLE_ROLE $INDEXER --rpc-url $RPC +# Expected: true + +# Note the eligibility period (seconds) +cast call $REO "getEligibilityPeriod()(uint256)" --rpc-url $RPC +``` + +--- + +## Test Sequence Overview + +| Set | Area | Tests | +| --- | ------------------------------ | --------- | +| 1 | Prepare Allocations | 1.1 | +| 2 | Eligible — Receive Rewards | 2.1 - 2.2 | +| 3 | Ineligible — Verify Denial | 3.1 - 3.2 | +| 4 | Optimistic Recovery | 4.1 - 4.2 | +| 5 | Validation Disabled | 5.1 | +| 2m | Eligible — Mock REO | 2m.1 | +| 3m | Ineligible — Mock REO | 3m.1 | +| 4m | Optimistic Recovery — Mock REO | 4m.1 | + +**Timing**: Set 1 opens allocations that need epoch maturity. Sets 2-4 use the production REO (sequential: renew → eligible close → wait for expiry → ineligible close → re-renew → recovery close). Sets 2m-4m use the mock REO for instant eligibility control -- no waiting for expiry. Set 5 requires coordinator to toggle validation. + +--- + +## Set 1: Prepare Allocations + +### 1.1 Open allocations for eligibility tests + +**Objective**: Open 3+ allocations on different deployments. These need to mature across epochs before they can be closed in Sets 2-4. + +**Prerequisites**: Indexer is staked, provisioned, and registered (BaselineTestPlan Cycles 1-3). Subgraph deployments with signal exist. + +**Steps**: + +1. Find subgraph deployments with signal +2. Open allocations on 3+ different deployments +3. Record allocation IDs and current epoch + +**Command**: + +```bash +graph indexer actions queue allocate +graph indexer actions queue allocate +graph indexer actions queue allocate +graph indexer actions approve +``` + +**Verification Query**: + +```graphql +{ + indexer(id: "INDEXER_ADDRESS") { + allocations(where: { status: "Active" }) { + id + subgraphDeployment { + ipfsHash + } + allocatedTokens + createdAtEpoch + } + } + graphNetwork(id: "1") { + currentEpoch + } +} +``` + +**Pass Criteria**: + +- 3+ active allocations visible in subgraph +- `createdAtEpoch` recorded (need at least 1 epoch to pass before closing) + +> While waiting for epoch maturity, proceed to Set 2 to renew eligibility. + +--- + +## Set 2: Eligible — Receive Rewards + +### 2.1 Renew eligibility + +**Objective**: Renew your own eligibility and confirm the REO reflects it. + +**Prerequisites**: ORACLE_ROLE confirmed in environment check. + +**Command**: + +```bash +cast send $REO "renewIndexerEligibility(address[],bytes)" "[$INDEXER]" "0x" \ + --rpc-url $RPC --private-key $INDEXER_KEY +``` + +**Verification**: + +```bash +cast call $REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: true + +cast call $REO "getEligibilityRenewalTime(address)(uint256)" $INDEXER --rpc-url $RPC +# Record this timestamp — eligibility expires at: renewal_time + eligibility_period +``` + +**Pass Criteria**: + +- `isEligible` returns `true` +- `getEligibilityRenewalTime` returns a recent timestamp + +--- + +### 2.2 Close allocation while eligible + +**Objective**: Verify that an eligible indexer receives indexing rewards when closing an allocation. + +**Prerequisites**: `isEligible` returns `true`. Allocation from Set 1 is at least 1 epoch old. + +**Command**: + +```bash +graph indexer actions queue close +graph indexer actions approve +``` + +**Verification Query**: + +```graphql +{ + allocations(where: { id: "ALLOCATION_ID" }) { + id + status + indexingRewards + closedAtEpoch + } +} +``` + +**Pass Criteria**: + +- Status changes to `Closed` +- `indexingRewards` is non-zero +- `closedAtEpoch` is current epoch + +--- + +## Set 3: Ineligible — Verify Denial + +### 3.1 Wait for eligibility expiry + +**Objective**: Confirm that eligibility expires after the configured period. + +**Prerequisites**: Renewal timestamp and eligibility period recorded from Set 2.1. + +**Steps**: + +1. Calculate expiry time: `renewal_timestamp + eligibility_period` +2. Wait until current block time exceeds expiry +3. Verify eligibility has expired + +**Verification**: + +```bash +cast call $REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: false + +# Confirm by comparing timestamps: +cast call $REO "getEligibilityRenewalTime(address)(uint256)" $INDEXER --rpc-url $RPC +cast call $REO "getEligibilityPeriod()(uint256)" --rpc-url $RPC +cast block latest --field timestamp --rpc-url $RPC +# block_timestamp > renewal_time + period +``` + +**Pass Criteria**: + +- `isEligible` returns `false` +- Block timestamp exceeds renewal time + eligibility period + +--- + +### 3.2 Close allocation while ineligible + +**Objective**: Verify that an ineligible indexer receives zero indexing rewards when closing an allocation. Denied rewards are routed to the reclaim contract. + +**Prerequisites**: `isEligible` returns `false`. Allocation from Set 1 is at least 1 epoch old. + +**Steps**: + +1. Confirm ineligibility +2. Close an allocation +3. Verify zero rewards + +**Command**: + +```bash +# Confirm ineligible +cast call $REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: false + +# Close allocation +graph indexer actions queue close +graph indexer actions approve +``` + +**Verification Query**: + +```graphql +{ + allocations(where: { id: "ALLOCATION_ID" }) { + id + status + indexingRewards + closedAtEpoch + } +} +``` + +**Pass Criteria**: + +- Status changes to `Closed` +- `indexingRewards` is `0` +- Contrast with Set 2.2 where `indexingRewards` was non-zero + +--- + +## Set 4: Optimistic Recovery + +Eligibility denial is **optimistic**: rewards accrue to allocations during ineligible periods and are paid in full when the indexer closes while eligible. This is the key behavioral difference from subgraph denial. + +### 4.1 Re-renew eligibility + +**Objective**: Restore eligibility after expiry and confirm the REO reflects it. + +**Prerequisites**: Eligibility expired (Set 3.1). Do this promptly after Set 3. + +**Command**: + +```bash +cast send $REO "renewIndexerEligibility(address[],bytes)" "[$INDEXER]" "0x" \ + --rpc-url $RPC --private-key $INDEXER_KEY +``` + +**Verification**: + +```bash +cast call $REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: true +``` + +**Pass Criteria**: + +- `isEligible` returns `true` after re-renewal + +--- + +### 4.2 Close allocation — full rewards after re-renewal + +**Objective**: Verify that an allocation closed after re-renewal receives full rewards for its entire duration, including the ineligible period. + +**Prerequisites**: `isEligible` returns `true`. Active allocation from Set 1 has been open across multiple epochs including the ineligible period. + +**Command**: + +```bash +graph indexer actions queue close +graph indexer actions approve +``` + +**Verification Query**: + +```graphql +{ + allocations(where: { id: "ALLOCATION_ID" }) { + id + status + indexingRewards + createdAtEpoch + closedAtEpoch + } +} +``` + +**Pass Criteria**: + +- Status changes to `Closed` +- `indexingRewards` is non-zero +- Rewards reflect the full allocation duration (`closedAtEpoch - createdAtEpoch`), not reduced by the ineligible period +- Compare with Set 2.2: this allocation was open longer and should have proportionally more rewards + +--- + +## Set 5: Validation Disabled + +### 5.1 Verify eligibility when validation is off + +**Objective**: Confirm that all indexers are eligible when validation is disabled, regardless of renewal status. This is the default state and the emergency fallback. + +**Prerequisites**: Coordinator has disabled validation (`setEligibilityValidation(false)`). + +**Verification**: + +```bash +cast call $REO "getEligibilityValidation()(bool)" --rpc-url $RPC +# Expected: false + +cast call $REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: true +``` + +**Pass Criteria**: + +- `getEligibilityValidation` returns `false` +- `isEligible` returns `true` even without a recent renewal + +--- + +## Mock REO Test Sets (2m - 4m) + +These sets use the `MockRewardsEligibilityOracle` for direct eligibility control. The coordinator must have pointed RewardsManager at the mock. These replace Sets 2-4 when the mock is active. + +### 2m.1 Close allocation while eligible (mock) + +**Objective**: Verify rewards when eligible (the default mock state). + +**Prerequisites**: Allocation from Set 1 is at least 1 epoch old. + +```bash +# Confirm eligible (default) +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: true + +# Close allocation +graph indexer actions queue close +graph indexer actions approve +``` + +**Pass Criteria**: `indexingRewards` is non-zero. + +--- + +### 3m.1 Toggle ineligible and close allocation (mock) + +**Objective**: Verify reward denial after toggling ineligible. + +```bash +# Toggle ineligible +cast send $MOCK_REO "setEligible(bool)" false --rpc-url $RPC --private-key $INDEXER_KEY + +# Confirm +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: false + +# Close allocation +graph indexer actions queue close +graph indexer actions approve +``` + +**Pass Criteria**: `indexingRewards` = `0`. Allocation still transitions to `Closed`. + +--- + +### 4m.1 Re-enable and close allocation -- full rewards (mock) + +**Objective**: Verify optimistic recovery: toggle eligible again and receive full rewards. + +**Prerequisites**: Active allocation open across multiple epochs, including time while ineligible. + +```bash +# Toggle eligible +cast send $MOCK_REO "setEligible(bool)" true --rpc-url $RPC --private-key $INDEXER_KEY + +# Confirm +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: true + +# Close allocation +graph indexer actions queue close +graph indexer actions approve +``` + +**Pass Criteria**: + +- `indexingRewards` is non-zero +- Rewards reflect the full allocation duration (not reduced by the ineligible period) +- Compare with 2m.1: longer-open allocation should have proportionally more rewards + +--- + +## Indexer Awareness: Denial and Reward Conditions + +These situations are managed by the coordinator, not the indexer. No indexer action is needed — but indexers should understand the expected behaviour. + +### During subgraph denial + +If a coordinator denies a subgraph you have allocations on: + +- **Continue presenting POIs** — deferred presentations reset the staleness clock, preventing STALE_POI reclaim when the subgraph is later undenied +- `getRewards()` returns a frozen value (pre-denial uncollected rewards are preserved) +- Closing an allocation on a denied subgraph returns 0 rewards but preserves the pre-denial amount + +**Verification during denial:** + +```bash +cast call $REWARDS_MANAGER "isDenied(bytes32)(bool)" --rpc-url $RPC +# Expected: true (if coordinator denied it) + +cast call $REWARDS_MANAGER "getRewards(address,address)(uint256)" --rpc-url $RPC +# Returns frozen pre-denial rewards (non-zero if you had uncollected rewards) +``` + +### After subgraph undeny + +After a coordinator undenies a subgraph: + +- Accumulators resume growing +- Close allocation normally — rewards include pre-denial + post-undeny amounts +- Denial-period rewards were reclaimed to the protocol (not included in your claim) + +**Verification after undeny:** + +```bash +cast call $REWARDS_MANAGER "isDenied(bytes32)(bool)" --rpc-url $RPC +# Expected: false + +cast call $REWARDS_MANAGER "getRewards(address,address)(uint256)" --rpc-url $RPC +# Should be growing again (pre-denial + post-undeny rewards) +``` + +### POI staleness + +If an allocation goes without POI presentation for longer than `maxPOIStaleness`, rewards are reclaimed as STALE_POI instead of being paid to the indexer. + +```bash +cast call "maxPOIStaleness()(uint256)" --rpc-url $RPC +# Note this value — present POIs more frequently than this +``` + +**Action**: Ensure your indexer agent is healthy and presenting POIs regularly. + +### Signal-related conditions + +Rewards require curation signal above the minimum threshold. If signal drops below `minimumSubgraphSignal`, rewards freeze and are reclaimed. This is not actionable by indexers — it depends on curators. + +```bash +cast call $REWARDS_MANAGER "minimumSubgraphSignal()(uint256)" --rpc-url $RPC +``` + +**Related**: [RewardsConditionsTestPlan.md](RewardsConditionsTestPlan.md) | [SubgraphDenialTestPlan.md](SubgraphDenialTestPlan.md) + +--- + +## Troubleshooting + +**`isEligible` returns `false` unexpectedly:** + +- Check if validation is enabled: `getEligibilityValidation()` +- Check your renewal time: `getEligibilityRenewalTime(address)` +- Check the eligibility period: `getEligibilityPeriod()` +- Your renewal may have expired: compare `renewal_time + period` with current block time + +**Renewal transaction reverts:** + +- Confirm you have ORACLE_ROLE: `hasRole(ORACLE_ROLE, address)` +- Confirm the REO is not paused: `paused()` + +**Zero rewards on close despite being eligible:** + +- Check allocation maturity: must have been open for at least 1 full epoch +- Check if subgraph deployment has signal (no signal = no rewards) +- Verify RewardsManager points to the REO: `getRewardsEligibilityOracle()` + +--- + +**Related**: [BaselineTestPlan.md](BaselineTestPlan.md) | [ReoTestPlan.md](ReoTestPlan.md) diff --git a/packages/issuance/docs/testing/reo/MainnetDetails.md b/packages/issuance/docs/testing/reo/MainnetDetails.md new file mode 100644 index 000000000..590c3b134 --- /dev/null +++ b/packages/issuance/docs/testing/reo/MainnetDetails.md @@ -0,0 +1,38 @@ +# Arbitrum One — Mainnet Details + +## Network Parameters + +| Parameter | Value | +| ----------------- | ---------------------------------------------- | +| Explorer | | +| Gateway | | +| Network subgraph | `DZz4kDTdmzWLWsV373w2bSmoar3umKKH9y82SUKr5qmp` | +| Epoch length | ~6,646 blocks (~24 hours) | +| Min indexer stake | 100k GRT | + +## Network Subgraph + +**Query via Graph Explorer**: [Graph Network Arbitrum](https://thegraph.com/explorer/subgraphs/DZz4kDTdmzWLWsV373w2bSmoar3umKKH9y82SUKr5qmp?view=Query&chain=arbitrum-one) + +Or query directly: + +```bash +export GRAPH_API_KEY= +curl "https://gateway.thegraph.com/api/$GRAPH_API_KEY/subgraphs/id/DZz4kDTdmzWLWsV373w2bSmoar3umKKH9y82SUKr5qmp" \ + -H 'content-type: application/json' \ + -d '{"query": "{ _meta { block { number } } }"}' +``` + +## Contract Addresses + +| Contract | Address | +| ------------------------ | -------------------------------------------- | +| RewardsEligibilityOracle | TBD | +| RewardsManager | `0x971b9d3d0ae3eca029cab5ea1fb0f72c85e6a525` | +| SubgraphService | `0xb2bb92d0de618878e438b55d5846cfecd9301105` | +| GraphToken (L2) | `0x9623063377ad1b27544c965ccd7342f7ea7e88c7` | +| Controller | `0x0a8491544221dd212964fbb96487467291b2c97e` | + +--- + +- [← Back to REO Testing](README.md) diff --git a/packages/issuance/docs/testing/reo/README.md b/packages/issuance/docs/testing/reo/README.md new file mode 100644 index 000000000..666885c68 --- /dev/null +++ b/packages/issuance/docs/testing/reo/README.md @@ -0,0 +1,156 @@ +# Issuance Upgrade Testing Documentation + +Comprehensive test plans for validating The Graph Network after an upgrade. Three-layer approach: baseline indexer operations (upgrade-agnostic), REO-specific eligibility and oracle tests, and reward condition tests covering denial, reclaim, signal, POI paths, and allocation lifecycle changes. + +## Quick Start + +1. **Indexers start here** → Follow [IndexerTestGuide.md](IndexerTestGuide.md) +2. **Detailed baseline reference** → [BaselineTestPlan.md](BaselineTestPlan.md) +3. **REO eligibility tests** → [ReoTestPlan.md](ReoTestPlan.md) +4. **Subgraph denial tests** → [SubgraphDenialTestPlan.md](SubgraphDenialTestPlan.md) +5. **Reward conditions tests** → [RewardsConditionsTestPlan.md](RewardsConditionsTestPlan.md) + +**Mock REO available**: A `MockRewardsEligibilityOracle` at `0x5FB23365F8cf643D5f1459E9793EfF7254522400` (Arbitrum Sepolia) provides instant eligibility control for integration testing. See the mock-based test paths in [ReoTestPlan](ReoTestPlan.md#mock-reo-quick-test-path) and [IndexerTestGuide](IndexerTestGuide.md#mock-reo-option). + +## Reading Order + +1. **[BaselineTestPlan.md](BaselineTestPlan.md)** -- Upgrade-agnostic indexer operations (run first) +2. **[ReoTestPlan.md](ReoTestPlan.md)** -- REO-specific eligibility, oracle, and rewards tests (run after baseline passes) +3. **[RewardsConditionsTestPlan.md](RewardsConditionsTestPlan.md)** -- Reclaim system, signal conditions, POI paths, allocation lifecycle (run after baseline passes; Cycle 1 configures reclaim addresses needed by other plans) +4. **[SubgraphDenialTestPlan.md](SubgraphDenialTestPlan.md)** -- Subgraph denial two-level handling, accumulator freeze, deferral, deny/undeny lifecycle (run after reclaim setup) +5. **[IndexerTestGuide.md](IndexerTestGuide.md)** -- Condensed guide for indexers running eligibility tests (subset of ReoTestPlan) + +``` +BaselineTestPlan (7 cycles, 22 tests) + │ Covers: setup, staking, provisions, allocations, queries, health + │ + ├──▶ ReoTestPlan (8 cycles + mock path, 36 tests) + │ Covers: deployment, eligibility, oracle, rewards, emergency, UI + │ Depends on: Baseline Cycles 1-7 pass first + │ Cycle 2.3 opens allocations reused in Cycle 6 + │ Cycle 6m: mock REO path for fast integration testing + │ + ├──▶ RewardsConditionsTestPlan (7 cycles, 26 tests) + │ Covers: reclaim config, below-minimum signal, zero allocated tokens, + │ POI paths (stale/zero/too-young), allocation resize/close, observability + │ Depends on: Baseline Cycles 1-7 pass first + │ Cycle 1 configures reclaim addresses used by all reclaim tests + │ + ├──▶ SubgraphDenialTestPlan (6 cycles, 18 tests) + │ Covers: deny/undeny state, accumulator freeze, allocation deferral, + │ pre-denial reward recovery, edge cases + │ Depends on: Baseline + RewardsConditionsTestPlan Cycle 1 (reclaim setup) + │ + └──▶ IndexerTestGuide (5 sets + 3 mock sets, 11 tests) + Covers: eligible/ineligible/recovery flows + Depends on: Baseline Cycles 1-4 (staked, provisioned, can allocate) + Subset of ReoTestPlan focused on per-indexer eligibility + Sets 2m-4m: mock REO alternative for instant eligibility control +``` + +## Documentation + +### Test Plans + +| Document | Purpose | +| ------------------------------------------------------------ | --------------------------------------------------------------------------------------- | +| [BaselineTestPlan.md](BaselineTestPlan.md) | Detailed baseline indexer operational tests (7 cycles, 22 tests) | +| [ReoTestPlan.md](ReoTestPlan.md) | REO eligibility, oracle, and rewards integration (8 cycles + mock path, 36 tests) | +| [RewardsConditionsTestPlan.md](RewardsConditionsTestPlan.md) | Reclaim system, signal conditions, POI paths, allocation lifecycle (7 cycles, 26 tests) | +| [SubgraphDenialTestPlan.md](SubgraphDenialTestPlan.md) | Subgraph denial: accumulator freeze, deferral, recovery (6 cycles, 18 tests) | +| [IndexerTestGuide.md](IndexerTestGuide.md) | Condensed indexer eligibility tests (5 sets + 3 mock sets, 11 tests) | + +## Test Coverage + +### Baseline Tests (7 Cycles) + +1. **Cycle 1: Indexer Setup and Registration** (3 tests) + - Setup via Explorer, register URL/GEO, validate SubgraphService provision + +2. **Cycle 2: Stake Management** (2 tests) + - Add stake, unstake and withdraw after thawing + +3. **Cycle 3: Provision Management** (4 tests) + - View provision, add stake, thaw stake, remove thawed stake + +4. **Cycle 4: Allocation Management** (5 tests) + - Find rewarded deployments, create allocations (manual/queue/rules), reallocate + +5. **Cycle 5: Query Serving and Revenue** (4 tests) + - Send test queries, close allocations, verify rewards and fees + +6. **Cycle 6: Network Health** (3 tests) + - Monitor indexer health, check epoch progression, verify logs + +7. **Cycle 7: End-to-End Workflow** (1 test) + - Complete operational cycle from allocation to revenue collection + +### REO-Specific Tests (ReoTestPlan) + +1. **Eligibility State Transitions** + - Validation toggle, renewals, expiry, oracle timeout fail-open + +2. **Role-Based Operations** + - Governor, Operator, Oracle, Pause role actions and access control + +3. **Integration with RewardsManager** + - Eligible indexer rewards, ineligible indexer denial, reclaim flows + +4. **Edge Cases** + - Large eligibility period, same-block re-renewal, configuration races + +5. **Deployment Verification** + - Post-deploy role checks, parameter validation, proxy consistency + +### Reward Conditions Tests (RewardsConditionsTestPlan) + +1. **Reclaim System Configuration** + - Per-condition addresses, default fallback, routing verification, access control + +2. **Below-Minimum Signal** + - Threshold changes, accumulator freeze, reclaim, restoration + +3. **Zero Allocated Tokens** + - Detection, reclaim, allocation resumption from stored baseline + +4. **POI Presentation Paths** + - Normal claim (NONE), stale POI reclaim, zero POI reclaim, too-young deferral + +5. **Allocation Lifecycle** + - Stale resize reclaim, non-stale resize pass-through, close allocation reclaim + +6. **Observability** + - POIPresented event on every presentation, RewardsReclaimed event context, view function freeze + +### Subgraph Denial Tests (SubgraphDenialTestPlan) + +1. **Denial State Management** + - setDenied, isDenied, idempotent deny, access control + +2. **Accumulator Freeze** + - accRewardsForSubgraph freeze, getRewards freeze, reclaim during denial + +3. **Allocation-Level Deferral** + - POI defers (preserves rewards), multiple defers safe, continued POI presentation + +4. **Undeny and Recovery** + - Accumulator resumption, pre-denial rewards claimable, denial-period exclusion + +5. **Edge Cases** + - New allocation while denied, all-close-while-denied, rapid deny/undeny, denial vs eligibility precedence + +See also: [IssuanceAllocatorTestPlan](support/IssuanceAllocatorTestPlan.md) (independent of REO, pending deployment) + +## Network Configuration + +- [Arbitrum Sepolia (testnet)](TestnetDetails.md) — Explorer, Gateway, network subgraph, RPC, contract addresses +- [Arbitrum One (mainnet)](MainnetDetails.md) — Explorer, Gateway, network subgraph, contract addresses + +> **GraphQL note**: All addresses in queries must be lowercase. Invisible Unicode characters are sometimes introduced when copying queries from GitHub or chat tools and will inexplicably cause empty results. + +## Testing Approach + +1. **Testnet first** - All tests validated on Arbitrum Sepolia before mainnet +2. **Reusable baseline** - Upgrade-agnostic tests reused across protocol upgrades +3. **Incremental** - Baseline confidence first, then upgrade-specific scenarios +4. **Three-layer validation** - Standard operations + REO eligibility + reward conditions/denial diff --git a/packages/issuance/docs/testing/reo/ReoTestPlan.md b/packages/issuance/docs/testing/reo/ReoTestPlan.md new file mode 100644 index 000000000..d2ecf28a0 --- /dev/null +++ b/packages/issuance/docs/testing/reo/ReoTestPlan.md @@ -0,0 +1,1103 @@ +# REO Test Plan: Rewards Eligibility Oracle + +> **Navigation**: [← Back to REO Testing](README.md) | [BaselineTestPlan](BaselineTestPlan.md) + +Tests specific to the Rewards Eligibility Oracle upgrade. Run these **after** the [baseline tests](./BaselineTestPlan.md) pass to confirm standard indexer operations are unaffected. + +> All contract reads use `cast call`. All addresses must be **lowercase**. Replace placeholder addresses with actual deployed addresses for your network. + +## Contract Addresses + +| Contract | Arbitrum Sepolia | Arbitrum One | +| -------------------------------- | -------------------------------------------- | ------------ | +| RewardsEligibilityOracle (proxy) | `0x62c2305739cc75f19a3a6d52387ceb3690d99a99` | TBD | +| MockRewardsEligibilityOracle | `0x5FB23365F8cf643D5f1459E9793EfF7254522400` | N/A | +| RewardsManager (proxy) | `0x1f49cae7669086c8ba53cc35d1e9f80176d67e79` | TBD | +| GraphToken (L2) | `0xf8c05dcf59e8b28bfd5eed176c562bebcfc7ac04` | TBD | + +**Address sources**: `packages/issuance/addresses.json` (REO), `packages/horizon/addresses.json` (RewardsManager, GraphToken) in the `post-audit` worktree. + +### RPC + +| Network | RPC URL | +| ---------------- | ---------------------------------------- | +| Arbitrum Sepolia | `https://sepolia-rollup.arbitrum.io/rpc` | + +### Hardhat Tasks + +The deployment package provides Hardhat tasks that read from the address books and handle governance workflow automatically. Run from `packages/deployment` in the `post-audit` worktree: + +```bash +npx hardhat reo:status --network arbitrumSepolia # Full status: config, oracle activity, role holders +npx hardhat reo:enable --network arbitrumSepolia # Enable eligibility validation (requires OPERATOR_ROLE) +npx hardhat reo:disable --network arbitrumSepolia # Disable eligibility validation (requires OPERATOR_ROLE) +``` + +These are alternatives to the raw `cast` commands used below. `reo:status` in particular is useful as a quick check at any point during testing. + +--- + +## Testing Approach + +**Multi-indexer cycling**: Three indexers cycle through eligibility states individually (not simultaneously). Each indexer transitions through eligible/ineligible states in sequence, allowing controlled observation of each transition. + +| Phase | Indexer A | Indexer B | Indexer C | +| ----- | -------------------- | -------------------- | -------------------- | +| 1 | Eligible | -- | -- | +| 2 | Ineligible (expired) | Eligible | -- | +| 3 | Re-renewed | Ineligible (expired) | Eligible | +| 4 | Eligible | Re-renewed | Ineligible (expired) | + +**Oracle control**: Use a dedicated test oracle account (fake oracle) to manually control eligibility state transitions rather than relying on the actual reporting software. Grant ORACLE_ROLE to this account in Cycle 3. + +**Testnet parameter acceleration**: Reduce time-dependent parameters for practical testing: + +| Parameter | Default | Test Value | Purpose | +| --------------------- | -------------------- | ----------------------- | ------------------------------------------ | +| Eligibility period | 14 days (1,209,600s) | 5-10 minutes (300-600s) | Allow expiration within a test session | +| Oracle update timeout | 7 days (604,800s) | 5-10 minutes (300-600s) | Allow fail-open testing without long waits | + +> Testnet epochs are ~554 blocks (~110 minutes) vs ~6,646 blocks (~24h) on mainnet. Issuance rates are adjusted proportionally. + +**Stakeholder coordination**: Discord channel for testing. UI/Explorer team and network subgraph team monitor throughout for display accuracy during denial scenarios. + +--- + +## Execution Phases + +| Phase | Cycles | Activity | +| ----------- | ------ | -------------------------------------------------------------------------------------------------------- | +| Setup | — | Run [BaselineTestPlan](BaselineTestPlan.md) Cycles 1-7, confirm testnet environment | +| REO Phase 1 | 1-3 | Deployment verification, default state, oracle setup | +| REO Phase 2 | 4-5 | Validation enabled, timeout fail-open, begin indexer cycling | +| REO Phase 3 | 6/6m | Integration with rewards -- use mock REO (6m) for fast iteration, production REO (6) for full validation | +| REO Phase 4 | 7-8 | Emergency ops, UI/subgraph verification | +| Wrap-up | — | Results review, cleanup checklist, mainnet readiness assessment | + +--- + +## Execution Notes + +### Roles needed + +Testing requires access to three roles on the REO contract. On Arbitrum Sepolia: + +| Role | Needed for | Current holder | +| ------------- | --------------------------------------------------------- | ------------------------------------------------------------- | +| OPERATOR_ROLE | Enable/disable validation, set periods, grant ORACLE_ROLE | NetworkOperator: `0xade6b8eb69a49b56929c1d4f4b428d791861db6f` | +| ORACLE_ROLE | Renew indexer eligibility | Not yet assigned -- must be granted in Cycle 3 | +| PAUSE_ROLE | Pause/unpause (Cycle 8) | Check with `reo:status` | + +The tester needs the NetworkOperator key (or governance access) to execute Cycles 3-5 and 8. If the tester doesn't hold OPERATOR_ROLE directly, the Hardhat tasks generate governance TX files for Safe multisig execution. + +### Advance planning for Cycle 6 + +Cycle 6 tests reward integration with live indexers. These tests take multiple epochs (~110 minutes each on Sepolia) and require allocations that were opened **before** validation was enabled. Plan ahead: + +1. During **Cycle 2** (validation still disabled): open allocations for at least two indexers on rewarded deployments -- one that will be renewed (for test 6.1) and one that will NOT be renewed (for test 6.2) +2. These allocations need to mature for 2-3 epochs before they can be closed in Cycle 6 +3. When you enable validation in **Cycle 4**, the non-renewed indexer becomes ineligible while their allocation is still open -- this is the setup for test 6.2 + +### Parameter changes during testing + +Tests 4.4, 5.1, and 8.1 temporarily modify live parameters (eligibility period, oracle timeout, pause state). Each test includes a restore step. If a session is interrupted: + +```bash +# Verify and restore defaults +npx hardhat reo:status --network arbitrumSepolia + +# If needed, restore manually (as operator): +cast send "setEligibilityPeriod(uint256)" 1209600 --rpc-url --private-key +cast send "setOracleUpdateTimeout(uint256)" 604800 --rpc-url --private-key +cast send "unpause()" --rpc-url --private-key +``` + +--- + +## Test Sequence Overview + +| Cycle | Area | Tests | Notes | +| ----- | ------------------------------------------------ | ----------- | -------------------------------------------- | +| 1 | Deployment Verification | 1.1 - 1.5 | Read-only, no role access needed | +| 2 | Eligibility: Default State (Validation Disabled) | 2.1 - 2.3 | Open allocations here for Cycle 6 | +| 3 | Oracle Operations | 3.1 - 3.5 | Requires OPERATOR_ROLE + ORACLE_ROLE | +| 4 | Eligibility: Validation Enabled | 4.1 - 4.4 | Requires OPERATOR_ROLE; 4.4 changes params | +| 5 | Eligibility: Timeout Fail-Open | 5.1 - 5.2 | Requires OPERATOR_ROLE; 5.1 changes params | +| 6 | Integration with Rewards | 6.1 - 6.6 | Requires mature allocations from Cycle 2 | +| 6m | Integration with Rewards (Mock REO) | 6.1m - 6.5m | Uses mock REO for direct eligibility control | +| 7 | Emergency Operations | 7.1 - 7.3 | Requires PAUSE_ROLE; changes live state | +| 8 | UI and Subgraph Verification | 8.1 - 8.3 | Coordinate with Explorer and subgraph teams | + +--- + +## Cycle 1: Deployment Verification + +> Tests 1.2, 1.3, and 1.5 can be checked in one step with `npx hardhat reo:status --network arbitrumSepolia`, which displays role holders, configuration, and contract state. The individual `cast` commands below are useful for scripted or more granular verification. + +### 1.1 Verify proxy and implementation + +**Objective**: Confirm the REO proxy points to the correct implementation and bytecode matches expectations. + +**Steps**: + +1. Query the proxy's implementation address +2. Compare deployed bytecode hash against expected artifact + +```bash +# Get implementation address from proxy admin +cast call "getProxyImplementation(address)" --rpc-url + +# Get deployed bytecode hash +cast keccak $(cast code --rpc-url ) +``` + +**Pass Criteria**: + +- Implementation address matches address book (`0x4eb1de98440a39339817bdeeb3b3fff410b0b924` on Sepolia) +- Bytecode hash matches expected artifact hash + +--- + +### 1.2 Verify role assignments + +**Objective**: Confirm the correct accounts hold each role and the deployer has been removed. + +**Steps**: + +```bash +# Role constants +GOVERNOR_ROLE=0x0000... # DEFAULT_ADMIN_ROLE = 0x00 +OPERATOR_ROLE=$(cast keccak "OPERATOR_ROLE") +ORACLE_ROLE=$(cast keccak "ORACLE_ROLE") +PAUSE_ROLE=$(cast keccak "PAUSE_ROLE") + +# Check role assignments +cast call "hasRole(bytes32,address)(bool)" $GOVERNOR_ROLE --rpc-url +cast call "hasRole(bytes32,address)(bool)" $OPERATOR_ROLE --rpc-url +cast call "hasRole(bytes32,address)(bool)" $PAUSE_ROLE --rpc-url + +# Verify deployer does NOT have governor role +cast call "hasRole(bytes32,address)(bool)" $GOVERNOR_ROLE --rpc-url +``` + +**Pass Criteria**: + +- Governor address has GOVERNOR_ROLE: `true` +- Operator address has OPERATOR_ROLE: `true` +- Pause guardian has PAUSE_ROLE: `true` +- Deployer does NOT have GOVERNOR_ROLE: `false` + +--- + +### 1.3 Verify default parameters + +**Objective**: Confirm the REO is deployed with expected default configuration. + +**Steps**: + +```bash +cast call "getEligibilityPeriod()(uint256)" --rpc-url +cast call "getOracleUpdateTimeout()(uint256)" --rpc-url +cast call "getEligibilityValidation()(bool)" --rpc-url +cast call "getLastOracleUpdateTime()(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- `eligibilityPeriod` = `1209600` (14 days in seconds) +- `oracleUpdateTimeout` = `604800` (7 days in seconds) +- `eligibilityValidation` = `false` (disabled by default) +- `lastOracleUpdateTime` = `0` (no oracle updates yet) or reflects actual oracle activity + +--- + +### 1.4 Verify RewardsManager integration + +**Objective**: Confirm the RewardsManager is configured to use the REO for eligibility checks. + +**Steps**: + +```bash +cast call "getRewardsEligibilityOracle()(address)" --rpc-url +``` + +**Pass Criteria**: + +- Returns the REO proxy address + +--- + +### 1.5 Verify contract is not paused + +**Objective**: Confirm the REO is operational. + +**Steps**: + +```bash +cast call "paused()(bool)" --rpc-url +``` + +**Pass Criteria**: + +- Returns `false` + +--- + +## Cycle 2: Eligibility -- Default State (Validation Disabled) + +### 2.1 All indexers eligible when validation disabled + +**Objective**: With validation disabled (default), every indexer should be eligible regardless of renewal status. + +**Steps**: + +1. Confirm validation is disabled +2. Check eligibility for a known indexer +3. Check eligibility for a random address that has never been renewed + +```bash +# Confirm validation disabled +cast call "getEligibilityValidation()(bool)" --rpc-url + +# Known indexer +cast call "isEligible(address)(bool)" --rpc-url + +# Random/never-renewed address +cast call "isEligible(address)(bool)" 0x0000000000000000000000000000000000000001 --rpc-url +``` + +**Pass Criteria**: + +- `getEligibilityValidation()` = `false` +- Both addresses return `isEligible` = `true` + +--- + +### 2.2 Indexer with no renewal history is eligible + +**Objective**: Confirm that an indexer with zero renewal timestamp is still eligible when validation is disabled. + +**Steps**: + +```bash +cast call "getEligibilityRenewalTime(address)(uint256)" --rpc-url +cast call "isEligible(address)(bool)" --rpc-url +``` + +**Pass Criteria**: + +- `getEligibilityRenewalTime` = `0` +- `isEligible` = `true` + +--- + +### 2.3 Rewards still flow with validation disabled + +**Objective**: Confirm the baseline rewards flow is unaffected by the REO when validation is off. + +**Prerequisites**: Indexer has an active allocation on a rewarded deployment, open for at least 2 epochs. This should already exist from running [Baseline Cycle 4](./BaselineTestPlan.md#cycle-4-allocation-management). + +> **Cross-reference**: The allocations opened here (and in [Baseline Cycles 4-5](./BaselineTestPlan.md#cycle-4-allocation-management)) serve as setup for [Cycle 6](#cycle-6-integration-with-rewards) reward integration tests. Open extra allocations now for the indexers you plan to cycle through eligibility states. + +**Steps**: Close the allocation per [Baseline 5.2](./BaselineTestPlan.md#52-close-allocation-and-collect-indexing-rewards) and verify rewards. + +> **Advance setup for Cycle 6**: Before moving to Cycle 3, open allocations for the indexers you plan to use in Cycle 6. You need at least: +> +> - One allocation for a **renewed** indexer (test 6.1 -- will receive rewards) +> - One allocation for a **non-renewed** indexer (test 6.2 -- will be denied rewards) +> +> These allocations must mature for 2-3 epochs before Cycle 6. Since validation is still disabled, both will accrue potential rewards. Use [Baseline 4.2](./BaselineTestPlan.md#42-create-allocation-manually) to create them. + +**Pass Criteria**: + +- Indexing rewards are non-zero on allocation closure +- No change in behavior from baseline + +--- + +## Cycle 3: Oracle Operations + +### 3.1 Grant oracle role + +**Objective**: Verify an operator can grant ORACLE_ROLE to an oracle address. + +**Prerequisites**: Transaction signed by OPERATOR_ROLE holder. + +**Steps**: + +```bash +# Grant oracle role (as operator) +cast send "grantRole(bytes32,address)" $ORACLE_ROLE --rpc-url --private-key + +# Verify +cast call "hasRole(bytes32,address)(bool)" $ORACLE_ROLE --rpc-url +``` + +**Pass Criteria**: + +- Transaction succeeds +- `hasRole` returns `true` for the oracle address + +--- + +### 3.2 Renew single indexer eligibility + +**Objective**: Verify an oracle can renew eligibility for a single indexer. + +**Prerequisites**: Caller has ORACLE_ROLE. + +**Steps**: + +```bash +# Renew eligibility for one indexer +cast send "renewIndexerEligibility(address[],bytes)" "[]" "0x" --rpc-url --private-key + +# Check renewal timestamp +cast call "getEligibilityRenewalTime(address)(uint256)" --rpc-url + +# Check last oracle update time +cast call "getLastOracleUpdateTime()(uint256)" --rpc-url +``` + +**Verification**: Check for emitted events: + +- `IndexerEligibilityRenewed(indexer, oracle)` +- `IndexerEligibilityData(oracle, data)` + +**Pass Criteria**: + +- Transaction succeeds, returns count `1` +- `getEligibilityRenewalTime` is approximately `block.timestamp` of the renewal tx +- `lastOracleUpdateTime` updated to the same timestamp +- Events emitted correctly + +--- + +### 3.3 Renew multiple indexers in batch + +**Objective**: Verify batch renewal works correctly. + +**Steps**: + +```bash +cast send "renewIndexerEligibility(address[],bytes)" "[,,]" "0x" --rpc-url --private-key +``` + +**Verification**: Check renewal timestamps for all three indexers. + +**Pass Criteria**: + +- Transaction succeeds, returns count `3` +- All three indexers have updated renewal timestamps +- One `IndexerEligibilityRenewed` event per indexer + +--- + +### 3.4 Zero addresses skipped in renewal + +**Objective**: Verify zero addresses in the renewal array are silently skipped. + +**Steps**: + +```bash +cast send "renewIndexerEligibility(address[],bytes)" "[0x0000000000000000000000000000000000000000,]" "0x" --rpc-url --private-key +``` + +**Pass Criteria**: + +- Transaction succeeds, returns count `1` (not 2) +- Only the non-zero indexer has a `IndexerEligibilityRenewed` event + +--- + +### 3.5 Unauthorized renewal reverts + +**Objective**: Verify that accounts without ORACLE_ROLE cannot renew eligibility. + +**Steps**: + +```bash +# Attempt renewal from a non-oracle account +cast send "renewIndexerEligibility(address[],bytes)" "[]" "0x" --rpc-url --private-key +``` + +**Pass Criteria**: + +- Transaction reverts with AccessControl error + +--- + +## Cycle 4: Eligibility -- Validation Enabled + +### 4.1 Enable eligibility validation + +**Objective**: Verify an operator can enable validation, switching from "all eligible" to oracle-based eligibility. + +**Prerequisites**: OPERATOR_ROLE holder. Some indexers should have been renewed (Cycle 3), others not. + +> **Before enabling**: Confirm the allocations you opened during Cycle 2 for Cycle 6 testing are still active. Once validation is enabled, any non-renewed indexer with an open allocation becomes ineligible for rewards -- this is the intended setup for test 6.2. + +**Steps**: + +```bash +# Enable validation (alternative: npx hardhat reo:enable --network arbitrumSepolia) +cast send "setEligibilityValidation(bool)" true --rpc-url --private-key + +# Verify +cast call "getEligibilityValidation()(bool)" --rpc-url +``` + +**Verification**: Check for `EligibilityValidationUpdated(true)` event. + +**Pass Criteria**: + +- Transaction succeeds +- `getEligibilityValidation()` = `true` + +--- + +### 4.2 Renewed indexer is eligible + +**Objective**: After enabling validation, a recently renewed indexer should still be eligible. + +**Prerequisites**: Indexer was renewed in Cycle 3. Validation is enabled (4.1). + +**Steps**: + +```bash +cast call "isEligible(address)(bool)" --rpc-url +cast call "getEligibilityRenewalTime(address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- `isEligible` = `true` +- `getEligibilityRenewalTime` is within the last `eligibilityPeriod` (14 days) + +--- + +### 4.3 Non-renewed indexer is NOT eligible + +**Objective**: An indexer that was never renewed should be ineligible when validation is enabled. + +**Steps**: + +```bash +cast call "isEligible(address)(bool)" --rpc-url +cast call "getEligibilityRenewalTime(address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- `isEligible` = `false` +- `getEligibilityRenewalTime` = `0` + +--- + +### 4.4 Eligibility expires after period + +**Objective**: Verify that an indexer's eligibility expires when the eligibility period has passed since their last renewal. + +**Approach**: This is easiest to test by temporarily reducing the eligibility period to a short duration. + +**Steps**: + +1. Renew an indexer's eligibility +2. Reduce eligibility period to a short value (e.g., 60 seconds) +3. Wait for the period to elapse +4. Check eligibility + +```bash +# Renew indexer +cast send "renewIndexerEligibility(address[],bytes)" "[]" "0x" --rpc-url --private-key + +# Reduce period to 60 seconds (as operator) +cast send "setEligibilityPeriod(uint256)" 60 --rpc-url --private-key + +# Immediately check -- should still be eligible +cast call "isEligible(address)(bool)" --rpc-url + +# Wait 60+ seconds, then check again +sleep 65 +cast call "isEligible(address)(bool)" --rpc-url + +# IMPORTANT: Restore eligibility period to default +cast send "setEligibilityPeriod(uint256)" 1209600 --rpc-url --private-key +``` + +**Pass Criteria**: + +- First check (immediately after renewal): `isEligible` = `true` +- Second check (after period elapsed): `isEligible` = `false` +- Eligibility period restored to default + +--- + +## Cycle 5: Eligibility -- Timeout Fail-Open + +### 5.1 Oracle timeout makes all indexers eligible + +**Objective**: Verify the fail-open mechanism: if no oracle updates occur for longer than `oracleUpdateTimeout`, all indexers become eligible. + +**Approach**: Reduce the oracle timeout to a short duration and wait. + +**Prerequisites**: Validation enabled (4.1). At least one indexer is NOT renewed (should be ineligible). + +**Steps**: + +```bash +# Confirm non-renewed indexer is currently ineligible +cast call "isEligible(address)(bool)" --rpc-url +# Expected: false + +# Reduce oracle timeout to 60 seconds (as operator) +cast send "setOracleUpdateTimeout(uint256)" 60 --rpc-url --private-key + +# Wait for timeout to elapse +sleep 65 + +# Check -- should now be eligible due to fail-open +cast call "isEligible(address)(bool)" --rpc-url + +# IMPORTANT: Restore oracle timeout to default +cast send "setOracleUpdateTimeout(uint256)" 604800 --rpc-url --private-key +``` + +**Pass Criteria**: + +- Before timeout: `isEligible` = `false` +- After timeout: `isEligible` = `true` +- Timeout restored to default + +--- + +### 5.2 Oracle renewal resets timeout + +**Objective**: Verify that an oracle renewal resets the `lastOracleUpdateTime`, closing the fail-open window. + +**Steps**: + +```bash +# Record current lastOracleUpdateTime +cast call "getLastOracleUpdateTime()(uint256)" --rpc-url + +# Renew any indexer +cast send "renewIndexerEligibility(address[],bytes)" "[]" "0x" --rpc-url --private-key + +# Check lastOracleUpdateTime again +cast call "getLastOracleUpdateTime()(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- `lastOracleUpdateTime` updated to the block timestamp of the renewal transaction + +--- + +## Cycle 6: Integration with Rewards + +These tests verify the end-to-end interaction between the REO and the rewards system using live indexers. + +> **Timing**: These tests require allocations that have been open for 2-3 epochs (~3.5-5.5 hours on Sepolia). The allocations should have been opened during Cycle 2, before validation was enabled. If they weren't, you'll need to open them now and wait before proceeding. Cycles 7 and 8 can be run while waiting. + +### Mock REO Quick-Test Path + +A `MockRewardsEligibilityOracle` is deployed at `0x5FB23365F8cf643D5f1459E9793EfF7254522400` on Arbitrum Sepolia. This provides direct, instant control over eligibility without oracle roles, renewal periods, or timeout logic. Use it for faster iteration on the Cycle 6 integration tests. + +**How the mock works**: Everyone starts eligible. Indexers call `setEligible(false)` from their own address to become ineligible, and `setEligible(true)` to restore eligibility. No roles or expiry -- just a toggle. + +**Setup**: Point RewardsManager at the mock (requires Governor): + +```bash +MOCK_REO=0x5FB23365F8cf643D5f1459E9793EfF7254522400 + +# Point RewardsManager to mock REO +cast send $REWARDS_MANAGER "setRewardsEligibilityOracle(address)" $MOCK_REO \ + --rpc-url $RPC --private-key $GOVERNOR_KEY + +# Verify +cast call $REWARDS_MANAGER "getRewardsEligibilityOracle()(address)" --rpc-url $RPC +# Expected: 0x5FB23365F8cf643D5f1459E9793EfF7254522400 +``` + +**Control eligibility**: + +```bash +# Query eligibility for any address +cast call $MOCK_REO "isEligible(address)(bool)" --rpc-url $RPC + +# Make yourself ineligible (signed by the indexer) +cast send $MOCK_REO "setEligible(bool)" false --rpc-url $RPC --private-key $INDEXER_KEY + +# Restore eligibility +cast send $MOCK_REO "setEligible(bool)" true --rpc-url $RPC --private-key $INDEXER_KEY +``` + +**After testing**: Restore the production REO on RewardsManager: + +```bash +cast send $REWARDS_MANAGER "setRewardsEligibilityOracle(address)" 0x62c2305739cc75f19a3a6d52387ceb3690d99a99 \ + --rpc-url $RPC --private-key $GOVERNOR_KEY +``` + +> The mock-based tests below (6.1m-6.5m) are equivalents of tests 6.1-6.5 using the mock for eligibility control. They can be run instead of or in addition to the production REO tests. The mock path eliminates time-dependent waits and simplifies the setup, making it the recommended approach for initial integration validation. + +### 6.1 Eligible indexer receives indexing rewards + +**Objective**: Confirm that a renewed (eligible) indexer receives rewards when closing an allocation. + +**Prerequisites**: Validation enabled (Cycle 4). Indexer renewed by oracle (Cycle 3). Indexer has an active allocation open for several epochs on a rewarded deployment (opened during Cycle 2). + +**Steps**: + +1. Confirm eligibility: `isEligible(indexer)` = `true` +2. Close allocation per [Baseline 5.2](./BaselineTestPlan.md#52-close-allocation-and-collect-indexing-rewards) +3. Check rewards + +**Verification Query**: + +```graphql +{ + allocations(where: { id: "ALLOCATION_ID" }) { + id + status + indexingRewards + closedAtEpoch + } +} +``` + +**Pass Criteria**: + +- `indexingRewards` is non-zero +- Rewards amount is consistent with allocation size and epoch duration + +--- + +### 6.2 Ineligible indexer denied rewards + +**Objective**: Confirm that a non-renewed (ineligible) indexer receives zero rewards when closing an allocation. + +**Prerequisites**: Validation enabled (Cycle 4). Indexer has NOT been renewed by the oracle. Indexer has an active allocation on a rewarded deployment that was opened during Cycle 2 (before validation was enabled). + +**Steps**: + +1. Confirm ineligibility: `isEligible(indexer)` = `false` +2. Close allocation +3. Check rewards + +**Pass Criteria**: + +- `indexingRewards` = `0` +- Allocation still transitions to `Closed` status (closure succeeds, just no rewards) + +--- + +### 6.3 Reclaimed rewards flow to reclaim contract + +**Objective**: When an ineligible indexer is denied rewards, verify the denied rewards are routed to the `ReclaimedRewards` contract (default reclaim address). + +**Prerequisites**: Same as 6.2. + +**Steps**: + +1. Close allocation for ineligible indexer +2. Check the reclaim contract balance or events + +```bash +# Check for RewardsDeniedDueToEligibility event on RewardsManager +# (implementation detail -- exact event name may vary) +cast logs --from-block --to-block --address --rpc-url +``` + +**Pass Criteria**: + +- Denied rewards event emitted +- Reclaim contract receives the tokens that would have been the indexer's rewards + +--- + +### 6.4 Re-renewal restores reward eligibility + +**Objective**: After an indexer's eligibility expires and they are denied rewards, verify that a new oracle renewal restores their ability to earn rewards. + +> **Timing**: This test requires opening a new allocation and waiting 2-3 epochs (~3.5-5.5 hours). It can be run as the final validation step, or skipped on testnet if time is constrained and covered by the combination of 6.2 + Cycle 3 (which together demonstrate the renewal mechanism works). + +**Steps**: + +1. Confirm indexer is currently ineligible (the indexer from test 6.2) +2. Renew the indexer via oracle (as in test 3.2) +3. Confirm eligibility restored: `isEligible` = `true` +4. Open new allocation, wait 2-3 epochs, close, check rewards + +**Pass Criteria**: + +- After renewal: `isEligible` = `true` +- New allocation closure yields non-zero `indexingRewards` + +--- + +### 6.5 View functions reflect zero for ineligible indexer + +**Objective**: Verify that RewardsManager view functions do not over-report claimable rewards for an ineligible indexer. Previously, view functions could show unclaimable balances, misleading indexers into thinking they had earned rewards. + +**Prerequisites**: Validation enabled. Indexer is ineligible. Indexer has an active allocation that has been open several epochs. + +**Steps**: + +1. Confirm ineligibility: `isEligible(indexer)` = `false` +2. Query the view function for pending rewards on the allocation + +```bash +# Check pending rewards for an active allocation +cast call "getRewards(bytes32)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Returns `0` (or near-zero), not the full accumulated amount +- This prevents the UI from displaying rewards the indexer cannot actually claim + +--- + +### 6.6 Eligibility denial is optimistic -- full rewards after re-renewal + +**Objective**: Verify that rewards continue accumulating during an ineligible period (optimistic model). After re-renewal, closing the allocation yields the full accumulated amount including epochs where the indexer was ineligible. This differs from subgraph denial, which permanently stops accumulation. + +**Prerequisites**: Indexer has an active allocation open for several epochs. Indexer was eligible when allocation was opened. + +**Steps**: + +1. Confirm indexer is currently eligible with an active allocation +2. Let eligibility expire (or reduce eligibility period as in test 4.4) +3. Confirm `isEligible(indexer)` = `false` +4. Wait 1-2 additional epochs while ineligible +5. Re-renew the indexer via oracle +6. Confirm `isEligible(indexer)` = `true` +7. Close allocation and check rewards + +**Pass Criteria**: + +- `indexingRewards` reflects the full allocation lifetime (eligible + ineligible epochs) +- Amount is comparable to what a continuously-eligible indexer would earn for the same period +- Temporary ineligibility does not cause permanent reward loss + +--- + +### Mock-Based Integration Tests (6.1m - 6.5m) + +These tests use the `MockRewardsEligibilityOracle` at `0x5FB23365F8cf643D5f1459E9793EfF7254522400` for direct eligibility control. See [Mock REO Quick-Test Path](#mock-reo-quick-test-path) above for setup. + +**Prerequisites**: RewardsManager pointed at the mock REO. Indexer has active allocations open for at least 1 epoch. + +#### 6.1m Eligible indexer receives rewards (mock) + +**Objective**: Confirm that an eligible indexer receives rewards when closing an allocation. + +**Steps**: + +```bash +MOCK_REO=0x5FB23365F8cf643D5f1459E9793EfF7254522400 + +# Confirm eligible (default state) +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: true + +# Close allocation +graph indexer actions queue close +graph indexer actions approve +``` + +**Pass Criteria**: + +- `indexingRewards` is non-zero + +--- + +#### 6.2m Ineligible indexer denied rewards (mock) + +**Objective**: Confirm that toggling eligibility off causes reward denial. + +**Steps**: + +```bash +# Make indexer ineligible +cast send $MOCK_REO "setEligible(bool)" false --rpc-url $RPC --private-key $INDEXER_KEY + +# Confirm +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: false + +# Close allocation +graph indexer actions queue close +graph indexer actions approve +``` + +**Pass Criteria**: + +- `indexingRewards` = `0` +- Allocation still transitions to `Closed` status + +--- + +#### 6.3m Reclaimed rewards flow to reclaim contract (mock) + +**Objective**: When the mock makes an indexer ineligible, denied rewards are routed to the reclaim contract. + +**Prerequisites**: Indexer set to ineligible via mock (6.2m). + +**Steps**: + +```bash +# Check for denial event on the close transaction from 6.2m +cast logs --from-block --to-block --address $REWARDS_MANAGER --rpc-url $RPC +``` + +**Pass Criteria**: + +- Denied rewards event emitted +- Reclaim contract receives the denied tokens + +--- + +#### 6.4m View functions reflect zero for ineligible indexer (mock) + +**Objective**: Verify pending rewards show zero while ineligible. + +**Prerequisites**: Indexer ineligible via mock. Active allocation open for several epochs. + +**Steps**: + +```bash +# Confirm ineligible +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: false + +# Check pending rewards +cast call $REWARDS_MANAGER "getRewards(bytes32)(uint256)" --rpc-url $RPC +``` + +**Pass Criteria**: + +- Returns `0` (or near-zero), not the full accumulated amount + +--- + +#### 6.5m Optimistic recovery -- full rewards after re-enabling (mock) + +**Objective**: Verify the optimistic model: toggle ineligible, wait, toggle back, and confirm full rewards on close. + +**Steps**: + +```bash +# Ensure indexer has an active allocation open across multiple epochs + +# 1. Toggle ineligible +cast send $MOCK_REO "setEligible(bool)" false --rpc-url $RPC --private-key $INDEXER_KEY +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: false + +# 2. Wait 1-2 epochs while ineligible (~110-220 min on Sepolia) + +# 3. Toggle eligible again +cast send $MOCK_REO "setEligible(bool)" true --rpc-url $RPC --private-key $INDEXER_KEY +cast call $MOCK_REO "isEligible(address)(bool)" $INDEXER --rpc-url $RPC +# Expected: true + +# 4. Close allocation +graph indexer actions queue close +graph indexer actions approve +``` + +**Pass Criteria**: + +- `indexingRewards` reflects the full allocation lifetime (eligible + ineligible epochs) +- Temporary ineligibility does not cause permanent reward loss +- Compare with 6.1m: this allocation was open longer and should have proportionally more rewards + +--- + +## Cycle 7: Emergency Operations + +### 7.1 Pause REO + +**Objective**: Verify the pause guardian can pause the REO. + +**Prerequisites**: Caller has PAUSE_ROLE. + +**Steps**: + +```bash +# Pause +cast send "pause()" --rpc-url --private-key + +# Verify paused +cast call "paused()(bool)" --rpc-url + +# View functions should still work +cast call "isEligible(address)(bool)" --rpc-url + +# IMPORTANT: Unpause when done +cast send "unpause()" --rpc-url --private-key +``` + +**Pass Criteria**: + +- Pause succeeds, `paused()` = `true` +- View functions (`isEligible`) still return results +- Oracle write operations (`renewIndexerEligibility`) revert while paused +- Unpause succeeds, `paused()` = `false` + +--- + +### 7.2 Disable eligibility validation (emergency override) + +**Objective**: Verify an operator can disable validation to immediately make all indexers eligible. + +**Steps**: + +```bash +# Disable validation (alternative: npx hardhat reo:disable --network arbitrumSepolia) +cast send "setEligibilityValidation(bool)" false --rpc-url --private-key + +# Previously ineligible indexer should now be eligible +cast call "isEligible(address)(bool)" --rpc-url +``` + +**Pass Criteria**: + +- Transaction succeeds +- All indexers return `isEligible` = `true` + +--- + +### 7.3 Access control prevents unauthorized configuration + +**Objective**: Verify that only authorized roles can perform privileged operations. + +**Steps** (all should revert): + +```bash +# Non-operator tries to set eligibility period +cast send "setEligibilityPeriod(uint256)" 100 --rpc-url --private-key + +# Non-operator tries to enable validation +cast send "setEligibilityValidation(bool)" true --rpc-url --private-key + +# Non-pause-role tries to pause +cast send "pause()" --rpc-url --private-key +``` + +**Pass Criteria**: + +- All three transactions revert with AccessControl errors + +--- + +## Cycle 8: UI and Subgraph Verification + +These tests verify that the Graph Explorer and network subgraph correctly reflect eligibility states and denial scenarios. Run these in coordination with the Explorer and subgraph teams. + +### 8.1 Explorer displays correct rewards during denial + +**Objective**: Verify that the Graph Explorer does not show incorrect indexing reward amounts when an indexer is ineligible and claims are denied. + +**Prerequisites**: At least one indexer is ineligible with an active allocation. Explorer team monitoring. + +**Steps**: + +1. Open Explorer to the ineligible indexer's profile +2. Check displayed pending rewards for active allocations +3. Close allocation (will be denied rewards) +4. Verify Explorer updates to reflect the actual outcome (zero rewards) + +**Pass Criteria**: + +- Explorer does not display inflated or false pending rewards for ineligible indexers +- After allocation closure with denial, Explorer shows `0` indexing rewards for that allocation +- No discrepancy between on-chain state and Explorer display + +--- + +### 8.2 Network subgraph reflects eligibility transitions + +**Objective**: Verify the network subgraph correctly indexes eligibility renewal events and displays accurate stake/delegation amounts through state transitions. + +**Steps**: + +1. Renew indexer eligibility via oracle +2. Query network subgraph for the indexer +3. Let eligibility expire +4. Query again and compare + +```graphql +{ + indexers(where: { id: "INDEXER_ADDRESS" }) { + id + stakedTokens + delegatedTokens + allocatedTokens + rewardsEarned + } +} +``` + +**Pass Criteria**: + +- `stakedTokens` and `delegatedTokens` remain accurate regardless of eligibility state +- Subgraph does not show incorrect amounts during eligibility transitions +- No indexing errors in the subgraph during REO-related transactions + +--- + +### 8.3 Denied transaction appears correct in Explorer history + +**Objective**: When an ineligible indexer closes an allocation and rewards are denied, the transaction should not appear "successful" in a way that misleads the indexer. + +**Steps**: + +1. Close allocation for an ineligible indexer +2. Check the transaction in Explorer's history view +3. Verify the displayed outcome matches reality (0 rewards) + +**Pass Criteria**: + +- Transaction status is clear (not misleadingly shown as a successful reward claim) +- Reward amount displayed is `0` or clearly indicates denial +- Explorer team confirms no confusing UX for the indexer + +--- + +## Post-Testing Cleanup Checklist + +Run `npx hardhat reo:status --network arbitrumSepolia` to verify. Ensure the REO is left in the expected state: + +- [ ] `eligibilityValidation` set to intended value (disabled or enabled per rollout plan) +- [ ] `eligibilityPeriod` = `1209600` (14 days) +- [ ] `oracleUpdateTimeout` = `604800` (7 days) +- [ ] Contract is NOT paused +- [ ] Oracle roles assigned to intended oracle addresses only +- [ ] No test accounts retain elevated roles +- [ ] If mock REO was used: RewardsManager points back to the production REO (`0x62c2305739cc75f19a3a6d52387ceb3690d99a99`) + +--- + +## Monitoring Checklist + +After the upgrade is live, continuously monitor: + +- [ ] `IndexerEligibilityRenewed` events flowing regularly from oracles +- [ ] `lastOracleUpdateTime` advancing (oracles are active) +- [ ] No `RewardsDeniedDueToEligibility` events for indexers that should be eligible +- [ ] Epoch progression and total rewards issuance unchanged from pre-upgrade baseline + +--- + +## Related Documentation + +- [← Back to REO Testing](README.md) +- [BaselineTestPlan.md](BaselineTestPlan.md) - Baseline operational tests (run first) + +--- + +_Derived from REO contract specification and audit reports. Source contracts: `/packages/issuance/contracts/eligibility/`_ diff --git a/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md b/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md new file mode 100644 index 000000000..b665e0b58 --- /dev/null +++ b/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md @@ -0,0 +1,781 @@ +# Rewards Conditions Test Plan + +> **Status: Complete** — Local network automation validates Cycles 1-4 and 6. Cycles 5 (resize) and 7 (zero signal) need testnet or special setup. +> +> **Navigation**: [← Back to REO Testing](README.md) | [BaselineTestPlan](BaselineTestPlan.md) | [SubgraphDenialTestPlan](SubgraphDenialTestPlan.md) + +Tests for the reclaim system, signal-related conditions, POI presentation paths, allocation lifecycle changes, and observability improvements introduced in the issuance upgrade. + +These tests cover all reward conditions **except** `INDEXER_INELIGIBLE` (covered by [ReoTestPlan](ReoTestPlan.md)) and `SUBGRAPH_DENIED` (covered by [SubgraphDenialTestPlan](SubgraphDenialTestPlan.md)). + +> All contract reads use `cast call`. All addresses must be **lowercase**. Replace placeholder addresses with actual deployed addresses for your network. + +## Contract Addresses + +| Contract | Arbitrum Sepolia | Arbitrum One | +| ----------------------- | -------------------------------------------- | -------------------------------------------- | +| RewardsManager (proxy) | `0x1f49cae7669086c8ba53cc35d1e9f80176d67e79` | `0x971b9d3d0ae3eca029cab5ea1fb0f72c85e6a525` | +| SubgraphService (proxy) | `0xc24a3dac5d06d771f657a48b20ce1a671b78f26b` | `0xb2bb92d0de618878e438b55d5846cfecd9301105` | +| GraphToken (L2) | `0xf8c05dcf59e8b28bfd5eed176c562bebcfc7ac04` | `0x9623063377ad1b27544c965ccd7342f7ea7e88c7` | +| Controller | `0x9db3ee191681f092607035d9bda6e59fbeaca695` | `0x0a8491544221dd212964fbb96487467291b2c97e` | + +### RPC + +| Network | RPC URL | +| ---------------- | ---------------------------------------- | +| Arbitrum Sepolia | `https://sepolia-rollup.arbitrum.io/rpc` | + +--- + +## Background + +The issuance upgrade introduces a `RewardsCondition` system that classifies every situation where rewards cannot be distributed normally. Instead of silently dropping undistributable rewards, each condition has a defined handling path: + +- **Reclaim**: Mint to a configured address (per-condition or default fallback) +- **Defer**: Preserve for later collection (snapshot not advanced) + +This test plan validates the reclaim infrastructure, each condition's handling, and the new observability features. + +--- + +## Prerequisites + +- [Baseline tests](BaselineTestPlan.md) Cycles 1-7 pass +- Governor access for reclaim address configuration +- SAO or Governor access for `setMinimumSubgraphSignal()` +- At least two indexers with active allocations +- Access to subgraph deployments with varying signal levels + +--- + +## Test Sequence Overview + +| Cycle | Area | Tests | Notes | +| ----- | ---------------------------- | --------- | -------------------------------------------------- | +| 1 | Reclaim System Configuration | 1.1 - 1.5 | Governor access needed | +| 2 | Below-Minimum Signal | 2.1 - 2.4 | Governor/SAO access; signal threshold changes | +| 3 | Zero Allocated Tokens | 3.1 - 3.3 | Requires subgraph with signal but no allocations | +| 4 | POI Presentation Paths | 4.1 - 4.5 | Requires mature and young allocations | +| 5 | Allocation Lifecycle | 5.1 - 5.3 | Resize and close operations | +| 6 | Observability | 6.1 - 6.3 | Event and view function verification | +| 7 | Zero Global Signal | 7.1 - 7.2 | Difficult on shared testnet; may be unit-test only | + +--- + +## Cycle 1: Reclaim System Configuration + +### 1.1 Configure per-condition reclaim addresses + +**Objective**: Set reclaim addresses for each condition and verify the routing. + +**Steps**: + +```bash +# Compute condition identifiers +NO_SIGNAL=$(cast keccak "NO_SIGNAL") +SUBGRAPH_DENIED=$(cast keccak "SUBGRAPH_DENIED") +BELOW_MINIMUM_SIGNAL=$(cast keccak "BELOW_MINIMUM_SIGNAL") +NO_ALLOCATED_TOKENS=$(cast keccak "NO_ALLOCATED_TOKENS") +STALE_POI=$(cast keccak "STALE_POI") +ZERO_POI=$(cast keccak "ZERO_POI") +CLOSE_ALLOCATION=$(cast keccak "CLOSE_ALLOCATION") +INDEXER_INELIGIBLE=$(cast keccak "INDEXER_INELIGIBLE") + +# Set per-condition reclaim addresses (as Governor) +# Using a single address for simplicity; in production these may differ +cast send "setReclaimAddress(bytes32,address)" $NO_SIGNAL --rpc-url --private-key + +cast send "setReclaimAddress(bytes32,address)" $BELOW_MINIMUM_SIGNAL --rpc-url --private-key + +cast send "setReclaimAddress(bytes32,address)" $NO_ALLOCATED_TOKENS --rpc-url --private-key + +cast send "setReclaimAddress(bytes32,address)" $STALE_POI --rpc-url --private-key + +cast send "setReclaimAddress(bytes32,address)" $ZERO_POI --rpc-url --private-key + +cast send "setReclaimAddress(bytes32,address)" $CLOSE_ALLOCATION --rpc-url --private-key + +# Verify each +cast call "getReclaimAddress(bytes32)(address)" $STALE_POI --rpc-url +cast call "getReclaimAddress(bytes32)(address)" $ZERO_POI --rpc-url +cast call "getReclaimAddress(bytes32)(address)" $CLOSE_ALLOCATION --rpc-url +``` + +**Pass Criteria**: + +- Each `setReclaimAddress` transaction succeeds +- `ReclaimAddressSet` event emitted for each +- `getReclaimAddress()` returns the correct address for each condition + +--- + +### 1.2 Configure default reclaim address + +**Objective**: Set the fallback reclaim address used when no per-condition address is configured. + +**Steps**: + +```bash +# Set default reclaim address (as Governor) +cast send "setDefaultReclaimAddress(address)" --rpc-url --private-key + +# Verify +cast call "getDefaultReclaimAddress()(address)" --rpc-url +``` + +**Pass Criteria**: + +- Transaction succeeds +- `DefaultReclaimAddressSet` event emitted +- `getDefaultReclaimAddress()` returns the configured address + +--- + +### 1.3 Verify fallback routing: unconfigured condition uses default + +**Objective**: A condition with no per-condition address should route to the default address. + +**Steps**: + +```bash +# Use a condition that does NOT have a per-condition address set +# (e.g., skip setting ALTRUISTIC_ALLOCATION in test 1.1) +ALTRUISTIC=$(cast keccak "ALTRUISTIC_ALLOCATION") + +# Verify no per-condition address +cast call "getReclaimAddress(bytes32)(address)" $ALTRUISTIC --rpc-url +# Expected: 0x0000... + +# The default address should catch this (verified by observing reclaim events when triggered) +cast call "getDefaultReclaimAddress()(address)" --rpc-url +``` + +**Pass Criteria**: + +- Per-condition address = `0x0` (not set) +- Default address is configured (non-zero) +- When this condition is triggered, `RewardsReclaimed` event shows tokens going to default address + +--- + +### 1.4 Unauthorized reclaim address change reverts + +**Objective**: Only the Governor can set reclaim addresses. + +**Steps**: + +```bash +# Non-governor attempts to set reclaim address +cast send "setReclaimAddress(bytes32,address)" $STALE_POI --rpc-url --private-key + +# Non-governor attempts to set default reclaim address +cast send "setDefaultReclaimAddress(address)" --rpc-url --private-key +``` + +**Pass Criteria**: + +- Both transactions revert + +--- + +### 1.5 Record baseline balances + +**Objective**: Record GRT balances of all reclaim addresses for comparison during later tests. + +**Steps**: + +```bash +cast call "balanceOf(address)(uint256)" --rpc-url +cast call "balanceOf(address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Balances recorded for comparison + +--- + +## Cycle 2: Below-Minimum Signal + +### 2.1 Verify current minimum signal threshold + +**Objective**: Check the current `minimumSubgraphSignal` value and identify subgraphs near the threshold. + +**Steps**: + +```bash +# Check current threshold +cast call "minimumSubgraphSignal()(uint256)" --rpc-url +``` + +**Verification Query** (find subgraphs near the threshold): + +```graphql +{ + subgraphDeployments(orderBy: signalledTokens, orderDirection: asc, where: { signalledTokens_gt: 0 }) { + ipfsHash + signalledTokens + stakedTokens + indexingRewardAmount + } +} +``` + +**Pass Criteria**: + +- Threshold value known +- At least one subgraph identified that is close to (or can be made to fall below) the threshold + +--- + +### 2.2 Raise threshold to trigger BELOW_MINIMUM_SIGNAL + +**Objective**: Increase `minimumSubgraphSignal` so that a target subgraph falls below the threshold, then verify rewards are reclaimed. + +> **Important**: Before changing the threshold, call `onSubgraphSignalUpdate()` on affected subgraphs to snapshot accumulators under the current rules. This prevents retroactive application over a long period. + +**Steps**: + +```bash +# Record accumulator for target subgraph +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url + +# Snapshot accumulators before threshold change +cast send "onSubgraphSignalUpdate(bytes32)" --rpc-url --private-key + +# Raise threshold (as Governor or SAO) +cast send "setMinimumSubgraphSignal(uint256)" --rpc-url --private-key + +# Verify threshold changed +cast call "minimumSubgraphSignal()(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Threshold changed successfully +- Target subgraph signal is now below the new threshold + +--- + +### 2.3 Accumulator freezes for below-threshold subgraph + +**Objective**: After the threshold increase, the below-threshold subgraph's accumulators should freeze and new rewards should be reclaimed. + +**Steps**: + +```bash +# Wait some time, then check accumulators +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url + +# Trigger accumulator update to process reclaim +cast send "onSubgraphSignalUpdate(bytes32)" --rpc-url --private-key + +# Check for RewardsReclaimed events +RECLAIM_EVENT_SIG=$(cast sig-event "RewardsReclaimed(bytes32,uint256,address,address,bytes32)") +cast logs --from-block --to-block latest --address --topic0 $RECLAIM_EVENT_SIG --rpc-url +``` + +**Pass Criteria**: + +- `accRewardsForSubgraph` frozen (not increasing) +- `RewardsReclaimed` event with reason = `BELOW_MINIMUM_SIGNAL` +- Reclaim address balance increased + +--- + +### 2.4 Restore threshold and verify resumption + +**Objective**: Lower the threshold back so the subgraph is above minimum. Accumulators should resume. + +**Steps**: + +```bash +# Snapshot before change +cast send "onSubgraphSignalUpdate(bytes32)" --rpc-url --private-key + +# Restore threshold +cast send "setMinimumSubgraphSignal(uint256)" --rpc-url --private-key + +# Wait, then check accumulators +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Threshold restored to original value +- `accRewardsForSubgraph` resumes increasing +- Allocations on this subgraph can claim rewards again + +--- + +## Cycle 3: Zero Allocated Tokens + +### 3.1 Identify subgraph with signal but no allocations + +**Objective**: Find or create a subgraph deployment that has curation signal but zero allocated tokens. + +**Verification Query**: + +```graphql +{ + subgraphDeployments(where: { signalledTokens_gt: 0, stakedTokens: 0 }) { + ipfsHash + signalledTokens + stakedTokens + } +} +``` + +Alternatively, close all allocations on a test subgraph while leaving signal intact. + +**Pass Criteria**: + +- Subgraph deployment identified with `signalledTokens > 0` and `stakedTokens = 0` + +--- + +### 3.2 Verify NO_ALLOCATED_TOKENS reclaim + +**Objective**: When a subgraph has signal but no allocations, rewards for that signal share are reclaimed as `NO_ALLOCATED_TOKENS`. + +**Steps**: + +```bash +# Trigger accumulator update for the zero-allocation subgraph +cast send "onSubgraphAllocationUpdate(bytes32)" --rpc-url --private-key + +# Check for RewardsReclaimed events +NO_ALLOCATED_TOKENS=$(cast keccak "NO_ALLOCATED_TOKENS") +RECLAIM_EVENT_SIG=$(cast sig-event "RewardsReclaimed(bytes32,uint256,address,address,bytes32)") +cast logs --from-block --to-block --address --topic0 $RECLAIM_EVENT_SIG --rpc-url +``` + +**Pass Criteria**: + +- `RewardsReclaimed` event with reason = `NO_ALLOCATED_TOKENS` +- Reclaim address received tokens + +--- + +### 3.3 Allocations resume from stored baseline + +**Objective**: When a new allocation is created on a subgraph that previously had zero allocations, `accRewardsPerAllocatedToken` resumes from its stored value rather than resetting to zero. + +**Steps**: + +```bash +# Record current accRewardsPerAllocatedToken +cast call "getAccRewardsPerAllocatedToken(bytes32)(uint256,uint256)" --rpc-url + +# Create allocation +graph indexer allocations create + +# Check accRewardsPerAllocatedToken after creation +cast call "getAccRewardsPerAllocatedToken(bytes32)(uint256,uint256)" --rpc-url +``` + +**Pass Criteria**: + +- New allocation created successfully +- `accRewardsPerAllocatedToken` not reset to zero (maintains stored value) +- New allocation starts accruing from current accumulator value + +--- + +## Cycle 4: POI Presentation Paths + +The issuance upgrade introduces three distinct POI presentation outcomes: **claim**, **reclaim**, and **defer**. Each condition routes to one of these paths. + +### 4.1 Normal claim path (NONE condition) + +**Objective**: Verify that a valid POI on a non-denied, signal-above-threshold, non-stale allocation claims rewards normally. The `POIPresented` event should show `condition = bytes32(0)`. + +**Prerequisites**: Active allocation, open 2+ epochs, not stale, on a non-denied subgraph with signal above threshold. + +**Steps**: + +```bash +# Confirm allocation is healthy +cast call "getRewards(address,address)(uint256)" --rpc-url +# Expected: non-zero + +# Close allocation (presents POI and claims) +graph indexer allocations close +``` + +**Verification**: Check transaction for `POIPresented` event: + +```bash +POI_EVENT_SIG=$(cast sig-event "POIPresented(address,address,bytes32,bytes32,bytes,bytes32)") +cast logs --from-block --to-block --address --topic0 $POI_EVENT_SIG --rpc-url +``` + +**Pass Criteria**: + +- `POIPresented` event emitted with `condition = 0x00...00` (NONE) +- `indexingRewards` non-zero +- Normal `HorizonRewardsAssigned` event emitted + +--- + +### 4.2 Reclaim path: STALE_POI + +**Objective**: When an allocation is stale (no POI presented within `maxPOIStaleness`), presenting a POI reclaims rewards instead of claiming them. + +**Prerequisites**: An allocation that has not had a POI presented for longer than `maxPOIStaleness`. + +**Steps**: + +```bash +# Check maxPOIStaleness +cast call "maxPOIStaleness()(uint256)" --rpc-url + +# Find or wait for a stale allocation +# (Let an allocation go without POI presentation for maxPOIStaleness seconds) + +# Close the stale allocation +graph indexer allocations close +``` + +**Pass Criteria**: + +- `POIPresented` event emitted with `condition = keccak256("STALE_POI")` +- `indexingRewards` = 0 (rewards not claimed by indexer) +- `RewardsReclaimed` event with reason = `STALE_POI` +- Reclaim address received the tokens +- Allocation snapshot advanced (pending rewards cleared) + +--- + +### 4.3 Reclaim path: ZERO_POI + +**Objective**: Submitting a zero POI (`bytes32(0)`) reclaims rewards. + +**Prerequisites**: Active allocation, mature (2+ epochs). + +**Steps**: + +```bash +# Close allocation with explicit zero POI +graph indexer allocations close --poi 0x0000000000000000000000000000000000000000000000000000000000000000 +``` + +**Pass Criteria**: + +- `POIPresented` event emitted with `condition = keccak256("ZERO_POI")` +- `indexingRewards` = 0 +- `RewardsReclaimed` event with reason = `ZERO_POI` +- Reclaim address received the tokens +- Allocation snapshot advanced (pending rewards cleared) + +--- + +### 4.4 Defer path: ALLOCATION_TOO_YOUNG + +**Objective**: Presenting a POI for an allocation created in the current epoch defers — returns 0 without advancing the snapshot, preserving rewards for later. + +**Prerequisites**: Create a new allocation and attempt POI presentation in the same epoch. + +**Steps**: + +```bash +# Create allocation +graph indexer allocations create + +# Immediately attempt POI presentation (same epoch) +# (via manual cast send or indexer agent action) +``` + +**Pass Criteria**: + +- `POIPresented` event emitted with `condition = keccak256("ALLOCATION_TOO_YOUNG")` +- Returns 0 rewards +- **Critical**: Allocation snapshot NOT advanced (rewards preserved for later) +- Allocation remains open and healthy +- After waiting for epoch boundary: normal claim succeeds + +--- + +### 4.5 POI presentation always updates timestamp + +**Objective**: Verify that the POI presentation timestamp is recorded regardless of the condition outcome. This means even reclaimed or deferred presentations reset the staleness clock. + +**Steps**: + +1. Present a POI that results in a defer (e.g., too young) +2. Check that the staleness timer reset +3. Present a POI that results in a reclaim (e.g., zero POI) +4. Check that the staleness timer reset + +**Pass Criteria**: + +- Staleness timer resets on every POI presentation, regardless of outcome +- An allocation that regularly presents POIs (even deferred ones) does not become stale + +--- + +## Cycle 5: Allocation Lifecycle + +### 5.1 Allocation resize reclaims stale rewards + +**Objective**: Resizing a stale allocation reclaims pending rewards as `STALE_POI` and clears them. This prevents stale allocations from silently accumulating rewards through repeated resizes. + +**Prerequisites**: An allocation that is stale (no POI for `maxPOIStaleness`). The allocation has pending rewards from before it went stale. + +**Steps**: + +```bash +# Confirm allocation is stale +# (Check last POI timestamp vs maxPOIStaleness) + +# Check pending rewards before resize +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Resize the allocation +graph indexer allocations reallocate +``` + +**Pass Criteria**: + +- `RewardsReclaimed` event with reason = `STALE_POI` +- Pending rewards cleared (not carried forward through resize) +- Reclaim address received the stale rewards +- New allocation starts fresh (no carried-over stale rewards) + +--- + +### 5.2 Allocation resize does NOT reclaim for non-stale allocation + +**Objective**: Resizing a healthy (non-stale) allocation should accumulate pending rewards normally, not reclaim them. + +**Prerequisites**: Active, non-stale allocation with pending rewards. + +**Steps**: + +```bash +# Check pending rewards +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Resize +graph indexer allocations reallocate + +# Check that no STALE_POI reclaim event occurred +``` + +**Pass Criteria**: + +- No `RewardsReclaimed` event with reason = `STALE_POI` +- Pending rewards accumulated into `accRewardsPending` (carried through resize) +- New allocation can claim accumulated rewards on next close + +--- + +### 5.3 Allocation close reclaims uncollected rewards + +**Objective**: When an allocation is closed, any uncollected rewards are reclaimed as `CLOSE_ALLOCATION` before the allocation is finalized. This prevents rewards from being permanently lost on close. + +**Prerequisites**: An allocation with uncollected rewards (e.g., the indexer has not presented a POI recently, or rewards accumulated since last POI). + +**Steps**: + +```bash +# Record reclaim address balance +cast call "balanceOf(address)(uint256)" --rpc-url + +# Close allocation +graph indexer allocations close + +# Check for CLOSE_ALLOCATION reclaim +CLOSE_ALLOC=$(cast keccak "CLOSE_ALLOCATION") +RECLAIM_EVENT_SIG=$(cast sig-event "RewardsReclaimed(bytes32,uint256,address,address,bytes32)") +cast logs --from-block --to-block --address --topic0 $RECLAIM_EVENT_SIG --rpc-url + +# Check reclaim address balance increased +cast call "balanceOf(address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- `RewardsReclaimed` event with reason = `CLOSE_ALLOCATION` +- Reclaim address balance increased +- Rewards not permanently lost (either claimed by indexer via POI or reclaimed to protocol) + +--- + +## Cycle 6: Observability + +### 6.1 POIPresented event emitted on every presentation + +**Objective**: Verify that every POI presentation emits a `POIPresented` event with the determined condition, regardless of outcome. + +**Steps**: + +Collect events across multiple scenarios from previous cycles: + +```bash +POI_EVENT_SIG=$(cast sig-event "POIPresented(address,address,bytes32,bytes32,bytes,bytes32)") + +# Query all POIPresented events from the test session +cast logs --from-block --to-block latest --address --topic0 $POI_EVENT_SIG --rpc-url +``` + +**Pass Criteria**: + +- Every POI presentation (from Cycles 4-5) has a corresponding `POIPresented` event +- Each event contains: + - `indexer`: correct indexer address + - `allocationId`: correct allocation + - `subgraphDeploymentId`: correct deployment + - `poi`: the submitted POI value + - `condition`: matches the expected outcome (NONE, STALE_POI, ZERO_POI, ALLOCATION_TOO_YOUNG, SUBGRAPH_DENIED) + +--- + +### 6.2 RewardsReclaimed events include full context + +**Objective**: Verify that `RewardsReclaimed` events contain all necessary context for off-chain accounting. + +**Steps**: + +```bash +RECLAIM_EVENT_SIG=$(cast sig-event "RewardsReclaimed(bytes32,uint256,address,address,bytes32)") + +# Query all RewardsReclaimed events from the test session +cast logs --from-block --to-block latest --address --topic0 $RECLAIM_EVENT_SIG --rpc-url +``` + +**Pass Criteria**: + +- Each `RewardsReclaimed` event contains: + - `reason`: valid `RewardsCondition` identifier (not zero) + - `amount`: non-zero GRT amount + - `indexer`: address of the affected indexer (or zero for subgraph-level reclaims) + - `allocationID`: address of the affected allocation (or zero for subgraph-level reclaims) + - `subgraphDeploymentID`: deployment hash + +--- + +### 6.3 View functions reflect frozen state accurately + +**Objective**: Verify that `getAccRewardsForSubgraph()`, `getAccRewardsPerAllocatedToken()`, and `getRewards()` correctly return frozen values for non-claimable subgraphs and growing values for claimable ones. + +**Steps**: + +```bash +# For a denied subgraph (if one is still denied from SubgraphDenialTestPlan) +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url +# Wait, read again — should be unchanged + +# For a below-threshold subgraph (if one is still below from Cycle 2) +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url +# Wait, read again — should be unchanged + +# For a healthy subgraph (control) +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url +# Wait, read again — should have increased + +# getRewards for allocation on non-claimable subgraph +cast call "getRewards(address,address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Non-claimable subgraphs: view functions return frozen (non-increasing) values +- Claimable subgraphs: view functions return growing values +- `getRewards()` for allocations on non-claimable subgraphs returns a frozen value +- Pre-existing `accRewardsPending` from prior resizes is still included in `getRewards()` even for non-claimable subgraphs + +--- + +## Cycle 7: Zero Global Signal + +> **Note**: These tests require zero total curation signal across the entire network, which is impractical on a shared testnet. They are documented here for completeness and should be validated via Foundry unit tests or on a dedicated test network. + +### 7.1 NO_SIGNAL detection + +**Objective**: When total curation signal across all subgraphs is zero, issuance during that period should be reclaimed as `NO_SIGNAL`. + +**Steps** (dedicated testnet only): + +```bash +# Remove all curation signal from all subgraphs +# (Only feasible on a private testnet) + +# Wait for blocks to pass (issuance accrues to nobody) + +# Trigger accumulator update +cast send "updateAccRewardsPerSignal()" --rpc-url --private-key + +# Check for RewardsReclaimed with NO_SIGNAL +NO_SIGNAL=$(cast keccak "NO_SIGNAL") +RECLAIM_EVENT_SIG=$(cast sig-event "RewardsReclaimed(bytes32,uint256,address,address,bytes32)") +cast logs --from-block --to-block --address --topic0 $RECLAIM_EVENT_SIG --rpc-url +``` + +**Pass Criteria**: + +- `RewardsReclaimed` event with reason = `NO_SIGNAL` +- Reclaimed amount corresponds to issuance during zero-signal period +- `getNewRewardsPerSignal()` still returns claimable portion only (unchanged from legacy behavior) + +--- + +### 7.2 Signal restoration resumes normal distribution + +**Objective**: After signal is restored, rewards distribution resumes normally. + +**Steps** (dedicated testnet only): + +1. Add curation signal to a subgraph +2. Verify `getNewRewardsPerSignal()` returns non-zero +3. Verify accumulators resume growing + +**Pass Criteria**: + +- Rewards flow normally after signal restoration +- No rewards from the zero-signal period leak into the normal distribution + +--- + +## Post-Testing Checklist + +- [ ] Reclaim addresses verified for all conditions +- [ ] `minimumSubgraphSignal` restored to original value +- [ ] No subgraphs left in unintended denied state +- [ ] Reclaim address balances reconciled with expected amounts +- [ ] All `POIPresented` events collected and categorized +- [ ] Results documented in test tracker + +--- + +## Test Summary + +| Condition | Test(s) | Cycle | Testnet Feasibility | +| ------------------------ | --------- | ----- | ---------------------- | +| Reclaim infrastructure | 1.1 - 1.5 | 1 | Full | +| `BELOW_MINIMUM_SIGNAL` | 2.1 - 2.4 | 2 | Full | +| `NO_ALLOCATED_TOKENS` | 3.1 - 3.3 | 3 | Full | +| `NONE` (normal claim) | 4.1 | 4 | Full | +| `STALE_POI` | 4.2 | 4 | Full (wait needed) | +| `ZERO_POI` | 4.3 | 4 | Full | +| `ALLOCATION_TOO_YOUNG` | 4.4 | 4 | Full | +| POI timestamp behavior | 4.5 | 4 | Full | +| Stale resize reclaim | 5.1 - 5.2 | 5 | Full (wait needed) | +| `CLOSE_ALLOCATION` | 5.3 | 5 | Full | +| `POIPresented` event | 6.1 | 6 | Full | +| `RewardsReclaimed` event | 6.2 | 6 | Full | +| View function freeze | 6.3 | 6 | Full | +| `NO_SIGNAL` | 7.1 - 7.2 | 7 | Dedicated testnet only | + +--- + +## Related Documentation + +- [← Back to REO Testing](README.md) +- [SubgraphDenialTestPlan.md](SubgraphDenialTestPlan.md) — Subgraph denial behavior tests +- [BaselineTestPlan.md](BaselineTestPlan.md) — Baseline operational tests (run first) +- [ReoTestPlan.md](ReoTestPlan.md) — REO eligibility tests + +--- + +_Derived from issuance upgrade behavior changes. Source: [RewardsBehaviourChanges.md](/docs/RewardsBehaviourChanges.md), [RewardConditions.md](/docs/RewardConditions.md). Contracts: `packages/contracts/contracts/rewards/RewardsManager.sol`, `packages/subgraph-service/contracts/utilities/AllocationManager.sol`._ diff --git a/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md b/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md new file mode 100644 index 000000000..cc03a7d7d --- /dev/null +++ b/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md @@ -0,0 +1,680 @@ +# Subgraph Denial Test Plan + +> **Status: Complete** — Local network automation validates Cycles 2, 3, 5, and 6 (edge cases). Cycle 4 (allocation-level deferral) needs direct POI presentation. +> +> **Navigation**: [← Back to REO Testing](README.md) | [BaselineTestPlan](BaselineTestPlan.md) | [RewardsConditionsTestPlan](RewardsConditionsTestPlan.md) + +Tests for the subgraph denial behavior changes introduced in the issuance upgrade. Denial handling changed significantly: accumulators now freeze during denial (reclaiming new rewards), while uncollected pre-denial rewards are preserved and become claimable after undeny. + +> All contract reads use `cast call`. All addresses must be **lowercase**. Replace placeholder addresses with actual deployed addresses for your network. + +## Contract Addresses + +| Contract | Arbitrum Sepolia | Arbitrum One | +| ----------------------- | -------------------------------------------- | -------------------------------------------- | +| RewardsManager (proxy) | `0x1f49cae7669086c8ba53cc35d1e9f80176d67e79` | `0x971b9d3d0ae3eca029cab5ea1fb0f72c85e6a525` | +| SubgraphService (proxy) | `0xc24a3dac5d06d771f657a48b20ce1a671b78f26b` | `0xb2bb92d0de618878e438b55d5846cfecd9301105` | +| GraphToken (L2) | `0xf8c05dcf59e8b28bfd5eed176c562bebcfc7ac04` | `0x9623063377ad1b27544c965ccd7342f7ea7e88c7` | +| Controller | `0x9db3ee191681f092607035d9bda6e59fbeaca695` | `0x0a8491544221dd212964fbb96487467291b2c97e` | + +**Address sources**: `packages/horizon/addresses.json` (RewardsManager, GraphToken, Controller), `packages/subgraph-service/addresses.json` (SubgraphService). + +### RPC + +| Network | RPC URL | +| ---------------- | ---------------------------------------- | +| Arbitrum Sepolia | `https://sepolia-rollup.arbitrum.io/rpc` | + +--- + +## Background + +### What Changed + +**Before (Horizon baseline):** Denial was a binary gate at `takeRewards()` time. When a subgraph was denied, rewards were returned as 0 and the allocation snapshot advanced, permanently dropping those rewards. + +**After (issuance upgrade):** Denial is handled at two levels: + +1. **RewardsManager (accumulator level):** When accumulator updates encounter a denied subgraph, `accRewardsForSubgraph` and `accRewardsPerAllocatedToken` freeze. New rewards during denial are reclaimed instead of accumulated. `setDenied()` snapshots accumulators before changing state so the boundary is clean. + +2. **AllocationManager (claim level):** POI presentation for a denied subgraph is _deferred_ — returns 0 **without advancing the allocation snapshot**. Uncollected pre-denial rewards are preserved and become claimable after undeny. + +### Key Invariants + +- Accumulators never decrease (they freeze during denial, not decrease) +- Pre-denial uncollected rewards are preserved through the deny/undeny cycle +- Denial-period rewards are reclaimed (or dropped if no reclaim address) +- `setDenied()` snapshots accumulators before state change (clean boundary) +- Redundant deny/undeny calls are idempotent (no state change) + +--- + +## Prerequisites + +- [Baseline tests](BaselineTestPlan.md) Cycles 1-7 pass +- [Reclaim system configured](RewardsConditionsTestPlan.md#cycle-1-reclaim-system-configuration) (Cycle 1 of RewardsConditionsTestPlan) — or configure inline during Cycle 1 below +- At least two indexers with active allocations on rewarded subgraph deployments +- Access to the Governor or SubgraphAvailabilityOracle (SAO) account that can call `setDenied()` +- Allocations must be mature (open for 2+ epochs) before denial tests + +### Roles Needed + +| Role | Needed For | Holder | +| --------------- | --------------------------------------------- | -------------------------------- | +| Governor or SAO | `setDenied()` calls | Check Controller configuration | +| Governor | `setReclaimAddress()` (if not yet configured) | Council/NetworkOperator multisig | + +### Identifying the SAO + +```bash +# The SAO is stored in the Controller as the subgraphAvailabilityOracle +# Alternatively, check who can call setDenied on RewardsManager +cast call "getContractProxy(bytes32)(address)" $(cast keccak "SubgraphAvailabilityOracle") --rpc-url +``` + +--- + +## Testing Approach + +**Dedicated test subgraph**: Use a subgraph deployment that is not critical to other testing. The deployment should have: + +- Non-zero curation signal +- At least two active allocations from different indexers +- Signal above `minimumSubgraphSignal` (to isolate denial behavior from signal threshold behavior) + +**Epoch timing**: Many tests require waiting for epoch boundaries. On Sepolia, epochs are ~554 blocks (~110 minutes). Plan sessions accordingly. + +**Reclaim address monitoring**: Before starting, configure a reclaim address for `SUBGRAPH_DENIED` so reclaimed tokens are observable. If no reclaim address is set, denial-period rewards are silently dropped. + +--- + +## Test Sequence Overview + +| Cycle | Area | Tests | Notes | +| ----- | ------------------------------- | --------- | -------------------------------------------------- | +| 1 | Reclaim Setup for Denial | 1.1 - 1.2 | Governor access needed; skip if already configured | +| 2 | Denial State Management | 2.1 - 2.4 | SAO or Governor access needed | +| 3 | Accumulator Freeze Verification | 3.1 - 3.4 | Read-only after denial; wait for epochs | +| 4 | Allocation-Level Deferral | 4.1 - 4.3 | Requires active allocations on denied subgraph | +| 5 | Undeny and Reward Recovery | 5.1 - 5.4 | Full deny→undeny→claim lifecycle | +| 6 | Edge Cases | 6.1 - 6.4 | Advanced scenarios | + +--- + +## Cycle 1: Reclaim Setup for Denial + +> Skip this cycle if reclaim addresses are already configured (verify with tests 1.1 reads). + +### 1.1 Configure SUBGRAPH_DENIED reclaim address + +**Objective**: Set a reclaim address for `SUBGRAPH_DENIED` so that denial-period rewards are minted to a trackable address instead of being silently dropped. + +**Steps**: + +```bash +# Compute the SUBGRAPH_DENIED condition identifier +SUBGRAPH_DENIED=$(cast keccak "SUBGRAPH_DENIED") + +# Check current reclaim address (expect zero if unconfigured) +cast call "getReclaimAddress(bytes32)(address)" $SUBGRAPH_DENIED --rpc-url + +# Set reclaim address (as Governor) +cast send "setReclaimAddress(bytes32,address)" $SUBGRAPH_DENIED --rpc-url --private-key + +# Verify +cast call "getReclaimAddress(bytes32)(address)" $SUBGRAPH_DENIED --rpc-url +``` + +**Pass Criteria**: + +- `ReclaimAddressSet` event emitted with correct reason and address +- `getReclaimAddress(SUBGRAPH_DENIED)` returns the configured address + +--- + +### 1.2 Record reclaim address GRT balance + +**Objective**: Record the starting GRT balance of the reclaim address so we can measure tokens reclaimed during denial. + +**Steps**: + +```bash +cast call "balanceOf(address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Balance recorded for later comparison + +--- + +## Cycle 2: Denial State Management + +### 2.1 Verify subgraph is not denied (pre-test) + +**Objective**: Confirm the test subgraph deployment is currently not denied and accumulators are growing. + +**Steps**: + +```bash +# Check denial status +cast call "isDenied(bytes32)(bool)" --rpc-url + +# Record current accumulator values +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url + +cast call "getAccRewardsPerAllocatedToken(bytes32)(uint256,uint256)" --rpc-url +``` + +**Pass Criteria**: + +- `isDenied` = `false` +- Accumulator values recorded as baseline + +--- + +### 2.2 Deny subgraph deployment + +**Objective**: Deny a subgraph and verify the state transition. Confirm `setDenied()` snapshots accumulators before applying denial. + +**Steps**: + +```bash +# Deny the subgraph (as SAO or Governor) +cast send "setDenied(bytes32,bool)" true --rpc-url --private-key + +# Verify denial +cast call "isDenied(bytes32)(bool)" --rpc-url +``` + +**Verification**: Check for `RewardsDenylistUpdated` event: + +```bash +# Check the transaction receipt for RewardsDenylistUpdated event +cast receipt --rpc-url +``` + +**Pass Criteria**: + +- Transaction succeeds +- `isDenied` = `true` +- `RewardsDenylistUpdated(subgraphDeploymentID, sinceBlock)` event emitted with `sinceBlock` = block number of the transaction + +--- + +### 2.3 Redundant deny is idempotent + +**Objective**: Calling `setDenied(true)` on an already-denied subgraph should not change state or emit new events. + +**Steps**: + +```bash +# Deny again (already denied) +cast send "setDenied(bytes32,bool)" true --rpc-url --private-key + +# Verify still denied +cast call "isDenied(bytes32)(bool)" --rpc-url +``` + +**Pass Criteria**: + +- Transaction succeeds (does not revert) +- `isDenied` still = `true` +- No additional `RewardsDenylistUpdated` event (or event has unchanged `sinceBlock`) + +--- + +### 2.4 Unauthorized deny reverts + +**Objective**: Only the SAO or Governor can deny subgraphs. + +**Steps**: + +```bash +# Attempt deny from unauthorized account +cast send "setDenied(bytes32,bool)" true --rpc-url --private-key +``` + +**Pass Criteria**: + +- Transaction reverts + +--- + +## Cycle 3: Accumulator Freeze Verification + +> **Timing**: These tests require waiting for time to pass after denial. At minimum, wait for part of an epoch (~30-60 minutes on Sepolia) between reads to observe that accumulators have stopped growing. + +### 3.1 Accumulators freeze after denial + +**Objective**: Verify that `accRewardsForSubgraph` and `accRewardsPerAllocatedToken` stop growing for a denied subgraph. + +**Prerequisites**: Subgraph denied in test 2.2. Wait at least 30 minutes. + +**Steps**: + +```bash +# Read accumulators (should match or be very close to values recorded at denial time) +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url + +cast call "getAccRewardsPerAllocatedToken(bytes32)(uint256,uint256)" --rpc-url + +# Compare with a non-denied subgraph (should be growing) +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Denied subgraph: `accRewardsForSubgraph` has NOT increased since denial +- Denied subgraph: `accRewardsPerAllocatedToken` has NOT increased since denial +- Non-denied subgraph: accumulators continue to increase normally (control) + +--- + +### 3.2 getRewards returns frozen value for allocations on denied subgraph + +**Objective**: Verify that `getRewards()` for an allocation on a denied subgraph returns a frozen value (no new rewards accumulate). + +**Steps**: + +```bash +# Check pending rewards for allocation on denied subgraph +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Wait some time, check again +# (wait 30+ minutes) +cast call "getRewards(address,address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Both reads return the same value (frozen — no new rewards accruing) +- The value represents pre-denial uncollected rewards (may be non-zero) + +--- + +### 3.3 Denial-period rewards reclaimed + +**Objective**: Verify that rewards that would have gone to the denied subgraph are being reclaimed to the configured address. + +**Prerequisites**: Reclaim address configured in Cycle 1. Some time has passed since denial. + +**Steps**: + +```bash +# Trigger an accumulator update that processes the denied subgraph +# This happens automatically on signal/allocation changes, but can be forced: +cast send "onSubgraphSignalUpdate(bytes32)" --rpc-url --private-key + +# Check reclaim address balance +cast call "balanceOf(address)(uint256)" --rpc-url +``` + +**Verification**: Check for `RewardsReclaimed` events: + +```bash +RECLAIM_EVENT_SIG=$(cast sig-event "RewardsReclaimed(bytes32,uint256,address,address,bytes32)") +cast logs --from-block --to-block latest --address --topic0 $RECLAIM_EVENT_SIG --rpc-url +``` + +**Pass Criteria**: + +- `RewardsReclaimed` event(s) emitted with reason = `SUBGRAPH_DENIED` +- Reclaim address GRT balance has increased from the Cycle 1 baseline +- Reclaimed amount is proportional to the denied subgraph's signal share and denial duration + +--- + +### 3.4 Non-denied subgraphs unaffected + +**Objective**: Confirm that denying one subgraph does not affect reward accumulation for other subgraphs. + +**Steps**: + +```bash +# Check a non-denied subgraph's accumulator +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url + +# Check allocation rewards on non-denied subgraph +cast call "getRewards(address,address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Non-denied subgraph accumulators continue increasing +- Allocation rewards on non-denied subgraph continue accruing + +--- + +## Cycle 4: Allocation-Level Deferral + +### 4.1 POI presentation on denied subgraph defers (returns 0, preserves state) + +**Objective**: When an indexer presents a POI for a denied subgraph, the allocation should return 0 rewards WITHOUT advancing the snapshot. The `POIPresented` event should show `condition = SUBGRAPH_DENIED`. + +**Prerequisites**: Indexer has an active allocation on the denied subgraph. Allocation is mature (open 2+ epochs). + +**Steps**: + +1. Record the allocation's current reward snapshot (via view functions) +2. Close or present POI for the allocation on the denied subgraph + +```bash +# Check pending rewards before POI presentation +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Present POI (via indexer agent or manual close attempt) +# The exact mechanism depends on your indexer setup +graph indexer allocations close +``` + +**Verification**: Check transaction logs for `POIPresented` event: + +```bash +POI_EVENT_SIG=$(cast sig-event "POIPresented(address,address,bytes32,bytes32,bytes,bytes32)") +cast logs --from-block --to-block --address --topic0 $POI_EVENT_SIG --rpc-url +``` + +**Pass Criteria**: + +- `POIPresented` event emitted with `condition` = `keccak256("SUBGRAPH_DENIED")` +- Rewards returned = 0 +- **Critical**: Allocation snapshot NOT advanced (pre-denial rewards preserved) +- Allocation remains open if this was a POI presentation (not a force-close) + +--- + +### 4.2 Multiple POI presentations while denied do not lose rewards + +**Objective**: An indexer can present POIs multiple times while a subgraph is denied without losing any pre-denial rewards. Each presentation should defer without advancing the snapshot. + +**Steps**: + +```bash +# First POI presentation (while denied) +# Record getRewards value +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Present POI +# (use indexer agent or cast send to SubgraphService) + +# Second POI presentation (still denied, next epoch) +# Wait one epoch +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Present POI again +``` + +**Pass Criteria**: + +- `getRewards()` returns the same frozen value across all presentations +- No `RewardsReclaimed` events for the allocation's pre-denial rewards +- Pre-denial rewards remain preserved through multiple POI cycles + +--- + +### 4.3 Indexers should continue presenting POIs during denial + +**Objective**: Document that continuing POI presentation during denial prevents staleness. The POI timestamp is updated even on deferred presentations. + +**Steps**: + +1. Confirm the denied subgraph has active allocations +2. Present POI normally (via indexer agent) +3. Verify the allocation's last POI timestamp is updated + +**Pass Criteria**: + +- POI presentation succeeds (transaction does not revert) +- Allocation does not become stale during denial period +- When subgraph is later undenied, the allocation is still healthy (not stale) + +--- + +## Cycle 5: Undeny and Reward Recovery + +### 5.1 Undeny subgraph deployment + +**Objective**: Remove denial and verify accumulators resume growing. + +**Steps**: + +```bash +# Record accumulators just before undeny +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url + +# Undeny +cast send "setDenied(bytes32,bool)" false --rpc-url --private-key + +# Verify +cast call "isDenied(bytes32)(bool)" --rpc-url +``` + +**Verification**: Check for `RewardsDenylistUpdated` event with `sinceBlock = 0`. + +**Pass Criteria**: + +- `isDenied` = `false` +- `RewardsDenylistUpdated(subgraphDeploymentID, 0)` event emitted + +--- + +### 5.2 Accumulators resume after undeny + +**Objective**: Verify that accumulators start growing again after undeny. + +**Prerequisites**: Subgraph undenied in test 5.1. Wait at least 30 minutes. + +**Steps**: + +```bash +# Read accumulators (should now be growing again) +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url + +cast call "getAccRewardsPerAllocatedToken(bytes32)(uint256,uint256)" --rpc-url +``` + +**Pass Criteria**: + +- `accRewardsForSubgraph` has increased since undeny +- `accRewardsPerAllocatedToken` has increased since undeny +- Growth rate is consistent with the subgraph's signal proportion + +--- + +### 5.3 Pre-denial rewards claimable after undeny + +**Objective**: Verify that uncollected rewards from before the denial period are now claimable. This is the critical test: the new behavior preserves these rewards rather than dropping them. + +**Prerequisites**: Indexer has allocation that was open before denial and still active. Subgraph is now undenied. Wait 1-2 epochs after undeny. + +**Steps**: + +```bash +# Check pending rewards (should include pre-denial uncollected + post-undeny new rewards) +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Close allocation to claim +graph indexer allocations close +``` + +**Verification Query**: + +```graphql +{ + allocations(where: { id: "ALLOCATION_ID" }) { + id + status + indexingRewards + closedAtEpoch + } +} +``` + +**Pass Criteria**: + +- `indexingRewards` is non-zero +- Reward amount includes: + - Pre-denial uncollected rewards (accumulated before deny) + - Post-undeny rewards (accumulated after undeny) +- Reward amount does NOT include denial-period rewards (those were reclaimed in Cycle 3) +- `POIPresented` event shows `condition = NONE` (normal claim) + +--- + +### 5.4 Denial-period rewards are NOT included in claim + +**Objective**: Verify that the claimed rewards exclude the denial period. Compare the claimed amount against what a continuously-active allocation would have earned. + +**Steps**: + +1. Calculate expected rewards: + - Pre-denial period: from allocation creation to deny block + - Post-undeny period: from undeny block to close block + - Denial period: from deny block to undeny block (should be excluded) +2. Compare actual `indexingRewards` from test 5.3 + +**Pass Criteria**: + +- Claimed rewards approximate (pre-denial + post-undeny) only +- Denial-period rewards were reclaimed (verified in Cycle 3) +- Total of (claimed + reclaimed) approximately equals what would have been earned with no denial + +--- + +## Cycle 6: Edge Cases + +### 6.1 New allocation created while subgraph is denied + +**Objective**: An allocation opened on a denied subgraph starts with a frozen baseline. It should only earn rewards after undeny. + +**Prerequisites**: Subgraph currently denied. + +**Steps**: + +```bash +# Create allocation on denied subgraph +graph indexer allocations create + +# Check rewards immediately +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Wait some time (still denied) +# Check rewards again +cast call "getRewards(address,address)(uint256)" --rpc-url + +# Undeny +cast send "setDenied(bytes32,bool)" false --rpc-url --private-key + +# Wait 1-2 epochs after undeny +# Check rewards again +cast call "getRewards(address,address)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- While denied: `getRewards()` returns 0 (no rewards accumulate) +- After undeny: `getRewards()` starts increasing (rewards resume from undeny point) +- Allocation only earns post-undeny rewards + +--- + +### 6.2 All allocations close while denied, then new allocation after undeny + +**Objective**: When all allocations close during denial, the frozen accumulator state is preserved. A new allocation after undeny should use that preserved baseline. + +**Steps**: + +1. Deny subgraph (if not already denied) +2. Close all allocations on the denied subgraph +3. Undeny subgraph +4. Create new allocation +5. Wait 1-2 epochs, close, check rewards + +**Pass Criteria**: + +- New allocation earns rewards only for the post-undeny period +- Frozen state was correctly preserved through the "no allocations" period +- No rewards are double-counted or lost at the transition + +--- + +### 6.3 Deny and undeny in rapid succession + +**Objective**: A quick deny→undeny cycle correctly handles the boundary. Accumulators are snapshotted on each transition. + +**Steps**: + +```bash +# Record accumulators +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url + +# Deny +cast send "setDenied(bytes32,bool)" true --rpc-url --private-key + +# Undeny (in next block or shortly after) +cast send "setDenied(bytes32,bool)" false --rpc-url --private-key + +# Check accumulators +cast call "getAccRewardsForSubgraph(bytes32)(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Both transactions succeed +- Accumulators resume growing after undeny +- Minimal reward loss (only the few blocks between deny and undeny) +- No contract reverts or unexpected state + +--- + +### 6.4 Denial interaction with indexer eligibility + +**Objective**: Subgraph denial takes precedence over indexer eligibility. When a subgraph is denied, POI presentation defers regardless of eligibility status — ensuring pre-denial rewards are preserved even for ineligible indexers. + +**Prerequisites**: REO validation enabled, one indexer ineligible, subgraph denied. + +**Steps**: + +```bash +# Confirm indexer is ineligible +cast call "isEligible(address)(bool)" --rpc-url +# Expected: false + +# Confirm subgraph is denied +cast call "isDenied(bytes32)(bool)" --rpc-url +# Expected: true + +# Present POI for ineligible indexer on denied subgraph +# (via indexer agent or manual) +``` + +**Pass Criteria**: + +- POI presentation defers (not reclaimed as INDEXER_INELIGIBLE) +- `POIPresented` event shows `condition = SUBGRAPH_DENIED` (denial takes precedence) +- Pre-denial rewards preserved (not reclaimed due to ineligibility) +- After undeny + re-renewal: rewards become claimable + +--- + +## Post-Testing Checklist + +- [ ] All denied subgraphs undenied (or left in intended state) +- [ ] Reclaim addresses verified +- [ ] No allocations stuck in unexpected state +- [ ] Reclaim address balance increase accounted for +- [ ] Results documented in test tracker + +--- + +## Related Documentation + +- [← Back to REO Testing](README.md) +- [RewardsConditionsTestPlan.md](RewardsConditionsTestPlan.md) — Signal, POI, and allocation lifecycle conditions +- [BaselineTestPlan.md](BaselineTestPlan.md) — Baseline operational tests (run first) +- [ReoTestPlan.md](ReoTestPlan.md) — REO eligibility tests + +--- + +_Derived from issuance upgrade behavior changes. Source: [RewardsBehaviourChanges.md](/docs/RewardsBehaviourChanges.md), [RewardConditions.md](/docs/RewardConditions.md). Contract: `packages/contracts/contracts/rewards/RewardsManager.sol`, `packages/subgraph-service/contracts/utilities/AllocationManager.sol`._ diff --git a/packages/issuance/docs/testing/reo/TestnetDetails.md b/packages/issuance/docs/testing/reo/TestnetDetails.md new file mode 100644 index 000000000..88ceffd34 --- /dev/null +++ b/packages/issuance/docs/testing/reo/TestnetDetails.md @@ -0,0 +1,65 @@ +# Arbitrum Sepolia — Testnet Details + +## Network Parameters + +| Parameter | Value | +| ----------------------- | ---------------------------------------------- | +| Explorer | | +| Gateway | | +| Network subgraph | `3xQHhMudr1oh69ut36G2mbzpYmYxwqCeU6wwqyCDCnqV` | +| RPC | | +| Epoch length | ~554 blocks (~110 minutes) | +| Max allocation lifetime | 8 epochs (~15 hours) | +| Min indexer stake | 100k GRT | +| Thawing period | Shortened for faster testing | + +## Network Subgraph + +**Query via Graph Explorer**: [Graph Network Arbitrum Sepolia](https://thegraph.com/explorer/subgraphs/3xQHhMudr1oh69ut36G2mbzpYmYxwqCeU6wwqyCDCnqV?view=Query&chain=arbitrum-one) + +Or query directly: + +```bash +export GRAPH_API_KEY= +curl "https://gateway.thegraph.com/api/$GRAPH_API_KEY/subgraphs/id/3xQHhMudr1oh69ut36G2mbzpYmYxwqCeU6wwqyCDCnqV" \ + -H 'content-type: application/json' \ + -d '{"query": "{ _meta { block { number } } }"}' +``` + +## Contract Addresses + +| Contract | Address | +| ---------------------------- | -------------------------------------------- | +| RewardsEligibilityOracle | `0x62c2305739cc75f19a3a6d52387ceb3690d99a99` | +| MockRewardsEligibilityOracle | `0x5FB23365F8cf643D5f1459E9793EfF7254522400` | +| RewardsManager | `0x1f49cae7669086c8ba53cc35d1e9f80176d67e79` | +| SubgraphService | `0xc24a3dac5d06d771f657a48b20ce1a671b78f26b` | +| GraphToken (L2) | `0xf8c05dcf59e8b28bfd5eed176c562bebcfc7ac04` | +| Controller | `0x9db3ee191681f092607035d9bda6e59fbeaca695` | + +## Mock REO (Testnet) + +The testnet RewardsManager is configured to use the `MockRewardsEligibilityOracle` rather than the real REO, to allow indexers to control their own eligibility during testing. + +The mock uses `msg.sender` as the indexer address, so each indexer controls their own eligibility by sending transactions from their own key. + +Check what the mock reports to RewardsManager for an address: + +```bash +cast call --rpc-url https://sepolia-rollup.arbitrum.io/rpc \ + 0x5FB23365F8cf643D5f1459E9793EfF7254522400 \ + "isEligible(address)(bool)"
+``` + +Set your own eligibility (send from the indexer key): + +```bash +cast send --rpc-url https://sepolia-rollup.arbitrum.io/rpc \ + --private-key $PRIVATE_KEY \ + 0x5FB23365F8cf643D5f1459E9793EfF7254522400 \ + "setEligible(bool)" +``` + +--- + +- [← Back to REO Testing](README.md) diff --git a/packages/issuance/docs/testing/reo/support/IssuanceAllocatorTestPlan.md b/packages/issuance/docs/testing/reo/support/IssuanceAllocatorTestPlan.md new file mode 100644 index 000000000..d8ab63f85 --- /dev/null +++ b/packages/issuance/docs/testing/reo/support/IssuanceAllocatorTestPlan.md @@ -0,0 +1,98 @@ +# IssuanceAllocator Test Plan + +> **Navigation**: [← Back to REO Testing](../README.md) + +Separated from the REO test plan — IssuanceAllocator is independent of the Rewards Eligibility Oracle. Test when deployed. + +## Contract Addresses + +| Contract | Arbitrum Sepolia | Arbitrum One | +| ------------------------- | -------------------------------------------- | ------------ | +| IssuanceAllocator (proxy) | Not yet deployed | TBD | +| RewardsManager (proxy) | `0x1f49cae7669086c8ba53cc35d1e9f80176d67e79` | TBD | +| GraphToken (L2) | `0xf8c05dcf59e8b28bfd5eed176c562bebcfc7ac04` | TBD | + +--- + +## Tests + +### 1. Verify IssuanceAllocator configuration + +**Objective**: Confirm the IssuanceAllocator is correctly configured with RewardsManager as a self-minting target. + +**Steps**: + +```bash +# Check issuance rate +cast call "getIssuancePerBlock()(uint256)" --rpc-url + +# Check RewardsManager target allocation +cast call "getTargetIssuancePerBlock(address)(uint256,uint256)" --rpc-url + +# Check if IssuanceAllocator is minter +cast call "isMinter(address)(bool)" --rpc-url + +# Check RewardsManager knows about IssuanceAllocator +cast call "getIssuanceAllocator()(address)" --rpc-url +``` + +**Pass Criteria**: + +- `getIssuancePerBlock` returns the expected issuance rate +- RewardsManager has self-minting allocation = 100% of issuance +- IssuanceAllocator is a minter on GraphToken +- RewardsManager points to IssuanceAllocator + +--- + +### 2. Distribute issuance + +**Objective**: Verify `distributeIssuance()` executes correctly. + +**Steps**: + +```bash +# Anyone can call this +cast send "distributeIssuance()" --rpc-url --private-key +``` + +**Pass Criteria**: + +- Transaction succeeds +- No unexpected reverts + +--- + +### 3. Verify issuance rate matches RewardsManager + +**Objective**: Confirm the issuance rate in IssuanceAllocator matches what RewardsManager expects. + +**Steps**: + +```bash +# IssuanceAllocator rate +cast call "getIssuancePerBlock()(uint256)" --rpc-url + +# RewardsManager effective rate +cast call "issuancePerBlock()(uint256)" --rpc-url +``` + +**Pass Criteria**: + +- Both values are identical + +--- + +### 4. IssuanceAllocator not paused + +**Objective**: Confirm the IssuanceAllocator is operational. + +**Steps**: + +```bash +cast call "paused()(bool)" --rpc-url +``` + +**Pass Criteria**: + +- Returns `false` diff --git a/packages/issuance/docs/testing/reo/support/NotionSetup.md b/packages/issuance/docs/testing/reo/support/NotionSetup.md new file mode 100644 index 000000000..2ebcc8e6c --- /dev/null +++ b/packages/issuance/docs/testing/reo/support/NotionSetup.md @@ -0,0 +1,70 @@ +# Notion Tracker Setup + +> **Navigation**: [← Back to REO Testing](../README.md) + +Instructions for setting up the Notion-based test tracker from [NotionTracker.csv](NotionTracker.csv). + +## Import into Notion + +1. Open Notion, navigate to the workspace where you want the tracker +2. Click **Import** (sidebar → Import, or `...` menu → Import) +3. Select **CSV** and upload `NotionTracker.csv` +4. Notion creates a database from the CSV + +## Configure Column Types + +After import, change these column types in the database: + +| Column | Change to | Notes | +| --------- | ------------ | --------------------------------------------------------------- | +| Indexer A | **Checkbox** | Indexer marks when they've completed the test | +| Indexer B | **Checkbox** | Same | +| Indexer C | **Checkbox** | Same | +| Status | **Select** | Options: Not Started, In Progress, Pass, Fail, Blocked, Skipped | +| Link | **URL** | Links are already full GitHub URLs | +| Plan | **Select** | Enables grouping by test plan (Baseline / Eligibility) | + +### Add Indexer Columns + +If you have more than 3 indexers, add additional checkbox columns. Rename the generic "Indexer A/B/C" columns to the actual indexer names or addresses. + +## Recommended Views + +### 1. Main Tracker (Table) + +Default view — all tests in sequence. Sort by **Test ID**. + +### 2. By Plan (Board) + +Board view grouped by **Plan**. Shows progress through Baseline vs Eligibility at a glance. + +### 3. Per-Indexer (Filtered Tables) + +Create a filtered table for each indexer showing their checkbox and status columns. + +### 4. Blocked / Failed + +Filter: Status = Fail or Blocked. Use during testing to track issues. + +## Workflow + +1. **Before testing**: Share the Notion page with participating indexers (edit access) +2. **During testing**: Indexers check their checkbox when they complete a test. Update Status column. +3. **Coordinator**: Updates Status and Notes columns as tests progress +4. **After each session**: Review blocked/failed tests, update Notes with details + +## Column Reference + +| Column | Purpose | +| ----------- | -------------------------------------------------- | +| Test ID | Unique identifier (e.g. B-3.2 = Baseline test 3.2) | +| Plan | Test plan: Baseline or Eligibility | +| Test Name | Short test title | +| Link | Link to detailed test steps in IndexerTestGuide.md | +| Indexer A-C | Checkboxes for each indexer to confirm completion | +| Status | Current test status | +| Notes | Free text for issues, observations, tx hashes | + +--- + +**Related**: [NotionTracker.csv](NotionTracker.csv) | [IndexerTestGuide.md](../IndexerTestGuide.md) diff --git a/packages/issuance/docs/testing/reo/support/NotionTracker.csv b/packages/issuance/docs/testing/reo/support/NotionTracker.csv new file mode 100644 index 000000000..c8ad3a5be --- /dev/null +++ b/packages/issuance/docs/testing/reo/support/NotionTracker.csv @@ -0,0 +1,77 @@ +Test ID,Plan,Test Name,Link,Indexer A,Indexer B,Indexer C,Status,Notes +B-1.1,Baseline,Setup indexer via Explorer,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#11-setup-indexer-via-explorer,,,,Not Started, +B-1.2,Baseline,Register indexer URL and GEO coordinates,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#12-register-indexer-url-and-geo-coordinates,,,,Not Started, +B-1.3,Baseline,Validate Subgraph Service provision and registration,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#13-validate-subgraph-service-provision-and-registration,,,,Not Started, +B-2.1,Baseline,Add stake via Explorer,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#21-add-stake-via-explorer,,,,Not Started, +B-2.2,Baseline,Unstake tokens and withdraw after thawing,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#22-unstake-tokens-and-withdraw-after-thawing,,,,Not Started, +B-3.1,Baseline,View current provision,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#31-view-current-provision,,,,Not Started, +B-3.2,Baseline,Add stake to provision,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#32-add-stake-to-provision,,,,Not Started, +B-3.3,Baseline,Thaw stake from provision,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#33-thaw-stake-from-provision,,,,Not Started, +B-3.4,Baseline,Remove thawed stake from provision,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#34-remove-thawed-stake-from-provision,,,,Not Started, +B-4.1,Baseline,Find subgraph deployments with rewards,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#41-find-subgraph-deployments-with-rewards,,,,Not Started, +B-4.2,Baseline,Create allocation manually,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#42-create-allocation-manually,,,,Not Started, +B-4.3,Baseline,Create allocation via actions queue,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#43-create-allocation-via-actions-queue,,,,Not Started, +B-4.4,Baseline,Create allocation via deployment rules,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#44-create-allocation-via-deployment-rules,,,,Not Started, +B-4.5,Baseline,Reallocate a deployment,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#45-reallocate-a-deployment,,,,Not Started, +B-5.1,Baseline,Send test queries,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#51-send-test-queries,,,,Not Started, +B-5.2,Baseline,Close allocation and collect indexing rewards,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#52-close-allocation-and-collect-indexing-rewards,,,,Not Started, +B-5.3,Baseline,Verify query fee collection,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#53-verify-query-fee-collection,,,,Not Started, +B-5.4,Baseline,Close allocation with explicit POI,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#54-close-allocation-with-explicit-poi,,,,Not Started, +B-6.1,Baseline,Monitor indexer health,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#61-monitor-indexer-health,,,,Not Started, +B-6.2,Baseline,Check epoch progression,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#62-check-epoch-progression,,,,Not Started, +B-6.3,Baseline,Verify no unexpected errors in logs,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#63-verify-no-unexpected-errors-in-logs,,,,Not Started, +B-7.1,Baseline,Full operational cycle,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/BaselineTestPlan.md#71-full-operational-cycle,,,,Not Started, +E-1.1,Eligibility,Open 3+ allocations for eligibility tests,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/IndexerTestGuide.md#11-open-allocations-for-eligibility-tests,,,,Not Started,Need epoch maturity before Set 2 +E-2.1,Eligibility,Renew eligibility,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/IndexerTestGuide.md#21-renew-eligibility,,,,Not Started, +E-2.2,Eligibility,Close allocation while eligible,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/IndexerTestGuide.md#22-close-allocation-while-eligible,,,,Not Started,Requires epoch maturity from Set 1 +E-3.1,Eligibility,Wait for eligibility expiry,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/IndexerTestGuide.md#31-wait-for-eligibility-expiry,,,,Not Started, +E-3.2,Eligibility,Close allocation while ineligible,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/IndexerTestGuide.md#32-close-allocation-while-ineligible,,,,Not Started,Confirm indexingRewards is 0 +E-4.1,Eligibility,Re-renew eligibility,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/IndexerTestGuide.md#41-re-renew-eligibility,,,,Not Started,Do promptly after Set 3 +E-4.2,Eligibility,Close allocation — full rewards after re-renewal,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/IndexerTestGuide.md#42-close-allocation--full-rewards-after-re-renewal,,,,Not Started,Key test: rewards include ineligible period +E-5.1,Eligibility,Verify eligibility when validation is off,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/IndexerTestGuide.md#51-verify-eligibility-when-validation-is-off,,,,Not Started,Coordinator toggles validation +D-1.1,Denial,Configure SUBGRAPH_DENIED reclaim address,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#11-configure-subgraph_denied-reclaim-address,,,,Not Started,Governor access needed +D-1.2,Denial,Record reclaim address GRT balance,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#12-record-reclaim-address-grt-balance,,,,Not Started, +D-2.1,Denial,Verify subgraph is not denied (pre-test),https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#21-verify-subgraph-is-not-denied-pre-test,,,,Not Started,Record accumulator baseline +D-2.2,Denial,Deny subgraph deployment,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#22-deny-subgraph-deployment,,,,Not Started,SAO or Governor access needed +D-2.3,Denial,Redundant deny is idempotent,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#23-redundant-deny-is-idempotent,,,,Not Started, +D-2.4,Denial,Unauthorized deny reverts,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#24-unauthorized-deny-reverts,,,,Not Started, +D-3.1,Denial,Accumulators freeze after denial,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#31-accumulators-freeze-after-denial,,,,Not Started,Wait 30+ min after denial +D-3.2,Denial,getRewards returns frozen value for denied subgraph,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#32-getrewards-returns-frozen-value-for-allocations-on-denied-subgraph,,,,Not Started, +D-3.3,Denial,Denial-period rewards reclaimed,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#33-denial-period-rewards-reclaimed,,,,Not Started,Check RewardsReclaimed events +D-3.4,Denial,Non-denied subgraphs unaffected,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#34-non-denied-subgraphs-unaffected,,,,Not Started,Control test +D-4.1,Denial,POI on denied subgraph defers,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#41-poi-presentation-on-denied-subgraph-defers-returns-0-preserves-state,,,,Not Started,Critical: snapshot NOT advanced +D-4.2,Denial,Multiple POI presentations while denied safe,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#42-multiple-poi-presentations-while-denied-do-not-lose-rewards,,,,Not Started, +D-4.3,Denial,Continue presenting POIs during denial,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#43-indexers-should-continue-presenting-pois-during-denial,,,,Not Started,Prevents staleness +D-5.1,Denial,Undeny subgraph deployment,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#51-undeny-subgraph-deployment,,,,Not Started, +D-5.2,Denial,Accumulators resume after undeny,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#52-accumulators-resume-after-undeny,,,,Not Started,Wait 30+ min after undeny +D-5.3,Denial,Pre-denial rewards claimable after undeny,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#53-pre-denial-rewards-claimable-after-undeny,,,,Not Started,Critical: preserved rewards claimable +D-5.4,Denial,Denial-period rewards excluded from claim,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#54-denial-period-rewards-are-not-included-in-claim,,,,Not Started, +D-6.1,Denial,New allocation while denied,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#61-new-allocation-created-while-subgraph-is-denied,,,,Not Started,Only earns post-undeny rewards +D-6.2,Denial,All allocations close while denied then resume,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#62-all-allocations-close-while-denied-then-new-allocation-after-undeny,,,,Not Started, +D-6.3,Denial,Rapid deny/undeny cycle,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#63-deny-and-undeny-in-rapid-succession,,,,Not Started, +D-6.4,Denial,Denial vs eligibility precedence,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/SubgraphDenialTestPlan.md#64-denial-interaction-with-indexer-eligibility,,,,Not Started,Denial takes precedence over REO +RC-1.1,Conditions,Configure per-condition reclaim addresses,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#11-configure-per-condition-reclaim-addresses,,,,Not Started,Governor access needed +RC-1.2,Conditions,Configure default reclaim address,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#12-configure-default-reclaim-address,,,,Not Started, +RC-1.3,Conditions,Verify fallback routing,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#13-verify-fallback-routing-unconfigured-condition-uses-default,,,,Not Started, +RC-1.4,Conditions,Unauthorized reclaim address change reverts,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#14-unauthorized-reclaim-address-change-reverts,,,,Not Started, +RC-1.5,Conditions,Record baseline balances,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#15-record-baseline-balances,,,,Not Started, +RC-2.1,Conditions,Verify current minimum signal threshold,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#21-verify-current-minimum-signal-threshold,,,,Not Started, +RC-2.2,Conditions,Raise threshold to trigger BELOW_MINIMUM_SIGNAL,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#22-raise-threshold-to-trigger-below_minimum_signal,,,,Not Started,Snapshot accumulators first +RC-2.3,Conditions,Accumulator freezes for below-threshold subgraph,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#23-accumulator-freezes-for-below-threshold-subgraph,,,,Not Started, +RC-2.4,Conditions,Restore threshold and verify resumption,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#24-restore-threshold-and-verify-resumption,,,,Not Started, +RC-3.1,Conditions,Identify subgraph with signal but no allocations,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#31-identify-subgraph-with-signal-but-no-allocations,,,,Not Started, +RC-3.2,Conditions,Verify NO_ALLOCATED_TOKENS reclaim,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#32-verify-no_allocated_tokens-reclaim,,,,Not Started, +RC-3.3,Conditions,Allocations resume from stored baseline,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#33-allocations-resume-from-stored-baseline,,,,Not Started, +RC-4.1,Conditions,Normal claim path (NONE condition),https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#41-normal-claim-path-none-condition,,,,Not Started, +RC-4.2,Conditions,Reclaim path: STALE_POI,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#42-reclaim-path-stale_poi,,,,Not Started,Wait for maxPOIStaleness +RC-4.3,Conditions,Reclaim path: ZERO_POI,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#43-reclaim-path-zero_poi,,,,Not Started, +RC-4.4,Conditions,Defer path: ALLOCATION_TOO_YOUNG,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#44-defer-path-allocation_too_young,,,,Not Started,Same-epoch POI attempt +RC-4.5,Conditions,POI presentation always updates timestamp,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#45-poi-presentation-always-updates-timestamp,,,,Not Started, +RC-5.1,Conditions,Allocation resize reclaims stale rewards,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#51-allocation-resize-reclaims-stale-rewards,,,,Not Started,Wait for staleness +RC-5.2,Conditions,Non-stale resize does not reclaim,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#52-allocation-resize-does-not-reclaim-for-non-stale-allocation,,,,Not Started, +RC-5.3,Conditions,Allocation close reclaims uncollected rewards,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#53-allocation-close-reclaims-uncollected-rewards,,,,Not Started, +RC-6.1,Conditions,POIPresented event on every presentation,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#61-poipresented-event-emitted-on-every-presentation,,,,Not Started,Cross-check all Cycle 4-5 events +RC-6.2,Conditions,RewardsReclaimed events include full context,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#62-rewardsreclaimed-events-include-full-context,,,,Not Started, +RC-6.3,Conditions,View functions reflect frozen state accurately,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#63-view-functions-reflect-frozen-state-accurately,,,,Not Started, +RC-7.1,Conditions,NO_SIGNAL detection,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#71-no_signal-detection,,,,Not Started,Dedicated testnet only +RC-7.2,Conditions,Signal restoration resumes normal distribution,https://github.com/graphprotocol/contracts/blob/reo-testing/packages/issuance/docs/testing/reo/RewardsConditionsTestPlan.md#72-signal-restoration-resumes-normal-distribution,,,,Not Started,Dedicated testnet only diff --git a/packages/issuance/docs/testing/reo/support/indexer-status.sh b/packages/issuance/docs/testing/reo/support/indexer-status.sh new file mode 100755 index 000000000..c914580be --- /dev/null +++ b/packages/issuance/docs/testing/reo/support/indexer-status.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Query basic indexer status from the network subgraph. +# +# Usage: +# ./indexer-status.sh [mainnet] +# +# Environment: +# GRAPH_API_KEY Required. Your Graph API key. +# +# Examples: +# GRAPH_API_KEY=abc123 ./indexer-status.sh 0xdeadbeef... +# GRAPH_API_KEY=abc123 ./indexer-status.sh 0xdeadbeef... mainnet + +set -euo pipefail + +INDEXER=${1:-} +NETWORK=${2:-testnet} + +if [[ -z "$INDEXER" ]]; then + echo "Usage: $0 [mainnet]" >&2 + exit 1 +fi + +if [[ -z "${GRAPH_API_KEY:-}" ]]; then + echo "Error: GRAPH_API_KEY is not set" >&2 + exit 1 +fi + +# Addresses must be lowercase for the subgraph +INDEXER=$(echo "$INDEXER" | tr '[:upper:]' '[:lower:]') + +if [[ "$NETWORK" == "mainnet" ]]; then + SUBGRAPH_URL="https://gateway.thegraph.com/api/$GRAPH_API_KEY/subgraphs/id/DZz4kDTdmzWLWsV373w2bSmoar3umKKH9y82SUKr5qmp" +else + SUBGRAPH_URL="https://gateway.thegraph.com/api/$GRAPH_API_KEY/subgraphs/id/3xQHhMudr1oh69ut36G2mbzpYmYxwqCeU6wwqyCDCnqV" +fi + +QUERY=$(cat < { const disputeManagerProxyAddress = m.getParameter('disputeManagerProxyAddress') const graphTallyCollectorAddress = m.getParameter('graphTallyCollectorAddress') const curationProxyAddress = m.getParameter('curationProxyAddress') + const recurringCollectorAddress = m.getParameter('recurringCollectorAddress') const minimumProvisionTokens = m.getParameter('minimumProvisionTokens') const maximumDelegationRatio = m.getParameter('maximumDelegationRatio') const stakeToFeesRatio = m.getParameter('stakeToFeesRatio') @@ -28,12 +29,40 @@ export default buildModule('SubgraphService', (m) => { subgraphServiceProxyAddress, ) - // Deploy implementation - const SubgraphServiceImplementation = deployImplementation(m, { - name: 'SubgraphService', - constructorArgs: [controllerAddress, disputeManagerProxyAddress, graphTallyCollectorAddress, curationProxyAddress], + // Deploy libraries required by SubgraphService + const StakeClaims = m.library('StakeClaims') + const AllocationHandler = m.library('AllocationHandler') + const IndexingAgreementDecoderRaw = m.library('IndexingAgreementDecoderRaw') + const IndexingAgreementDecoder = m.library('IndexingAgreementDecoder', { + libraries: { IndexingAgreementDecoderRaw }, + }) + const IndexingAgreement = m.library('IndexingAgreement', { + libraries: { IndexingAgreementDecoder }, }) + // Deploy implementation + const SubgraphServiceImplementation = deployImplementation( + m, + { + name: 'SubgraphService', + constructorArgs: [ + controllerAddress, + disputeManagerProxyAddress, + graphTallyCollectorAddress, + curationProxyAddress, + recurringCollectorAddress, + ], + }, + { + libraries: { + StakeClaims, + AllocationHandler, + IndexingAgreement, + IndexingAgreementDecoder, + }, + }, + ) + // Upgrade implementation const SubgraphService = upgradeTransparentUpgradeableProxy( m, diff --git a/packages/subgraph-service/tasks/deploy.ts b/packages/subgraph-service/tasks/deploy.ts index 581138439..860e8c67b 100644 --- a/packages/subgraph-service/tasks/deploy.ts +++ b/packages/subgraph-service/tasks/deploy.ts @@ -91,6 +91,7 @@ task('deploy:protocol', 'Deploy a new version of the Graph Protocol Horizon cont subgraphServiceProxyAddress: proxiesDeployment.Transparent_Proxy_SubgraphService.target as string, subgraphServiceProxyAdminAddress: proxiesDeployment.Transparent_ProxyAdmin_SubgraphService.target as string, graphTallyCollectorAddress: horizonDeployment.GraphTallyCollector.target as string, + recurringCollectorAddress: horizonDeployment.Transparent_Proxy_RecurringCollector.target as string, gnsProxyAddress: horizonDeployment.Graph_Proxy_L2GNS.target as string, gnsImplementationAddress: horizonDeployment.Implementation_L2GNS.target as string, subgraphNFTAddress: horizonDeployment.SubgraphNFT.target as string, diff --git a/packages/token-distribution/.solhint.json b/packages/token-distribution/.solhint.json index d30847305..780d82f39 100644 --- a/packages/token-distribution/.solhint.json +++ b/packages/token-distribution/.solhint.json @@ -1,3 +1,3 @@ { - "extends": ["solhint:recommended", "./../../.solhint.json"] + "extends": "./../../.solhint.json" } diff --git a/packages/toolshed/package.json b/packages/toolshed/package.json index b60e2a86d..430fa8144 100644 --- a/packages/toolshed/package.json +++ b/packages/toolshed/package.json @@ -1,6 +1,6 @@ { "name": "@graphprotocol/toolshed", - "version": "1.1.2", + "version": "1.2.2", "publishConfig": { "access": "public" }, diff --git a/packages/toolshed/src/core/recurring-collector.ts b/packages/toolshed/src/core/recurring-collector.ts index 42c1bc7be..6b43564f8 100644 --- a/packages/toolshed/src/core/recurring-collector.ts +++ b/packages/toolshed/src/core/recurring-collector.ts @@ -1,3 +1,5 @@ +import { BytesLike, ethers } from 'ethers' + /** * Constants for constructing RCA / RCAU offers against `RecurringCollector`. * @@ -35,3 +37,87 @@ export const RC_EIP712_RCA_TYPESTRING = /** EIP-712 typestring for a RecurringCollectionAgreementUpdate (RCAU). */ export const RC_EIP712_RCAU_TYPESTRING = 'RecurringCollectionAgreementUpdate(bytes16 agreementId,uint64 deadline,uint64 endsAt,uint256 maxInitialTokens,uint256 maxOngoingTokensPerSecond,uint32 minSecondsPerCollection,uint32 maxSecondsPerCollection,uint16 conditions,uint32 nonce,bytes metadata)' + +// -- ABI tuple types for decoding -- + +const RCA_TUPLE = + 'tuple(uint64 deadline, uint64 endsAt, address payer, address dataService, address serviceProvider, uint256 maxInitialTokens, uint256 maxOngoingTokensPerSecond, uint32 minSecondsPerCollection, uint32 maxSecondsPerCollection, uint16 conditions, uint256 nonce, bytes metadata)' + +const SIGNED_RCA_TUPLE = `tuple(${RCA_TUPLE} rca, bytes signature)` + +const ACCEPT_METADATA_TUPLE = 'tuple(bytes32 subgraphDeploymentId, uint8 version, bytes terms)' + +const TERMS_V1_TUPLE = 'tuple(uint256 tokensPerSecond, uint256 tokensPerEntityPerSecond)' + +// -- Return types -- + +export interface RecurringCollectionAgreement { + deadline: bigint + endsAt: bigint + payer: string + dataService: string + serviceProvider: string + maxInitialTokens: bigint + maxOngoingTokensPerSecond: bigint + minSecondsPerCollection: bigint + maxSecondsPerCollection: bigint + conditions: bigint + nonce: bigint + metadata: string +} + +export interface SignedRCA { + rca: RecurringCollectionAgreement + signature: string +} + +export interface AcceptIndexingAgreementMetadata { + subgraphDeploymentId: string + version: bigint + terms: string +} + +export interface IndexingAgreementTermsV1 { + tokensPerSecond: bigint + tokensPerEntityPerSecond: bigint +} + +// -- Decoders -- + +export function decodeSignedRCA(data: BytesLike): SignedRCA { + const [decoded] = ethers.AbiCoder.defaultAbiCoder().decode([SIGNED_RCA_TUPLE], data) + return { + rca: { + deadline: decoded.rca.deadline, + endsAt: decoded.rca.endsAt, + payer: decoded.rca.payer, + dataService: decoded.rca.dataService, + serviceProvider: decoded.rca.serviceProvider, + maxInitialTokens: decoded.rca.maxInitialTokens, + maxOngoingTokensPerSecond: decoded.rca.maxOngoingTokensPerSecond, + minSecondsPerCollection: decoded.rca.minSecondsPerCollection, + maxSecondsPerCollection: decoded.rca.maxSecondsPerCollection, + conditions: decoded.rca.conditions, + nonce: decoded.rca.nonce, + metadata: decoded.rca.metadata, + }, + signature: decoded.signature, + } +} + +export function decodeAcceptIndexingAgreementMetadata(data: BytesLike): AcceptIndexingAgreementMetadata { + const [decoded] = ethers.AbiCoder.defaultAbiCoder().decode([ACCEPT_METADATA_TUPLE], data) + return { + subgraphDeploymentId: decoded.subgraphDeploymentId, + version: decoded.version, + terms: decoded.terms, + } +} + +export function decodeIndexingAgreementTermsV1(data: BytesLike): IndexingAgreementTermsV1 { + const [decoded] = ethers.AbiCoder.defaultAbiCoder().decode([TERMS_V1_TUPLE], data) + return { + tokensPerSecond: decoded.tokensPerSecond, + tokensPerEntityPerSecond: decoded.tokensPerEntityPerSecond, + } +} diff --git a/packages/toolshed/src/core/subgraph-service.ts b/packages/toolshed/src/core/subgraph-service.ts index b4301900f..03a7840d0 100644 --- a/packages/toolshed/src/core/subgraph-service.ts +++ b/packages/toolshed/src/core/subgraph-service.ts @@ -32,6 +32,21 @@ export function encodeCollectQueryFeesData(rav: RAV, signature: string, tokensTo ) } +export function encodeCollectIndexingFeesData( + agreementId: string, + entities: bigint, + poi: BytesLike, + poiBlockNumber: bigint, + metadata: BytesLike, + maxSlippage: bigint, +) { + const innerData = ethers.AbiCoder.defaultAbiCoder().encode( + ['uint256', 'bytes32', 'uint256', 'bytes', 'uint256'], + [entities, poi, poiBlockNumber, metadata, maxSlippage], + ) + return ethers.AbiCoder.defaultAbiCoder().encode(['bytes16', 'bytes'], [agreementId, innerData]) +} + export function encodeStopServiceData(allocationId: string) { return ethers.AbiCoder.defaultAbiCoder().encode(['address'], [allocationId]) } diff --git a/packages/toolshed/src/deployments/address-book.ts b/packages/toolshed/src/deployments/address-book.ts index 63bbc26f6..147bc61c5 100644 --- a/packages/toolshed/src/deployments/address-book.ts +++ b/packages/toolshed/src/deployments/address-book.ts @@ -17,8 +17,8 @@ export type AddressBookJson { @@ -56,6 +58,7 @@ export interface GraphHorizonContracts extends ContractList { + DefaultAllocation: DirectAllocation DirectAllocation_Implementation: Contract IssuanceAllocator: IssuanceAllocator NetworkOperator: Contract // Address holder for network operator (not an actual contract) - PilotAllocation: DirectAllocation - ReclaimedRewardsForCloseAllocation: DirectAllocation - ReclaimedRewardsForIndexerIneligible: DirectAllocation - ReclaimedRewardsForStalePoi: DirectAllocation - ReclaimedRewardsForSubgraphDenied: DirectAllocation - ReclaimedRewardsForZeroPoi: DirectAllocation - RewardsEligibilityOracle: RewardsEligibilityOracle + ReclaimedRewards: DirectAllocation + RecurringAgreementManager: RecurringAgreementManager + RewardsEligibilityOracleA: RewardsEligibilityOracle + RewardsEligibilityOracleB: RewardsEligibilityOracle + RewardsEligibilityOracleMock: Contract } diff --git a/packages/toolshed/src/hardhat/hardhat.base.config.ts b/packages/toolshed/src/hardhat/hardhat.base.config.ts index 3b7e3b66d..cf6fd0579 100644 --- a/packages/toolshed/src/hardhat/hardhat.base.config.ts +++ b/packages/toolshed/src/hardhat/hardhat.base.config.ts @@ -60,8 +60,15 @@ export const projectPathsUserConfig: ProjectPathsUserConfig = { // Etherscan v2 API uses a single API key for all networks // See: https://docs.etherscan.io/etherscan-v2/getting-started/creating-an-account +// Check keystore first (vars), then environment variables +// Support both ETHERSCAN_API_KEY and ARBISCAN_API_KEY for compatibility +const getEtherscanApiKey = (): string => { + if (vars.has('ETHERSCAN_API_KEY')) return vars.get('ETHERSCAN_API_KEY') + if (vars.has('ARBISCAN_API_KEY')) return vars.get('ARBISCAN_API_KEY') + return process.env.ETHERSCAN_API_KEY ?? process.env.ARBISCAN_API_KEY ?? '' +} export const etherscanUserConfig: Partial = { - apiKey: vars.has('ETHERSCAN_API_KEY') ? vars.get('ETHERSCAN_API_KEY') : '', + apiKey: getEtherscanApiKey(), } // In general: diff --git a/packages/toolshed/tsconfig.json b/packages/toolshed/tsconfig.json index f6387508a..8d3b58663 100644 --- a/packages/toolshed/tsconfig.json +++ b/packages/toolshed/tsconfig.json @@ -3,5 +3,6 @@ "compilerOptions": { "outDir": "dist" }, - "include": ["src/**/*.ts", "test/**/*.ts"] + "include": ["src/**/*.ts"], + "exclude": ["test/**/*.ts"] } diff --git a/patches/rocketh@0.17.13.patch b/patches/rocketh@0.17.13.patch new file mode 100644 index 000000000..e16957b96 --- /dev/null +++ b/patches/rocketh@0.17.13.patch @@ -0,0 +1,33 @@ +diff --git a/dist/executor/index.js b/dist/executor/index.js +index 05324b861bfa25afe89da4afba244f92c17c9c1c..e3529b53f92e4657a160e899715216267a25f6af 100644 +--- a/dist/executor/index.js ++++ b/dist/executor/index.js +@@ -338,19 +338,16 @@ Do you want to proceed (note that gas price can change for each tx)`, + logger.info(`skipping ${deployScript.id} as migrations already executed and complete`); + continue; + } +- let skip = false; ++ if (deployScript.func.skip) { ++ try { ++ const skip = await deployScript.func.skip(external, args); ++ if (skip) continue; ++ } catch (e) { ++ throw e; ++ } ++ } + const spinner = spin(`- Executing ${deployScript.id}`); +- // if (deployScript.func.skip) { +- // const spinner = spin(` - skip?()`); +- // try { +- // skip = await deployScript.func.skip(external, args); +- // spinner.succeed(skip ? `skipping ${filename}` : undefined); +- // } catch (e) { +- // spinner.fail(); +- // throw e; +- // } +- // } +- if (!skip) { ++ { + let result; + try { + result = await deployScript.func(external, args); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24d52b5dd..f9eab6c88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,18 +18,30 @@ catalogs: '@eslint/js': specifier: ^9.39.2 version: 9.39.2 + '@nomicfoundation/hardhat-chai-matchers': + specifier: ^2.0.0 + version: 2.1.0 '@nomicfoundation/hardhat-ethers': specifier: ^3.1.0 version: 3.1.0 '@nomicfoundation/hardhat-keystore': specifier: ^3.0.3 version: 3.0.3 + '@nomicfoundation/hardhat-verify': + specifier: ^2.0.10 + version: 2.1.1 + '@typechain/hardhat': + specifier: ^9.0.0 + version: 9.1.0 '@typescript-eslint/eslint-plugin': specifier: ^8.53.0 version: 8.53.1 '@typescript-eslint/parser': specifier: ^8.53.0 version: 8.53.1 + chai: + specifier: ^4.2.0 + version: 4.5.0 dotenv: specifier: ^16.5.0 version: 16.6.1 @@ -75,6 +87,9 @@ catalogs: hardhat-ignore-warnings: specifier: ^0.2.12 version: 0.2.12 + hardhat-secure-accounts: + specifier: ^1.0.5 + version: 1.0.5 hardhat-storage-layout: specifier: ^0.1.7 version: 0.1.7 @@ -94,11 +109,14 @@ catalogs: specifier: ^2.1.0 version: 2.1.0 solhint: - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^6.2.1 + version: 6.2.1 ts-node: specifier: ^10.9.2 version: 10.9.2 + typechain: + specifier: ^8.3.2 + version: 8.3.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -115,7 +133,12 @@ catalogs: overrides: '@types/node': ^20.17.50 +packageExtensionsChecksum: sha256-anBhQrlJaJ3Z62unAlKotKwV/itS9LEqUbuFem1Cbv8= + patchedDependencies: + rocketh@0.17.13: + hash: 9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f + path: patches/rocketh@0.17.13.patch typechain@8.3.2: hash: b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6 path: patches/typechain@8.3.2.patch @@ -180,7 +203,7 @@ importers: version: 2.1.0(prettier@3.8.1) solhint: specifier: 'catalog:' - version: 6.0.3(typescript@5.9.3) + version: 6.2.1(typescript@5.9.3) typescript: specifier: 'catalog:' version: 5.9.3 @@ -240,7 +263,7 @@ importers: version: 3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@nomiclabs/hardhat-waffle': specifier: ^2.0.6 - version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + version: 2.0.6(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@openzeppelin/contracts': specifier: 3.4.2 version: 3.4.2 @@ -324,7 +347,7 @@ importers: version: 2.1.0(prettier@3.8.1) solhint: specifier: 'catalog:' - version: 6.0.3(typescript@5.9.3) + version: 6.2.1(typescript@5.9.3) solidity-coverage: specifier: ^0.8.16 version: 0.8.16(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -394,7 +417,7 @@ importers: version: 3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@nomiclabs/hardhat-waffle': specifier: ^2.0.6 - version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + version: 2.0.6(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@openzeppelin/contracts': specifier: 3.4.2 version: 3.4.2 @@ -630,42 +653,21 @@ importers: packages/data-edge: devDependencies: - '@ethersproject/abi': - specifier: ^5.7.0 - version: 5.8.0 - '@ethersproject/bytes': - specifier: ^5.7.0 - version: 5.8.0 - '@ethersproject/providers': - specifier: ^5.7.0 - version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@nomiclabs/hardhat-ethers': - specifier: ^2.0.2 - version: 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - '@nomiclabs/hardhat-etherscan': - specifier: ^3.1.2 - version: 3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - '@nomiclabs/hardhat-waffle': - specifier: ^2.0.1 - version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - '@openzeppelin/contracts': - specifier: ^4.5.0 - version: 4.9.6 - '@openzeppelin/hardhat-upgrades': - specifier: ^1.8.2 - version: 1.28.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - '@tenderly/api-client': - specifier: ^1.0.13 - version: 1.1.0(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3) - '@tenderly/hardhat-tenderly': - specifier: ^1.0.13 - version: 1.11.0(@types/node@20.19.14)(bufferutil@4.0.9)(encoding@0.1.13)(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) - '@typechain/ethers-v5': - specifier: ^10.2.1 - version: 10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3) + '@nomicfoundation/hardhat-chai-matchers': + specifier: 'catalog:' + version: 2.1.0(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': + specifier: 'catalog:' + version: 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-verify': + specifier: 'catalog:' + version: 2.1.1(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@typechain/ethers-v6': + specifier: ^0.5.0 + version: 0.5.1(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3) '@typechain/hardhat': - specifier: ^6.1.6 - version: 6.1.6(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@typechain/ethers-v5@10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3)) + specifier: 'catalog:' + version: 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3))(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3)) '@types/mocha': specifier: ^9.0.0 version: 9.1.1 @@ -676,23 +678,17 @@ importers: specifier: ^3.2.12 version: 3.2.12 chai: - specifier: ^4.2.0 + specifier: 'catalog:' version: 4.5.0 dotenv: - specifier: ^16.0.0 + specifier: 'catalog:' version: 16.6.1 eslint: specifier: 'catalog:' version: 9.39.2(jiti@2.5.1) - ethereum-waffle: - specifier: ^3.0.2 - version: 3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) ethers: - specifier: ^5.7.2 - version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - ethlint: - specifier: ^1.2.5 - version: 1.2.5(solium@1.2.5) + specifier: 'catalog:' + version: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: specifier: 'catalog:' version: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -700,26 +696,17 @@ importers: specifier: ^2.2.0 version: 2.11.0(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) hardhat-contract-sizer: - specifier: ^2.0.3 + specifier: 'catalog:' version: 2.10.1(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) hardhat-gas-reporter: - specifier: ^1.0.4 + specifier: 'catalog:' version: 1.0.10(bufferutil@4.0.9)(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) hardhat-secure-accounts: - specifier: 0.0.6 - version: 0.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - husky: - specifier: ^7.0.4 - version: 7.0.4 - lint-staged: - specifier: ^12.3.5 - version: 12.5.0(enquirer@2.4.1) - lodash: - specifier: ^4.17.21 - version: 4.17.21 + specifier: 'catalog:' + version: 1.0.5(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) markdownlint-cli: - specifier: 0.45.0 - version: 0.45.0 + specifier: 'catalog:' + version: 0.47.0 prettier: specifier: 'catalog:' version: 3.8.1 @@ -728,18 +715,15 @@ importers: version: 2.1.0(prettier@3.8.1) solhint: specifier: 'catalog:' - version: 6.0.3(typescript@5.9.3) + version: 6.2.1(typescript@5.9.3) solidity-coverage: specifier: ^0.8.16 version: 0.8.16(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - truffle-flattener: - specifier: ^1.4.4 - version: 1.6.0 ts-node: - specifier: '>=8.0.0' + specifier: 'catalog:' version: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) typechain: - specifier: ^8.3.0 + specifier: 'catalog:' version: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) typescript: specifier: 'catalog:' @@ -753,6 +737,9 @@ importers: '@graphprotocol/horizon': specifier: workspace:* version: link:../horizon + '@graphprotocol/interfaces': + specifier: workspace:* + version: link:../interfaces '@graphprotocol/issuance': specifier: workspace:* version: link:../issuance @@ -798,13 +785,13 @@ importers: version: 0.17.11(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@rocketh/doc': specifier: ^0.17.16 - version: 0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@rocketh/export': specifier: ^0.17.16 - version: 0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@rocketh/node': specifier: ^0.17.16 - version: 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@rocketh/proxy': specifier: ^0.17.12 version: 0.17.12(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) @@ -813,7 +800,7 @@ importers: version: 0.17.8(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@rocketh/verifier': specifier: ^0.17.16 - version: 0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@types/chai': specifier: ^4.3.0 version: 4.3.20 @@ -831,7 +818,10 @@ importers: version: 9.39.2(jiti@2.5.1) hardhat-deploy: specifier: 2.0.0-next.61 - version: 2.0.0-next.61(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(hardhat@3.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 2.0.0-next.61(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(hardhat@3.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + json5: + specifier: ^2.2.3 + version: 2.2.3 lint-staged: specifier: 'catalog:' version: 16.2.7 @@ -840,7 +830,7 @@ importers: version: 10.8.2 rocketh: specifier: ^0.17.13 - version: 0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) ts-node: specifier: ^10.9.0 version: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) @@ -861,7 +851,7 @@ importers: version: 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) debug: specifier: ^4.3.7 - version: 4.4.3(supports-color@9.4.0) + version: 4.4.3(supports-color@8.1.1) json5: specifier: ^2.2.3 version: 2.2.3 @@ -1009,7 +999,7 @@ importers: version: 2.1.0(prettier@3.8.1) solhint: specifier: 'catalog:' - version: 6.0.3(typescript@5.9.3) + version: 6.2.1(typescript@5.9.3) solidity-coverage: specifier: ^0.8.0 version: 0.8.16(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -1075,7 +1065,7 @@ importers: version: 2.1.0(prettier@3.8.1) solhint: specifier: 'catalog:' - version: 6.0.3(typescript@5.9.3) + version: 6.2.1(typescript@5.9.3) ts-node: specifier: 'catalog:' version: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) @@ -1160,7 +1150,7 @@ importers: version: 2.1.0(prettier@3.8.1) solhint: specifier: 'catalog:' - version: 6.0.3(typescript@5.9.3) + version: 6.2.1(typescript@5.9.3) typechain: specifier: ^8.3.2 version: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) @@ -1286,7 +1276,7 @@ importers: version: 2.1.0(prettier@3.8.1) solhint: specifier: 'catalog:' - version: 6.0.3(typescript@5.9.3) + version: 6.2.1(typescript@5.9.3) solidity-coverage: specifier: ^0.8.0 version: 0.8.16(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -1371,7 +1361,7 @@ importers: version: 3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@nomiclabs/hardhat-waffle': specifier: ^2.0.6 - version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + version: 2.0.6(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@openzeppelin/contracts': specifier: 3.4.2 version: 3.4.2 @@ -1461,7 +1451,7 @@ importers: version: 2.1.0(prettier@3.8.1) solhint: specifier: 'catalog:' - version: 6.0.3(typescript@5.9.3) + version: 6.2.1(typescript@5.9.3) solidity-coverage: specifier: ^0.8.16 version: 0.8.16(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -1494,7 +1484,7 @@ importers: version: 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) debug: specifier: ^4.4.0 - version: 4.4.3(supports-color@9.4.0) + version: 4.4.3(supports-color@8.1.1) ethers: specifier: 'catalog:' version: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -2563,20 +2553,12 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ethereum-waffle/chai@3.4.4': - resolution: {integrity: sha512-/K8czydBtXXkcM9X6q29EqEkc5dN3oYenyH2a9hF7rGAApAJUpH8QBtojxOY/xQ2up5W332jqgxwp0yPiYug1g==} - engines: {node: '>=10.0'} - '@ethereum-waffle/chai@4.0.10': resolution: {integrity: sha512-X5RepE7Dn8KQLFO7HHAAe+KeGaX/by14hn90wePGBhzL54tq4Y8JscZFu+/LCwCl6TnkAAy5ebiMoqJ37sFtWw==} engines: {node: '>=10.0'} peerDependencies: ethers: '*' - '@ethereum-waffle/compiler@3.4.4': - resolution: {integrity: sha512-RUK3axJ8IkD5xpWjWoJgyHclOeEzDLQFga6gKpeGxiS/zBu+HB0W2FvsrrLalTFIaPw/CGYACRBSIxqiCqwqTQ==} - engines: {node: '>=10.0'} - '@ethereum-waffle/compiler@4.0.3': resolution: {integrity: sha512-5x5U52tSvEVJS6dpCeXXKvRKyf8GICDwiTwUvGD3/WD+DpvgvaoHOL82XqpTSUHgV3bBq6ma5/8gKUJUIAnJCw==} engines: {node: '>=10.0'} @@ -2585,10 +2567,6 @@ packages: solc: '*' typechain: ^8.0.0 - '@ethereum-waffle/ens@3.4.4': - resolution: {integrity: sha512-0m4NdwWxliy3heBYva1Wr4WbJKLnwXizmy5FfSSr5PMbjI7SIGCdCB59U7/ZzY773/hY3bLnzLwvG5mggVjJWg==} - engines: {node: '>=10.0'} - '@ethereum-waffle/ens@4.0.3': resolution: {integrity: sha512-PVLcdnTbaTfCrfSOrvtlA9Fih73EeDvFS28JQnT5M5P4JMplqmchhcZB1yg/fCtx4cvgHlZXa0+rOCAk2Jk0Jw==} engines: {node: '>=10.0'} @@ -2597,20 +2575,12 @@ packages: '@ensdomains/resolver': ^0.2.4 ethers: '*' - '@ethereum-waffle/mock-contract@3.4.4': - resolution: {integrity: sha512-Mp0iB2YNWYGUV+VMl5tjPsaXKbKo8MDH9wSJ702l9EBjdxFf/vBvnMBAC1Fub1lLtmD0JHtp1pq+mWzg/xlLnA==} - engines: {node: '>=10.0'} - '@ethereum-waffle/mock-contract@4.0.4': resolution: {integrity: sha512-LwEj5SIuEe9/gnrXgtqIkWbk2g15imM/qcJcxpLyAkOj981tQxXmtV4XmQMZsdedEsZ/D/rbUAOtZbgwqgUwQA==} engines: {node: '>=10.0'} peerDependencies: ethers: '*' - '@ethereum-waffle/provider@3.4.4': - resolution: {integrity: sha512-GK8oKJAM8+PKy2nK08yDgl4A80mFuI8zBkE0C9GqTRYQqvuxIyXoLmJ5NZU9lIwyWVv5/KsoA11BgAv2jXE82g==} - engines: {node: '>=10.0'} - '@ethereum-waffle/provider@4.0.5': resolution: {integrity: sha512-40uzfyzcrPh+Gbdzv89JJTMBlZwzya1YLDyim8mVbEqYLP5VRYWoGp0JMyaizgV3hMoUFRqJKVmIUw4v7r3hYw==} engines: {node: '>=10.0'} @@ -2659,9 +2629,6 @@ packages: '@ethereumjs/vm@5.6.0': resolution: {integrity: sha512-J2m/OgjjiGdWF2P9bj/4LnZQ1zRoZhY8mRNVw/N3tXliGI8ai1sI1mlDPkLpeUUM4vq54gH6n0ZlSpz8U/qlYQ==} - '@ethersproject/abi@5.0.0-beta.153': - resolution: {integrity: sha512-aXweZ1Z7vMNzJdLpR1CZUAIgnwjrZeUSvN9syCwlBaEBUFJmFY+HHnfuTI5vIhVs/mRkfJVrbEyl51JZQqyjAg==} - '@ethersproject/abi@5.6.0': resolution: {integrity: sha512-AhVByTwdXCc2YQ20v300w6KVHle9g2OFc28ZAFCPnJyEpkv1xKXjZcSTgWOlv1i+0dqlgF8RCF2Rn2KC1t+1Vg==} @@ -3535,14 +3502,6 @@ packages: '@ledgerhq/logs@5.50.0': resolution: {integrity: sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA==} - '@ljharb/resumer@0.0.1': - resolution: {integrity: sha512-skQiAOrCfO7vRTq53cxznMpks7wS1va95UCidALlOVWqvBAzwPVErwizDwoMqNVMEn1mDq0utxZd02eIrvF1lw==} - engines: {node: '>= 0.4'} - - '@ljharb/through@2.3.14': - resolution: {integrity: sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==} - engines: {node: '>= 0.4'} - '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -3888,9 +3847,6 @@ packages: '@openzeppelin/contracts@3.4.2': resolution: {integrity: sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA==} - '@openzeppelin/contracts@4.9.6': - resolution: {integrity: sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==} - '@openzeppelin/contracts@5.4.0': resolution: {integrity: sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==} @@ -4023,27 +3979,15 @@ packages: '@repeaterjs/repeater@3.0.6': resolution: {integrity: sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==} - '@resolver-engine/core@0.2.1': - resolution: {integrity: sha512-nsLQHmPJ77QuifqsIvqjaF5B9aHnDzJjp73Q1z6apY3e9nqYrx4Dtowhpsf7Jwftg/XzVDEMQC+OzUBNTS+S1A==} - '@resolver-engine/core@0.3.3': resolution: {integrity: sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ==} - '@resolver-engine/fs@0.2.1': - resolution: {integrity: sha512-7kJInM1Qo2LJcKyDhuYzh9ZWd+mal/fynfL9BNjWOiTcOpX+jNfqb/UmGUqros5pceBITlWGqS4lU709yHFUbg==} - '@resolver-engine/fs@0.3.3': resolution: {integrity: sha512-wQ9RhPUcny02Wm0IuJwYMyAG8fXVeKdmhm8xizNByD4ryZlx6PP6kRen+t/haF43cMfmaV7T3Cx6ChOdHEhFUQ==} - '@resolver-engine/imports-fs@0.2.2': - resolution: {integrity: sha512-gFCgMvCwyppjwq0UzIjde/WI+yDs3oatJhozG9xdjJdewwtd7LiF0T5i9lrHAUtqrQbqoFE4E+ZMRVHWpWHpKQ==} - '@resolver-engine/imports-fs@0.3.3': resolution: {integrity: sha512-7Pjg/ZAZtxpeyCFlZR5zqYkz+Wdo84ugB5LApwriT8XFeQoLwGUj4tZFFvvCuxaNCcqZzCYbonJgmGObYBzyCA==} - '@resolver-engine/imports@0.2.2': - resolution: {integrity: sha512-u5/HUkvo8q34AA+hnxxqqXGfby5swnH0Myw91o3Sm2TETJlNKXibFGSKBavAH+wvWdBi4Z5gS2Odu0PowgVOUg==} - '@resolver-engine/imports@0.3.3': resolution: {integrity: sha512-anHpS4wN4sRMwsAbMXhMfOD/y4a4Oo0Cw/5+rue7hSwGWsDOQaAU1ClK1OxjUC35/peazxEl8JaSRRS+Xb8t3Q==} @@ -4149,14 +4093,6 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sindresorhus/is@0.14.0': - resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} - engines: {node: '>=6'} - - '@sindresorhus/is@4.6.0': - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - '@sindresorhus/is@5.6.0': resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} @@ -4371,14 +4307,6 @@ packages: '@streamparser/json@0.0.22': resolution: {integrity: sha512-b6gTSBjJ8G8SuO3Gbbj+zXbVx8NSs1EbpbMKpzGLWMdkR+98McH9bEjSz3+0mPJf68c5nxa3CrJHp5EQNXM6zQ==} - '@szmarczak/http-timer@1.1.2': - resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} - engines: {node: '>=6'} - - '@szmarczak/http-timer@4.0.6': - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} - '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -4427,12 +4355,6 @@ packages: typechain: ^8.1.1 typescript: '>=4.3.0' - '@typechain/ethers-v5@2.0.0': - resolution: {integrity: sha512-0xdCkyGOzdqh4h5JSf+zoWx85IusEjDcPIwNEHP8mrWSnCae4rvrqB+/gtpdNfX7zjlFlZiMeePn2r63EI3Lrw==} - peerDependencies: - ethers: ^5.0.0 - typechain: ^3.0.0 - '@typechain/ethers-v6@0.5.1': resolution: {integrity: sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==} peerDependencies: @@ -4479,9 +4401,6 @@ packages: '@types/bn.js@5.2.0': resolution: {integrity: sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==} - '@types/cacheable-request@6.0.3': - resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} - '@types/chai-as-promised@7.1.8': resolution: {integrity: sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==} @@ -4546,9 +4465,6 @@ packages: '@types/katex@0.16.7': resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} - '@types/keyv@3.1.4': - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - '@types/level-errors@3.0.2': resolution: {integrity: sha512-gyZHbcQ2X5hNXf/9KS2qGEmgDe9EN2WDM3rJ5Ele467C0nA1sLhtmv1bZiPMDYfAYCfPWft0uQIaTvXbASSTRA==} @@ -4592,12 +4508,6 @@ packages: '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} - '@types/resolve@0.0.8': - resolution: {integrity: sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==} - - '@types/responselike@1.0.3': - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/secp256k1@4.0.6': resolution: {integrity: sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==} @@ -4764,9 +4674,6 @@ packages: '@whatwg-node/server@0.7.7': resolution: {integrity: sha512-aHURgNDFm/48WVV3vhTMfnEKCYwYgdaRdRhZsQZx4UVFjGGkGay7Ys0+AYu9QT/jpoImv2oONkstoTMUprDofg==} - '@yarnpkg/lockfile@1.1.0': - resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} - JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -4800,24 +4707,6 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} - abstract-leveldown@2.6.3: - resolution: {integrity: sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - - abstract-leveldown@2.7.2: - resolution: {integrity: sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - - abstract-leveldown@3.0.0: - resolution: {integrity: sha512-KUWx9UWGQD12zsmLNj64/pndaz4iJh/Pj7nopgkfDG6RlCcbMZvT6+9l7dchK4idog2Is8VdC/PvNbFuFmalIQ==} - engines: {node: '>=4'} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - - abstract-leveldown@5.0.0: - resolution: {integrity: sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==} - engines: {node: '>=6'} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - abstract-leveldown@6.2.3: resolution: {integrity: sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==} engines: {node: '>=6'} @@ -4853,9 +4742,6 @@ packages: aes-js@3.0.0: resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} - aes-js@3.1.2: - resolution: {integrity: sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==} - aes-js@4.0.0-beta.5: resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} @@ -4871,10 +4757,10 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ajv-errors@1.0.1: - resolution: {integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==} + ajv-errors@3.0.0: + resolution: {integrity: sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==} peerDependencies: - ajv: '>=5.0.0' + ajv: ^8.0.1 ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} @@ -4884,15 +4770,15 @@ packages: ajv: optional: true - ajv@5.5.2: - resolution: {integrity: sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==} - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + amazon-cognito-identity-js@6.3.15: resolution: {integrity: sha512-G2mzTlGYHKYh9oZDO0Gk94xVQ4iY9GYWBaYScbDYvz05ps6dqi0IvdNx1Lxi7oA3tjS5X+mUN7/svFJJdOB9YA==} @@ -4938,10 +4824,6 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - ansi-styles@2.2.1: - resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} - engines: {node: '>=0.10.0'} - ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -4961,9 +4843,6 @@ packages: antlr4ts@0.5.0-alpha.4: resolution: {integrity: sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==} - anymatch@1.3.2: - resolution: {integrity: sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==} - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -4988,30 +4867,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - arr-diff@2.0.0: - resolution: {integrity: sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==} - engines: {node: '>=0.10.0'} - - arr-diff@4.0.0: - resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} - engines: {node: '>=0.10.0'} - - arr-flatten@1.1.0: - resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} - engines: {node: '>=0.10.0'} - - arr-union@3.1.0: - resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} - engines: {node: '>=0.10.0'} - - array-back@1.0.4: - resolution: {integrity: sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==} - engines: {node: '>=0.12.0'} - - array-back@2.0.0: - resolution: {integrity: sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==} - engines: {node: '>=4'} - array-back@3.1.0: resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} engines: {node: '>=6'} @@ -5042,14 +4897,6 @@ packages: resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==} engines: {node: '>=0.10.0'} - array-unique@0.2.1: - resolution: {integrity: sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==} - engines: {node: '>=0.10.0'} - - array-unique@0.3.2: - resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} - engines: {node: '>=0.10.0'} - array.prototype.findlastindex@1.2.6: resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} engines: {node: '>= 0.4'} @@ -5062,10 +4909,6 @@ packages: resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} engines: {node: '>= 0.4'} - array.prototype.reduce@1.0.8: - resolution: {integrity: sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==} - engines: {node: '>= 0.4'} - arraybuffer.prototype.slice@1.0.4: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} @@ -5073,9 +4916,6 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - asn1.js@4.10.1: - resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==} - asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -5094,10 +4934,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - assign-symbols@1.0.0: - resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} - engines: {node: '>=0.10.0'} - ast-parents@0.0.1: resolution: {integrity: sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==} @@ -5105,9 +4941,6 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} - async-each@1.0.6: - resolution: {integrity: sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==} - async-eventemitter@0.2.4: resolution: {integrity: sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==} @@ -5127,9 +4960,6 @@ packages: async@1.5.2: resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} - async@2.6.2: - resolution: {integrity: sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==} - async@2.6.4: resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} @@ -5143,11 +4973,6 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} - atob@2.1.2: - resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} - engines: {node: '>= 4.5.0'} - hasBin: true - atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -5175,63 +5000,12 @@ packages: axios@1.12.2: resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} - babel-code-frame@6.26.0: - resolution: {integrity: sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==} - - babel-core@6.26.3: - resolution: {integrity: sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==} - - babel-generator@6.26.1: - resolution: {integrity: sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==} - - babel-helper-builder-binary-assignment-operator-visitor@6.24.1: - resolution: {integrity: sha512-gCtfYORSG1fUMX4kKraymq607FWgMWg+j42IFPc18kFQEsmtaibP4UrqsXt8FlEJle25HUd4tsoDR7H2wDhe9Q==} - - babel-helper-call-delegate@6.24.1: - resolution: {integrity: sha512-RL8n2NiEj+kKztlrVJM9JT1cXzzAdvWFh76xh/H1I4nKwunzE4INBXn8ieCZ+wh4zWszZk7NBS1s/8HR5jDkzQ==} - - babel-helper-define-map@6.26.0: - resolution: {integrity: sha512-bHkmjcC9lM1kmZcVpA5t2om2nzT/xiZpo6TJq7UlZ3wqKfzia4veeXbIhKvJXAMzhhEBd3cR1IElL5AenWEUpA==} - - babel-helper-explode-assignable-expression@6.24.1: - resolution: {integrity: sha512-qe5csbhbvq6ccry9G7tkXbzNtcDiH4r51rrPUbwwoTzZ18AqxWYRZT6AOmxrpxKnQBW0pYlBI/8vh73Z//78nQ==} - - babel-helper-function-name@6.24.1: - resolution: {integrity: sha512-Oo6+e2iX+o9eVvJ9Y5eKL5iryeRdsIkwRYheCuhYdVHsdEQysbc2z2QkqCLIYnNxkT5Ss3ggrHdXiDI7Dhrn4Q==} - - babel-helper-get-function-arity@6.24.1: - resolution: {integrity: sha512-WfgKFX6swFB1jS2vo+DwivRN4NB8XUdM3ij0Y1gnC21y1tdBoe6xjVnd7NSI6alv+gZXCtJqvrTeMW3fR/c0ng==} - - babel-helper-hoist-variables@6.24.1: - resolution: {integrity: sha512-zAYl3tqerLItvG5cKYw7f1SpvIxS9zi7ohyGHaI9cgDUjAT6YcY9jIEH5CstetP5wHIVSceXwNS7Z5BpJg+rOw==} - - babel-helper-optimise-call-expression@6.24.1: - resolution: {integrity: sha512-Op9IhEaxhbRT8MDXx2iNuMgciu2V8lDvYCNQbDGjdBNCjaMvyLf4wl4A3b8IgndCyQF8TwfgsQ8T3VD8aX1/pA==} - - babel-helper-regex@6.26.0: - resolution: {integrity: sha512-VlPiWmqmGJp0x0oK27Out1D+71nVVCTSdlbhIVoaBAj2lUgrNjBCRR9+llO4lTSb2O4r7PJg+RobRkhBrf6ofg==} - - babel-helper-remap-async-to-generator@6.24.1: - resolution: {integrity: sha512-RYqaPD0mQyQIFRu7Ho5wE2yvA/5jxqCIj/Lv4BXNq23mHYu/vxikOy2JueLiBxQknwapwrJeNCesvY0ZcfnlHg==} - - babel-helper-replace-supers@6.24.1: - resolution: {integrity: sha512-sLI+u7sXJh6+ToqDr57Bv973kCepItDhMou0xCP2YPVmR1jkHSCY+p1no8xErbV1Siz5QE8qKT1WIwybSWlqjw==} - - babel-helpers@6.24.1: - resolution: {integrity: sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==} - babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 - babel-messages@6.23.0: - resolution: {integrity: sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==} - - babel-plugin-check-es2015-constants@6.22.0: - resolution: {integrity: sha512-B1M5KBP29248dViEo1owyY32lk1ZSH2DaNNrXLGt8lyjjHm7pBqAdQ7VKUPR6EEDO323+OvT3MQXbCin8ooWdA==} - babel-plugin-istanbul@6.1.1: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} @@ -5240,107 +5014,17 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - babel-plugin-syntax-async-functions@6.13.0: - resolution: {integrity: sha512-4Zp4unmHgw30A1eWI5EpACji2qMocisdXhAftfhXoSV9j0Tvj6nRFE3tOmRY912E0FMRm/L5xWE7MGVT2FoLnw==} - - babel-plugin-syntax-exponentiation-operator@6.13.0: - resolution: {integrity: sha512-Z/flU+T9ta0aIEKl1tGEmN/pZiI1uXmCiGFRegKacQfEJzp7iNsKloZmyJlQr+75FCJtiFfGIK03SiCvCt9cPQ==} - babel-plugin-syntax-hermes-parser@0.29.1: resolution: {integrity: sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA==} - babel-plugin-syntax-trailing-function-commas@6.22.0: - resolution: {integrity: sha512-Gx9CH3Q/3GKbhs07Bszw5fPTlU+ygrOGfAhEt7W2JICwufpC4SuO0mG0+4NykPBSYPMJhqvVlDBU17qB1D+hMQ==} - babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: resolution: {integrity: sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==} - babel-plugin-transform-async-to-generator@6.24.1: - resolution: {integrity: sha512-7BgYJujNCg0Ti3x0c/DL3tStvnKS6ktIYOmo9wginv/dfZOrbSZ+qG4IRRHMBOzZ5Awb1skTiAsQXg/+IWkZYw==} - - babel-plugin-transform-es2015-arrow-functions@6.22.0: - resolution: {integrity: sha512-PCqwwzODXW7JMrzu+yZIaYbPQSKjDTAsNNlK2l5Gg9g4rz2VzLnZsStvp/3c46GfXpwkyufb3NCyG9+50FF1Vg==} - - babel-plugin-transform-es2015-block-scoped-functions@6.22.0: - resolution: {integrity: sha512-2+ujAT2UMBzYFm7tidUsYh+ZoIutxJ3pN9IYrF1/H6dCKtECfhmB8UkHVpyxDwkj0CYbQG35ykoz925TUnBc3A==} - - babel-plugin-transform-es2015-block-scoping@6.26.0: - resolution: {integrity: sha512-YiN6sFAQ5lML8JjCmr7uerS5Yc/EMbgg9G8ZNmk2E3nYX4ckHR01wrkeeMijEf5WHNK5TW0Sl0Uu3pv3EdOJWw==} - - babel-plugin-transform-es2015-classes@6.24.1: - resolution: {integrity: sha512-5Dy7ZbRinGrNtmWpquZKZ3EGY8sDgIVB4CU8Om8q8tnMLrD/m94cKglVcHps0BCTdZ0TJeeAWOq2TK9MIY6cag==} - - babel-plugin-transform-es2015-computed-properties@6.24.1: - resolution: {integrity: sha512-C/uAv4ktFP/Hmh01gMTvYvICrKze0XVX9f2PdIXuriCSvUmV9j+u+BB9f5fJK3+878yMK6dkdcq+Ymr9mrcLzw==} - - babel-plugin-transform-es2015-destructuring@6.23.0: - resolution: {integrity: sha512-aNv/GDAW0j/f4Uy1OEPZn1mqD+Nfy9viFGBfQ5bZyT35YqOiqx7/tXdyfZkJ1sC21NyEsBdfDY6PYmLHF4r5iA==} - - babel-plugin-transform-es2015-duplicate-keys@6.24.1: - resolution: {integrity: sha512-ossocTuPOssfxO2h+Z3/Ea1Vo1wWx31Uqy9vIiJusOP4TbF7tPs9U0sJ9pX9OJPf4lXRGj5+6Gkl/HHKiAP5ug==} - - babel-plugin-transform-es2015-for-of@6.23.0: - resolution: {integrity: sha512-DLuRwoygCoXx+YfxHLkVx5/NpeSbVwfoTeBykpJK7JhYWlL/O8hgAK/reforUnZDlxasOrVPPJVI/guE3dCwkw==} - - babel-plugin-transform-es2015-function-name@6.24.1: - resolution: {integrity: sha512-iFp5KIcorf11iBqu/y/a7DK3MN5di3pNCzto61FqCNnUX4qeBwcV1SLqe10oXNnCaxBUImX3SckX2/o1nsrTcg==} - - babel-plugin-transform-es2015-literals@6.22.0: - resolution: {integrity: sha512-tjFl0cwMPpDYyoqYA9li1/7mGFit39XiNX5DKC/uCNjBctMxyL1/PT/l4rSlbvBG1pOKI88STRdUsWXB3/Q9hQ==} - - babel-plugin-transform-es2015-modules-amd@6.24.1: - resolution: {integrity: sha512-LnIIdGWIKdw7zwckqx+eGjcS8/cl8D74A3BpJbGjKTFFNJSMrjN4bIh22HY1AlkUbeLG6X6OZj56BDvWD+OeFA==} - - babel-plugin-transform-es2015-modules-commonjs@6.26.2: - resolution: {integrity: sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==} - - babel-plugin-transform-es2015-modules-systemjs@6.24.1: - resolution: {integrity: sha512-ONFIPsq8y4bls5PPsAWYXH/21Hqv64TBxdje0FvU3MhIV6QM2j5YS7KvAzg/nTIVLot2D2fmFQrFWCbgHlFEjg==} - - babel-plugin-transform-es2015-modules-umd@6.24.1: - resolution: {integrity: sha512-LpVbiT9CLsuAIp3IG0tfbVo81QIhn6pE8xBJ7XSeCtFlMltuar5VuBV6y6Q45tpui9QWcy5i0vLQfCfrnF7Kiw==} - - babel-plugin-transform-es2015-object-super@6.24.1: - resolution: {integrity: sha512-8G5hpZMecb53vpD3mjs64NhI1au24TAmokQ4B+TBFBjN9cVoGoOvotdrMMRmHvVZUEvqGUPWL514woru1ChZMA==} - - babel-plugin-transform-es2015-parameters@6.24.1: - resolution: {integrity: sha512-8HxlW+BB5HqniD+nLkQ4xSAVq3bR/pcYW9IigY+2y0dI+Y7INFeTbfAQr+63T3E4UDsZGjyb+l9txUnABWxlOQ==} - - babel-plugin-transform-es2015-shorthand-properties@6.24.1: - resolution: {integrity: sha512-mDdocSfUVm1/7Jw/FIRNw9vPrBQNePy6wZJlR8HAUBLybNp1w/6lr6zZ2pjMShee65t/ybR5pT8ulkLzD1xwiw==} - - babel-plugin-transform-es2015-spread@6.22.0: - resolution: {integrity: sha512-3Ghhi26r4l3d0Js933E5+IhHwk0A1yiutj9gwvzmFbVV0sPMYk2lekhOufHBswX7NCoSeF4Xrl3sCIuSIa+zOg==} - - babel-plugin-transform-es2015-sticky-regex@6.24.1: - resolution: {integrity: sha512-CYP359ADryTo3pCsH0oxRo/0yn6UsEZLqYohHmvLQdfS9xkf+MbCzE3/Kolw9OYIY4ZMilH25z/5CbQbwDD+lQ==} - - babel-plugin-transform-es2015-template-literals@6.22.0: - resolution: {integrity: sha512-x8b9W0ngnKzDMHimVtTfn5ryimars1ByTqsfBDwAqLibmuuQY6pgBQi5z1ErIsUOWBdw1bW9FSz5RZUojM4apg==} - - babel-plugin-transform-es2015-typeof-symbol@6.23.0: - resolution: {integrity: sha512-fz6J2Sf4gYN6gWgRZaoFXmq93X+Li/8vf+fb0sGDVtdeWvxC9y5/bTD7bvfWMEq6zetGEHpWjtzRGSugt5kNqw==} - - babel-plugin-transform-es2015-unicode-regex@6.24.1: - resolution: {integrity: sha512-v61Dbbihf5XxnYjtBN04B/JBvsScY37R1cZT5r9permN1cp+b70DY3Ib3fIkgn1DI9U3tGgBJZVD8p/mE/4JbQ==} - - babel-plugin-transform-exponentiation-operator@6.24.1: - resolution: {integrity: sha512-LzXDmbMkklvNhprr20//RStKVcT8Cu+SQtX18eMHLhjHf2yFzwtQ0S2f0jQ+89rokoNdmwoSqYzAhq86FxlLSQ==} - - babel-plugin-transform-regenerator@6.26.0: - resolution: {integrity: sha512-LS+dBkUGlNR15/5WHKe/8Neawx663qttS6AGqoOUhICc9d1KciBvtrQSuc0PI+CxQ2Q/S1aKuJ+u64GtLdcEZg==} - - babel-plugin-transform-strict-mode@6.24.1: - resolution: {integrity: sha512-j3KtSpjyLSJxNoCDrhwiJad8kw0gJ9REGj8/CqL0HeRyLnvUNYV9zcqluL6QJSXh3nfsLEmSLvwRfGzrgR96Pw==} - babel-preset-current-node-syntax@1.2.0: resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-env@1.7.0: - resolution: {integrity: sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==} - babel-preset-fbjs@3.4.0: resolution: {integrity: sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==} peerDependencies: @@ -5352,35 +5036,13 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - babel-register@6.26.0: - resolution: {integrity: sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==} - - babel-runtime@6.26.0: - resolution: {integrity: sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==} - - babel-template@6.26.0: - resolution: {integrity: sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==} - - babel-traverse@6.26.0: - resolution: {integrity: sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==} - - babel-types@6.26.0: - resolution: {integrity: sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==} - - babelify@7.3.0: - resolution: {integrity: sha512-vID8Fz6pPN5pJMdlUnNFSfrlcx5MUule4k9aKs/zbZPyXxMTcRrB0M4Tarw22L8afr8eYSWxDPYCob3TdrqtlA==} - - babylon@6.18.0: - resolution: {integrity: sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==} - hasBin: true - - backoff@2.5.0: - resolution: {integrity: sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==} - engines: {node: '>= 0.6'} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base-64@0.1.0: resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==} @@ -5393,10 +5055,6 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - base@0.11.2: - resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} - engines: {node: '>=0.10.0'} - baseline-browser-mapping@2.8.4: resolution: {integrity: sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==} hasBin: true @@ -5424,10 +5082,6 @@ packages: bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - binary-extensions@1.13.1: - resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} - engines: {node: '>=0.10.0'} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -5438,9 +5092,6 @@ packages: bintrees@1.0.2: resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} - bip39@2.5.0: - resolution: {integrity: sha512-xwIx/8JKoT2+IPJpFEfXoWdYwP7UVAoUxxLNfGCfVowaJE7yg1Y5B1BVPqlUNsBq5/nGwmFkwRJ8xDW4sX8OdA==} - bip39@3.0.4: resolution: {integrity: sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==} @@ -5478,10 +5129,6 @@ packages: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - bowser@2.12.1: resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} @@ -5495,13 +5142,9 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - braces@1.8.5: - resolution: {integrity: sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==} - engines: {node: '>=0.10.0'} - - braces@2.3.2: - resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} - engines: {node: '>=0.10.0'} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -5510,33 +5153,12 @@ packages: brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - browser-stdout@1.3.0: - resolution: {integrity: sha512-7Rfk377tpSM9TWBEeHs0FlDZGoAIei2V/4MdZJoFMBFAK6BqLpxAIUepGRHGdPFgGsLb02PXovC4qddyHvQqTg==} - browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} browserify-aes@1.2.0: resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} - browserify-cipher@1.0.1: - resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==} - - browserify-des@1.0.2: - resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} - - browserify-rsa@4.1.1: - resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==} - engines: {node: '>= 0.10'} - - browserify-sign@4.2.3: - resolution: {integrity: sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==} - engines: {node: '>= 0.12'} - - browserslist@3.2.8: - resolution: {integrity: sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==} - hasBin: true - browserslist@4.26.0: resolution: {integrity: sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -5557,9 +5179,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer-to-arraybuffer@0.0.5: - resolution: {integrity: sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==} - buffer-writer@2.0.0: resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} engines: {node: '>=4'} @@ -5602,12 +5221,6 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - bytewise-core@1.2.3: - resolution: {integrity: sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==} - - bytewise@1.1.0: - resolution: {integrity: sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -5616,14 +5229,6 @@ packages: resolution: {integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==} engines: {node: ^16.14.0 || >=18.0.0} - cache-base@1.0.1: - resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} - engines: {node: '>=0.10.0'} - - cacheable-lookup@5.0.4: - resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} - engines: {node: '>=10.6.0'} - cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -5632,17 +5237,6 @@ packages: resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} engines: {node: '>=14.16'} - cacheable-request@6.1.0: - resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} - engines: {node: '>=8'} - - cacheable-request@7.0.4: - resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} - engines: {node: '>=8'} - - cachedown@1.0.0: - resolution: {integrity: sha512-t+yVk82vQWCJF3PsWHMld+jhhjkkWjcAzz8NbFx1iULOXWl8Tm/FdM4smZNVw3MRr0X+lVTx9PKzvEn4Ng19RQ==} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -5678,10 +5272,6 @@ packages: resolution: {integrity: sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==} engines: {node: '>=0.10.0'} - camelcase@4.1.0: - resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} - engines: {node: '>=4'} - camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -5733,10 +5323,6 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} - chalk@1.1.3: - resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} - engines: {node: '>=0.10.0'} - chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -5786,12 +5372,6 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} - checkpoint-store@1.1.0: - resolution: {integrity: sha512-J/NdY2WvIx654cc6LWSq/IYFFCUf75fFTgwzFnmbqyORH4MwgiQCgswLLKBGzmsyTI5V7i5bp/So6sMbDWhedg==} - - chokidar@1.7.0: - resolution: {integrity: sha512-mk8fAWcRUOxY7btlLtitj3A45jOwSAxH4tOFOoEGbVsl6cL6pPMWUy7dwZ/canfj3QEdP6FHSnf/l1c6/WkzVg==} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -5826,22 +5406,10 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - cids@0.7.5: - resolution: {integrity: sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==} - engines: {node: '>=4.0.0', npm: '>=3.0.0'} - deprecated: This module has been superseded by the multiformats module - cipher-base@1.0.6: resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} engines: {node: '>= 0.10'} - class-is@1.1.0: - resolution: {integrity: sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==} - - class-utils@0.3.6: - resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} - engines: {node: '>=0.10.0'} - clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -5866,14 +5434,6 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} - cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} - - cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-truncate@5.0.0: resolution: {integrity: sha512-ds7u02fPOOBpcUl2VSjLF3lfnAik9u7Zt0BTaaAQlT5RtABALl4cvpJHthXx+rM50J4gSfXKPH5Tix/tfdefUQ==} engines: {node: '>=20'} @@ -5885,9 +5445,6 @@ packages: cliui@3.2.0: resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==} - cliui@4.1.0: - resolution: {integrity: sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==} - cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} @@ -5898,17 +5455,6 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - - clone@2.1.2: - resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} - engines: {node: '>=0.8'} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - code-point-at@1.1.0: resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} engines: {node: '>=0.10.0'} @@ -5916,10 +5462,6 @@ packages: coingecko-api@1.0.10: resolution: {integrity: sha512-7YLLC85+daxAw5QlBWoHVBVpJRwoPr4HtwanCr8V/WRjoyHTa1Lb9DQAvv4MDJZHiz4no6HGnDQnddtjV35oRA==} - collection-visit@1.0.0: - resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} - engines: {node: '>=0.10.0'} - color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -5956,10 +5498,6 @@ packages: command-exists@1.2.9: resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} - command-line-args@4.0.7: - resolution: {integrity: sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA==} - hasBin: true - command-line-args@5.2.1: resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} engines: {node: '>=4.0.0'} @@ -5984,15 +5522,9 @@ packages: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} - commander@2.11.0: - resolution: {integrity: sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==} - commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@3.0.2: - resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} - commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -6011,9 +5543,6 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - component-emitter@1.3.1: - resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -6044,9 +5573,6 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-hash@2.5.2: - resolution: {integrity: sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==} - content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -6064,9 +5590,6 @@ packages: engines: {node: '>=16'} hasBin: true - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -6081,24 +5604,9 @@ packages: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - - cookiejar@2.1.4: - resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - - copy-descriptor@0.1.1: - resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} - engines: {node: '>=0.10.0'} - core-js-pure@3.45.1: resolution: {integrity: sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ==} - core-js@2.6.12: - resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==} - deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. - core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -6144,9 +5652,6 @@ packages: engines: {node: '>=0.8'} hasBin: true - create-ecdh@4.0.4: - resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} - create-hash@1.1.3: resolution: {integrity: sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==} @@ -6159,9 +5664,6 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-fetch@2.2.6: - resolution: {integrity: sha512-9JZz+vXCmfKUZ68zAptS7k4Nu8e2qcibe7WVZYps7sAgk5R8GYTc+T1WR0v1rlP9HxgARmOX1UTIJZFytajpNA==} - cross-fetch@3.1.5: resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} @@ -6175,13 +5677,6 @@ packages: resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} engines: {node: '>=16.0.0'} - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} - - cross-spawn@6.0.6: - resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} - engines: {node: '>=4.8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -6189,13 +5684,6 @@ packages: crypt@0.0.2: resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} - crypto-browserify@3.12.0: - resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==} - - d@1.0.2: - resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} - engines: {node: '>=0.12'} - dargs@8.1.0: resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} engines: {node: '>=12'} @@ -6236,23 +5724,6 @@ packages: supports-color: optional: true - debug@3.1.0: - resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@3.2.6: - resolution: {integrity: sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==} - deprecated: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -6281,14 +5752,6 @@ packages: decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - - decompress-response@3.3.0: - resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} - engines: {node: '>=4'} - decompress-response@4.2.1: resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} engines: {node: '>=8'} @@ -6308,10 +5771,6 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - deep-equal@1.1.2: - resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} - engines: {node: '>= 0.4'} - deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -6319,22 +5778,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - defer-to-connect@1.1.3: - resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} - defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} - deferred-leveldown@1.2.2: - resolution: {integrity: sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - - deferred-leveldown@4.0.2: - resolution: {integrity: sha512-5fMC8ek8alH16QiV0lTCis610D1Zt1+LA4MS4d63JgS32lrCjTFDUFz2ao09/j2I4Bqb5jL4FZYwu7Jz0XO1ww==} - engines: {node: '>=6'} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - deferred-leveldown@5.3.0: resolution: {integrity: sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==} engines: {node: '>=6'} @@ -6352,21 +5799,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - define-property@0.2.5: - resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} - engines: {node: '>=0.10.0'} - - define-property@1.0.0: - resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} - engines: {node: '>=0.10.0'} - - define-property@2.0.2: - resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} - engines: {node: '>=0.10.0'} - - defined@1.0.1: - resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -6395,9 +5827,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - des.js@1.1.0: - resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} - destroy@1.0.4: resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==} @@ -6405,10 +5834,6 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-indent@4.0.0: - resolution: {integrity: sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==} - engines: {node: '>=0.10.0'} - detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -6421,14 +5846,6 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - diff@3.3.1: - resolution: {integrity: sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==} - engines: {node: '>=0.3.1'} - - diff@3.5.0: - resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} - engines: {node: '>=0.3.1'} - diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -6437,9 +5854,6 @@ packages: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} - diffie-hellman@5.0.3: - resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} - difflib@0.2.4: resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} @@ -6454,9 +5868,6 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - dom-walk@0.1.2: - resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} - dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -6472,10 +5883,6 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dotignore@0.1.2: - resolution: {integrity: sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==} - hasBin: true - dottie@2.0.6: resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} @@ -6487,9 +5894,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - duplexer3@0.1.5: - resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - duplexify@4.1.3: resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} @@ -6544,11 +5948,6 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - encoding-down@5.0.4: - resolution: {integrity: sha512-8CIZLDcSKxgzT+zX8ZVfgNbu8Md2wq/iqa1Y7zyVR18QBEAc0Nmzuvj/N5ykSKpfGzjM8qxbaFntLPwnVoUhZw==} - engines: {node: '>=6'} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - encoding-down@6.3.0: resolution: {integrity: sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==} engines: {node: '>=6'} @@ -6576,9 +5975,6 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - eol@0.9.1: - resolution: {integrity: sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==} - err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -6596,9 +5992,6 @@ packages: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} - es-array-method-boxes-properly@1.0.0: - resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -6623,17 +6016,6 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - es5-ext@0.10.64: - resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} - engines: {node: '>=0.10'} - - es6-iterator@2.0.3: - resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} - - es6-symbol@3.1.4: - resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} - engines: {node: '>=0.12'} - esbuild@0.25.9: resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} @@ -6748,10 +6130,6 @@ packages: jiti: optional: true - esniff@2.0.1: - resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} - engines: {node: '>=0.10'} - espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6790,9 +6168,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - eth-block-tracker@3.0.1: - resolution: {integrity: sha512-WUVxWLuhMmsfenfZvFO5sbl1qFY2IqUlw/FPVmjjdElpqLsZtSG+wPe9Dz7W/sB6e80HgFKknOmKk2eNlznHug==} - eth-ens-namehash@2.0.8: resolution: {integrity: sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==} @@ -6804,46 +6179,9 @@ packages: '@codechecks/client': optional: true - eth-json-rpc-infura@3.2.1: - resolution: {integrity: sha512-W7zR4DZvyTn23Bxc0EWsq4XGDdD63+XPUCEhV2zQvQGavDVC4ZpFDK4k99qN7bd7/fjj37+rxmuBOBeIqCA5Mw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - - eth-json-rpc-middleware@1.6.0: - resolution: {integrity: sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==} - - eth-lib@0.1.29: - resolution: {integrity: sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==} - - eth-lib@0.2.8: - resolution: {integrity: sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==} - - eth-query@2.1.2: - resolution: {integrity: sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA==} - - eth-sig-util@1.4.2: - resolution: {integrity: sha512-iNZ576iTOGcfllftB73cPB5AN+XUQAT/T8xzsILsghXC1o8gJUqe3RHlcDqagu+biFpYQ61KQrZZJza8eRSYqw==} - deprecated: Deprecated in favor of '@metamask/eth-sig-util' - - eth-sig-util@3.0.0: - resolution: {integrity: sha512-4eFkMOhpGbTxBQ3AMzVf0haUX2uTur7DpWiHzWyTURa28BVJJtOkcb9Ok5TV0YvEPG61DODPW7ZUATbJTslioQ==} - deprecated: Deprecated in favor of '@metamask/eth-sig-util' - - eth-tx-summary@3.2.4: - resolution: {integrity: sha512-NtlDnaVZah146Rm8HMRUNMgIwG/ED4jiqk0TME9zFheMl1jOp6jL1m0NKGjJwehXQ6ZKCPr16MTr+qspKpEXNg==} - - ethashjs@0.0.8: - resolution: {integrity: sha512-/MSbf/r2/Ld8o0l15AymjOTlPqpN8Cr4ByUEA9GtR4x0yAh3TdtDzEg29zMjXCNPI7u6E5fOQdj/Cf9Tc7oVNw==} - deprecated: 'New package name format for new versions: @ethereumjs/ethash. Please update.' - ethereum-bloom-filters@1.2.0: resolution: {integrity: sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==} - ethereum-common@0.0.18: - resolution: {integrity: sha512-EoltVQTRNg2Uy4o84qpa2aXymXDJhxm7eos/ACOg0DG4baAbMjhbdAEsx9GeE8sC3XCxnYvrrzZDH8D8MtA2iQ==} - - ethereum-common@0.2.0: - resolution: {integrity: sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==} - ethereum-cryptography@0.1.3: resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==} @@ -6853,11 +6191,6 @@ packages: ethereum-cryptography@2.2.1: resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} - ethereum-waffle@3.4.4: - resolution: {integrity: sha512-PA9+jCjw4WC3Oc5ocSMBj5sXvueWQeAbvCA+hUlb6oFgwwKyq5ka3bWQ7QZcjzIX+TdFkxP4IbFmoY2D8Dkj9Q==} - engines: {node: '>=10.0'} - hasBin: true - ethereum-waffle@4.0.10: resolution: {integrity: sha512-iw9z1otq7qNkGDNcMoeNeLIATF9yKl1M8AIeu42ElfNBplq0e+5PeasQmm8ybY/elkZ1XyRO0JBQxQdVRb8bqQ==} engines: {node: '>=10.0'} @@ -6865,55 +6198,10 @@ packages: peerDependencies: ethers: '*' - ethereumjs-abi@0.6.5: - resolution: {integrity: sha512-rCjJZ/AE96c/AAZc6O3kaog4FhOsAViaysBxqJNy2+LHP0ttH0zkZ7nXdVHOAyt6lFwLO0nlCwWszysG/ao1+g==} - deprecated: This library has been deprecated and usage is discouraged. - ethereumjs-abi@0.6.8: resolution: {integrity: sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==} deprecated: This library has been deprecated and usage is discouraged. - ethereumjs-abi@https://codeload.github.com/ethereumjs/ethereumjs-abi/tar.gz/ee3994657fa7a427238e6ba92a84d0b529bbcde0: - resolution: {tarball: https://codeload.github.com/ethereumjs/ethereumjs-abi/tar.gz/ee3994657fa7a427238e6ba92a84d0b529bbcde0} - version: 0.6.8 - - ethereumjs-account@2.0.5: - resolution: {integrity: sha512-bgDojnXGjhMwo6eXQC0bY6UK2liSFUSMwwylOmQvZbSl/D7NXQ3+vrGO46ZeOgjGfxXmgIeVNDIiHw7fNZM4VA==} - - ethereumjs-account@3.0.0: - resolution: {integrity: sha512-WP6BdscjiiPkQfF9PVfMcwx/rDvfZTjFKY0Uwc09zSQr9JfIVH87dYIJu0gNhBhpmovV4yq295fdllS925fnBA==} - deprecated: Please use Util.Account class found on package ethereumjs-util@^7.0.6 https://github.com/ethereumjs/ethereumjs-util/releases/tag/v7.0.6 - - ethereumjs-block@1.7.1: - resolution: {integrity: sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==} - deprecated: 'New package name format for new versions: @ethereumjs/block. Please update.' - - ethereumjs-block@2.2.2: - resolution: {integrity: sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg==} - deprecated: 'New package name format for new versions: @ethereumjs/block. Please update.' - - ethereumjs-blockchain@4.0.4: - resolution: {integrity: sha512-zCxaRMUOzzjvX78DTGiKjA+4h2/sF0OYL1QuPux0DHpyq8XiNoF5GYHtb++GUxVlMsMfZV7AVyzbtgcRdIcEPQ==} - deprecated: 'New package name format for new versions: @ethereumjs/blockchain. Please update.' - - ethereumjs-common@1.5.0: - resolution: {integrity: sha512-SZOjgK1356hIY7MRj3/ma5qtfr/4B5BL+G4rP/XSMYr2z1H5el4RX5GReYCKmQmYI/nSBmRnwrZ17IfHuG0viQ==} - deprecated: 'New package name format for new versions: @ethereumjs/common. Please update.' - - ethereumjs-tx@1.3.7: - resolution: {integrity: sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==} - deprecated: 'New package name format for new versions: @ethereumjs/tx. Please update.' - - ethereumjs-tx@2.1.2: - resolution: {integrity: sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==} - deprecated: 'New package name format for new versions: @ethereumjs/tx. Please update.' - - ethereumjs-util@4.5.1: - resolution: {integrity: sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w==} - - ethereumjs-util@5.2.1: - resolution: {integrity: sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==} - ethereumjs-util@6.2.1: resolution: {integrity: sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==} @@ -6925,18 +6213,6 @@ packages: resolution: {integrity: sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==} engines: {node: '>=10.0.0'} - ethereumjs-vm@2.6.0: - resolution: {integrity: sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw==} - deprecated: 'New package name format for new versions: @ethereumjs/vm. Please update.' - - ethereumjs-vm@4.2.0: - resolution: {integrity: sha512-X6qqZbsY33p5FTuZqCnQ4+lo957iUJMM6Mpa6bL4UW0dxM6WmDSHuI4j/zOp1E2TDKImBGCJA9QPfc08PaNubA==} - deprecated: 'New package name format for new versions: @ethereumjs/vm. Please update.' - - ethereumjs-wallet@0.6.5: - resolution: {integrity: sha512-MDwjwB9VQVnpp/Dc1XzA6J1a3wgHQ4hSvA1uWNatdpOrtCbPVuQSKSyRnjLvS0a+KKMw2pvQ9Ybqpb3+eW8oNA==} - deprecated: 'New package name format for new versions: @ethereumjs/wallet. Please update.' - ethers@5.6.2: resolution: {integrity: sha512-EzGCbns24/Yluu7+ToWnMca3SXJ1Jk1BvWB7CCmVNxyOeM4LLvw2OLuIHhlkhQk1dtOcj9UMsdkxUh8RiG1dxQ==} @@ -6965,20 +6241,10 @@ packages: resolution: {integrity: sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==} engines: {node: '>=6.5.0', npm: '>=3'} - ethlint@1.2.5: - resolution: {integrity: sha512-x2nKK98zmd72SFWL3Ul1S6scWYf5QqG221N6/mFNMO661g7ASvTRINGIWVvHzsvflW6y4tvgMSjnTN5RCTuZug==} - hasBin: true - - event-emitter@0.3.5: - resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} - event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} - eventemitter3@4.0.4: - resolution: {integrity: sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==} - eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -6992,26 +6258,6 @@ packages: evp_bytestokey@1.0.3: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} - execa@0.7.0: - resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==} - engines: {node: '>=4'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - expand-brackets@0.1.5: - resolution: {integrity: sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==} - engines: {node: '>=0.10.0'} - - expand-brackets@2.1.4: - resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} - engines: {node: '>=0.10.0'} - - expand-range@1.8.2: - resolution: {integrity: sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==} - engines: {node: '>=0.10.0'} - expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -7027,21 +6273,6 @@ packages: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} - express@4.21.2: - resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} - engines: {node: '>= 0.10.0'} - - ext@1.7.0: - resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} - - extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - - extend-shallow@3.0.2: - resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} - engines: {node: '>=0.10.0'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -7052,14 +6283,6 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - extglob@0.3.2: - resolution: {integrity: sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==} - engines: {node: '>=0.10.0'} - - extglob@2.0.4: - resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} - engines: {node: '>=0.10.0'} - extract-files@11.0.0: resolution: {integrity: sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==} engines: {node: ^12.20 || >= 14.13} @@ -7068,18 +6291,12 @@ packages: resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} engines: {'0': node >=0.6.0} - fake-merkle-patricia-tree@1.0.1: - resolution: {integrity: sha512-Tgq37lkc9pUIgIKw5uitNUKcgcYL3R6JvXtKQbOf/ZSavXbidsksgp/pAY6p//uhw0I4yoMsvTSovvVIsk/qxA==} - fast-base64-decode@1.0.0: resolution: {integrity: sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==} fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} - fast-deep-equal@1.1.0: - resolution: {integrity: sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -7145,9 +6362,6 @@ packages: fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - fetch-ponyfill@4.1.0: - resolution: {integrity: sha512-knK9sGskIg2T7OnYLdZ2hZXn0CtDrAIBxYQLpmEf0BqfdWnwmM1weccUl5+4EdA44tzNSFAuxITPbXtPehUB3g==} - fets@0.1.5: resolution: {integrity: sha512-mL/ya591WOgCP1yBBPbp8E37nynj8QQF6iQCUVl0aHDL80BZ9SOL4BcKBy0dnKdC+clnnAkMm05KB9hsj4m4jQ==} @@ -7162,18 +6376,6 @@ packages: file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - filename-regex@2.0.1: - resolution: {integrity: sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==} - engines: {node: '>=0.10.0'} - - fill-range@2.2.4: - resolution: {integrity: sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==} - engines: {node: '>=0.10.0'} - - fill-range@4.0.0: - resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} - engines: {node: '>=0.10.0'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -7186,14 +6388,6 @@ packages: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} engines: {node: '>= 0.8'} - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} - engines: {node: '>= 0.8'} - - find-replace@1.0.3: - resolution: {integrity: sha512-KrUnjzDCD9426YnCP56zGYy/eieTnhtK6Vn++j+JJzmlsWWwEkDnsyVF575spT6HJ6Ow9tlbT3TQTDsa+O4UWA==} - engines: {node: '>=4.0.0'} - find-replace@3.0.0: resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} engines: {node: '>=4.0.0'} @@ -7202,10 +6396,6 @@ packages: resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==} engines: {node: '>=0.10.0'} - find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -7218,12 +6408,6 @@ packages: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} engines: {node: '>=18'} - find-yarn-workspace-root@1.2.1: - resolution: {integrity: sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==} - - find-yarn-workspace-root@2.0.0: - resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} - flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -7238,9 +6422,6 @@ packages: flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} - flow-stoplight@1.0.0: - resolution: {integrity: sha512-rDjbZUKpN8OYhB0IE/vY/I8UWO/602IIJEU/76Tv4LvYnwHCk0BCsvz4eRr9n+FQcri7L5cyaXOo0+/Kh4HisA==} - fmix@0.1.0: resolution: {integrity: sha512-Y6hyofImk9JdzU8k5INtTXX1cu8LDlePWDFU5sftm9H+zKCr5SGrVjdhkvsim646cw5zD0nADj8oHyXMZmCZ9w==} @@ -7260,14 +6441,6 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - for-in@1.0.2: - resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} - engines: {node: '>=0.10.0'} - - for-own@0.1.5: - resolution: {integrity: sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==} - engines: {node: '>=0.10.0'} - foreach@2.0.6: resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} @@ -7309,10 +6482,6 @@ packages: fp-ts@1.19.3: resolution: {integrity: sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==} - fragment-cache@0.2.1: - resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} - engines: {node: '>=0.10.0'} - fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -7331,9 +6500,6 @@ packages: resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} - fs-extra@4.0.3: - resolution: {integrity: sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==} - fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -7346,9 +6512,6 @@ packages: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} - fs-minipass@1.2.7: - resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==} - fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -7363,12 +6526,6 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@1.2.13: - resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} - engines: {node: '>= 4.0'} - os: [darwin] - deprecated: Upgrade to fsevents v2 to mitigate potential security issues - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -7387,13 +6544,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - ganache-core@2.13.2: - resolution: {integrity: sha512-tIF5cR+ANQz0+3pHWxHjIwHqFXcVo0Mb+kcsNhglNFALcYo49aQpnS9dqHartqPfMFjiHh/qFoD3mYK0d/qGgw==} - engines: {node: '>=8.9.0'} - deprecated: ganache-core is now ganache; visit https://trfl.io/g7 for details - bundledDependencies: - - keccak - ganache@7.4.3: resolution: {integrity: sha512-RpEDUiCkqbouyE7+NMXG26ynZ+7sGiODU84Kz+FVoXUnQ4qQM4M8wif3Y4qUCt+D/eM1RVeGq0my62FPD6Y1KA==} hasBin: true @@ -7445,18 +6595,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@3.0.0: - resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} - engines: {node: '>=4'} - - get-stream@4.1.0: - resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} - engines: {node: '>=6'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -7468,10 +6606,6 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} - get-value@2.0.6: - resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} - engines: {node: '>=0.10.0'} - getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} @@ -7487,13 +6621,6 @@ packages: github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - glob-base@0.3.0: - resolution: {integrity: sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==} - engines: {node: '>=0.10.0'} - - glob-parent@2.0.0: - resolution: {integrity: sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -7504,6 +6631,7 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@11.0.3: @@ -7511,21 +6639,21 @@ packages: engines: {node: 20 || >=22} hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@5.0.15: resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} - deprecated: Glob versions prior to v9 are no longer supported - - glob@7.1.2: - resolution: {integrity: sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} @@ -7544,9 +6672,6 @@ packages: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} engines: {node: '>=6'} - global@4.4.0: - resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -7555,10 +6680,6 @@ packages: resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} engines: {node: '>=18'} - globals@9.18.0: - resolution: {integrity: sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==} - engines: {node: '>=0.10.0'} - globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -7575,18 +6696,10 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - got@11.8.6: - resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} - engines: {node: '>=10.19.0'} - got@12.6.1: resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} engines: {node: '>=14.16'} - got@9.6.0: - resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} - engines: {node: '>=8.6'} - graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -7639,10 +6752,6 @@ packages: resolution: {integrity: sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - growl@1.10.3: - resolution: {integrity: sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==} - engines: {node: '>=4.x'} - handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -7726,10 +6835,6 @@ packages: resolution: {integrity: sha512-0Z0KI/m6wJYCMZgDK3QuVqR59lSa3aMu6QHKqnbIYXKu/phQ+YFKJZAY4zkUKX21ZjcrrRg25qLUzZw1bO6g/A==} hasBin: true - has-ansi@2.0.0: - resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} - engines: {node: '>=0.10.0'} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -7738,10 +6843,6 @@ packages: resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==} engines: {node: '>=0.10.0'} - has-flag@2.0.0: - resolution: {integrity: sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==} - engines: {node: '>=0.10.0'} - has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -7768,33 +6869,9 @@ packages: has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - has-value@0.3.1: - resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} - engines: {node: '>=0.10.0'} - - has-value@1.0.0: - resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} - engines: {node: '>=0.10.0'} - - has-values@0.1.4: - resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} - engines: {node: '>=0.10.0'} - - has-values@1.0.0: - resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} - engines: {node: '>=0.10.0'} - - has@1.0.4: - resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} - engines: {node: '>= 0.4.0'} - hash-base@2.0.2: resolution: {integrity: sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==} - hash-base@3.0.5: - resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==} - engines: {node: '>= 0.10'} - hash-base@3.1.0: resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} engines: {node: '>=4'} @@ -7809,10 +6886,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - he@1.1.1: - resolution: {integrity: sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==} - hasBin: true - he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -7820,9 +6893,6 @@ packages: header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} - heap@0.2.6: - resolution: {integrity: sha512-MzzWcnfB1e4EG2vHi3dXHoBupmuXNZzx6pY6HldVS55JKKBoq3xOyzfSaZRkJp37HIhEYC78knabHff3zc4dQQ==} - heap@0.2.7: resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} @@ -7843,10 +6913,6 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - home-or-tmp@2.0.0: - resolution: {integrity: sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==} - engines: {node: '>=0.10.0'} - hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -7872,9 +6938,6 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} - http-https@1.0.0: - resolution: {integrity: sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==} - http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -7886,10 +6949,6 @@ packages: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} - http2-wrapper@1.0.3: - resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} - engines: {node: '>=10.19.0'} - http2-wrapper@2.2.1: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} @@ -7906,15 +6965,6 @@ packages: resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==} hasBin: true - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - husky@7.0.4: - resolution: {integrity: sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==} - engines: {node: '>=12'} - hasBin: true - husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} @@ -8058,20 +7108,12 @@ packages: resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} engines: {node: '>=0.10.0'} - is-accessor-descriptor@1.0.1: - resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==} - engines: {node: '>= 0.10'} - is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - is-arguments@1.2.0: - resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} - engines: {node: '>= 0.4'} - is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -8090,10 +7132,6 @@ packages: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} - is-binary-path@1.0.1: - resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==} - engines: {node: '>=0.10.0'} - is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -8102,25 +7140,14 @@ packages: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} - is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-ci@2.0.0: - resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} - hasBin: true - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-data-descriptor@1.0.1: - resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==} - engines: {node: '>= 0.4'} - is-data-view@1.0.2: resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} @@ -8132,14 +7159,6 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - is-descriptor@0.1.7: - resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==} - engines: {node: '>= 0.4'} - - is-descriptor@1.0.3: - resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==} - engines: {node: '>= 0.4'} - is-directory@0.3.1: resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} engines: {node: '>=0.10.0'} @@ -8149,26 +7168,6 @@ packages: engines: {node: '>=8'} hasBin: true - is-dotfile@1.0.3: - resolution: {integrity: sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==} - engines: {node: '>=0.10.0'} - - is-equal-shallow@0.1.3: - resolution: {integrity: sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==} - engines: {node: '>=0.10.0'} - - is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - - is-extendable@1.0.1: - resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} - engines: {node: '>=0.10.0'} - - is-extglob@1.0.0: - resolution: {integrity: sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==} - engines: {node: '>=0.10.0'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -8177,14 +7176,6 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} - is-finite@1.1.0: - resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==} - engines: {node: '>=0.10.0'} - - is-fn@1.0.0: - resolution: {integrity: sha512-XoFPJQmsAShb3jEQRfzf2rqXavq7fIqF/jOekp308JlThqrODnMpweVSGilKTCXELfLhltGP2AGgbQGVP8F1dg==} - engines: {node: '>=0.10.0'} - is-fullwidth-code-point@1.0.0: resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} engines: {node: '>=0.10.0'} @@ -8197,25 +7188,14 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - is-fullwidth-code-point@5.1.0: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} engines: {node: '>=18'} - is-function@1.0.2: - resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} - is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} - is-glob@2.0.1: - resolution: {integrity: sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==} - engines: {node: '>=0.10.0'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -8245,18 +7225,6 @@ packages: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} - is-number@2.1.0: - resolution: {integrity: sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==} - engines: {node: '>=0.10.0'} - - is-number@3.0.0: - resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} - engines: {node: '>=0.10.0'} - - is-number@4.0.0: - resolution: {integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==} - engines: {node: '>=0.10.0'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -8269,22 +7237,6 @@ packages: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} - is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - - is-posix-bracket@0.1.1: - resolution: {integrity: sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==} - engines: {node: '>=0.10.0'} - - is-primitive@2.0.0: - resolution: {integrity: sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==} - engines: {node: '>=0.10.0'} - - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -8301,10 +7253,6 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} - is-stream@1.1.0: - resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} - engines: {node: '>=0.10.0'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -8369,9 +7317,6 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} - isarray@0.0.1: - resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -8381,14 +7326,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isobject@2.1.0: - resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} - engines: {node: '>=0.10.0'} - - isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} - isomorphic-unfetch@3.1.0: resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==} @@ -8469,13 +7406,6 @@ packages: js-sha3@0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - js-string-escape@1.0.1: - resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} - engines: {node: '>= 0.8'} - - js-tokens@3.0.2: - resolution: {integrity: sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -8497,14 +7427,6 @@ packages: jsc-safe-url@0.2.4: resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} - jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true - - jsesc@1.3.0: - resolution: {integrity: sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==} - hasBin: true - jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -8516,9 +7438,6 @@ packages: json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} - json-buffer@3.0.0: - resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} - json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -8531,22 +7450,10 @@ packages: json-pointer@0.6.2: resolution: {integrity: sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==} - json-rpc-engine@3.8.0: - resolution: {integrity: sha512-6QNcvm2gFuuK4TKU1uwfH0Qd/cOSb9c1lls0gbnIhciktIUQJwz6NQNAW4B1KiGPenv7IKu97V222Yo1bNhGuA==} - - json-rpc-error@2.0.0: - resolution: {integrity: sha512-EwUeWP+KgAZ/xqFpaP6YDAXMtCJi+o/QQpCQFIYyxr01AdADi2y413eM8hSqJcoQym9WMePAJWoaODEJufC4Ug==} - - json-rpc-random-id@1.0.1: - resolution: {integrity: sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==} - json-schema-to-ts@2.12.0: resolution: {integrity: sha512-uTde38yBm5lzJSRPWRaasxZo72pb+JGE4iUksNdNfAkFaLhV4N9akeBxPPUpZy5onINt9Zo0oTLrAoEXyZESiQ==} engines: {node: '>=16'} - json-schema-traverse@0.3.1: - resolution: {integrity: sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -8559,10 +7466,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stable-stringify@1.3.0: - resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} - engines: {node: '>= 0.4'} - json-stream-stringify@3.1.6: resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==} engines: {node: '>=7.10.1'} @@ -8570,10 +7473,6 @@ packages: json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@0.5.1: - resolution: {integrity: sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==} - hasBin: true - json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -8595,9 +7494,6 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jsonify@0.0.1: - resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} - jsonparse@1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} @@ -8625,27 +7521,13 @@ packages: resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} engines: {node: '>=10.0.0'} - keyv@3.1.0: - resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kind-of@3.2.2: - resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} - engines: {node: '>=0.10.0'} - - kind-of@4.0.0: - resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} - engines: {node: '>=0.10.0'} - kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - klaw-sync@6.0.0: - resolution: {integrity: sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==} - klaw@1.3.1: resolution: {integrity: sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==} @@ -8668,10 +7550,6 @@ packages: resolution: {integrity: sha512-ShaNPPzgUi+iGj9bsQ0TPRm6MuOcPpc1NklL0/IzJsvB0OdHwWoPhmeTVR5z0oC3zzLebrojozo/nt8d2XTZbQ==} hasBin: true - level-codec@7.0.1: - resolution: {integrity: sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==} - deprecated: Superseded by level-transcoder (https://github.com/Level/community#faq) - level-codec@9.0.2: resolution: {integrity: sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==} engines: {node: '>=6'} @@ -8682,80 +7560,33 @@ packages: engines: {node: '>=6'} deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - level-errors@1.0.5: - resolution: {integrity: sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - level-errors@2.0.1: resolution: {integrity: sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==} engines: {node: '>=6'} deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - level-iterator-stream@1.3.1: - resolution: {integrity: sha512-1qua0RHNtr4nrZBgYlpV0qHHeHpcRRWTxEZJ8xsemoHAXNL5tbooh4tPEEqIqsbWCAJBmUmkwYK/sW5OrFjWWw==} - - level-iterator-stream@2.0.3: - resolution: {integrity: sha512-I6Heg70nfF+e5Y3/qfthJFexhRw/Gi3bIymCoXAlijZdAcLaPuWSJs3KXyTYf23ID6g0o2QF62Yh+grOXY3Rig==} - engines: {node: '>=4'} - - level-iterator-stream@3.0.1: - resolution: {integrity: sha512-nEIQvxEED9yRThxvOrq8Aqziy4EGzrxSZK+QzEFAVuJvQ8glfyZ96GB6BoI4sBbLfjMXm2w4vu3Tkcm9obcY0g==} - engines: {node: '>=6'} - level-iterator-stream@4.0.2: resolution: {integrity: sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==} engines: {node: '>=6'} - level-mem@3.0.1: - resolution: {integrity: sha512-LbtfK9+3Ug1UmvvhR2DqLqXiPW1OJ5jEh0a3m9ZgAipiwpSxGj/qaVVy54RG5vAQN1nCuXqjvprCuKSCxcJHBg==} - engines: {node: '>=6'} - deprecated: Superseded by memory-level (https://github.com/Level/community#faq) - level-mem@5.0.1: resolution: {integrity: sha512-qd+qUJHXsGSFoHTziptAKXoLX87QjR7v2KMbqncDXPxQuCdsQlzmyX+gwrEHhlzn08vkf8TyipYyMmiC6Gobzg==} engines: {node: '>=6'} deprecated: Superseded by memory-level (https://github.com/Level/community#faq) - level-packager@4.0.1: - resolution: {integrity: sha512-svCRKfYLn9/4CoFfi+d8krOtrp6RoX8+xm0Na5cgXMqSyRru0AnDYdLl+YI8u1FyS6gGZ94ILLZDE5dh2but3Q==} - engines: {node: '>=6'} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - level-packager@5.1.1: resolution: {integrity: sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==} engines: {node: '>=6'} deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - level-post@1.0.7: - resolution: {integrity: sha512-PWYqG4Q00asOrLhX7BejSajByB4EmG2GaKHfj3h5UmmZ2duciXLPGYWIjBzLECFWUGOZWlm5B20h/n3Gs3HKew==} - - level-sublevel@6.6.4: - resolution: {integrity: sha512-pcCrTUOiO48+Kp6F1+UAzF/OtWqLcQVTVF39HLdZ3RO8XBoXt+XVPKZO1vVr1aUoxHZA9OtD2e1v7G+3S5KFDA==} - level-supports@1.0.1: resolution: {integrity: sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==} engines: {node: '>=6'} - level-ws@0.0.0: - resolution: {integrity: sha512-XUTaO/+Db51Uiyp/t7fCMGVFOTdtLS/NIACxE/GHsij15mKzxksZifKVjlXDF41JMUP/oM1Oc4YNGdKnc3dVLw==} - - level-ws@1.0.0: - resolution: {integrity: sha512-RXEfCmkd6WWFlArh3X8ONvQPm8jNpfA0s/36M4QzLqrLEIt1iJE9WBHLZ5vZJK6haMjJPJGJCQWfjMNnRcq/9Q==} - engines: {node: '>=6'} - level-ws@2.0.0: resolution: {integrity: sha512-1iv7VXx0G9ec1isqQZ7y5LmoZo/ewAsyDHNA8EFDW5hqH2Kqovm33nSFkSdnLLAK+I5FlT+lo5Cw9itGe+CpQA==} engines: {node: '>=6'} - levelup@1.3.9: - resolution: {integrity: sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - - levelup@3.1.1: - resolution: {integrity: sha512-9N10xRkUU4dShSRRFTBdNaBxofz+PGaIZO962ckboJZiNmLuhVT6FZ6ZKAsICKfUBO76ySaYU6fJWX/jnj3Lcg==} - engines: {node: '>=6'} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - levelup@4.4.0: resolution: {integrity: sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==} engines: {node: '>=6'} @@ -8779,35 +7610,17 @@ packages: lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} - lilconfig@2.0.5: - resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==} - engines: {node: '>=10'} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - lint-staged@12.5.0: - resolution: {integrity: sha512-BKLUjWDsKquV/JuIcoQW4MSAI3ggwEImF1+sB4zaKvyVx1wBk3FsG7UK9bpnmBTN1pm7EH2BBcMwINJzCRv12g==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - lint-staged@16.2.7: resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==} engines: {node: '>=20.17'} hasBin: true - listr2@4.0.5: - resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} - engines: {node: '>=12'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true - listr2@9.0.5: resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} @@ -8823,10 +7636,6 @@ packages: localforage@1.10.0: resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} - locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -8896,9 +7705,6 @@ packages: lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} - lodash@4.17.20: - resolution: {integrity: sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==} - lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -8906,10 +7712,6 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - log-update@4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} - log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -8918,12 +7720,6 @@ packages: resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} engines: {node: '>= 12.0.0'} - looper@2.0.0: - resolution: {integrity: sha512-6DzMHJcjbQX/UPHc1rRCBfKlLwDkvuGZ715cIR36wSdYqWXFT35uLXq5P/2orl3tz+t+VOVPxw4yPinQlUDGDQ==} - - looper@3.0.0: - resolution: {integrity: sha512-LJ9wplN/uSn72oJRsXTx+snxPet5c8XiZmOKCm906NVYu+ag6SB6vUcnJcWxgnl2NfbIyeobAn7Bwv6xRj2XJg==} - loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -8940,14 +7736,6 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lowercase-keys@1.0.1: - resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} - engines: {node: '>=0.10.0'} - - lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8959,12 +7747,6 @@ packages: resolution: {integrity: sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==} engines: {node: 20 || >=22} - lru-cache@3.2.0: - resolution: {integrity: sha512-91gyOKTc2k66UG6kHiH4h3S2eltcPwE1STVfMYC/NG+nZwf8IIuiamfmpGZjpbbxzSyEJaLC0tNSmhjlQUTJow==} - - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -8979,9 +7761,6 @@ packages: lru_map@0.3.3: resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} - ltgt@2.1.3: - resolution: {integrity: sha512-5VjHC5GsENtIi5rbJd+feEpDKhfr7j0odoUR2Uh978g+2p93nd5o34cTjQWohXsPsCZeqoDnIqEf88mPCe0Pfw==} - ltgt@2.2.1: resolution: {integrity: sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==} @@ -8999,10 +7778,6 @@ packages: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} - map-visit@1.0.0: - resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} - engines: {node: '>=0.10.0'} - markdown-it@14.1.0: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true @@ -9038,9 +7813,6 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - math-random@1.0.4: - resolution: {integrity: sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==} - mcl-wasm@0.7.9: resolution: {integrity: sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==} engines: {node: '>=8.9.0'} @@ -9055,19 +7827,6 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - mem@1.1.0: - resolution: {integrity: sha512-nOBDrc/wgpkd3X/JOhMqYR+/eLqlfLP4oQfoBA6QExIxEl+GU01oyEkwWyueyO8110pUKijtiHGhEmYoOn88oQ==} - engines: {node: '>=4'} - - memdown@1.4.1: - resolution: {integrity: sha512-iVrGHZB8i4OQfM155xx8akvG9FIj+ht14DX5CQkCTG4EHzZ3d3sgckIf/Lm9ivZalEsFuEVnWv2B2WZvbrro2w==} - deprecated: Superseded by memory-level (https://github.com/Level/community#faq) - - memdown@3.0.0: - resolution: {integrity: sha512-tbV02LfZMWLcHcq4tw++NuqMO+FZX8tNJEiD2aNRm48ZZusVg5N8NART+dmBkepJVye986oixErf7jfXboMGMA==} - engines: {node: '>=6'} - deprecated: Superseded by memory-level (https://github.com/Level/community#faq) - memdown@5.1.0: resolution: {integrity: sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw==} engines: {node: '>=6'} @@ -9087,9 +7846,6 @@ packages: merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -9097,12 +7853,6 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - merkle-patricia-tree@2.3.2: - resolution: {integrity: sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==} - - merkle-patricia-tree@3.0.0: - resolution: {integrity: sha512-soRaMuNf/ILmw3KWbybaCjhx86EYeBbD8ph0edQCTed0JN/rxDt1EBN52Ajre3VyGo+91f8+/rfPIRQnnGMqmQ==} - merkle-patricia-tree@4.2.4: resolution: {integrity: sha512-eHbf/BG6eGNsqqfbLED9rIqbsF4+sykEaBn6OLNs71tjclbMcMOk1tEPmJKcNcNCLkvbpY/lwyOlizWsqPNo8w==} @@ -9261,14 +8011,6 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - micromatch@2.3.11: - resolution: {integrity: sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==} - engines: {node: '>=0.10.0'} - - micromatch@3.1.10: - resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} - engines: {node: '>=0.10.0'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -9290,10 +8032,6 @@ packages: engines: {node: '>=4'} hasBin: true - mimic-fn@1.2.0: - resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} - engines: {node: '>=4'} - mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -9302,10 +8040,6 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - mimic-response@2.1.0: resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} engines: {node: '>=8'} @@ -9318,9 +8052,6 @@ packages: resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - min-document@2.19.0: - resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} - minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -9335,6 +8066,10 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -9346,9 +8081,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist@0.0.8: - resolution: {integrity: sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -9372,9 +8104,6 @@ packages: resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} engines: {node: '>=8'} - minipass@2.9.0: - resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==} - minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -9387,30 +8116,17 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@1.3.3: - resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} - mixin-deep@1.3.2: - resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} - engines: {node: '>=0.10.0'} - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp-promise@5.0.1: - resolution: {integrity: sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==} - engines: {node: '>=4'} - deprecated: This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that. - - mkdirp@0.5.1: - resolution: {integrity: sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==} - deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.) - hasBin: true - mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -9433,18 +8149,6 @@ packages: engines: {node: '>= 14.0.0'} hasBin: true - mocha@4.1.0: - resolution: {integrity: sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==} - engines: {node: '>= 4.0.0'} - hasBin: true - - mock-fs@4.14.0: - resolution: {integrity: sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==} - - mock-property@1.0.3: - resolution: {integrity: sha512-2emPTb1reeLLYwHxyVx993iYyCHEiRRO+y8NFXFPL5kl5q14sgTK76cXyEKkeKCHeRw35SfdkUJ10Q1KfHuiIQ==} - engines: {node: '>= 0.4'} - moment-timezone@0.5.48: resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} @@ -9465,25 +8169,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - multibase@0.6.1: - resolution: {integrity: sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==} - deprecated: This module has been superseded by the multiformats module - - multibase@0.7.0: - resolution: {integrity: sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==} - deprecated: This module has been superseded by the multiformats module - - multicodec@0.5.7: - resolution: {integrity: sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==} - deprecated: This module has been superseded by the multiformats module - - multicodec@1.0.4: - resolution: {integrity: sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==} - deprecated: This module has been superseded by the multiformats module - - multihashes@0.4.21: - resolution: {integrity: sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==} - murmur-128@0.2.1: resolution: {integrity: sha512-WseEgiRkI6aMFBbj8Cg9yBj/y+OdipwVC7zUo3W2W1JAJITwouUOtpqsmGSg67EQmwwSyod7hsVsWY5LsrfQVg==} @@ -9502,17 +8187,10 @@ packages: nan@2.23.0: resolution: {integrity: sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==} - nano-json-stream-parser@0.1.2: - resolution: {integrity: sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==} - nano-spawn@2.0.0: resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} - nanomatch@1.2.13: - resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} - engines: {node: '>=0.10.0'} - nanospinner@1.2.2: resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} @@ -9545,16 +8223,10 @@ packages: neoqs@6.13.0: resolution: {integrity: sha512-IysBpjrEG9qiUb/IT6XrXSz2ASzBxLebp4s8/GBm7STYC315vMNqH0aWdRR+f7KvXK4aRlLcf5r2Z6dOTxQSrQ==} - next-tick@1.1.0: - resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - ngeohash@0.6.3: resolution: {integrity: sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==} engines: {node: '>=v0.2.0'} - nice-try@1.0.5: - resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -9576,9 +8248,6 @@ packages: node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} - node-fetch@1.7.3: - resolution: {integrity: sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==} - node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} @@ -9647,14 +8316,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-url@4.5.1: - resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} - engines: {node: '>=8'} - - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - normalize-url@8.1.0: resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==} engines: {node: '>=14.16'} @@ -9667,14 +8328,6 @@ packages: resolution: {integrity: sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==} engines: {node: ^16.14.0 || >=18.0.0} - npm-run-path@2.0.2: - resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} - engines: {node: '>=4'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - npmlog@4.1.2: resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==} deprecated: This package is no longer supported. @@ -9701,35 +8354,17 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-copy@0.1.0: - resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} - engines: {node: '>=0.10.0'} - object-inspect@1.10.3: resolution: {integrity: sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==} - object-inspect@1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - - object-keys@0.4.0: - resolution: {integrity: sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==} - object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object-visit@1.0.1: - resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} - engines: {node: '>=0.10.0'} - object.assign@4.1.7: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} @@ -9738,22 +8373,10 @@ packages: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} - object.getownpropertydescriptors@2.1.8: - resolution: {integrity: sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==} - engines: {node: '>= 0.8'} - object.groupby@1.0.3: resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} engines: {node: '>= 0.4'} - object.omit@2.0.1: - resolution: {integrity: sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==} - engines: {node: '>=0.10.0'} - - object.pick@1.3.0: - resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} - engines: {node: '>=0.10.0'} - object.values@1.2.1: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} @@ -9761,9 +8384,6 @@ packages: obliterator@2.0.5: resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} - oboe@2.1.4: - resolution: {integrity: sha512-ymBJ4xSC6GBXLT9Y7lirj+xbqBLa+jADGJldGEYG7u8sZbS9GyG+u1Xk9c5cbriKwSpCg41qUhPjvU5xOpvIyQ==} - on-exit-leak-free@0.2.0: resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==} @@ -9815,18 +8435,10 @@ packages: ordinal@1.0.3: resolution: {integrity: sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==} - os-homedir@1.0.2: - resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} - engines: {node: '>=0.10.0'} - os-locale@1.4.0: resolution: {integrity: sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==} engines: {node: '>=0.10.0'} - os-locale@2.1.0: - resolution: {integrity: sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==} - engines: {node: '>=4'} - os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -9854,14 +8466,6 @@ packages: typescript: optional: true - p-cancelable@1.1.0: - resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} - engines: {node: '>=6'} - - p-cancelable@2.1.1: - resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} - engines: {node: '>=8'} - p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} @@ -9874,10 +8478,6 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} - p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -9890,10 +8490,6 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -9926,10 +8522,6 @@ packages: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} - p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -9954,10 +8546,6 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-asn1@5.1.7: - resolution: {integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==} - engines: {node: '>= 0.10'} - parse-cache-control@1.0.1: resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} @@ -9968,13 +8556,6 @@ packages: resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} engines: {node: '>=0.8'} - parse-glob@3.0.4: - resolution: {integrity: sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==} - engines: {node: '>=0.10.0'} - - parse-headers@2.0.6: - resolution: {integrity: sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==} - parse-json@2.2.0: resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==} engines: {node: '>=0.10.0'} @@ -9994,20 +8575,6 @@ packages: pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - pascalcase@0.1.1: - resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} - engines: {node: '>=0.10.0'} - - patch-package@6.2.2: - resolution: {integrity: sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg==} - engines: {npm: '>5'} - hasBin: true - - patch-package@6.5.1: - resolution: {integrity: sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==} - engines: {node: '>=10', npm: '>5'} - hasBin: true - path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -10018,10 +8585,6 @@ packages: resolution: {integrity: sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==} engines: {node: '>=0.10.0'} - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -10034,10 +8597,6 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - path-key@2.0.1: - resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} - engines: {node: '>=4'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -10061,13 +8620,14 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-starts-with@2.0.1: resolution: {integrity: sha512-wZ3AeiRBRlNwkdUxvBANh0+esnt38DLffHDujZyRHkqkaKHTglnY2EP5UX3b8rdeiSutgO4y9NEJwXezNP5vHg==} engines: {node: '>=8'} - path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -10093,11 +8653,6 @@ packages: resolution: {integrity: sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==} engines: {node: '>=0.12'} - pegjs@0.10.0: - resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} - engines: {node: '>=0.10'} - hasBin: true - performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} @@ -10163,11 +8718,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pidtree@0.5.0: - resolution: {integrity: sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==} - engines: {node: '>=0.10'} - hasBin: true - pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -10211,10 +8761,6 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - posix-character-classes@0.1.1: - resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} - engines: {node: '>=0.10.0'} - possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -10235,9 +8781,6 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - postinstall-postinstall@2.1.0: - resolution: {integrity: sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==} - prebuild-install@5.3.6: resolution: {integrity: sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==} engines: {node: '>=6'} @@ -10248,10 +8791,6 @@ packages: engines: {node: '>=6'} hasBin: true - precond@0.2.3: - resolution: {integrity: sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==} - engines: {node: '>= 0.6'} - prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -10260,14 +8799,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prepend-http@2.0.0: - resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} - engines: {node: '>=4'} - - preserve@0.2.0: - resolution: {integrity: sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==} - engines: {node: '>=0.10.0'} - prettier-plugin-solidity@2.1.0: resolution: {integrity: sha512-O5HX4/PCE5aqiaEiNGbSRLbSBZQ6kLswAav5LBSewwzhT+sZlN6iAaLZlZcJzPEnIAxwLEHP03xKEg92fflT9Q==} engines: {node: '>=20'} @@ -10288,10 +8819,6 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - private@0.1.8: - resolution: {integrity: sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==} - engines: {node: '>= 0.6'} - proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -10299,10 +8826,6 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - prom-client@14.0.1: resolution: {integrity: sha512-HxTArb6fkOntQHoRGvv4qd/BkorjliiuO2uSWC2KC17MUTKYttWdDoXX/vxOhQdkoECEM9BBH0pj2l8G8kev6w==} engines: {node: '>=10'} @@ -10318,10 +8841,6 @@ packages: promise-throttle@1.1.2: resolution: {integrity: sha512-dij7vjyXNewuuN/gyr+TX2KRjw48mbV5FEtgyXaIoJjGYAKT0au23/voNvy9eS4UNJjx2KUdEcO5Yyfc1h7vWQ==} - promise-to-callback@1.0.0: - resolution: {integrity: sha512-uhMIZmKM5ZteDMfLgJnoSq9GCwsNKrYau73Awf1jIy6/eUcuuZ3P+CD9zUv0kJsIUbU+x6uLNIhXhLHDs1pNPA==} - engines: {node: '>=0.10.0'} - promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} @@ -10351,36 +8870,9 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - public-encrypt@4.0.3: - resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} - - pull-cat@1.1.11: - resolution: {integrity: sha512-i3w+xZ3DCtTVz8S62hBOuNLRHqVDsHMNZmgrZsjPnsxXUgbWtXEee84lo1XswE7W2a3WHyqsNuDJTjVLAQR8xg==} - - pull-defer@0.2.3: - resolution: {integrity: sha512-/An3KE7mVjZCqNhZsr22k1Tx8MACnUnHZZNPSJ0S62td8JtYr/AiRG42Vz7Syu31SoTLUzVIe61jtT/pNdjVYA==} - - pull-level@2.0.4: - resolution: {integrity: sha512-fW6pljDeUThpq5KXwKbRG3X7Ogk3vc75d5OQU/TvXXui65ykm+Bn+fiktg+MOx2jJ85cd+sheufPL+rw9QSVZg==} - - pull-live@1.0.1: - resolution: {integrity: sha512-tkNz1QT5gId8aPhV5+dmwoIiA1nmfDOzJDlOOUpU5DNusj6neNd3EePybJ5+sITr2FwyCs/FVpx74YMCfc8YeA==} - - pull-pushable@2.2.0: - resolution: {integrity: sha512-M7dp95enQ2kaHvfCt2+DJfyzgCSpWVR2h2kWYnVsW6ZpxQBx5wOu0QWOvQPVoPnBLUZYitYP2y7HyHkLQNeGXg==} - - pull-stream@3.7.0: - resolution: {integrity: sha512-Eco+/R004UaCK2qEDE8vGklcTG2OeZSVm1kTUQNrykEjDwcFXDZhygFDsW49DbXyJMEhHeRL3z5cRVqPAhXlIw==} - - pull-window@2.1.4: - resolution: {integrity: sha512-cbDzN76BMlcGG46OImrgpkMf/VkCnupj8JhsrpBw3aWBM9ye345aYnqitmZCgauBkc0HbbRRn9hCnsa3k2FNUg==} - pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -10413,10 +8905,6 @@ packages: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -10436,10 +8924,6 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - query-string@5.1.1: - resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==} - engines: {node: '>=0.10.0'} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -10453,16 +8937,9 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - randomatic@3.1.1: - resolution: {integrity: sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==} - engines: {node: '>= 0.10.0'} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - randomfill@1.0.4: - resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -10536,12 +9013,6 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} - readable-stream@1.0.34: - resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} - - readable-stream@1.1.14: - resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -10549,10 +9020,6 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@2.2.1: - resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} - engines: {node: '>=0.10'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -10581,33 +9048,13 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - - regenerator-runtime@0.11.1: - resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==} - regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - regenerator-transform@0.10.1: - resolution: {integrity: sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==} - - regex-cache@0.4.4: - resolution: {integrity: sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==} - engines: {node: '>=0.10.0'} - - regex-not@1.0.2: - resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} - engines: {node: '>=0.10.0'} - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - regexpu-core@2.0.0: - resolution: {integrity: sha512-tJ9+S4oKjxY8IZ9jmjnp/mtytu1u3iyIQAfmI51IKWH6bFf7XR1ybtaO6j7INhZKXOTYADk7V5qxaqLkmNxiZQ==} - registry-auth-token@5.1.0: resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} engines: {node: '>=14'} @@ -10616,31 +9063,12 @@ packages: resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} engines: {node: '>=12'} - regjsgen@0.2.0: - resolution: {integrity: sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g==} - - regjsparser@0.1.5: - resolution: {integrity: sha512-jlQ9gYLfk2p3V5Ag5fYhA7fv7OHzd1KUH0PRP46xc3TgwjwgROIW572AfYg/X9kaNq/LJnu6oJcFRXlIrGoTRw==} - hasBin: true - relay-runtime@12.0.0: resolution: {integrity: sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug==} remove-trailing-separator@1.1.0: resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} - repeat-element@1.1.4: - resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} - engines: {node: '>=0.10.0'} - - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - - repeating@2.0.1: - resolution: {integrity: sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==} - engines: {node: '>=0.10.0'} - req-cwd@2.0.0: resolution: {integrity: sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==} engines: {node: '>=4'} @@ -10690,10 +9118,6 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve-url@0.2.1: - resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} - deprecated: https://github.com/lydell/resolve-url#deprecated - resolve.exports@2.0.3: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} @@ -10709,12 +9133,6 @@ packages: engines: {node: '>= 0.4'} hasBin: true - responselike@1.0.2: - resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} - - responselike@2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} - responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} engines: {node: '>=14.16'} @@ -10727,10 +9145,6 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - ret@0.1.15: - resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} - engines: {node: '>=0.12'} - retry-as-promised@5.0.0: resolution: {integrity: sha512-6S+5LvtTl2ggBumk04hBo/4Uf6fRJUwIgunGZ7CYEBCeufGFW1Pu6ucUf/UskHeWOIsUcLOGLFXPig5tR5V1nA==} @@ -10814,10 +9228,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-event-emitter@1.0.1: - resolution: {integrity: sha512-e1wFe99A91XYYxoQbcq2ZJUWurxEyP8vfz7A7vuUe1s95q8r5ebraVaA1BukYJcpM6V16ugWoD9vngi8Ccu5fg==} - deprecated: Renamed to @metamask/safe-event-emitter - safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -10826,9 +9236,6 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} - safe-regex@1.1.0: - resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} - safe-stable-stringify@2.5.0: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} @@ -10846,9 +9253,6 @@ packages: scrypt-js@3.0.1: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - scryptsy@1.2.1: - resolution: {integrity: sha512-aldIRgMozSJ/Gl6K6qmJZysRP82lz83Wb42vl4PWN8SaLFHIaOzLPc9nUUW2jQN88CuGm5q5HefJ9jZ3nWSmTw==} - secp256k1@4.0.4: resolution: {integrity: sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==} engines: {node: '>=18.0.0'} @@ -10856,9 +9260,6 @@ packages: secure-keys@1.0.0: resolution: {integrity: sha512-nZi59hW3Sl5P3+wOO89eHBAAGwmCPd2aE1+dLZV5MO+ItQctIvAqihzaAXIQhvtH4KJPxM080HsnqltR2y8cWg==} - seedrandom@3.0.1: - resolution: {integrity: sha512-1/02Y/rUeU1CJBAGLebiC5Lbo5FnB22gQbIFFYTLkwvp1xdABZJH1sn4ZT1MzXmPpzv+Rf/Lu2NcsLJiK4rcDg==} - seedrandom@3.0.5: resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} @@ -10866,14 +9267,6 @@ packages: resolution: {integrity: sha512-b/ptP11hETwYWpeilHXXQiV5UJNJl7ZWWooKRE5eBIYWoom6dZ0SluCIdCtKycsMtZgKWE01/qAw6jblw1YVhg==} engines: {node: '>=4.1'} - semaphore@1.1.0: - resolution: {integrity: sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==} - engines: {node: '>=0.8.0'} - - semver@5.4.1: - resolution: {integrity: sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==} - hasBin: true - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -10993,10 +9386,6 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - servify@0.1.12: - resolution: {integrity: sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==} - engines: {node: '>=6'} - set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -11008,18 +9397,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} - set-immediate-shim@1.0.1: - resolution: {integrity: sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ==} - engines: {node: '>=0.10.0'} - set-proto@1.0.0: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} - setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -11037,18 +9418,10 @@ packages: shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -11091,9 +9464,6 @@ packages: simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - simple-get@2.8.2: - resolution: {integrity: sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==} - simple-get@3.1.1: resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} @@ -11106,14 +9476,6 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@1.0.0: - resolution: {integrity: sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==} - engines: {node: '>=0.10.0'} - - slash@2.0.0: - resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} - engines: {node: '>=6'} - slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -11122,18 +9484,10 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} - slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} - slice-ansi@4.0.0: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - slice-ansi@7.1.2: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} @@ -11153,18 +9507,6 @@ packages: snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - snapdragon-node@2.1.1: - resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} - engines: {node: '>=0.10.0'} - - snapdragon-util@3.0.1: - resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} - engines: {node: '>=0.10.0'} - - snapdragon@0.8.2: - resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} - engines: {node: '>=0.10.0'} - socks-proxy-agent@8.0.5: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} @@ -11173,21 +9515,10 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sol-digger@0.0.2: - resolution: {integrity: sha512-oqrw1E/X2WWYUYCzKDM5INDDH2nWOWos4p2Cw2OF52qoZcTDzlKMJQ5pJFXKOCADCg6KggBO5WYE/vNb+kJ0Hg==} - - sol-explore@1.6.1: - resolution: {integrity: sha512-cmwg7l+QLj2LE3Qvwrdo4aPYcNYY425+bN5VPkgCjkO0CiSz33G5vM5BmMZNrfd/6yNGwcm0KtwDJmh5lUElEQ==} - solc@0.4.26: resolution: {integrity: sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA==} hasBin: true - solc@0.6.12: - resolution: {integrity: sha512-Lm0Ql2G9Qc7yPP2Ba+WNmzw2jwsrd3u4PobHYlSOxaut3TtUbj9+5ZrT6f4DUpNPEoBaFUOEg9Op9C0mk7ge9g==} - engines: {node: '>=8.0.0'} - hasBin: true - solc@0.8.15: resolution: {integrity: sha512-Riv0GNHNk/SddN/JyEuFKwbcWcEeho15iyupTSHw5Np6WuXA5D8kEHbyzDHi6sqmvLzu2l+8b1YmL8Ytple+8w==} engines: {node: '>=10.0.0'} @@ -11198,8 +9529,9 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - solhint@6.0.3: - resolution: {integrity: sha512-LYiy1bN8X9eUsti13mbS4fY6ILVxhP6VoOgqbHxCsHl5VPnxOWf7U1V9ZvgizxdInKBMW82D1FNJO+daAcWHbA==} + solhint@6.2.1: + resolution: {integrity: sha512-+VHSa84CRjm2s+KZWYxIDnI+NokcLsZHOSpRtg5nBFmnVfh6RPmPaFd5TN922Cfrm2i85kNoQtLiapALe26b5w==} + engines: {node: '>=20'} hasBin: true solidity-ast@0.4.61: @@ -11290,39 +9622,12 @@ packages: peerDependencies: hardhat: ^2.8.0 - solium-plugin-security@0.1.1: - resolution: {integrity: sha512-kpLirBwIq4mhxk0Y/nn5cQ6qdJTI+U1LO3gpoNIcqNaW+sI058moXBe2UiHs+9wvF9IzYD49jcKhFTxcR9u9SQ==} - peerDependencies: - solium: ^1.0.0 - - solium@1.2.5: - resolution: {integrity: sha512-NuNrm7fp8JcDN/P+SAdM5TVa4wYDtwVtLY/rG4eBOZrC5qItsUhmQKR/YhjszaEW4c8tNUYhkhQcwOsS25znpw==} - hasBin: true - - solparse@2.2.8: - resolution: {integrity: sha512-Tm6hdfG72DOxD40SD+T5ddbekWglNWjzDRSNq7ZDIOHVsyaJSeeunUuWNj4DE7uDrJK3tGQuX0ZTDZWNYsGPMA==} - hasBin: true - sonic-boom@2.8.0: resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==} - source-map-resolve@0.5.3: - resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated - - source-map-support@0.4.18: - resolution: {integrity: sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==} - - source-map-support@0.5.12: - resolution: {integrity: sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==} - source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map-url@0.4.1: - resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} - deprecated: See https://github.com/lydell/source-map-url#deprecated - source-map@0.2.0: resolution: {integrity: sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==} engines: {node: '>=0.8.0'} @@ -11350,10 +9655,6 @@ packages: spdx-license-ids@3.0.22: resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - split-string@3.1.0: - resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} - engines: {node: '>=0.10.0'} - split2@3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} @@ -11390,10 +9691,6 @@ packages: resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} engines: {node: '>=6'} - static-extend@0.1.2: - resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} - engines: {node: '>=0.10.0'} - statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -11409,17 +9706,10 @@ packages: stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - stream-to-pull-stream@1.7.3: - resolution: {integrity: sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg==} - streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - strict-uri-encode@1.1.0: - resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} - engines: {node: '>=0.10.0'} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -11463,9 +9753,6 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - string_decoder@0.10.31: - resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} - string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -11500,14 +9787,6 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-eof@1.0.0: - resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} - engines: {node: '>=0.10.0'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - strip-hex-prefix@1.0.0: resolution: {integrity: sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==} engines: {node: '>=6.5.0', npm: '>=3'} @@ -11523,18 +9802,10 @@ packages: strnum@2.1.1: resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} - supports-color@2.0.0: - resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} - engines: {node: '>=0.8.0'} - supports-color@3.2.3: resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==} engines: {node: '>=0.8.0'} - supports-color@4.4.0: - resolution: {integrity: sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==} - engines: {node: '>=4'} - supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -11547,10 +9818,6 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - supports-color@9.4.0: - resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} - engines: {node: '>=12'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -11558,9 +9825,6 @@ packages: swap-case@2.0.2: resolution: {integrity: sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==} - swarm-js@0.1.42: - resolution: {integrity: sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==} - sync-request@6.1.0: resolution: {integrity: sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==} engines: {node: '>=8.0.0'} @@ -11576,10 +9840,6 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tape@4.17.0: - resolution: {integrity: sha512-KCuXjYxCZ3ru40dmND+oCLsXyuA8hoseu2SS404Px5ouyS0A99v8X/mdiLqsR5MTAyamMBN7PRwt2Dv3+xGIxw==} - hasBin: true - tar-fs@2.1.3: resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} @@ -11587,11 +9847,6 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar@4.4.19: - resolution: {integrity: sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==} - engines: {node: '>=4.5'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me - tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -11613,10 +9868,6 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - test-value@2.1.0: - resolution: {integrity: sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==} - engines: {node: '>=0.10.0'} - testrpc@0.0.1: resolution: {integrity: sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA==} deprecated: testrpc has been renamed to ganache-cli, please use this package from now on. @@ -11641,9 +9892,6 @@ packages: throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} - through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - through2@3.0.2: resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==} @@ -11653,10 +9901,6 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - timed-out@4.0.1: - resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==} - engines: {node: '>=0.10.0'} - tiny-lru@8.0.2: resolution: {integrity: sha512-ApGvZ6vVvTNdsmt676grvCkUCGwzG9IqXma5Z07xJgiC5L7akUMof5U8G2JTI9Rz/ovtVhJBlY6mNhEvtjzOIg==} engines: {node: '>=6'} @@ -11675,10 +9919,6 @@ packages: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} - tmp@0.1.0: - resolution: {integrity: sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==} - engines: {node: '>=6'} - tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -11686,30 +9926,10 @@ packages: resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} engines: {node: '>= 0.4'} - to-fast-properties@1.0.3: - resolution: {integrity: sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==} - engines: {node: '>=0.10.0'} - - to-object-path@0.3.0: - resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} - engines: {node: '>=0.10.0'} - - to-readable-stream@1.0.0: - resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} - engines: {node: '>=6'} - - to-regex-range@2.1.1: - resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} - engines: {node: '>=0.10.0'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - to-regex@3.0.2: - resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} - engines: {node: '>=0.10.0'} - toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -11724,18 +9944,10 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - trim-right@1.0.1: - resolution: {integrity: sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==} - engines: {node: '>=0.10.0'} - triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} - truffle-flattener@1.6.0: - resolution: {integrity: sha512-scS5Bsi4CZyvlrmD4iQcLHTiG2RQFUXVheTgWeH6PuafmI+Lk5U87Es98loM3w3ImqC9/fPHq+3QIXbcPuoJ1Q==} - hasBin: true - ts-algebra@1.2.2: resolution: {integrity: sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==} @@ -11749,23 +9961,11 @@ packages: resolution: {integrity: sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==} hasBin: true - ts-essentials@1.0.4: - resolution: {integrity: sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ==} - - ts-essentials@6.0.7: - resolution: {integrity: sha512-2E4HIIj4tQJlIHuATRHayv0EfMGK3ris/GRk1E3CFnsZzeNV+hUmelbaTZHLtXaZppM5oLhHRtO04gINC4Jusw==} - peerDependencies: - typescript: '>=3.7.0' - ts-essentials@7.0.3: resolution: {integrity: sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==} peerDependencies: typescript: '>=3.7.0' - ts-generator@0.1.1: - resolution: {integrity: sha512-N+ahhZxTLYu1HNTQetwWcx3so8hcYbkKBHTr4b4/YgObFTIKkOSSsaa+nal12w8mfrJAyzJfETXawbNjSfP2gQ==} - hasBin: true - ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -11824,15 +10024,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - tweetnacl-util@0.15.1: - resolution: {integrity: sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==} - tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - tweetnacl@1.0.3: - resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} - type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} @@ -11865,13 +10059,6 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type@2.7.3: - resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} - - typechain@3.0.0: - resolution: {integrity: sha512-ft4KVmiN3zH4JUFu2WJBrwfHeDf772Tt2d8bssDTo/YcckKW2D+OwFrHXRC6hJvO3mHjFQTihoMV6fJOi0Hngg==} - hasBin: true - typechain@8.3.2: resolution: {integrity: sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==} hasBin: true @@ -11894,9 +10081,6 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} @@ -11912,18 +10096,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - typewise-core@1.2.0: - resolution: {integrity: sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==} - - typewise@1.0.3: - resolution: {integrity: sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==} - - typewiselite@1.0.0: - resolution: {integrity: sha512-J9alhjVHupW3Wfz6qFRGgQw0N3gr8hOkw6zm7FZ6UR1Cse/oD9/JVok7DNE9TT9IbciDHX2Ex9+ksE6cRmtymw==} - - typical@2.6.1: - resolution: {integrity: sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==} - typical@4.0.0: resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} engines: {node: '>=8'} @@ -11947,9 +10119,6 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - ultron@1.1.1: - resolution: {integrity: sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==} - unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -11961,9 +10130,6 @@ packages: underscore@1.13.7: resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} - underscore@1.9.1: - resolution: {integrity: sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -11982,10 +10148,6 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} - union-value@1.0.1: - resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} - engines: {node: '>=0.10.0'} - unique-filename@3.0.0: resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -12006,18 +10168,10 @@ packages: resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} engines: {node: '>=0.10.0'} - unorm@1.6.0: - resolution: {integrity: sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==} - engines: {node: '>= 0.4.0'} - unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - unset-value@1.0.0: - resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} - engines: {node: '>=0.10.0'} - update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -12033,17 +10187,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - urix@0.1.0: - resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} - deprecated: Please see https://github.com/lydell/urix#deprecated - - url-parse-lax@3.0.0: - resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} - engines: {node: '>=4'} - - url-set-query@1.0.0: - resolution: {integrity: sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==} - url@0.11.4: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} @@ -12058,10 +10201,6 @@ packages: resolution: {integrity: sha512-dryNz030LWBPAf6gj8vyq0Iev3vPbCLHCT8dBw3gQRXRzVNsIdeuU+VjPp3ksmSPkeMAl1k+kQ14Ij0QHyeiAg==} engines: {node: '>=10.16.0'} - use@3.1.1: - resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} - engines: {node: '>=0.10.0'} - utf-8-validate@5.0.10: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} @@ -12076,19 +10215,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - util.promisify@1.1.3: - resolution: {integrity: sha512-GIEaZ6o86fj09Wtf0VfZ5XP7tmd4t3jM5aZCgmBi231D0DB1AEBa3Aa6MP48DMsAIi96WkpWLimIWVwOjbDMOw==} - engines: {node: '>= 0.8'} - utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - uuid@3.3.2: - resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). - hasBin: true - uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. @@ -12122,9 +10252,6 @@ packages: resolution: {integrity: sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==} engines: {node: '>=12'} - varint@5.0.2: - resolution: {integrity: sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -12159,111 +10286,16 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - web3-bzz@1.2.11: - resolution: {integrity: sha512-XGpWUEElGypBjeFyUhTkiPXFbDVD6Nr/S5jznE3t8cWUA0FxRf1n3n/NuIZeb0H9RkN2Ctd/jNma/k8XGa3YKg==} - engines: {node: '>=8.0.0'} - - web3-core-helpers@1.2.11: - resolution: {integrity: sha512-PEPoAoZd5ME7UfbnCZBdzIerpe74GEvlwT4AjOmHeCVZoIFk7EqvOZDejJHt+feJA6kMVTdd0xzRNN295UhC1A==} - engines: {node: '>=8.0.0'} - - web3-core-method@1.2.11: - resolution: {integrity: sha512-ff0q76Cde94HAxLDZ6DbdmKniYCQVtvuaYh+rtOUMB6kssa5FX0q3vPmixi7NPooFnbKmmZCM6NvXg4IreTPIw==} - engines: {node: '>=8.0.0'} - - web3-core-promievent@1.2.11: - resolution: {integrity: sha512-il4McoDa/Ox9Agh4kyfQ8Ak/9ABYpnF8poBLL33R/EnxLsJOGQG2nZhkJa3I067hocrPSjEdlPt/0bHXsln4qA==} - engines: {node: '>=8.0.0'} - - web3-core-requestmanager@1.2.11: - resolution: {integrity: sha512-oFhBtLfOiIbmfl6T6gYjjj9igOvtyxJ+fjS+byRxiwFJyJ5BQOz4/9/17gWR1Cq74paTlI7vDGxYfuvfE/mKvA==} - engines: {node: '>=8.0.0'} - - web3-core-subscriptions@1.2.11: - resolution: {integrity: sha512-qEF/OVqkCvQ7MPs1JylIZCZkin0aKK9lDxpAtQ1F8niEDGFqn7DT8E/vzbIa0GsOjL2fZjDhWJsaW+BSoAW1gg==} - engines: {node: '>=8.0.0'} - - web3-core@1.2.11: - resolution: {integrity: sha512-CN7MEYOY5ryo5iVleIWRE3a3cZqVaLlIbIzDPsvQRUfzYnvzZQRZBm9Mq+ttDi2STOOzc1MKylspz/o3yq/LjQ==} - engines: {node: '>=8.0.0'} - - web3-eth-abi@1.2.11: - resolution: {integrity: sha512-PkRYc0+MjuLSgg03QVWqWlQivJqRwKItKtEpRUaxUAeLE7i/uU39gmzm2keHGcQXo3POXAbOnMqkDvOep89Crg==} - engines: {node: '>=8.0.0'} - - web3-eth-accounts@1.2.11: - resolution: {integrity: sha512-6FwPqEpCfKIh3nSSGeo3uBm2iFSnFJDfwL3oS9pyegRBXNsGRVpgiW63yhNzL0796StsvjHWwQnQHsZNxWAkGw==} - engines: {node: '>=8.0.0'} - - web3-eth-contract@1.2.11: - resolution: {integrity: sha512-MzYuI/Rq2o6gn7vCGcnQgco63isPNK5lMAan2E51AJLknjSLnOxwNY3gM8BcKoy4Z+v5Dv00a03Xuk78JowFow==} - engines: {node: '>=8.0.0'} - - web3-eth-ens@1.2.11: - resolution: {integrity: sha512-dbW7dXP6HqT1EAPvnniZVnmw6TmQEKF6/1KgAxbo8iBBYrVTMDGFQUUnZ+C4VETGrwwaqtX4L9d/FrQhZ6SUiA==} - engines: {node: '>=8.0.0'} - - web3-eth-iban@1.2.11: - resolution: {integrity: sha512-ozuVlZ5jwFC2hJY4+fH9pIcuH1xP0HEFhtWsR69u9uDIANHLPQQtWYmdj7xQ3p2YT4bQLq/axKhZi7EZVetmxQ==} - engines: {node: '>=8.0.0'} - - web3-eth-personal@1.2.11: - resolution: {integrity: sha512-42IzUtKq9iHZ8K9VN0vAI50iSU9tOA1V7XU2BhF/tb7We2iKBVdkley2fg26TxlOcKNEHm7o6HRtiiFsVK4Ifw==} - engines: {node: '>=8.0.0'} - - web3-eth@1.2.11: - resolution: {integrity: sha512-REvxW1wJ58AgHPcXPJOL49d1K/dPmuw4LjPLBPStOVkQjzDTVmJEIsiLwn2YeuNDd4pfakBwT8L3bz1G1/wVsQ==} - engines: {node: '>=8.0.0'} - - web3-net@1.2.11: - resolution: {integrity: sha512-sjrSDj0pTfZouR5BSTItCuZ5K/oZPVdVciPQ6981PPPIwJJkCMeVjD7I4zO3qDPCnBjBSbWvVnLdwqUBPtHxyg==} - engines: {node: '>=8.0.0'} - - web3-provider-engine@14.2.1: - resolution: {integrity: sha512-iSv31h2qXkr9vrL6UZDm4leZMc32SjWJFGOp/D92JXfcEboCqraZyuExDkpxKw8ziTufXieNM7LSXNHzszYdJw==} - deprecated: 'This package has been deprecated, see the README for details: https://github.com/MetaMask/web3-provider-engine' - - web3-providers-http@1.2.11: - resolution: {integrity: sha512-psh4hYGb1+ijWywfwpB2cvvOIMISlR44F/rJtYkRmQ5jMvG4FOCPlQJPiHQZo+2cc3HbktvvSJzIhkWQJdmvrA==} - engines: {node: '>=8.0.0'} - - web3-providers-ipc@1.2.11: - resolution: {integrity: sha512-yhc7Y/k8hBV/KlELxynWjJDzmgDEDjIjBzXK+e0rHBsYEhdCNdIH5Psa456c+l0qTEU2YzycF8VAjYpWfPnBpQ==} - engines: {node: '>=8.0.0'} - - web3-providers-ws@1.2.11: - resolution: {integrity: sha512-ZxnjIY1Er8Ty+cE4migzr43zA/+72AF1myzsLaU5eVgdsfV7Jqx7Dix1hbevNZDKFlSoEyq/3j/jYalh3So1Zg==} - engines: {node: '>=8.0.0'} - - web3-shh@1.2.11: - resolution: {integrity: sha512-B3OrO3oG1L+bv3E1sTwCx66injW1A8hhwpknDUbV+sw3fehFazA06z9SGXUefuFI1kVs4q2vRi0n4oCcI4dZDg==} - engines: {node: '>=8.0.0'} - web3-utils@1.10.4: resolution: {integrity: sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==} engines: {node: '>=8.0.0'} - web3-utils@1.2.11: - resolution: {integrity: sha512-3Tq09izhD+ThqHEaWYX4VOT7dNPdZiO+c/1QMA0s5X2lDFKK/xHJb7cyTRRVzN2LvlHbR7baS1tmQhSua51TcQ==} - engines: {node: '>=8.0.0'} - - web3@1.2.11: - resolution: {integrity: sha512-mjQ8HeU41G6hgOYm1pmeH0mRAeNKJGnJEUzDMoerkpw7QUQT4exVREgF1MYPvL/z6vAshOXei25LE/t/Bxl8yQ==} - engines: {node: '>=8.0.0'} - webcrypto-core@1.8.1: resolution: {integrity: sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==} webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - websocket@1.0.32: - resolution: {integrity: sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q==} - engines: {node: '>=4.0.0'} - - whatwg-fetch@2.0.4: - resolution: {integrity: sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==} - whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} @@ -12375,28 +10407,6 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - ws@3.3.3: - resolution: {integrity: sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@5.2.4: - resolution: {integrity: sha512-fFCejsuC8f9kOSu9FYaOw8CdO68O3h5v0lg4p74o8JqWpwTf9tniOD+nOB78aWoVSS6WptVUmDrp/KPsMVBWFQ==} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@6.2.3: resolution: {integrity: sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==} peerDependencies: @@ -12480,22 +10490,6 @@ packages: utf-8-validate: optional: true - xhr-request-promise@0.1.3: - resolution: {integrity: sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==} - - xhr-request@1.1.0: - resolution: {integrity: sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==} - - xhr2-cookies@1.1.0: - resolution: {integrity: sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==} - - xhr@2.6.0: - resolution: {integrity: sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==} - - xtend@2.1.2: - resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==} - engines: {node: '>=0.4'} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -12510,14 +10504,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yaeti@0.0.6: - resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} - engines: {node: '>=0.10.32'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -12552,16 +10538,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - yargs-parser@8.1.0: - resolution: {integrity: sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==} - yargs-unparser@2.0.0: resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} engines: {node: '>=10'} - yargs@10.1.2: - resolution: {integrity: sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==} - yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} @@ -13060,7 +11040,7 @@ snapshots: '@babel/types': 7.28.4 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -13419,7 +11399,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/types': 7.28.4 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -13446,7 +11426,7 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.7.2 + semver: 7.7.3 '@changesets/assemble-release-plan@6.0.9': dependencies: @@ -13455,7 +11435,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 - semver: 7.7.2 + semver: 7.7.3 '@changesets/changelog-git@0.2.1': dependencies: @@ -13513,7 +11493,7 @@ snapshots: '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 picocolors: 1.1.1 - semver: 7.7.2 + semver: 7.7.3 '@changesets/get-release-plan@4.0.13': dependencies: @@ -13623,7 +11603,7 @@ snapshots: '@commitlint/is-ignored@20.0.0': dependencies: '@commitlint/types': 20.0.0 - semver: 7.7.2 + semver: 7.7.3 '@commitlint/lint@20.0.0': dependencies: @@ -13942,7 +11922,7 @@ snapshots: '@eslint/config-array@0.21.1': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -13958,12 +11938,12 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -13978,20 +11958,10 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@ethereum-waffle/chai@3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)': - dependencies: - '@ethereum-waffle/provider': 3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - '@ethereum-waffle/chai@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@ethereum-waffle/provider': 4.0.5(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) json-bigint: 1.0.0 transitivePeerDependencies: @@ -13999,26 +11969,6 @@ snapshots: - '@ensdomains/resolver' - supports-color - '@ethereum-waffle/compiler@3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10)': - dependencies: - '@resolver-engine/imports': 0.3.3 - '@resolver-engine/imports-fs': 0.3.3 - '@typechain/ethers-v5': 2.0.0(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@3.0.0(typescript@5.9.3)) - '@types/mkdirp': 0.5.2 - '@types/node-fetch': 2.6.13 - ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - mkdirp: 0.5.6 - node-fetch: 2.7.0(encoding@0.1.13) - solc: 0.6.12 - ts-generator: 0.1.1 - typechain: 3.0.0(typescript@5.9.3) - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - '@ethereum-waffle/compiler@4.0.3(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(solc@0.8.15)(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@resolver-engine/imports': 0.3.3 @@ -14038,51 +11988,21 @@ snapshots: - supports-color - typescript - '@ethereum-waffle/ens@3.4.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@ethereum-waffle/ens@4.0.3(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@ensdomains/ens': 0.4.5 '@ensdomains/resolver': 0.2.4 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - '@ethereum-waffle/ens@4.0.3(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@ethereum-waffle/mock-contract@4.0.4(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@ensdomains/ens': 0.4.5 - '@ensdomains/resolver': 0.2.4 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@ethereum-waffle/mock-contract@3.4.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)': - dependencies: - '@ethersproject/abi': 5.8.0 - ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@ethereum-waffle/mock-contract@4.0.4(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - - '@ethereum-waffle/provider@3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)': - dependencies: - '@ethereum-waffle/ens': 3.4.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) - ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - ganache-core: 2.13.2(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - patch-package: 6.5.1 - postinstall-postinstall: 2.1.0 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - - '@ethereum-waffle/provider@4.0.5(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@ethereum-waffle/provider@4.0.5(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@ethereum-waffle/ens': 4.0.3(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@ganache/ethereum-options': 0.1.4 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) ganache: 7.4.3 transitivePeerDependencies: @@ -14102,7 +12022,7 @@ snapshots: '@ethereumjs/block': 3.6.3 '@ethereumjs/common': 2.6.5 '@ethereumjs/ethash': 1.1.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethereumjs-util: 7.1.5 level-mem: 5.0.1 lru-cache: 5.1.1 @@ -14170,19 +12090,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@ethersproject/abi@5.0.0-beta.153': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - optional: true - '@ethersproject/abi@5.6.0': dependencies: '@ethersproject/address': 5.8.0 @@ -15255,7 +13162,7 @@ snapshots: '@graphprotocol/contracts': 7.2.1 '@nomicfoundation/hardhat-network-helpers': 1.1.0(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) hardhat-secure-accounts: 0.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -15668,7 +13575,7 @@ snapshots: '@graphql-tools/utils': 10.9.1(graphql@16.11.0) dset: 3.1.4 graphql: 16.11.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 lodash.get: 4.4.2 lodash.topath: 4.5.2 tiny-lru: 8.0.2 @@ -15683,7 +13590,7 @@ snapshots: '@graphql-tools/utils': 9.2.1(graphql@16.11.0) dset: 3.1.4 graphql: 16.11.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 lodash.get: 4.4.2 lodash.topath: 4.5.2 tiny-lru: 8.0.2 @@ -16186,14 +14093,6 @@ snapshots: '@ledgerhq/logs@5.50.0': {} - '@ljharb/resumer@0.0.1': - dependencies: - '@ljharb/through': 2.3.14 - - '@ljharb/through@2.3.14': - dependencies: - call-bind: 1.0.8 - '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.28.4 @@ -16355,7 +14254,7 @@ snapshots: '@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) lodash.isequal: 4.5.0 @@ -16364,7 +14263,7 @@ snapshots: '@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) lodash.isequal: 4.5.0 @@ -16375,7 +14274,7 @@ snapshots: dependencies: '@nomicfoundation/hardhat-errors': 3.0.6 '@nomicfoundation/hardhat-utils': 3.0.6 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethereum-cryptography: 2.2.1 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 3.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -16403,7 +14302,7 @@ snapshots: '@nomicfoundation/ignition-core': 0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@nomicfoundation/ignition-ui': 0.15.12 chalk: 4.1.2 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 10.1.0 hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) json5: 2.2.3 @@ -16421,7 +14320,7 @@ snapshots: '@nomicfoundation/hardhat-utils': 3.0.6 '@nomicfoundation/hardhat-zod-utils': 3.0.1(zod@3.25.76) chalk: 5.6.2 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) hardhat: 3.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) zod: 3.25.76 transitivePeerDependencies: @@ -16504,7 +14403,7 @@ snapshots: '@nomicfoundation/hardhat-utils@3.0.6': dependencies: '@streamparser/json-node': 0.0.22 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) env-paths: 2.2.1 ethereum-cryptography: 2.2.1 fast-equals: 5.4.0 @@ -16521,7 +14420,7 @@ snapshots: '@ethersproject/abi': 5.8.0 '@ethersproject/address': 5.8.0 cbor: 8.1.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) lodash.clonedeep: 4.5.0 picocolors: 1.1.1 @@ -16536,7 +14435,7 @@ snapshots: '@ethersproject/abi': 5.8.0 '@ethersproject/address': 5.8.0 cbor: 8.1.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) lodash.clonedeep: 4.5.0 picocolors: 1.1.1 @@ -16554,7 +14453,7 @@ snapshots: '@nomicfoundation/hardhat-zod-utils': 3.0.1(zod@3.25.76) cbor2: 1.12.0 chalk: 5.6.2 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) hardhat: 3.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) semver: 7.7.2 zod: 3.25.76 @@ -16574,7 +14473,7 @@ snapshots: '@ethersproject/address': 5.6.1 '@nomicfoundation/solidity-analyzer': 0.1.2 cbor: 9.0.2 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) fs-extra: 10.1.0 immer: 10.0.2 @@ -16631,13 +14530,18 @@ snapshots: ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + '@nomiclabs/hardhat-ethers@2.2.3(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': + dependencies: + ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + '@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/address': 5.8.0 cbor: 8.1.0 chalk: 2.4.2 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 7.0.1 hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) lodash: 4.17.21 @@ -16647,21 +14551,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@nomiclabs/hardhat-waffle@2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': - dependencies: - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - '@types/sinon-chai': 3.2.12 - ethereum-waffle: 3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) - ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) - - '@nomiclabs/hardhat-waffle@2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': + '@nomiclabs/hardhat-waffle@2.0.6(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: + '@ethereum-waffle/chai': 4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@ethereum-waffle/provider': 4.0.5(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@types/sinon-chai': 3.2.12 ethereum-waffle: 4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - '@ensdomains/ens' + - '@ensdomains/resolver' + - supports-color '@npmcli/agent@2.2.2': dependencies: @@ -16687,8 +14589,6 @@ snapshots: '@openzeppelin/contracts@3.4.2': {} - '@openzeppelin/contracts@4.9.6': {} - '@openzeppelin/contracts@5.4.0': {} '@openzeppelin/defender-base-client@1.54.6(debug@4.4.3)(encoding@0.1.13)': @@ -16756,7 +14656,7 @@ snapshots: '@openzeppelin/platform-deploy-client': 0.8.0(debug@4.4.3)(encoding@0.1.13) '@openzeppelin/upgrades-core': 1.44.1 chalk: 4.1.2 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) proper-lockfile: 4.1.2 @@ -16782,7 +14682,7 @@ snapshots: cbor: 10.0.11 chalk: 4.1.2 compare-versions: 6.1.1 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) ethereumjs-util: 7.1.5 minimatch: 9.0.5 minimist: 1.2.8 @@ -16839,7 +14739,7 @@ snapshots: '@react-native/community-cli-plugin@0.81.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@react-native/dev-middleware': 0.81.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) invariant: 2.2.4 metro: 0.83.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) metro-config: 0.83.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -16859,7 +14759,7 @@ snapshots: chrome-launcher: 0.15.2 chromium-edge-launcher: 0.2.0 connect: 3.7.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) invariant: 2.2.4 nullthrows: 1.1.1 open: 7.4.2 @@ -16887,13 +14787,6 @@ snapshots: '@repeaterjs/repeater@3.0.6': {} - '@resolver-engine/core@0.2.1': - dependencies: - debug: 3.2.7 - request: 2.88.2 - transitivePeerDependencies: - - supports-color - '@resolver-engine/core@0.3.3': dependencies: debug: 3.2.7 @@ -16902,13 +14795,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@resolver-engine/fs@0.2.1': - dependencies: - '@resolver-engine/core': 0.2.1 - debug: 3.2.7 - transitivePeerDependencies: - - supports-color - '@resolver-engine/fs@0.3.3': dependencies: '@resolver-engine/core': 0.3.3 @@ -16916,14 +14802,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@resolver-engine/imports-fs@0.2.2': - dependencies: - '@resolver-engine/fs': 0.2.1 - '@resolver-engine/imports': 0.2.2 - debug: 3.2.7 - transitivePeerDependencies: - - supports-color - '@resolver-engine/imports-fs@0.3.3': dependencies: '@resolver-engine/fs': 0.3.3 @@ -16932,14 +14810,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@resolver-engine/imports@0.2.2': - dependencies: - '@resolver-engine/core': 0.2.1 - debug: 3.2.7 - hosted-git-info: 2.8.9 - transitivePeerDependencies: - - supports-color - '@resolver-engine/imports@0.3.3': dependencies: '@resolver-engine/core': 0.3.3 @@ -16990,10 +14860,10 @@ snapshots: - utf-8-validate - zod - '@rocketh/doc@0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@rocketh/doc@0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@rocketh/core': 0.17.8(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@rocketh/node': 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@rocketh/node': 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@types/fs-extra': 11.0.4 abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) commander: 14.0.2 @@ -17006,24 +14876,24 @@ snapshots: - utf-8-validate - zod - '@rocketh/export@0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@rocketh/export@0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@rocketh/core': 0.17.8(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@rocketh/node': 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@rocketh/node': 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@types/fs-extra': 11.0.4 abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) chalk: 5.6.2 commander: 14.0.2 eip-1193: 0.6.5 fs-extra: 11.3.3 - rocketh: 0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + rocketh: 0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod - '@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@rocketh/core': 0.17.8(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@types/prompts': 2.4.9 @@ -17034,7 +14904,7 @@ snapshots: named-logs: 0.4.1 named-logs-console: 0.5.1 prompts: 2.4.2 - rocketh: 0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + rocketh: 0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) tsx: 4.21.0 viem: 2.44.4(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: @@ -17071,10 +14941,10 @@ snapshots: - utf-8-validate - zod - '@rocketh/verifier@0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@rocketh/verifier@0.17.16(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@rocketh/core': 0.17.8(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@rocketh/node': 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@rocketh/node': 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@types/fs-extra': 11.0.4 '@types/qs': 6.14.0 chalk: 5.6.2 @@ -17180,12 +15050,6 @@ snapshots: '@sinclair/typebox@0.27.8': {} - '@sindresorhus/is@0.14.0': - optional: true - - '@sindresorhus/is@4.6.0': - optional: true - '@sindresorhus/is@5.6.0': {} '@sinonjs/commons@3.0.1': @@ -17516,16 +15380,6 @@ snapshots: '@streamparser/json@0.0.22': {} - '@szmarczak/http-timer@1.1.2': - dependencies: - defer-to-connect: 1.1.3 - optional: true - - '@szmarczak/http-timer@4.0.6': - dependencies: - defer-to-connect: 2.0.1 - optional: true - '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 @@ -17536,7 +15390,7 @@ snapshots: cli-table3: 0.6.5 commander: 9.5.0 dotenv: 16.6.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 open: 8.4.2 prompts: 2.4.2 tslog: 4.9.3 @@ -17555,7 +15409,7 @@ snapshots: hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) hardhat-deploy: 0.11.45(bufferutil@4.0.9)(utf-8-validate@5.0.10) npm-registry-fetch: 17.1.0 - semver: 7.7.2 + semver: 7.7.3 ts-node: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) tslog: 4.9.3 typescript: 5.9.3 @@ -17571,7 +15425,7 @@ snapshots: '@tenderly/hardhat-tenderly@1.11.0(@types/node@20.19.14)(bufferutil@4.0.9)(encoding@0.1.13)(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: '@ethersproject/bignumber': 5.8.0 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@nomiclabs/hardhat-etherscan': 3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@openzeppelin/hardhat-upgrades': 1.28.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@openzeppelin/upgrades-core': 1.44.1 @@ -17623,11 +15477,6 @@ snapshots: typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) typescript: 5.9.3 - '@typechain/ethers-v5@2.0.0(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@3.0.0(typescript@5.9.3))': - dependencies: - ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - typechain: 3.0.0(typescript@5.9.3) - '@typechain/ethers-v6@0.5.1(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3)': dependencies: ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -17685,14 +15534,6 @@ snapshots: dependencies: '@types/node': 20.19.14 - '@types/cacheable-request@6.0.3': - dependencies: - '@types/http-cache-semantics': 4.0.4 - '@types/keyv': 3.1.4 - '@types/node': 20.19.14 - '@types/responselike': 1.0.3 - optional: true - '@types/chai-as-promised@7.1.8': dependencies: '@types/chai': 4.3.20 @@ -17766,11 +15607,6 @@ snapshots: '@types/katex@0.16.7': {} - '@types/keyv@3.1.4': - dependencies: - '@types/node': 20.19.14 - optional: true - '@types/level-errors@3.0.2': {} '@types/levelup@4.3.3': @@ -17783,7 +15619,7 @@ snapshots: '@types/minimatch@6.0.0': dependencies: - minimatch: 10.0.3 + minimatch: 10.1.1 '@types/mkdirp@0.5.2': dependencies: @@ -17817,15 +15653,6 @@ snapshots: '@types/qs@6.14.0': {} - '@types/resolve@0.0.8': - dependencies: - '@types/node': 20.19.14 - - '@types/responselike@1.0.3': - dependencies: - '@types/node': 20.19.14 - optional: true - '@types/secp256k1@4.0.6': dependencies: '@types/node': 20.19.14 @@ -17883,7 +15710,7 @@ snapshots: '@typescript-eslint/types': 8.53.1 '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.53.1 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) eslint: 9.39.2(jiti@2.5.1) typescript: 5.9.3 transitivePeerDependencies: @@ -17893,7 +15720,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) '@typescript-eslint/types': 8.53.1 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -17912,7 +15739,7 @@ snapshots: '@typescript-eslint/types': 8.53.1 '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) '@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.5.1))(typescript@5.9.3) - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) eslint: 9.39.2(jiti@2.5.1) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 @@ -17927,7 +15754,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) '@typescript-eslint/types': 8.53.1 '@typescript-eslint/visitor-keys': 8.53.1 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.15 @@ -18064,8 +15891,6 @@ snapshots: '@whatwg-node/fetch': 0.8.8 tslib: 2.8.1 - '@yarnpkg/lockfile@1.1.0': {} - JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -18087,22 +15912,6 @@ snapshots: dependencies: event-target-shim: 5.0.1 - abstract-leveldown@2.6.3: - dependencies: - xtend: 4.0.2 - - abstract-leveldown@2.7.2: - dependencies: - xtend: 4.0.2 - - abstract-leveldown@3.0.0: - dependencies: - xtend: 4.0.2 - - abstract-leveldown@5.0.0: - dependencies: - xtend: 4.0.2 - abstract-leveldown@6.2.3: dependencies: buffer: 5.7.1 @@ -18138,14 +15947,11 @@ snapshots: aes-js@3.0.0: {} - aes-js@3.1.2: - optional: true - aes-js@4.0.0-beta.5: {} agent-base@6.0.2: dependencies: - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -18156,21 +15962,14 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ajv-errors@1.0.1(ajv@6.12.6): + ajv-errors@3.0.0(ajv@8.20.0): dependencies: - ajv: 6.12.6 + ajv: 8.20.0 ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 - ajv@5.5.2: - dependencies: - co: 4.6.0 - fast-deep-equal: 1.1.0 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.3.1 - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -18185,6 +15984,13 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + amazon-cognito-identity-js@6.3.15(encoding@0.1.13): dependencies: '@aws-crypto/sha256-js': 1.2.2 @@ -18224,8 +16030,6 @@ snapshots: ansi-regex@6.2.2: {} - ansi-styles@2.2.1: {} - ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -18240,11 +16044,6 @@ snapshots: antlr4ts@0.5.0-alpha.4: {} - anymatch@1.3.2: - dependencies: - micromatch: 2.3.11 - normalize-path: 2.1.1 - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -18277,24 +16076,6 @@ snapshots: argparse@2.0.1: {} - arr-diff@2.0.0: - dependencies: - arr-flatten: 1.1.0 - - arr-diff@4.0.0: {} - - arr-flatten@1.1.0: {} - - arr-union@3.1.0: {} - - array-back@1.0.4: - dependencies: - typical: 2.6.1 - - array-back@2.0.0: - dependencies: - typical: 2.6.1 - array-back@3.1.0: {} array-back@4.0.2: {} @@ -18323,10 +16104,6 @@ snapshots: array-uniq@1.0.3: {} - array-unique@0.2.1: {} - - array-unique@0.3.2: {} - array.prototype.findlastindex@1.2.6: dependencies: call-bind: 1.0.8 @@ -18351,17 +16128,6 @@ snapshots: es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 - array.prototype.reduce@1.0.8: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-array-method-boxes-properly: 1.0.0 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - is-string: 1.1.1 - arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 @@ -18374,13 +16140,6 @@ snapshots: asap@2.0.6: {} - asn1.js@4.10.1: - dependencies: - bn.js: 4.12.2 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - optional: true - asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -18397,14 +16156,10 @@ snapshots: assertion-error@2.0.1: {} - assign-symbols@1.0.0: {} - ast-parents@0.0.1: {} astral-regex@2.0.0: {} - async-each@1.0.6: {} - async-eventemitter@0.2.4: dependencies: async: 2.6.4 @@ -18423,10 +16178,6 @@ snapshots: async@1.5.2: {} - async@2.6.2: - dependencies: - lodash: 4.17.21 - async@2.6.4: dependencies: lodash: 4.17.21 @@ -18437,8 +16188,6 @@ snapshots: at-least-node@1.0.0: {} - atob@2.1.2: {} - atomic-sleep@1.0.0: {} auto-bind@4.0.0: {} @@ -18472,360 +16221,43 @@ snapshots: transitivePeerDependencies: - debug - babel-code-frame@6.26.0: - dependencies: - chalk: 1.1.3 - esutils: 2.0.3 - js-tokens: 3.0.2 - - babel-core@6.26.3: - dependencies: - babel-code-frame: 6.26.0 - babel-generator: 6.26.1 - babel-helpers: 6.24.1 - babel-messages: 6.23.0 - babel-register: 6.26.0 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - babylon: 6.18.0 - convert-source-map: 1.9.0 - debug: 2.6.9 - json5: 0.5.1 - lodash: 4.17.21 - minimatch: 3.1.2 - path-is-absolute: 1.0.1 - private: 0.1.8 - slash: 1.0.0 - source-map: 0.5.7 - transitivePeerDependencies: - - supports-color - - babel-generator@6.26.1: - dependencies: - babel-messages: 6.23.0 - babel-runtime: 6.26.0 - babel-types: 6.26.0 - detect-indent: 4.0.0 - jsesc: 1.3.0 - lodash: 4.17.21 - source-map: 0.5.7 - trim-right: 1.0.1 - - babel-helper-builder-binary-assignment-operator-visitor@6.24.1: - dependencies: - babel-helper-explode-assignable-expression: 6.24.1 - babel-runtime: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-helper-call-delegate@6.24.1: - dependencies: - babel-helper-hoist-variables: 6.24.1 - babel-runtime: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-helper-define-map@6.26.0: - dependencies: - babel-helper-function-name: 6.24.1 - babel-runtime: 6.26.0 - babel-types: 6.26.0 - lodash: 4.17.21 - transitivePeerDependencies: - - supports-color - - babel-helper-explode-assignable-expression@6.24.1: + babel-jest@29.7.0(@babel/core@7.28.4): dependencies: - babel-runtime: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 + '@babel/core': 7.28.4 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.28.4) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-helper-function-name@6.24.1: + babel-plugin-istanbul@6.1.1: dependencies: - babel-helper-get-function-arity: 6.24.1 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 transitivePeerDependencies: - supports-color - babel-helper-get-function-arity@6.24.1: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - - babel-helper-hoist-variables@6.24.1: + babel-plugin-jest-hoist@29.6.3: dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 - babel-helper-optimise-call-expression@6.24.1: + babel-plugin-syntax-hermes-parser@0.29.1: dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 + hermes-parser: 0.29.1 - babel-helper-regex@6.26.0: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - lodash: 4.17.21 + babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: {} - babel-helper-remap-async-to-generator@6.24.1: - dependencies: - babel-helper-function-name: 6.24.1 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-helper-replace-supers@6.24.1: - dependencies: - babel-helper-optimise-call-expression: 6.24.1 - babel-messages: 6.23.0 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-helpers@6.24.1: - dependencies: - babel-runtime: 6.26.0 - babel-template: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-jest@29.7.0(@babel/core@7.28.4): - dependencies: - '@babel/core': 7.28.4 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.28.4) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - - babel-messages@6.23.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-check-es2015-constants@6.22.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-istanbul@6.1.1: - dependencies: - '@babel/helper-plugin-utils': 7.27.1 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-jest-hoist@29.6.3: - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.28.0 - - babel-plugin-syntax-async-functions@6.13.0: {} - - babel-plugin-syntax-exponentiation-operator@6.13.0: {} - - babel-plugin-syntax-hermes-parser@0.29.1: - dependencies: - hermes-parser: 0.29.1 - - babel-plugin-syntax-trailing-function-commas@6.22.0: {} - - babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: {} - - babel-plugin-transform-async-to-generator@6.24.1: - dependencies: - babel-helper-remap-async-to-generator: 6.24.1 - babel-plugin-syntax-async-functions: 6.13.0 - babel-runtime: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-arrow-functions@6.22.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-transform-es2015-block-scoped-functions@6.22.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-transform-es2015-block-scoping@6.26.0: - dependencies: - babel-runtime: 6.26.0 - babel-template: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - lodash: 4.17.21 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-classes@6.24.1: - dependencies: - babel-helper-define-map: 6.26.0 - babel-helper-function-name: 6.24.1 - babel-helper-optimise-call-expression: 6.24.1 - babel-helper-replace-supers: 6.24.1 - babel-messages: 6.23.0 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-computed-properties@6.24.1: - dependencies: - babel-runtime: 6.26.0 - babel-template: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-destructuring@6.23.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-transform-es2015-duplicate-keys@6.24.1: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - - babel-plugin-transform-es2015-for-of@6.23.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-transform-es2015-function-name@6.24.1: - dependencies: - babel-helper-function-name: 6.24.1 - babel-runtime: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-literals@6.22.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-transform-es2015-modules-amd@6.24.1: - dependencies: - babel-plugin-transform-es2015-modules-commonjs: 6.26.2 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-modules-commonjs@6.26.2: - dependencies: - babel-plugin-transform-strict-mode: 6.24.1 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-modules-systemjs@6.24.1: - dependencies: - babel-helper-hoist-variables: 6.24.1 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-modules-umd@6.24.1: - dependencies: - babel-plugin-transform-es2015-modules-amd: 6.24.1 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-object-super@6.24.1: - dependencies: - babel-helper-replace-supers: 6.24.1 - babel-runtime: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-parameters@6.24.1: - dependencies: - babel-helper-call-delegate: 6.24.1 - babel-helper-get-function-arity: 6.24.1 - babel-runtime: 6.26.0 - babel-template: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-es2015-shorthand-properties@6.24.1: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - - babel-plugin-transform-es2015-spread@6.22.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-transform-es2015-sticky-regex@6.24.1: - dependencies: - babel-helper-regex: 6.26.0 - babel-runtime: 6.26.0 - babel-types: 6.26.0 - - babel-plugin-transform-es2015-template-literals@6.22.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-transform-es2015-typeof-symbol@6.23.0: - dependencies: - babel-runtime: 6.26.0 - - babel-plugin-transform-es2015-unicode-regex@6.24.1: - dependencies: - babel-helper-regex: 6.26.0 - babel-runtime: 6.26.0 - regexpu-core: 2.0.0 - - babel-plugin-transform-exponentiation-operator@6.24.1: - dependencies: - babel-helper-builder-binary-assignment-operator-visitor: 6.24.1 - babel-plugin-syntax-exponentiation-operator: 6.13.0 - babel-runtime: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-regenerator@6.26.0: - dependencies: - regenerator-transform: 0.10.1 - - babel-plugin-transform-strict-mode@6.24.1: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.4): + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.4): dependencies: '@babel/core': 7.28.4 '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.4) @@ -18844,41 +16276,6 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.4) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.4) - babel-preset-env@1.7.0: - dependencies: - babel-plugin-check-es2015-constants: 6.22.0 - babel-plugin-syntax-trailing-function-commas: 6.22.0 - babel-plugin-transform-async-to-generator: 6.24.1 - babel-plugin-transform-es2015-arrow-functions: 6.22.0 - babel-plugin-transform-es2015-block-scoped-functions: 6.22.0 - babel-plugin-transform-es2015-block-scoping: 6.26.0 - babel-plugin-transform-es2015-classes: 6.24.1 - babel-plugin-transform-es2015-computed-properties: 6.24.1 - babel-plugin-transform-es2015-destructuring: 6.23.0 - babel-plugin-transform-es2015-duplicate-keys: 6.24.1 - babel-plugin-transform-es2015-for-of: 6.23.0 - babel-plugin-transform-es2015-function-name: 6.24.1 - babel-plugin-transform-es2015-literals: 6.22.0 - babel-plugin-transform-es2015-modules-amd: 6.24.1 - babel-plugin-transform-es2015-modules-commonjs: 6.26.2 - babel-plugin-transform-es2015-modules-systemjs: 6.24.1 - babel-plugin-transform-es2015-modules-umd: 6.24.1 - babel-plugin-transform-es2015-object-super: 6.24.1 - babel-plugin-transform-es2015-parameters: 6.24.1 - babel-plugin-transform-es2015-shorthand-properties: 6.24.1 - babel-plugin-transform-es2015-spread: 6.22.0 - babel-plugin-transform-es2015-sticky-regex: 6.24.1 - babel-plugin-transform-es2015-template-literals: 6.22.0 - babel-plugin-transform-es2015-typeof-symbol: 6.23.0 - babel-plugin-transform-es2015-unicode-regex: 6.24.1 - babel-plugin-transform-exponentiation-operator: 6.24.1 - babel-plugin-transform-regenerator: 6.26.0 - browserslist: 3.2.8 - invariant: 2.2.4 - semver: 5.7.2 - transitivePeerDependencies: - - supports-color - babel-preset-fbjs@3.4.0(@babel/core@7.28.4): dependencies: '@babel/core': 7.28.4 @@ -18918,69 +16315,10 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.4) - babel-register@6.26.0: - dependencies: - babel-core: 6.26.3 - babel-runtime: 6.26.0 - core-js: 2.6.12 - home-or-tmp: 2.0.0 - lodash: 4.17.21 - mkdirp: 0.5.6 - source-map-support: 0.4.18 - transitivePeerDependencies: - - supports-color - - babel-runtime@6.26.0: - dependencies: - core-js: 2.6.12 - regenerator-runtime: 0.11.1 - - babel-template@6.26.0: - dependencies: - babel-runtime: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - babylon: 6.18.0 - lodash: 4.17.21 - transitivePeerDependencies: - - supports-color - - babel-traverse@6.26.0: - dependencies: - babel-code-frame: 6.26.0 - babel-messages: 6.23.0 - babel-runtime: 6.26.0 - babel-types: 6.26.0 - babylon: 6.18.0 - debug: 2.6.9 - globals: 9.18.0 - invariant: 2.2.4 - lodash: 4.17.21 - transitivePeerDependencies: - - supports-color - - babel-types@6.26.0: - dependencies: - babel-runtime: 6.26.0 - esutils: 2.0.3 - lodash: 4.17.21 - to-fast-properties: 1.0.3 - - babelify@7.3.0: - dependencies: - babel-core: 6.26.3 - object-assign: 4.1.1 - transitivePeerDependencies: - - supports-color - - babylon@6.18.0: {} - - backoff@2.5.0: - dependencies: - precond: 0.2.3 - balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base-64@0.1.0: {} base-x@3.0.11: @@ -18991,16 +16329,6 @@ snapshots: base64-js@1.5.1: {} - base@0.11.2: - dependencies: - cache-base: 1.0.1 - class-utils: 0.3.6 - component-emitter: 1.3.1 - define-property: 1.0.0 - isobject: 3.0.1 - mixin-deep: 1.3.2 - pascalcase: 0.1.1 - baseline-browser-mapping@2.8.4: {} basic-auth@2.0.1: @@ -19013,11 +16341,11 @@ snapshots: bech32@1.1.4: {} - better-ajv-errors@2.0.2(ajv@6.12.6): + better-ajv-errors@2.0.2(ajv@8.20.0): dependencies: '@babel/code-frame': 7.27.1 '@humanwhocodes/momoa': 2.0.4 - ajv: 6.12.6 + ajv: 8.20.0 chalk: 4.1.2 jsonpointer: 5.0.1 leven: 3.1.0 @@ -19028,8 +16356,6 @@ snapshots: bignumber.js@9.3.1: {} - binary-extensions@1.13.1: {} - binary-extensions@2.3.0: {} bindings@1.5.0: @@ -19039,14 +16365,6 @@ snapshots: bintrees@1.0.2: {} - bip39@2.5.0: - dependencies: - create-hash: 1.2.0 - pbkdf2: 3.1.3 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - unorm: 1.6.0 - bip39@3.0.4: dependencies: '@types/node': 20.19.14 @@ -19135,24 +16453,6 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@1.20.3: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - optional: true - bowser@2.12.1: {} boxen@5.1.2: @@ -19175,26 +16475,9 @@ snapshots: dependencies: balanced-match: 1.0.2 - braces@1.8.5: - dependencies: - expand-range: 1.8.2 - preserve: 0.2.0 - repeat-element: 1.1.4 - - braces@2.3.2: + brace-expansion@5.0.5: dependencies: - arr-flatten: 1.1.0 - array-unique: 0.3.2 - extend-shallow: 2.0.1 - fill-range: 4.0.0 - isobject: 3.0.1 - repeat-element: 1.1.4 - snapdragon: 0.8.2 - snapdragon-node: 2.1.1 - split-string: 3.1.0 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color + balanced-match: 4.0.4 braces@3.0.3: dependencies: @@ -19202,8 +16485,6 @@ snapshots: brorand@1.1.0: {} - browser-stdout@1.3.0: {} - browser-stdout@1.3.1: {} browserify-aes@1.2.0: @@ -19215,47 +16496,6 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 - browserify-cipher@1.0.1: - dependencies: - browserify-aes: 1.2.0 - browserify-des: 1.0.2 - evp_bytestokey: 1.0.3 - optional: true - - browserify-des@1.0.2: - dependencies: - cipher-base: 1.0.6 - des.js: 1.1.0 - inherits: 2.0.4 - safe-buffer: 5.2.1 - optional: true - - browserify-rsa@4.1.1: - dependencies: - bn.js: 5.2.2 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - optional: true - - browserify-sign@4.2.3: - dependencies: - bn.js: 5.2.2 - browserify-rsa: 4.1.1 - create-hash: 1.2.0 - create-hmac: 1.1.7 - elliptic: 6.6.1 - hash-base: 3.0.5 - inherits: 2.0.4 - parse-asn1: 5.1.7 - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - optional: true - - browserslist@3.2.8: - dependencies: - caniuse-lite: 1.0.30001741 - electron-to-chromium: 1.5.218 - browserslist@4.26.0: dependencies: baseline-browser-mapping: 2.8.4 @@ -19284,9 +16524,6 @@ snapshots: buffer-from@1.1.2: {} - buffer-to-arraybuffer@0.0.5: - optional: true - buffer-writer@2.0.0: {} buffer-xor@1.0.3: {} @@ -19314,6 +16551,7 @@ snapshots: bufferutil@4.0.9: dependencies: node-gyp-build: 4.8.4 + optional: true bundle-require@5.1.0(esbuild@0.25.9): dependencies: @@ -19328,15 +16566,6 @@ snapshots: bytes@3.1.2: {} - bytewise-core@1.2.3: - dependencies: - typewise-core: 1.2.0 - - bytewise@1.1.0: - dependencies: - bytewise-core: 1.2.3 - typewise: 1.0.3 - cac@6.7.14: {} cacache@18.0.4: @@ -19354,21 +16583,6 @@ snapshots: tar: 6.2.1 unique-filename: 3.0.0 - cache-base@1.0.1: - dependencies: - collection-visit: 1.0.0 - component-emitter: 1.3.1 - get-value: 2.0.6 - has-value: 1.0.0 - isobject: 3.0.1 - set-value: 2.0.1 - to-object-path: 0.3.0 - union-value: 1.0.1 - unset-value: 1.0.0 - - cacheable-lookup@5.0.4: - optional: true - cacheable-lookup@7.0.0: {} cacheable-request@10.2.14: @@ -19381,33 +16595,6 @@ snapshots: normalize-url: 8.1.0 responselike: 3.0.0 - cacheable-request@6.1.0: - dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 - http-cache-semantics: 4.2.0 - keyv: 3.1.0 - lowercase-keys: 2.0.0 - normalize-url: 4.5.1 - responselike: 1.0.2 - optional: true - - cacheable-request@7.0.4: - dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 - http-cache-semantics: 4.2.0 - keyv: 4.5.4 - lowercase-keys: 2.0.0 - normalize-url: 6.1.0 - responselike: 2.0.1 - optional: true - - cachedown@1.0.0: - dependencies: - abstract-leveldown: 2.7.2 - lru-cache: 3.2.0 - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -19444,8 +16631,6 @@ snapshots: camelcase@3.0.0: {} - camelcase@4.1.0: {} - camelcase@5.3.1: {} camelcase@6.3.0: {} @@ -19507,14 +16692,6 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chalk@1.1.3: - dependencies: - ansi-styles: 2.2.1 - escape-string-regexp: 1.0.5 - has-ansi: 2.0.0 - strip-ansi: 3.0.1 - supports-color: 2.0.0 - chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -19589,25 +16766,6 @@ snapshots: check-error@2.1.3: {} - checkpoint-store@1.1.0: - dependencies: - functional-red-black-tree: 1.0.1 - - chokidar@1.7.0: - dependencies: - anymatch: 1.3.2 - async-each: 1.0.6 - glob-parent: 2.0.0 - inherits: 2.0.4 - is-binary-path: 1.0.1 - is-glob: 2.0.1 - path-is-absolute: 1.0.1 - readdirp: 2.2.1 - optionalDependencies: - fsevents: 1.2.13 - transitivePeerDependencies: - - supports-color - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -19657,30 +16815,11 @@ snapshots: ci-info@3.9.0: {} - cids@0.7.5: - dependencies: - buffer: 5.7.1 - class-is: 1.1.0 - multibase: 0.6.1 - multicodec: 1.0.4 - multihashes: 0.4.21 - optional: true - cipher-base@1.0.6: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 - class-is@1.1.0: - optional: true - - class-utils@0.3.6: - dependencies: - arr-union: 3.1.0 - define-property: 0.2.5 - isobject: 3.0.1 - static-extend: 0.1.2 - clean-stack@2.2.0: {} cli-boxes@2.2.1: {} @@ -19706,16 +16845,6 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 - cli-truncate@2.1.0: - dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 - - cli-truncate@3.1.0: - dependencies: - slice-ansi: 5.0.0 - string-width: 5.1.2 - cli-truncate@5.0.0: dependencies: slice-ansi: 7.1.2 @@ -19729,12 +16858,6 @@ snapshots: strip-ansi: 3.0.1 wrap-ansi: 2.1.0 - cliui@4.1.0: - dependencies: - string-width: 2.1.1 - strip-ansi: 4.0.0 - wrap-ansi: 2.1.0 - cliui@6.0.0: dependencies: string-width: 4.2.3 @@ -19753,24 +16876,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - clone-response@1.0.3: - dependencies: - mimic-response: 1.0.1 - optional: true - - clone@2.1.2: {} - - co@4.6.0: {} - code-point-at@1.1.0: {} coingecko-api@1.0.10: {} - collection-visit@1.0.0: - dependencies: - map-visit: 1.0.0 - object-visit: 1.0.1 - color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -19808,12 +16917,6 @@ snapshots: command-exists@1.2.9: {} - command-line-args@4.0.7: - dependencies: - array-back: 2.0.0 - find-replace: 1.0.3 - typical: 2.6.1 - command-line-args@5.2.1: dependencies: array-back: 3.1.0 @@ -19836,12 +16939,8 @@ snapshots: commander@14.0.2: {} - commander@2.11.0: {} - commander@2.20.3: {} - commander@3.0.2: {} - commander@8.3.0: {} commander@9.5.0: {} @@ -19855,8 +16954,6 @@ snapshots: compare-versions@6.1.1: {} - component-emitter@1.3.1: {} - concat-map@0.0.1: {} concat-stream@1.6.2: @@ -19899,13 +16996,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-hash@2.5.2: - dependencies: - cids: 0.7.5 - multicodec: 0.5.7 - multihashes: 0.4.21 - optional: true - content-type@1.0.5: {} conventional-changelog-angular@7.0.0: @@ -19923,8 +17013,6 @@ snapshots: meow: 12.1.1 split2: 4.2.0 - convert-source-map@1.9.0: {} - convert-source-map@2.0.0: {} cookie-signature@1.0.6: {} @@ -19933,18 +17021,8 @@ snapshots: cookie@0.5.0: {} - cookie@0.7.1: - optional: true - - cookiejar@2.1.4: - optional: true - - copy-descriptor@0.1.1: {} - core-js-pure@3.45.1: {} - core-js@2.6.12: {} - core-util-is@1.0.2: {} core-util-is@1.0.3: {} @@ -19971,7 +17049,7 @@ snapshots: cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -19981,19 +17059,13 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 crc-32@1.2.2: {} - create-ecdh@4.0.4: - dependencies: - bn.js: 4.12.2 - elliptic: 6.6.1 - optional: true - create-hash@1.1.3: dependencies: cipher-base: 1.0.6 @@ -20020,13 +17092,6 @@ snapshots: create-require@1.1.1: {} - cross-fetch@2.2.6(encoding@0.1.13): - dependencies: - node-fetch: 2.7.0(encoding@0.1.13) - whatwg-fetch: 2.0.4 - transitivePeerDependencies: - - encoding - cross-fetch@3.1.5(encoding@0.1.13): dependencies: node-fetch: 2.6.7(encoding@0.1.13) @@ -20049,20 +17114,6 @@ snapshots: dependencies: tslib: 2.8.1 - cross-spawn@5.1.0: - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 - - cross-spawn@6.0.6: - dependencies: - nice-try: 1.0.5 - path-key: 2.0.1 - semver: 5.7.2 - shebang-command: 1.2.0 - which: 1.3.1 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -20071,26 +17122,6 @@ snapshots: crypt@0.0.2: {} - crypto-browserify@3.12.0: - dependencies: - browserify-cipher: 1.0.1 - browserify-sign: 4.2.3 - create-ecdh: 4.0.4 - create-hash: 1.2.0 - create-hmac: 1.1.7 - diffie-hellman: 5.0.3 - inherits: 2.0.4 - pbkdf2: 3.1.3 - public-encrypt: 4.0.3 - randombytes: 2.1.0 - randomfill: 1.0.4 - optional: true - - d@1.0.2: - dependencies: - es5-ext: 0.10.64 - type: 2.7.3 - dargs@8.1.0: {} dashdash@1.14.1: @@ -20127,16 +17158,6 @@ snapshots: dependencies: ms: 2.0.0 - debug@3.1.0(supports-color@4.4.0): - dependencies: - ms: 2.0.0 - optionalDependencies: - supports-color: 4.4.0 - - debug@3.2.6: - dependencies: - ms: 2.1.3 - debug@3.2.7: dependencies: ms: 2.1.3 @@ -20147,12 +17168,6 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - debug@4.4.3(supports-color@9.4.0): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 9.4.0 - decamelize@1.2.0: {} decamelize@4.0.0: {} @@ -20161,13 +17176,6 @@ snapshots: dependencies: character-entities: 2.0.2 - decode-uri-component@0.2.2: {} - - decompress-response@3.3.0: - dependencies: - mimic-response: 1.0.1 - optional: true - decompress-response@4.2.1: dependencies: mimic-response: 2.1.0 @@ -20185,33 +17193,12 @@ snapshots: deep-eql@5.0.2: {} - deep-equal@1.1.2: - dependencies: - is-arguments: 1.2.0 - is-date-object: 1.1.0 - is-regex: 1.2.1 - object-is: 1.1.6 - object-keys: 1.1.1 - regexp.prototype.flags: 1.5.4 - deep-extend@0.6.0: {} deep-is@0.1.4: {} - defer-to-connect@1.1.3: - optional: true - defer-to-connect@2.0.1: {} - deferred-leveldown@1.2.2: - dependencies: - abstract-leveldown: 2.6.3 - - deferred-leveldown@4.0.2: - dependencies: - abstract-leveldown: 5.0.0 - inherits: 2.0.4 - deferred-leveldown@5.3.0: dependencies: abstract-leveldown: 6.2.3 @@ -20223,28 +17210,13 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - define-lazy-prop@2.0.0: {} - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - define-property@0.2.5: - dependencies: - is-descriptor: 0.1.7 - - define-property@1.0.0: - dependencies: - is-descriptor: 1.0.3 + define-lazy-prop@2.0.0: {} - define-property@2.0.2: + define-properties@1.2.1: dependencies: - is-descriptor: 1.0.3 - isobject: 3.0.1 - - defined@1.0.1: {} + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 delayed-stream@1.0.0: {} @@ -20266,20 +17238,10 @@ snapshots: dequal@2.0.3: {} - des.js@1.1.0: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - optional: true - destroy@1.0.4: {} destroy@1.2.0: {} - detect-indent@4.0.0: - dependencies: - repeating: 2.0.1 - detect-indent@6.1.0: {} detect-libc@1.0.3: @@ -20289,21 +17251,10 @@ snapshots: dependencies: dequal: 2.0.3 - diff@3.3.1: {} - - diff@3.5.0: {} - diff@4.0.2: {} diff@5.2.0: {} - diffie-hellman@5.0.3: - dependencies: - bn.js: 4.12.2 - miller-rabin: 4.0.1 - randombytes: 2.1.0 - optional: true - difflib@0.2.4: dependencies: heap: 0.2.7 @@ -20321,8 +17272,6 @@ snapshots: dependencies: esutils: 2.0.3 - dom-walk@0.1.2: {} - dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -20336,10 +17285,6 @@ snapshots: dotenv@16.6.1: {} - dotignore@0.1.2: - dependencies: - minimatch: 3.1.2 - dottie@2.0.6: {} dset@3.1.4: {} @@ -20350,9 +17295,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - duplexer3@0.1.5: - optional: true - duplexify@4.1.3: dependencies: end-of-stream: 1.4.5 @@ -20414,14 +17356,6 @@ snapshots: encodeurl@2.0.0: {} - encoding-down@5.0.4: - dependencies: - abstract-leveldown: 5.0.0 - inherits: 2.0.4 - level-codec: 9.0.2 - level-errors: 2.0.1 - xtend: 4.0.2 - encoding-down@6.3.0: dependencies: abstract-leveldown: 6.3.0 @@ -20432,6 +17366,7 @@ snapshots: encoding@0.1.13: dependencies: iconv-lite: 0.6.3 + optional: true end-of-stream@1.4.5: dependencies: @@ -20448,8 +17383,6 @@ snapshots: environment@1.1.0: {} - eol@0.9.1: {} - err-code@2.0.3: {} errno@0.1.8: @@ -20521,8 +17454,6 @@ snapshots: unbox-primitive: 1.1.0 which-typed-array: 1.1.19 - es-array-method-boxes-properly@1.0.0: {} - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -20548,24 +17479,6 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es5-ext@0.10.64: - dependencies: - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - esniff: 2.0.1 - next-tick: 1.1.0 - - es6-iterator@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-symbol: 3.1.4 - - es6-symbol@3.1.4: - dependencies: - d: 1.0.2 - ext: 1.7.0 - esbuild@0.25.9: optionalDependencies: '@esbuild/aix-ppc64': 0.25.9 @@ -20732,7 +17645,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -20756,13 +17669,6 @@ snapshots: transitivePeerDependencies: - supports-color - esniff@2.0.1: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - event-emitter: 0.3.5 - type: 2.7.3 - espree@10.4.0: dependencies: acorn: 8.15.0 @@ -20789,18 +17695,6 @@ snapshots: etag@1.8.1: {} - eth-block-tracker@3.0.1: - dependencies: - eth-query: 2.1.2 - ethereumjs-tx: 1.3.7 - ethereumjs-util: 5.2.1 - ethjs-util: 0.1.6 - json-rpc-engine: 3.8.0 - pify: 2.3.0 - tape: 4.17.0 - transitivePeerDependencies: - - supports-color - eth-ens-namehash@2.0.8: dependencies: idna-uts46-hx: 2.3.1 @@ -20826,102 +17720,10 @@ snapshots: - debug - utf-8-validate - eth-json-rpc-infura@3.2.1(encoding@0.1.13): - dependencies: - cross-fetch: 2.2.6(encoding@0.1.13) - eth-json-rpc-middleware: 1.6.0 - json-rpc-engine: 3.8.0 - json-rpc-error: 2.0.0 - transitivePeerDependencies: - - encoding - - supports-color - - eth-json-rpc-middleware@1.6.0: - dependencies: - async: 2.6.4 - eth-query: 2.1.2 - eth-tx-summary: 3.2.4 - ethereumjs-block: 1.7.1 - ethereumjs-tx: 1.3.7 - ethereumjs-util: 5.2.1 - ethereumjs-vm: 2.6.0 - fetch-ponyfill: 4.1.0 - json-rpc-engine: 3.8.0 - json-rpc-error: 2.0.0 - json-stable-stringify: 1.3.0 - promise-to-callback: 1.0.0 - tape: 4.17.0 - transitivePeerDependencies: - - supports-color - - eth-lib@0.1.29(bufferutil@4.0.9)(utf-8-validate@5.0.10): - dependencies: - bn.js: 4.12.2 - elliptic: 6.6.1 - nano-json-stream-parser: 0.1.2 - servify: 0.1.12 - ws: 3.3.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - xhr-request-promise: 0.1.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - optional: true - - eth-lib@0.2.8: - dependencies: - bn.js: 4.12.2 - elliptic: 6.6.1 - xhr-request-promise: 0.1.3 - optional: true - - eth-query@2.1.2: - dependencies: - json-rpc-random-id: 1.0.1 - xtend: 4.0.2 - - eth-sig-util@1.4.2: - dependencies: - ethereumjs-abi: https://codeload.github.com/ethereumjs/ethereumjs-abi/tar.gz/ee3994657fa7a427238e6ba92a84d0b529bbcde0 - ethereumjs-util: 5.2.1 - - eth-sig-util@3.0.0: - dependencies: - buffer: 5.7.1 - elliptic: 6.6.1 - ethereumjs-abi: 0.6.5 - ethereumjs-util: 5.2.1 - tweetnacl: 1.0.3 - tweetnacl-util: 0.15.1 - - eth-tx-summary@3.2.4: - dependencies: - async: 2.6.4 - clone: 2.1.2 - concat-stream: 1.6.2 - end-of-stream: 1.4.5 - eth-query: 2.1.2 - ethereumjs-block: 1.7.1 - ethereumjs-tx: 1.3.7 - ethereumjs-util: 5.2.1 - ethereumjs-vm: 2.6.0 - through2: 2.0.5 - - ethashjs@0.0.8: - dependencies: - async: 2.6.4 - buffer-xor: 2.0.2 - ethereumjs-util: 7.1.5 - miller-rabin: 4.0.1 - ethereum-bloom-filters@1.2.0: dependencies: '@noble/hashes': 1.8.0 - ethereum-common@0.0.18: {} - - ethereum-common@0.2.0: {} - ethereum-cryptography@0.1.3: dependencies: '@types/pbkdf2': 3.1.2 @@ -20954,20 +17756,6 @@ snapshots: '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 - ethereum-waffle@3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10): - dependencies: - '@ethereum-waffle/chai': 3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - '@ethereum-waffle/compiler': 3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@ethereum-waffle/mock-contract': 3.4.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@ethereum-waffle/provider': 3.4.4(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3): dependencies: '@ethereum-waffle/chai': 4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) @@ -20987,92 +17775,11 @@ snapshots: - supports-color - typescript - ethereumjs-abi@0.6.5: - dependencies: - bn.js: 4.12.2 - ethereumjs-util: 4.5.1 - ethereumjs-abi@0.6.8: dependencies: bn.js: 4.12.2 ethereumjs-util: 6.2.1 - ethereumjs-abi@https://codeload.github.com/ethereumjs/ethereumjs-abi/tar.gz/ee3994657fa7a427238e6ba92a84d0b529bbcde0: - dependencies: - bn.js: 4.12.2 - ethereumjs-util: 6.2.1 - - ethereumjs-account@2.0.5: - dependencies: - ethereumjs-util: 5.2.1 - rlp: 2.2.7 - safe-buffer: 5.2.1 - - ethereumjs-account@3.0.0: - dependencies: - ethereumjs-util: 6.2.1 - rlp: 2.2.7 - safe-buffer: 5.2.1 - - ethereumjs-block@1.7.1: - dependencies: - async: 2.6.4 - ethereum-common: 0.2.0 - ethereumjs-tx: 1.3.7 - ethereumjs-util: 5.2.1 - merkle-patricia-tree: 2.3.2 - - ethereumjs-block@2.2.2: - dependencies: - async: 2.6.4 - ethereumjs-common: 1.5.0 - ethereumjs-tx: 2.1.2 - ethereumjs-util: 5.2.1 - merkle-patricia-tree: 2.3.2 - - ethereumjs-blockchain@4.0.4: - dependencies: - async: 2.6.4 - ethashjs: 0.0.8 - ethereumjs-block: 2.2.2 - ethereumjs-common: 1.5.0 - ethereumjs-util: 6.2.1 - flow-stoplight: 1.0.0 - level-mem: 3.0.1 - lru-cache: 5.1.1 - rlp: 2.2.7 - semaphore: 1.1.0 - - ethereumjs-common@1.5.0: {} - - ethereumjs-tx@1.3.7: - dependencies: - ethereum-common: 0.0.18 - ethereumjs-util: 5.2.1 - - ethereumjs-tx@2.1.2: - dependencies: - ethereumjs-common: 1.5.0 - ethereumjs-util: 6.2.1 - - ethereumjs-util@4.5.1: - dependencies: - bn.js: 4.12.2 - create-hash: 1.2.0 - elliptic: 6.6.1 - ethereum-cryptography: 0.1.3 - rlp: 2.2.7 - - ethereumjs-util@5.2.1: - dependencies: - bn.js: 4.12.2 - create-hash: 1.2.0 - elliptic: 6.6.1 - ethereum-cryptography: 0.1.3 - ethjs-util: 0.1.6 - rlp: 2.2.7 - safe-buffer: 5.2.1 - ethereumjs-util@6.2.1: dependencies: '@types/bn.js': 4.11.6 @@ -21099,51 +17806,6 @@ snapshots: ethereum-cryptography: 0.1.3 rlp: 2.2.7 - ethereumjs-vm@2.6.0: - dependencies: - async: 2.6.4 - async-eventemitter: 0.2.4 - ethereumjs-account: 2.0.5 - ethereumjs-block: 2.2.2 - ethereumjs-common: 1.5.0 - ethereumjs-util: 6.2.1 - fake-merkle-patricia-tree: 1.0.1 - functional-red-black-tree: 1.0.1 - merkle-patricia-tree: 2.3.2 - rustbn.js: 0.2.0 - safe-buffer: 5.2.1 - - ethereumjs-vm@4.2.0: - dependencies: - async: 2.6.4 - async-eventemitter: 0.2.4 - core-js-pure: 3.45.1 - ethereumjs-account: 3.0.0 - ethereumjs-block: 2.2.2 - ethereumjs-blockchain: 4.0.4 - ethereumjs-common: 1.5.0 - ethereumjs-tx: 2.1.2 - ethereumjs-util: 6.2.1 - fake-merkle-patricia-tree: 1.0.1 - functional-red-black-tree: 1.0.1 - merkle-patricia-tree: 2.3.2 - rustbn.js: 0.2.0 - safe-buffer: 5.2.1 - util.promisify: 1.1.3 - - ethereumjs-wallet@0.6.5: - dependencies: - aes-js: 3.1.2 - bs58check: 2.1.2 - ethereum-cryptography: 0.1.3 - ethereumjs-util: 6.2.1 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - scryptsy: 1.2.1 - utf8: 3.0.0 - uuid: 3.4.0 - optional: true - ethers@5.6.2(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: '@ethersproject/abi': 5.6.0 @@ -21324,35 +17986,8 @@ snapshots: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 - ethlint@1.2.5(solium@1.2.5): - dependencies: - ajv: 5.5.2 - chokidar: 1.7.0 - colors: 1.4.0 - commander: 2.20.3 - diff: 3.5.0 - eol: 0.9.1 - js-string-escape: 1.0.1 - lodash: 4.17.21 - sol-digger: 0.0.2 - sol-explore: 1.6.1 - solium-plugin-security: 0.1.1(solium@1.2.5) - solparse: 2.2.8 - text-table: 0.2.0 - transitivePeerDependencies: - - solium - - supports-color - - event-emitter@0.3.5: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - event-target-shim@5.0.1: {} - eventemitter3@4.0.4: - optional: true - eventemitter3@4.0.7: {} eventemitter3@5.0.1: {} @@ -21364,48 +17999,6 @@ snapshots: md5.js: 1.3.5 safe-buffer: 5.2.1 - execa@0.7.0: - dependencies: - cross-spawn: 5.1.0 - get-stream: 3.0.0 - is-stream: 1.1.0 - npm-run-path: 2.0.2 - p-finally: 1.0.0 - signal-exit: 3.0.7 - strip-eof: 1.0.0 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - expand-brackets@0.1.5: - dependencies: - is-posix-bracket: 0.1.1 - - expand-brackets@2.1.4: - dependencies: - debug: 2.6.9 - define-property: 0.2.5 - extend-shallow: 2.0.1 - posix-character-classes: 0.1.1 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - - expand-range@1.8.2: - dependencies: - fill-range: 2.2.4 - expand-template@2.0.3: optional: true @@ -21477,60 +18070,10 @@ snapshots: setprototypeof: 1.2.0 statuses: 2.0.1 type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - express@4.21.2: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.12 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - optional: true - - ext@1.7.0: - dependencies: - type: 2.7.3 - - extend-shallow@2.0.1: - dependencies: - is-extendable: 0.1.1 - - extend-shallow@3.0.2: - dependencies: - assign-symbols: 1.0.0 - is-extendable: 1.0.1 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color extend@3.0.2: {} @@ -21542,37 +18085,14 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 - extglob@0.3.2: - dependencies: - is-extglob: 1.0.0 - - extglob@2.0.4: - dependencies: - array-unique: 0.3.2 - define-property: 1.0.0 - expand-brackets: 2.1.4 - extend-shallow: 2.0.1 - fragment-cache: 0.2.1 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - extract-files@11.0.0: {} extsprintf@1.3.0: {} - fake-merkle-patricia-tree@1.0.1: - dependencies: - checkpoint-store: 1.1.0 - fast-base64-decode@1.0.0: {} fast-decode-uri-component@1.0.1: {} - fast-deep-equal@1.1.0: {} - fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -21641,10 +18161,6 @@ snapshots: fecha@4.2.3: {} - fetch-ponyfill@4.1.0: - dependencies: - node-fetch: 1.7.3 - fets@0.1.5: dependencies: '@ardatan/fast-json-stringify': 0.0.6(ajv-formats@2.1.1(ajv@8.17.1))(ajv@8.17.1) @@ -21671,23 +18187,6 @@ snapshots: file-uri-to-path@1.0.0: optional: true - filename-regex@2.0.1: {} - - fill-range@2.2.4: - dependencies: - is-number: 2.1.0 - isobject: 2.1.0 - randomatic: 3.1.1 - repeat-element: 1.1.4 - repeat-string: 1.6.1 - - fill-range@4.0.0: - dependencies: - extend-shallow: 2.0.1 - is-number: 3.0.0 - repeat-string: 1.6.1 - to-regex-range: 2.1.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -21716,24 +18215,6 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@1.3.1: - dependencies: - debug: 2.6.9 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - optional: true - - find-replace@1.0.3: - dependencies: - array-back: 1.0.4 - test-value: 2.1.0 - find-replace@3.0.0: dependencies: array-back: 3.1.0 @@ -21743,10 +18224,6 @@ snapshots: path-exists: 2.1.0 pinkie-promise: 2.0.1 - find-up@2.1.0: - dependencies: - locate-path: 2.0.0 - find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -21763,17 +18240,6 @@ snapshots: path-exists: 5.0.0 unicorn-magic: 0.1.0 - find-yarn-workspace-root@1.2.1: - dependencies: - fs-extra: 4.0.3 - micromatch: 3.1.10 - transitivePeerDependencies: - - supports-color - - find-yarn-workspace-root@2.0.0: - dependencies: - micromatch: 4.0.8 - flat-cache@4.0.1: dependencies: flatted: 3.3.3 @@ -21785,8 +18251,6 @@ snapshots: flow-enums-runtime@0.0.6: {} - flow-stoplight@1.0.0: {} - fmix@0.1.0: dependencies: imul: 1.0.1 @@ -21795,18 +18259,12 @@ snapshots: follow-redirects@1.15.11(debug@4.4.3): optionalDependencies: - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) for-each@0.3.5: dependencies: is-callable: 1.2.7 - for-in@1.0.2: {} - - for-own@0.1.5: - dependencies: - for-in: 1.0.2 - foreach@2.0.6: {} foreground-child@3.3.1: @@ -21855,10 +18313,6 @@ snapshots: fp-ts@1.19.3: {} - fragment-cache@0.2.1: - dependencies: - map-cache: 0.2.2 - fresh@0.5.2: {} fs-constants@1.0.0: @@ -21884,12 +18338,6 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@4.0.3: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -21909,11 +18357,6 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-minipass@1.2.7: - dependencies: - minipass: 2.9.0 - optional: true - fs-minipass@2.1.0: dependencies: minipass: 3.3.6 @@ -21926,12 +18369,6 @@ snapshots: fs.realpath@1.0.0: {} - fsevents@1.2.13: - dependencies: - bindings: 1.5.0 - nan: 2.23.0 - optional: true - fsevents@2.3.3: optional: true @@ -21950,44 +18387,6 @@ snapshots: functions-have-names@1.2.3: {} - ganache-core@2.13.2(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10): - dependencies: - abstract-leveldown: 3.0.0 - async: 2.6.2 - bip39: 2.5.0 - cachedown: 1.0.0 - clone: 2.1.2 - debug: 3.2.6 - encoding-down: 5.0.4 - eth-sig-util: 3.0.0 - ethereumjs-abi: 0.6.8 - ethereumjs-account: 3.0.0 - ethereumjs-block: 2.2.2 - ethereumjs-common: 1.5.0 - ethereumjs-tx: 2.1.2 - ethereumjs-util: 6.2.1 - ethereumjs-vm: 4.2.0 - heap: 0.2.6 - level-sublevel: 6.6.4 - levelup: 3.1.1 - lodash: 4.17.20 - lru-cache: 5.1.1 - merkle-patricia-tree: 3.0.0 - patch-package: 6.2.2 - seedrandom: 3.0.1 - source-map-support: 0.5.12 - tmp: 0.1.0 - web3-provider-engine: 14.2.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - websocket: 1.0.32 - optionalDependencies: - ethereumjs-wallet: 0.6.5 - web3: 1.2.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - ganache@7.4.3: optionalDependencies: bufferutil: 4.0.5 @@ -22037,18 +18436,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@3.0.0: {} - - get-stream@4.1.0: - dependencies: - pump: 3.0.3 - optional: true - - get-stream@5.2.0: - dependencies: - pump: 3.0.3 - optional: true - get-stream@6.0.1: {} get-symbol-description@1.1.0: @@ -22061,8 +18448,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-value@2.0.6: {} - getpass@0.1.7: dependencies: assert-plus: 1.0.0 @@ -22081,15 +18466,6 @@ snapshots: github-from-package@0.0.0: optional: true - glob-base@0.3.0: - dependencies: - glob-parent: 2.0.0 - is-glob: 2.0.1 - - glob-parent@2.0.0: - dependencies: - is-glob: 2.0.1 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -22116,17 +18492,14 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.0 - glob@5.0.15: + glob@13.0.6: dependencies: - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 - glob@7.1.2: + glob@5.0.15: dependencies: - fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 minimatch: 3.1.2 @@ -22173,17 +18546,10 @@ snapshots: kind-of: 6.0.3 which: 1.3.1 - global@4.4.0: - dependencies: - min-document: 2.19.0 - process: 0.11.10 - globals@14.0.0: {} globals@16.4.0: {} - globals@9.18.0: {} - globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -22211,21 +18577,6 @@ snapshots: gopd@1.2.0: {} - got@11.8.6: - dependencies: - '@sindresorhus/is': 4.6.0 - '@szmarczak/http-timer': 4.0.6 - '@types/cacheable-request': 6.0.3 - '@types/responselike': 1.0.3 - cacheable-lookup: 5.0.4 - cacheable-request: 7.0.4 - decompress-response: 6.0.0 - http2-wrapper: 1.0.3 - lowercase-keys: 2.0.0 - p-cancelable: 2.1.1 - responselike: 2.0.1 - optional: true - got@12.6.1: dependencies: '@sindresorhus/is': 5.6.0 @@ -22240,23 +18591,6 @@ snapshots: p-cancelable: 3.0.0 responselike: 3.0.0 - got@9.6.0: - dependencies: - '@sindresorhus/is': 0.14.0 - '@szmarczak/http-timer': 1.1.2 - '@types/keyv': 3.1.4 - '@types/responselike': 1.0.3 - cacheable-request: 6.1.0 - decompress-response: 3.3.0 - duplexer3: 0.1.5 - get-stream: 4.1.0 - lowercase-keys: 1.0.1 - mimic-response: 1.0.1 - p-cancelable: 1.1.0 - to-readable-stream: 1.0.0 - url-parse-lax: 3.0.0 - optional: true - graceful-fs@4.2.10: {} graceful-fs@4.2.11: {} @@ -22327,8 +18661,6 @@ snapshots: graphql@16.8.0: {} - growl@1.10.3: {} - handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -22375,7 +18707,7 @@ snapshots: axios: 0.21.4(debug@4.4.3) chalk: 4.1.2 chokidar: 3.6.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) enquirer: 2.4.1 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) form-data: 4.0.4 @@ -22406,7 +18738,7 @@ snapshots: axios: 0.21.4(debug@4.4.3) chalk: 4.1.2 chokidar: 3.6.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) form-data: 3.0.4 fs-extra: 9.1.0 hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -22418,12 +18750,12 @@ snapshots: - supports-color - utf-8-validate - hardhat-deploy@2.0.0-next.61(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(hardhat@3.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + hardhat-deploy@2.0.0-next.61(@rocketh/node@0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(hardhat@3.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: '@nomicfoundation/hardhat-zod-utils': 3.0.1(zod@3.25.76) - '@rocketh/node': 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@rocketh/node': 0.17.16(bufferutil@4.0.9)(rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@types/debug': 4.1.12 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) hardhat: 3.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) named-logs-console: 0.5.1 slash: 5.1.0 @@ -22452,7 +18784,7 @@ snapshots: hardhat-secure-accounts@0.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): dependencies: '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) enquirer: 2.4.1 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -22464,7 +18796,7 @@ snapshots: hardhat-secure-accounts@1.0.5(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): dependencies: '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) enquirer: 2.4.1 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -22476,7 +18808,7 @@ snapshots: hardhat-secure-accounts@1.0.5(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): dependencies: '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) enquirer: 2.4.1 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -22503,7 +18835,7 @@ snapshots: boxen: 5.1.2 chokidar: 4.0.3 ci-info: 2.0.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) enquirer: 2.4.1 env-paths: 2.2.1 ethereum-cryptography: 1.2.0 @@ -22552,7 +18884,7 @@ snapshots: boxen: 5.1.2 chokidar: 4.0.3 ci-info: 2.0.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) enquirer: 2.4.1 env-paths: 2.2.1 ethereum-cryptography: 1.2.0 @@ -22600,7 +18932,7 @@ snapshots: adm-zip: 0.4.16 chalk: 5.6.2 chokidar: 4.0.3 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) enquirer: 2.4.1 ethereum-cryptography: 2.2.1 micro-eth-signer: 0.14.0 @@ -22615,16 +18947,10 @@ snapshots: - supports-color - utf-8-validate - has-ansi@2.0.0: - dependencies: - ansi-regex: 2.1.1 - has-bigints@1.1.0: {} has-flag@1.0.0: {} - has-flag@2.0.0: {} - has-flag@3.0.0: {} has-flag@4.0.0: {} @@ -22646,37 +18972,10 @@ snapshots: has-unicode@2.0.1: optional: true - has-value@0.3.1: - dependencies: - get-value: 2.0.6 - has-values: 0.1.4 - isobject: 2.1.0 - - has-value@1.0.0: - dependencies: - get-value: 2.0.6 - has-values: 1.0.0 - isobject: 3.0.1 - - has-values@0.1.4: {} - - has-values@1.0.0: - dependencies: - is-number: 3.0.0 - kind-of: 4.0.0 - - has@1.0.4: {} - hash-base@2.0.2: dependencies: inherits: 2.0.4 - hash-base@3.0.5: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - optional: true - hash-base@3.1.0: dependencies: inherits: 2.0.4 @@ -22694,8 +18993,6 @@ snapshots: dependencies: function-bind: 1.1.2 - he@1.1.1: {} - he@1.2.0: {} header-case@2.0.4: @@ -22703,8 +19000,6 @@ snapshots: capital-case: 1.0.4 tslib: 2.8.1 - heap@0.2.6: {} - heap@0.2.7: {} helmet@5.0.2: {} @@ -22723,11 +19018,6 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - home-or-tmp@2.0.0: - dependencies: - os-homedir: 1.0.2 - os-tmpdir: 1.0.2 - hosted-git-info@2.8.9: {} hosted-git-info@7.0.2: @@ -22761,13 +19051,10 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 - http-https@1.0.0: - optional: true - http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -22781,12 +19068,6 @@ snapshots: jsprim: 1.4.2 sshpk: 1.18.0 - http2-wrapper@1.0.3: - dependencies: - quick-lru: 5.1.1 - resolve-alpn: 1.2.1 - optional: true - http2-wrapper@2.2.1: dependencies: quick-lru: 5.1.1 @@ -22795,23 +19076,19 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color human-id@4.1.1: {} - human-signals@2.1.0: {} - - husky@7.0.4: {} - husky@9.1.7: {} iconv-lite@0.4.24: @@ -22821,6 +19098,7 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true iconv-lite@0.7.0: dependencies: @@ -22932,10 +19210,6 @@ snapshots: is-relative: 1.0.0 is-windows: 1.0.2 - is-accessor-descriptor@1.0.1: - dependencies: - hasown: 2.0.2 - is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -22943,11 +19217,6 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 - is-arguments@1.2.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -22970,10 +19239,6 @@ snapshots: dependencies: has-bigints: 1.1.0 - is-binary-path@1.0.1: - dependencies: - binary-extensions: 1.13.1 - is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -22983,22 +19248,12 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-buffer@1.1.6: {} - is-callable@1.2.7: {} - is-ci@2.0.0: - dependencies: - ci-info: 2.0.0 - is-core-module@2.16.1: dependencies: hasown: 2.0.2 - is-data-descriptor@1.0.1: - dependencies: - hasown: 2.0.2 - is-data-view@1.0.2: dependencies: call-bound: 1.0.4 @@ -23012,44 +19267,16 @@ snapshots: is-decimal@2.0.1: {} - is-descriptor@0.1.7: - dependencies: - is-accessor-descriptor: 1.0.1 - is-data-descriptor: 1.0.1 - - is-descriptor@1.0.3: - dependencies: - is-accessor-descriptor: 1.0.1 - is-data-descriptor: 1.0.1 - is-directory@0.3.1: {} is-docker@2.2.1: {} - is-dotfile@1.0.3: {} - - is-equal-shallow@0.1.3: - dependencies: - is-primitive: 2.0.0 - - is-extendable@0.1.1: {} - - is-extendable@1.0.1: - dependencies: - is-plain-object: 2.0.4 - - is-extglob@1.0.0: {} - is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: dependencies: call-bound: 1.0.4 - is-finite@1.1.0: {} - - is-fn@1.0.0: {} - is-fullwidth-code-point@1.0.0: dependencies: number-is-nan: 1.0.1 @@ -23058,14 +19285,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-fullwidth-code-point@4.0.0: {} - is-fullwidth-code-point@5.1.0: dependencies: get-east-asian-width: 1.4.0 - is-function@1.0.2: {} - is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 @@ -23073,10 +19296,6 @@ snapshots: has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 - is-glob@2.0.1: - dependencies: - is-extglob: 1.0.0 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -23100,35 +19319,12 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-number@2.1.0: - dependencies: - kind-of: 3.2.2 - - is-number@3.0.0: - dependencies: - kind-of: 3.2.2 - - is-number@4.0.0: {} - is-number@7.0.0: {} is-obj@2.0.0: {} is-plain-obj@2.1.0: {} - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - - is-posix-bracket@0.1.1: {} - - is-primitive@2.0.0: {} - - is-regex@1.1.4: - dependencies: - call-bind: 1.0.8 - has-tostringtag: 1.0.2 - is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -23146,8 +19342,6 @@ snapshots: dependencies: call-bound: 1.0.4 - is-stream@1.1.0: {} - is-stream@2.0.1: {} is-string@1.1.1: @@ -23206,20 +19400,12 @@ snapshots: dependencies: is-docker: 2.2.1 - isarray@0.0.1: {} - isarray@1.0.0: {} isarray@2.0.5: {} isexe@2.0.0: {} - isobject@2.1.0: - dependencies: - isarray: 1.0.0 - - isobject@3.0.1: {} - isomorphic-unfetch@3.1.0(encoding@0.1.13): dependencies: node-fetch: 2.7.0(encoding@0.1.13) @@ -23343,10 +19529,6 @@ snapshots: js-sha3@0.8.0: {} - js-string-escape@1.0.1: {} - - js-tokens@3.0.2: {} - js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -23366,10 +19548,6 @@ snapshots: jsc-safe-url@0.2.4: {} - jsesc@0.5.0: {} - - jsesc@1.3.0: {} - jsesc@3.1.0: {} json-bigint-patch@0.0.8: {} @@ -23378,9 +19556,6 @@ snapshots: dependencies: bignumber.js: 9.3.1 - json-buffer@3.0.0: - optional: true - json-buffer@3.0.1: {} json-parse-better-errors@1.0.2: {} @@ -23391,31 +19566,12 @@ snapshots: dependencies: foreach: 2.0.6 - json-rpc-engine@3.8.0: - dependencies: - async: 2.6.4 - babel-preset-env: 1.7.0 - babelify: 7.3.0 - json-rpc-error: 2.0.0 - promise-to-callback: 1.0.0 - safe-event-emitter: 1.0.1 - transitivePeerDependencies: - - supports-color - - json-rpc-error@2.0.0: - dependencies: - inherits: 2.0.4 - - json-rpc-random-id@1.0.1: {} - json-schema-to-ts@2.12.0: dependencies: '@babel/runtime': 7.28.4 '@types/json-schema': 7.0.15 ts-algebra: 1.2.2 - json-schema-traverse@0.3.1: {} - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -23424,20 +19580,10 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json-stable-stringify@1.3.0: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - isarray: 2.0.5 - jsonify: 0.0.1 - object-keys: 1.1.1 - json-stream-stringify@3.1.6: {} json-stringify-safe@5.0.1: {} - json5@0.5.1: {} - json5@1.0.2: dependencies: minimist: 1.2.8 @@ -23460,8 +19606,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonify@0.0.1: {} - jsonparse@1.3.1: {} jsonpointer@5.0.1: {} @@ -23490,29 +19634,12 @@ snapshots: node-gyp-build: 4.8.4 readable-stream: 3.6.2 - keyv@3.1.0: - dependencies: - json-buffer: 3.0.0 - optional: true - keyv@4.5.4: dependencies: json-buffer: 3.0.1 - kind-of@3.2.2: - dependencies: - is-buffer: 1.1.6 - - kind-of@4.0.0: - dependencies: - is-buffer: 1.1.6 - kind-of@6.0.3: {} - klaw-sync@6.0.0: - dependencies: - graceful-fs: 4.2.11 - klaw@1.3.1: optionalDependencies: graceful-fs: 4.2.11 @@ -23534,40 +19661,15 @@ snapshots: dotenv: 16.6.1 dotenv-expand: 10.0.0 - level-codec@7.0.1: {} - level-codec@9.0.2: dependencies: buffer: 5.7.1 level-concat-iterator@2.0.1: {} - level-errors@1.0.5: - dependencies: - errno: 0.1.8 - level-errors@2.0.1: dependencies: - errno: 0.1.8 - - level-iterator-stream@1.3.1: - dependencies: - inherits: 2.0.4 - level-errors: 1.0.5 - readable-stream: 1.1.14 - xtend: 4.0.2 - - level-iterator-stream@2.0.3: - dependencies: - inherits: 2.0.4 - readable-stream: 2.3.8 - xtend: 4.0.2 - - level-iterator-stream@3.0.1: - dependencies: - inherits: 2.0.4 - readable-stream: 2.3.8 - xtend: 4.0.2 + errno: 0.1.8 level-iterator-stream@4.0.2: dependencies: @@ -23575,81 +19677,26 @@ snapshots: readable-stream: 3.6.2 xtend: 4.0.2 - level-mem@3.0.1: - dependencies: - level-packager: 4.0.1 - memdown: 3.0.0 - level-mem@5.0.1: dependencies: level-packager: 5.1.1 memdown: 5.1.0 - level-packager@4.0.1: - dependencies: - encoding-down: 5.0.4 - levelup: 3.1.1 - level-packager@5.1.1: dependencies: encoding-down: 6.3.0 levelup: 4.4.0 - level-post@1.0.7: - dependencies: - ltgt: 2.1.3 - - level-sublevel@6.6.4: - dependencies: - bytewise: 1.1.0 - level-codec: 9.0.2 - level-errors: 2.0.1 - level-iterator-stream: 2.0.3 - ltgt: 2.1.3 - pull-defer: 0.2.3 - pull-level: 2.0.4 - pull-stream: 3.7.0 - typewiselite: 1.0.0 - xtend: 4.0.2 - level-supports@1.0.1: dependencies: xtend: 4.0.2 - level-ws@0.0.0: - dependencies: - readable-stream: 1.0.34 - xtend: 2.1.2 - - level-ws@1.0.0: - dependencies: - inherits: 2.0.4 - readable-stream: 2.3.8 - xtend: 4.0.2 - level-ws@2.0.0: dependencies: inherits: 2.0.4 readable-stream: 3.6.2 xtend: 4.0.2 - levelup@1.3.9: - dependencies: - deferred-leveldown: 1.2.2 - level-codec: 7.0.1 - level-errors: 1.0.5 - level-iterator-stream: 1.3.1 - prr: 1.0.1 - semver: 5.4.1 - xtend: 4.0.2 - - levelup@3.1.1: - dependencies: - deferred-leveldown: 4.0.2 - level-errors: 2.0.1 - level-iterator-stream: 3.0.1 - xtend: 4.0.2 - levelup@4.4.0: dependencies: deferred-leveldown: 5.3.0 @@ -23681,33 +19728,12 @@ snapshots: transitivePeerDependencies: - supports-color - lilconfig@2.0.5: {} - lines-and-columns@1.2.4: {} linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 - lint-staged@12.5.0(enquirer@2.4.1): - dependencies: - cli-truncate: 3.1.0 - colorette: 2.0.20 - commander: 9.5.0 - debug: 4.4.3(supports-color@9.4.0) - execa: 5.1.1 - lilconfig: 2.0.5 - listr2: 4.0.5(enquirer@2.4.1) - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-inspect: 1.13.4 - pidtree: 0.5.0 - string-argv: 0.3.2 - supports-color: 9.4.0 - yaml: 1.10.2 - transitivePeerDependencies: - - enquirer - lint-staged@16.2.7: dependencies: commander: 14.0.2 @@ -23718,19 +19744,6 @@ snapshots: string-argv: 0.3.2 yaml: 2.8.1 - listr2@4.0.5(enquirer@2.4.1): - dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.20 - log-update: 4.0.0 - p-map: 4.0.0 - rfdc: 1.4.1 - rxjs: 7.8.2 - through: 2.3.8 - wrap-ansi: 7.0.0 - optionalDependencies: - enquirer: 2.4.1 - listr2@9.0.5: dependencies: cli-truncate: 5.0.0 @@ -23754,11 +19767,6 @@ snapshots: dependencies: lie: 3.1.1 - locate-path@2.0.0: - dependencies: - p-locate: 2.0.0 - path-exists: 3.0.0 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -23807,8 +19815,6 @@ snapshots: lodash.upperfirst@4.3.1: {} - lodash@4.17.20: {} - lodash@4.17.21: {} log-symbols@4.1.0: @@ -23816,13 +19822,6 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 - log-update@4.0.0: - dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 - log-update@6.1.0: dependencies: ansi-escapes: 7.1.0 @@ -23840,10 +19839,6 @@ snapshots: safe-stable-stringify: 2.5.0 triple-beam: 1.4.1 - looper@2.0.0: {} - - looper@3.0.0: {} - loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -23862,27 +19857,12 @@ snapshots: dependencies: tslib: 2.8.1 - lowercase-keys@1.0.1: - optional: true - - lowercase-keys@2.0.0: - optional: true - lowercase-keys@3.0.0: {} lru-cache@10.4.3: {} lru-cache@11.2.1: {} - lru-cache@3.2.0: - dependencies: - pseudomap: 1.0.2 - - lru-cache@4.1.5: - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -23895,8 +19875,6 @@ snapshots: lru_map@0.3.3: {} - ltgt@2.1.3: {} - ltgt@2.2.1: {} make-error@1.3.6: {} @@ -23924,10 +19902,6 @@ snapshots: map-cache@0.2.2: {} - map-visit@1.0.0: - dependencies: - object-visit: 1.0.1 - markdown-it@14.1.0: dependencies: argparse: 2.0.1 @@ -24005,8 +19979,6 @@ snapshots: math-intrinsics@1.1.0: {} - math-random@1.0.4: {} - mcl-wasm@0.7.9: {} md5.js@1.3.5: @@ -24019,28 +19991,6 @@ snapshots: media-typer@0.3.0: {} - mem@1.1.0: - dependencies: - mimic-fn: 1.2.0 - - memdown@1.4.1: - dependencies: - abstract-leveldown: 2.7.2 - functional-red-black-tree: 1.0.1 - immediate: 3.3.0 - inherits: 2.0.4 - ltgt: 2.2.1 - safe-buffer: 5.1.2 - - memdown@3.0.0: - dependencies: - abstract-leveldown: 5.0.0 - functional-red-black-tree: 1.0.1 - immediate: 3.2.3 - inherits: 2.0.4 - ltgt: 2.2.1 - safe-buffer: 5.1.2 - memdown@5.1.0: dependencies: abstract-leveldown: 6.2.3 @@ -24058,34 +20008,10 @@ snapshots: merge-descriptors@1.0.1: {} - merge-descriptors@1.0.3: - optional: true - merge-stream@2.0.0: {} merge2@1.4.1: {} - merkle-patricia-tree@2.3.2: - dependencies: - async: 1.5.2 - ethereumjs-util: 5.2.1 - level-ws: 0.0.0 - levelup: 1.3.9 - memdown: 1.4.1 - readable-stream: 2.3.8 - rlp: 2.2.7 - semaphore: 1.1.0 - - merkle-patricia-tree@3.0.0: - dependencies: - async: 2.6.4 - ethereumjs-util: 5.2.1 - level-mem: 3.0.1 - level-ws: 1.0.0 - readable-stream: 3.6.2 - rlp: 2.2.7 - semaphore: 1.1.0 - merkle-patricia-tree@4.2.4: dependencies: '@types/levelup': 4.3.3 @@ -24146,7 +20072,7 @@ snapshots: metro-file-map@0.83.1: dependencies: - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) fb-watchman: 2.0.2 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 @@ -24242,7 +20168,7 @@ snapshots: chalk: 4.1.2 ci-info: 2.0.0 connect: 3.7.0 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) error-stack-parser: 2.1.4 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 @@ -24441,7 +20367,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) decode-named-character-reference: 1.2.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -24460,40 +20386,6 @@ snapshots: transitivePeerDependencies: - supports-color - micromatch@2.3.11: - dependencies: - arr-diff: 2.0.0 - array-unique: 0.2.1 - braces: 1.8.5 - expand-brackets: 0.1.5 - extglob: 0.3.2 - filename-regex: 2.0.1 - is-extglob: 1.0.0 - is-glob: 2.0.1 - kind-of: 3.2.2 - normalize-path: 2.1.1 - object.omit: 2.0.1 - parse-glob: 3.0.4 - regex-cache: 0.4.4 - - micromatch@3.1.10: - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - braces: 2.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - extglob: 2.0.4 - fragment-cache: 0.2.1 - kind-of: 6.0.3 - nanomatch: 1.2.13 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -24512,15 +20404,10 @@ snapshots: mime@1.6.0: {} - mimic-fn@1.2.0: {} - mimic-fn@2.1.0: {} mimic-function@5.0.1: {} - mimic-response@1.0.1: - optional: true - mimic-response@2.1.0: optional: true @@ -24528,10 +20415,6 @@ snapshots: mimic-response@4.0.0: {} - min-document@2.19.0: - dependencies: - dom-walk: 0.1.2 - minimalistic-assert@1.0.1: {} minimalistic-crypto-utils@1.0.1: {} @@ -24544,6 +20427,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -24556,8 +20443,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist@0.0.8: {} - minimist@1.2.8: {} minipass-collect@2.0.1: @@ -24584,12 +20469,6 @@ snapshots: dependencies: minipass: 3.3.6 - minipass@2.9.0: - dependencies: - safe-buffer: 5.2.1 - yallist: 3.1.1 - optional: true - minipass@3.3.6: dependencies: yallist: 4.0.0 @@ -24598,33 +20477,16 @@ snapshots: minipass@7.1.2: {} - minizlib@1.3.3: - dependencies: - minipass: 2.9.0 - optional: true + minipass@7.1.3: {} minizlib@2.1.2: dependencies: minipass: 3.3.6 yallist: 4.0.0 - mixin-deep@1.3.2: - dependencies: - for-in: 1.0.2 - is-extendable: 1.0.1 - mkdirp-classic@0.5.3: optional: true - mkdirp-promise@5.0.1: - dependencies: - mkdirp: 3.0.1 - optional: true - - mkdirp@0.5.1: - dependencies: - minimist: 0.0.8 - mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -24660,31 +20522,6 @@ snapshots: yargs-parser: 20.2.9 yargs-unparser: 2.0.0 - mocha@4.1.0: - dependencies: - browser-stdout: 1.3.0 - commander: 2.11.0 - debug: 3.1.0(supports-color@4.4.0) - diff: 3.3.1 - escape-string-regexp: 1.0.5 - glob: 7.1.2 - growl: 1.10.3 - he: 1.1.1 - mkdirp: 0.5.1 - supports-color: 4.4.0 - - mock-fs@4.14.0: - optional: true - - mock-property@1.0.3: - dependencies: - define-data-property: 1.1.4 - functions-have-names: 1.2.3 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - hasown: 2.0.2 - isarray: 2.0.5 - moment-timezone@0.5.48: dependencies: moment: 2.30.1 @@ -24707,36 +20544,6 @@ snapshots: ms@2.1.3: {} - multibase@0.6.1: - dependencies: - base-x: 3.0.11 - buffer: 5.7.1 - optional: true - - multibase@0.7.0: - dependencies: - base-x: 3.0.11 - buffer: 5.7.1 - optional: true - - multicodec@0.5.7: - dependencies: - varint: 5.0.2 - optional: true - - multicodec@1.0.4: - dependencies: - buffer: 5.7.1 - varint: 5.0.2 - optional: true - - multihashes@0.4.21: - dependencies: - buffer: 5.7.1 - multibase: 0.7.0 - varint: 5.0.2 - optional: true - murmur-128@0.2.1: dependencies: encode-utf8: 1.0.3 @@ -24756,27 +20563,8 @@ snapshots: nan@2.23.0: optional: true - nano-json-stream-parser@0.1.2: - optional: true - nano-spawn@2.0.0: {} - nanomatch@1.2.13: - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - fragment-cache: 0.2.1 - is-windows: 1.0.2 - kind-of: 6.0.3 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - nanospinner@1.2.2: dependencies: picocolors: 1.1.1 @@ -24809,12 +20597,8 @@ snapshots: neoqs@6.13.0: {} - next-tick@1.1.0: {} - ngeohash@0.6.3: {} - nice-try@1.0.5: {} - no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -24839,11 +20623,6 @@ snapshots: dependencies: lodash: 4.17.21 - node-fetch@1.7.3: - dependencies: - encoding: 0.1.13 - is-stream: 1.1.0 - node-fetch@2.6.7(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -24906,12 +20685,6 @@ snapshots: normalize-path@3.0.0: {} - normalize-url@4.5.1: - optional: true - - normalize-url@6.1.0: - optional: true - normalize-url@8.1.0: {} npm-package-arg@11.0.3: @@ -24934,14 +20707,6 @@ snapshots: transitivePeerDependencies: - supports-color - npm-run-path@2.0.2: - dependencies: - path-key: 2.0.1 - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - npmlog@4.1.2: dependencies: are-we-there-yet: 1.1.7 @@ -24967,31 +20732,12 @@ snapshots: object-assign@4.1.1: {} - object-copy@0.1.0: - dependencies: - copy-descriptor: 0.1.1 - define-property: 0.2.5 - kind-of: 3.2.2 - object-inspect@1.10.3: {} - object-inspect@1.12.3: {} - object-inspect@1.13.4: {} - object-is@1.1.6: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - - object-keys@0.4.0: {} - object-keys@1.1.1: {} - object-visit@1.0.1: - dependencies: - isobject: 3.0.1 - object.assign@4.1.7: dependencies: call-bind: 1.0.8 @@ -25008,31 +20754,12 @@ snapshots: es-abstract: 1.24.0 es-object-atoms: 1.1.1 - object.getownpropertydescriptors@2.1.8: - dependencies: - array.prototype.reduce: 1.0.8 - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-object-atoms: 1.1.1 - gopd: 1.2.0 - safe-array-concat: 1.1.3 - object.groupby@1.0.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 - object.omit@2.0.1: - dependencies: - for-own: 0.1.5 - is-extendable: 0.1.1 - - object.pick@1.3.0: - dependencies: - isobject: 3.0.1 - object.values@1.2.1: dependencies: call-bind: 1.0.8 @@ -25042,11 +20769,6 @@ snapshots: obliterator@2.0.5: {} - oboe@2.1.4: - dependencies: - http-https: 1.0.0 - optional: true - on-exit-leak-free@0.2.0: {} on-finished@2.3.0: @@ -25108,18 +20830,10 @@ snapshots: ordinal@1.0.3: {} - os-homedir@1.0.2: {} - os-locale@1.4.0: dependencies: lcid: 1.0.0 - os-locale@2.1.0: - dependencies: - execa: 0.7.0 - lcid: 1.0.0 - mem: 1.1.0 - os-tmpdir@1.0.2: {} outdent@0.5.0: {} @@ -25160,12 +20874,6 @@ snapshots: transitivePeerDependencies: - zod - p-cancelable@1.1.0: - optional: true - - p-cancelable@2.1.1: - optional: true - p-cancelable@3.0.0: {} p-filter@2.1.0: @@ -25174,10 +20882,6 @@ snapshots: p-finally@1.0.0: {} - p-limit@1.3.0: - dependencies: - p-try: 1.0.0 - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -25190,10 +20894,6 @@ snapshots: dependencies: yocto-queue: 1.2.1 - p-locate@2.0.0: - dependencies: - p-limit: 1.3.0 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -25223,8 +20923,6 @@ snapshots: dependencies: p-finally: 1.0.0 - p-try@1.0.0: {} - p-try@2.2.0: {} package-json-from-dist@1.0.1: {} @@ -25234,7 +20932,7 @@ snapshots: got: 12.6.1 registry-auth-token: 5.1.0 registry-url: 6.0.1 - semver: 7.7.2 + semver: 7.7.3 package-manager-detector@0.2.11: dependencies: @@ -25251,16 +20949,6 @@ snapshots: dependencies: callsites: 3.1.0 - parse-asn1@5.1.7: - dependencies: - asn1.js: 4.10.1 - browserify-aes: 1.2.0 - evp_bytestokey: 1.0.3 - hash-base: 3.0.5 - pbkdf2: 3.1.3 - safe-buffer: 5.2.1 - optional: true - parse-cache-control@1.0.1: {} parse-entities@4.0.2: @@ -25279,15 +20967,6 @@ snapshots: map-cache: 0.2.2 path-root: 0.1.1 - parse-glob@3.0.4: - dependencies: - glob-base: 0.3.0 - is-dotfile: 1.0.3 - is-extglob: 1.0.0 - is-glob: 2.0.1 - - parse-headers@2.0.6: {} - parse-json@2.2.0: dependencies: error-ex: 1.3.4 @@ -25301,51 +20980,15 @@ snapshots: dependencies: '@babel/code-frame': 7.27.1 error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - parseurl@1.3.3: {} - - pascal-case@3.1.2: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - - pascalcase@0.1.1: {} - - patch-package@6.2.2: - dependencies: - '@yarnpkg/lockfile': 1.1.0 - chalk: 2.4.2 - cross-spawn: 6.0.6 - find-yarn-workspace-root: 1.2.1 - fs-extra: 7.0.1 - is-ci: 2.0.0 - klaw-sync: 6.0.0 - minimist: 1.2.8 - rimraf: 2.7.1 - semver: 5.7.2 - slash: 2.0.0 - tmp: 0.0.33 - transitivePeerDependencies: - - supports-color + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parseurl@1.3.3: {} - patch-package@6.5.1: + pascal-case@3.1.2: dependencies: - '@yarnpkg/lockfile': 1.1.0 - chalk: 4.1.2 - cross-spawn: 6.0.6 - find-yarn-workspace-root: 2.0.0 - fs-extra: 9.1.0 - is-ci: 2.0.0 - klaw-sync: 6.0.0 - minimist: 1.2.8 - open: 7.4.2 - rimraf: 2.7.1 - semver: 5.7.2 - slash: 2.0.0 - tmp: 0.0.33 - yaml: 1.10.2 + no-case: 3.0.4 + tslib: 2.8.1 path-browserify@1.0.1: {} @@ -25358,16 +21001,12 @@ snapshots: dependencies: pinkie-promise: 2.0.1 - path-exists@3.0.0: {} - path-exists@4.0.0: {} path-exists@5.0.0: {} path-is-absolute@1.0.1: {} - path-key@2.0.1: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -25388,10 +21027,12 @@ snapshots: lru-cache: 11.2.1 minipass: 7.1.2 - path-starts-with@2.0.1: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.1 + minipass: 7.1.3 - path-to-regexp@0.1.12: - optional: true + path-starts-with@2.0.1: {} path-to-regexp@0.1.7: {} @@ -25418,8 +21059,6 @@ snapshots: sha.js: 2.4.12 to-buffer: 1.2.1 - pegjs@0.10.0: {} - performance-now@2.1.0: {} pg-cloudflare@1.2.7: @@ -25485,8 +21124,6 @@ snapshots: picomatch@4.0.3: {} - pidtree@0.5.0: {} - pidtree@0.6.0: {} pify@2.3.0: {} @@ -25527,8 +21164,6 @@ snapshots: pluralize@8.0.0: {} - posix-character-classes@0.1.1: {} - possible-typed-array-names@1.1.0: {} postgres-array@2.0.0: {} @@ -25541,8 +21176,6 @@ snapshots: dependencies: xtend: 4.0.2 - postinstall-postinstall@2.1.0: {} - prebuild-install@5.3.6: dependencies: detect-libc: 1.0.3 @@ -25579,17 +21212,10 @@ snapshots: tunnel-agent: 0.6.0 optional: true - precond@0.2.3: {} - prelude-ls@1.1.2: {} prelude-ls@1.2.1: {} - prepend-http@2.0.0: - optional: true - - preserve@0.2.0: {} - prettier-plugin-solidity@2.1.0(prettier@3.8.1): dependencies: '@nomicfoundation/slang': 1.2.0 @@ -25607,14 +21233,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - private@0.1.8: {} - proc-log@4.2.0: {} process-nextick-args@2.0.1: {} - process@0.11.10: {} - prom-client@14.0.1: dependencies: tdigest: 0.1.2 @@ -25630,11 +21252,6 @@ snapshots: promise-throttle@1.1.2: {} - promise-to-callback@1.0.0: - dependencies: - is-fn: 1.0.0 - set-immediate-shim: 1.0.1 - promise@7.3.1: dependencies: asap: 2.0.6 @@ -25669,49 +21286,10 @@ snapshots: prr@1.0.1: {} - pseudomap@1.0.2: {} - psl@1.15.0: dependencies: punycode: 2.3.1 - public-encrypt@4.0.3: - dependencies: - bn.js: 4.12.2 - browserify-rsa: 4.1.1 - create-hash: 1.2.0 - parse-asn1: 5.1.7 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - optional: true - - pull-cat@1.1.11: {} - - pull-defer@0.2.3: {} - - pull-level@2.0.4: - dependencies: - level-post: 1.0.7 - pull-cat: 1.1.11 - pull-live: 1.0.1 - pull-pushable: 2.2.0 - pull-stream: 3.7.0 - pull-window: 2.1.4 - stream-to-pull-stream: 1.7.3 - - pull-live@1.0.1: - dependencies: - pull-cat: 1.1.11 - pull-stream: 3.7.0 - - pull-pushable@2.2.0: {} - - pull-stream@3.7.0: {} - - pull-window@2.1.4: - dependencies: - looper: 2.0.0 - pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -25741,11 +21319,6 @@ snapshots: dependencies: side-channel: 1.1.0 - qs@6.13.0: - dependencies: - side-channel: 1.1.0 - optional: true - qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -25758,13 +21331,6 @@ snapshots: quansync@0.2.11: {} - query-string@5.1.1: - dependencies: - decode-uri-component: 0.2.2 - object-assign: 4.1.1 - strict-uri-encode: 1.1.0 - optional: true - queue-microtask@1.2.3: {} queue@6.0.2: @@ -25775,22 +21341,10 @@ snapshots: quick-lru@5.1.1: {} - randomatic@3.1.1: - dependencies: - is-number: 4.0.0 - kind-of: 6.0.3 - math-random: 1.0.4 - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 - randomfill@1.0.4: - dependencies: - randombytes: 2.1.0 - safe-buffer: 5.2.1 - optional: true - range-parser@1.2.1: {} raw-body@2.4.2: @@ -25913,20 +21467,6 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 - readable-stream@1.0.34: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 0.0.1 - string_decoder: 0.10.31 - - readable-stream@1.1.14: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 0.0.1 - string_decoder: 0.10.31 - readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -25943,14 +21483,6 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@2.2.1: - dependencies: - graceful-fs: 4.2.11 - micromatch: 3.1.10 - readable-stream: 2.3.8 - transitivePeerDependencies: - - supports-color - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -25980,27 +21512,8 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - regenerate@1.4.2: {} - - regenerator-runtime@0.11.1: {} - regenerator-runtime@0.13.11: {} - regenerator-transform@0.10.1: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - private: 0.1.8 - - regex-cache@0.4.4: - dependencies: - is-equal-shallow: 0.1.3 - - regex-not@1.0.2: - dependencies: - extend-shallow: 3.0.2 - safe-regex: 1.1.0 - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -26010,12 +21523,6 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - regexpu-core@2.0.0: - dependencies: - regenerate: 1.4.2 - regjsgen: 0.2.0 - regjsparser: 0.1.5 - registry-auth-token@5.1.0: dependencies: '@pnpm/npm-conf': 2.3.1 @@ -26024,12 +21531,6 @@ snapshots: dependencies: rc: 1.2.8 - regjsgen@0.2.0: {} - - regjsparser@0.1.5: - dependencies: - jsesc: 0.5.0 - relay-runtime@12.0.0(encoding@0.1.13): dependencies: '@babel/runtime': 7.28.4 @@ -26040,14 +21541,6 @@ snapshots: remove-trailing-separator@1.1.0: {} - repeat-element@1.1.4: {} - - repeat-string@1.6.1: {} - - repeating@2.0.1: - dependencies: - is-finite: 1.1.0 - req-cwd@2.0.0: dependencies: req-from: 2.0.0 @@ -26099,8 +21592,6 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve-url@0.2.1: {} - resolve.exports@2.0.3: {} resolve@1.1.7: {} @@ -26115,16 +21606,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - responselike@1.0.2: - dependencies: - lowercase-keys: 1.0.1 - optional: true - - responselike@2.0.1: - dependencies: - lowercase-keys: 2.0.0 - optional: true - responselike@3.0.0: dependencies: lowercase-keys: 3.0.0 @@ -26139,8 +21620,6 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 - ret@0.1.15: {} - retry-as-promised@5.0.0: {} retry-as-promised@7.1.1: {} @@ -26183,7 +21662,7 @@ snapshots: dependencies: bn.js: 5.2.2 - rocketh@0.17.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76): + rocketh@0.17.13(patch_hash=9922612567456c164edd9dd5a0c9304bfd66babcebfe7c39dca333659ff1248f)(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76): dependencies: '@rocketh/core': 0.17.8(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) @@ -26234,10 +21713,6 @@ snapshots: safe-buffer@5.2.1: {} - safe-event-emitter@1.0.1: - dependencies: - events: 3.3.0 - safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -26249,10 +21724,6 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - safe-regex@1.1.0: - dependencies: - ret: 0.1.15 - safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} @@ -26278,11 +21749,6 @@ snapshots: scrypt-js@3.0.1: {} - scryptsy@1.2.1: - dependencies: - pbkdf2: 3.1.3 - optional: true - secp256k1@4.0.4: dependencies: elliptic: 6.6.1 @@ -26291,16 +21757,10 @@ snapshots: secure-keys@1.0.0: {} - seedrandom@3.0.1: {} - seedrandom@3.0.5: {} semaphore-async-await@1.5.1: {} - semaphore@1.1.0: {} - - semver@5.4.1: {} - semver@5.7.2: {} semver@6.3.1: {} @@ -26375,7 +21835,7 @@ snapshots: dependencies: '@types/debug': 4.1.12 '@types/validator': 13.15.3 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) dottie: 2.0.6 inflection: 1.13.4 lodash: 4.17.21 @@ -26383,7 +21843,7 @@ snapshots: moment-timezone: 0.5.48 pg-connection-string: 2.9.1 retry-as-promised: 5.0.0 - semver: 7.7.2 + semver: 7.7.3 sequelize-pool: 7.1.0 toposort-class: 1.0.1 uuid: 8.3.2 @@ -26399,7 +21859,7 @@ snapshots: dependencies: '@types/debug': 4.1.12 '@types/validator': 13.15.3 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) dottie: 2.0.6 inflection: 1.13.4 lodash: 4.17.21 @@ -26452,17 +21912,6 @@ snapshots: transitivePeerDependencies: - supports-color - servify@0.1.12: - dependencies: - body-parser: 1.20.3 - cors: 2.8.5 - express: 4.21.2 - request: 2.88.2 - xhr: 2.6.0 - transitivePeerDependencies: - - supports-color - optional: true - set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -26481,21 +21930,12 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - set-immediate-shim@1.0.1: {} - set-proto@1.0.0: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 - set-value@2.0.1: - dependencies: - extend-shallow: 2.0.1 - is-extendable: 0.1.1 - is-plain-object: 2.0.4 - split-string: 3.1.0 - setimmediate@1.0.5: {} setprototypeof@1.2.0: {} @@ -26513,16 +21953,10 @@ snapshots: shallowequal@1.1.0: {} - shebang-command@1.2.0: - dependencies: - shebang-regex: 1.0.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} shell-quote@1.8.3: {} @@ -26570,13 +22004,6 @@ snapshots: simple-concat@1.0.1: optional: true - simple-get@2.8.2: - dependencies: - decompress-response: 3.3.0 - once: 1.4.0 - simple-concat: 1.0.1 - optional: true - simple-get@3.1.1: dependencies: decompress-response: 4.2.1 @@ -26592,31 +22019,16 @@ snapshots: sisteransi@1.0.5: {} - slash@1.0.0: {} - - slash@2.0.0: {} - slash@3.0.0: {} slash@5.1.0: {} - slice-ansi@3.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - slice-ansi@4.0.0: dependencies: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - slice-ansi@5.0.0: - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 4.0.0 - slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 @@ -26633,33 +22045,10 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 - snapdragon-node@2.1.1: - dependencies: - define-property: 1.0.0 - isobject: 3.0.1 - snapdragon-util: 3.0.1 - - snapdragon-util@3.0.1: - dependencies: - kind-of: 3.2.2 - - snapdragon@0.8.2: - dependencies: - base: 0.11.2 - debug: 2.6.9 - define-property: 0.2.5 - extend-shallow: 2.0.1 - map-cache: 0.2.2 - source-map: 0.5.7 - source-map-resolve: 0.5.3 - use: 3.1.1 - transitivePeerDependencies: - - supports-color - socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) socks: 2.8.7 transitivePeerDependencies: - supports-color @@ -26669,10 +22058,6 @@ snapshots: ip-address: 10.0.1 smart-buffer: 4.2.0 - sol-digger@0.0.2: {} - - sol-explore@1.6.1: {} - solc@0.4.26: dependencies: fs-extra: 0.30.0 @@ -26681,17 +22066,6 @@ snapshots: semver: 5.7.2 yargs: 4.8.1 - solc@0.6.12: - dependencies: - command-exists: 1.2.9 - commander: 3.0.2 - fs-extra: 0.30.0 - js-sha3: 0.8.0 - memorystream: 0.3.1 - require-from-string: 2.0.2 - semver: 5.7.2 - tmp: 0.0.33 - solc@0.8.15: dependencies: command-exists: 1.2.9 @@ -26716,28 +22090,28 @@ snapshots: transitivePeerDependencies: - debug - solhint@6.0.3(typescript@5.9.3): + solhint@6.2.1(typescript@5.9.3): dependencies: '@solidity-parser/parser': 0.20.2 - ajv: 6.12.6 - ajv-errors: 1.0.1(ajv@6.12.6) + ajv: 8.20.0 + ajv-errors: 3.0.0(ajv@8.20.0) ast-parents: 0.0.1 - better-ajv-errors: 2.0.2(ajv@6.12.6) + better-ajv-errors: 2.0.2(ajv@8.20.0) chalk: 4.1.2 commander: 10.0.1 cosmiconfig: 8.3.6(typescript@5.9.3) fast-diff: 1.3.0 - glob: 8.1.0 + glob: 13.0.6 ignore: 5.3.2 - js-yaml: 4.1.0 + js-yaml: 4.1.1 latest-version: 7.0.0 lodash: 4.17.21 pluralize: 8.0.0 - semver: 7.7.2 + semver: 7.7.3 table: 6.9.0 text-table: 0.2.0 optionalDependencies: - prettier: 2.8.8 + prettier: 3.8.1 transitivePeerDependencies: - typescript @@ -26838,62 +22212,15 @@ snapshots: hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) solidity-ast: 0.4.61 - solium-plugin-security@0.1.1(solium@1.2.5): - dependencies: - solium: 1.2.5 - - solium@1.2.5: - dependencies: - ajv: 5.5.2 - chokidar: 1.7.0 - colors: 1.4.0 - commander: 2.20.3 - diff: 3.5.0 - eol: 0.9.1 - js-string-escape: 1.0.1 - lodash: 4.17.21 - sol-digger: 0.0.2 - sol-explore: 1.6.1 - solium-plugin-security: 0.1.1(solium@1.2.5) - solparse: 2.2.8 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - solparse@2.2.8: - dependencies: - mocha: 4.1.0 - pegjs: 0.10.0 - yargs: 10.1.2 - sonic-boom@2.8.0: dependencies: atomic-sleep: 1.0.0 - source-map-resolve@0.5.3: - dependencies: - atob: 2.1.2 - decode-uri-component: 0.2.2 - resolve-url: 0.2.1 - source-map-url: 0.4.1 - urix: 0.1.0 - - source-map-support@0.4.18: - dependencies: - source-map: 0.5.7 - - source-map-support@0.5.12: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - source-map-url@0.4.1: {} - source-map@0.2.0: dependencies: amdefine: 1.0.1 @@ -26922,10 +22249,6 @@ snapshots: spdx-license-ids@3.0.22: {} - split-string@3.1.0: - dependencies: - extend-shallow: 3.0.2 - split2@3.2.2: dependencies: readable-stream: 3.6.2 @@ -26966,11 +22289,6 @@ snapshots: dependencies: type-fest: 0.7.1 - static-extend@0.1.2: - dependencies: - define-property: 0.2.5 - object-copy: 0.1.0 - statuses@1.5.0: {} statuses@2.0.1: {} @@ -26982,16 +22300,8 @@ snapshots: stream-shift@1.0.3: {} - stream-to-pull-stream@1.7.3: - dependencies: - looper: 3.0.0 - pull-stream: 3.7.0 - streamsearch@1.1.0: {} - strict-uri-encode@1.1.0: - optional: true - string-argv@0.3.2: {} string-format@2.0.0: {} @@ -27053,8 +22363,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - string_decoder@0.10.31: {} - string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -27089,10 +22397,6 @@ snapshots: strip-bom@3.0.0: {} - strip-eof@1.0.0: {} - - strip-final-newline@2.0.0: {} - strip-hex-prefix@1.0.0: dependencies: is-hex-prefixed: 1.0.0 @@ -27103,16 +22407,10 @@ snapshots: strnum@2.1.1: {} - supports-color@2.0.0: {} - supports-color@3.2.3: dependencies: has-flag: 1.0.0 - supports-color@4.4.0: - dependencies: - has-flag: 2.0.0 - supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -27125,33 +22423,12 @@ snapshots: dependencies: has-flag: 4.0.0 - supports-color@9.4.0: {} - supports-preserve-symlinks-flag@1.0.0: {} swap-case@2.0.2: dependencies: tslib: 2.8.1 - swarm-js@0.1.42(bufferutil@4.0.9)(utf-8-validate@5.0.10): - dependencies: - bluebird: 3.7.2 - buffer: 5.7.1 - eth-lib: 0.1.29(bufferutil@4.0.9)(utf-8-validate@5.0.10) - fs-extra: 4.0.3 - got: 11.8.6 - mime-types: 2.1.35 - mkdirp-promise: 5.0.1 - mock-fs: 4.14.0 - setimmediate: 1.0.5 - tar: 4.4.19 - xhr-request: 1.1.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - optional: true - sync-request@6.1.0: dependencies: http-response-object: 3.0.2 @@ -27171,31 +22448,12 @@ snapshots: table@6.9.0: dependencies: - ajv: 8.17.1 + ajv: 8.20.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 strip-ansi: 6.0.1 - tape@4.17.0: - dependencies: - '@ljharb/resumer': 0.0.1 - '@ljharb/through': 2.3.14 - call-bind: 1.0.8 - deep-equal: 1.1.2 - defined: 1.0.1 - dotignore: 0.1.2 - for-each: 0.3.5 - glob: 7.2.3 - has: 1.0.4 - inherits: 2.0.4 - is-regex: 1.1.4 - minimist: 1.2.8 - mock-property: 1.0.3 - object-inspect: 1.12.3 - resolve: 1.22.10 - string.prototype.trim: 1.2.10 - tar-fs@2.1.3: dependencies: chownr: 1.1.4 @@ -27213,17 +22471,6 @@ snapshots: readable-stream: 3.6.2 optional: true - tar@4.4.19: - dependencies: - chownr: 1.1.4 - fs-minipass: 1.2.7 - minipass: 2.9.0 - minizlib: 1.3.3 - mkdirp: 0.5.6 - safe-buffer: 5.2.1 - yallist: 3.1.1 - optional: true - tar@6.2.1: dependencies: chownr: 2.0.0 @@ -27252,11 +22499,6 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - test-value@2.1.0: - dependencies: - array-back: 1.0.4 - typical: 2.6.1 - testrpc@0.0.1: {} text-extensions@2.4.0: {} @@ -27285,11 +22527,6 @@ snapshots: throat@5.0.0: {} - through2@2.0.5: - dependencies: - readable-stream: 2.3.8 - xtend: 4.0.2 - through2@3.0.2: dependencies: inherits: 2.0.4 @@ -27299,10 +22536,7 @@ snapshots: dependencies: readable-stream: 3.6.2 - through@2.3.8: {} - - timed-out@4.0.1: - optional: true + through@2.3.8: {} tiny-lru@8.0.2: {} @@ -27321,10 +22555,6 @@ snapshots: dependencies: os-tmpdir: 1.0.2 - tmp@0.1.0: - dependencies: - rimraf: 2.7.1 - tmpl@1.0.5: {} to-buffer@1.2.1: @@ -27333,31 +22563,10 @@ snapshots: safe-buffer: 5.2.1 typed-array-buffer: 1.0.3 - to-fast-properties@1.0.3: {} - - to-object-path@0.3.0: - dependencies: - kind-of: 3.2.2 - - to-readable-stream@1.0.0: - optional: true - - to-regex-range@2.1.1: - dependencies: - is-number: 3.0.0 - repeat-string: 1.6.1 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - to-regex@3.0.2: - dependencies: - define-property: 2.0.2 - extend-shallow: 3.0.2 - regex-not: 1.0.2 - safe-regex: 1.1.0 - toidentifier@1.0.1: {} toposort-class@1.0.1: {} @@ -27369,20 +22578,8 @@ snapshots: tr46@0.0.3: {} - trim-right@1.0.1: {} - triple-beam@1.4.1: {} - truffle-flattener@1.6.0: - dependencies: - '@resolver-engine/imports-fs': 0.2.2 - '@solidity-parser/parser': 0.14.5 - find-up: 2.1.0 - mkdirp: 1.0.4 - tsort: 0.0.1 - transitivePeerDependencies: - - supports-color - ts-algebra@1.2.2: {} ts-api-utils@2.4.0(typescript@5.9.3): @@ -27396,28 +22593,10 @@ snapshots: command-line-usage: 6.1.3 string-format: 2.0.0 - ts-essentials@1.0.4: {} - - ts-essentials@6.0.7(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - ts-essentials@7.0.3(typescript@5.9.3): dependencies: typescript: 5.9.3 - ts-generator@0.1.1: - dependencies: - '@types/mkdirp': 0.5.2 - '@types/prettier': 2.7.3 - '@types/resolve': 0.0.8 - chalk: 2.4.2 - glob: 7.2.3 - mkdirp: 0.5.6 - prettier: 2.8.8 - resolve: 1.22.10 - ts-essentials: 1.0.4 - ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -27483,12 +22662,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 - tweetnacl-util@0.15.1: {} - tweetnacl@0.14.5: {} - tweetnacl@1.0.3: {} - type-check@0.3.2: dependencies: prelude-ls: 1.1.2 @@ -27512,25 +22687,10 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type@2.7.3: {} - - typechain@3.0.0(typescript@5.9.3): - dependencies: - command-line-args: 4.0.7 - debug: 4.4.3(supports-color@9.4.0) - fs-extra: 7.0.1 - js-sha3: 0.8.0 - lodash: 4.17.21 - ts-essentials: 6.0.7(typescript@5.9.3) - ts-generator: 0.1.1 - transitivePeerDependencies: - - supports-color - - typescript - typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3): dependencies: '@types/prettier': 2.7.3 - debug: 4.4.3(supports-color@9.4.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 7.0.1 glob: 7.1.7 js-sha3: 0.8.0 @@ -27576,10 +22736,6 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typedarray-to-buffer@3.1.5: - dependencies: - is-typedarray: 1.0.0 - typedarray@0.0.6: {} typescript-eslint@8.53.1(eslint@9.39.2(jiti@2.5.1))(typescript@5.9.3): @@ -27595,16 +22751,6 @@ snapshots: typescript@5.9.3: {} - typewise-core@1.2.0: {} - - typewise@1.0.3: - dependencies: - typewise-core: 1.2.0 - - typewiselite@1.0.0: {} - - typical@2.6.1: {} - typical@4.0.0: {} typical@5.2.0: {} @@ -27618,9 +22764,6 @@ snapshots: uglify-js@3.19.3: optional: true - ultron@1.1.1: - optional: true - unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -27632,9 +22775,6 @@ snapshots: underscore@1.13.7: {} - underscore@1.9.1: - optional: true - undici-types@6.21.0: {} undici@5.29.0: @@ -27647,13 +22787,6 @@ snapshots: unicorn-magic@0.1.0: {} - union-value@1.0.1: - dependencies: - arr-union: 3.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - set-value: 2.0.1 - unique-filename@3.0.0: dependencies: unique-slug: 4.0.0 @@ -27670,15 +22803,8 @@ snapshots: dependencies: normalize-path: 2.1.1 - unorm@1.6.0: {} - unpipe@1.0.0: {} - unset-value@1.0.0: - dependencies: - has-value: 0.3.1 - isobject: 3.0.1 - update-browserslist-db@1.1.3(browserslist@4.26.0): dependencies: browserslist: 4.26.0 @@ -27697,16 +22823,6 @@ snapshots: dependencies: punycode: 2.3.1 - urix@0.1.0: {} - - url-parse-lax@3.0.0: - dependencies: - prepend-http: 2.0.0 - optional: true - - url-set-query@1.0.0: - optional: true - url@0.11.4: dependencies: punycode: 1.4.1 @@ -27722,11 +22838,10 @@ snapshots: node-gyp-build: 4.8.4 optional: true - use@3.1.1: {} - utf-8-validate@5.0.10: dependencies: node-gyp-build: 4.8.4 + optional: true utf-8-validate@5.0.7: dependencies: @@ -27737,26 +22852,8 @@ snapshots: util-deprecate@1.0.2: {} - util.promisify@1.1.3: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - for-each: 0.3.5 - get-intrinsic: 1.3.0 - has-proto: 1.2.0 - has-symbols: 1.1.0 - object.getownpropertydescriptors: 2.1.8 - safe-array-concat: 1.1.3 - utils-merge@1.0.1: {} - uuid@3.3.2: - optional: true - uuid@3.4.0: {} uuid@8.3.2: {} @@ -27776,9 +22873,6 @@ snapshots: value-or-promise@1.0.12: {} - varint@5.0.2: - optional: true - vary@1.1.2: {} verror@1.10.0: @@ -27829,232 +22923,6 @@ snapshots: web-streams-polyfill@3.3.3: {} - web3-bzz@1.2.11(bufferutil@4.0.9)(utf-8-validate@5.0.10): - dependencies: - '@types/node': 20.19.14 - got: 9.6.0 - swarm-js: 0.1.42(bufferutil@4.0.9)(utf-8-validate@5.0.10) - underscore: 1.9.1 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - optional: true - - web3-core-helpers@1.2.11: - dependencies: - underscore: 1.9.1 - web3-eth-iban: 1.2.11 - web3-utils: 1.2.11 - optional: true - - web3-core-method@1.2.11: - dependencies: - '@ethersproject/transactions': 5.8.0 - underscore: 1.9.1 - web3-core-helpers: 1.2.11 - web3-core-promievent: 1.2.11 - web3-core-subscriptions: 1.2.11 - web3-utils: 1.2.11 - optional: true - - web3-core-promievent@1.2.11: - dependencies: - eventemitter3: 4.0.4 - optional: true - - web3-core-requestmanager@1.2.11: - dependencies: - underscore: 1.9.1 - web3-core-helpers: 1.2.11 - web3-providers-http: 1.2.11 - web3-providers-ipc: 1.2.11 - web3-providers-ws: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - - web3-core-subscriptions@1.2.11: - dependencies: - eventemitter3: 4.0.4 - underscore: 1.9.1 - web3-core-helpers: 1.2.11 - optional: true - - web3-core@1.2.11: - dependencies: - '@types/bn.js': 4.11.6 - '@types/node': 20.19.14 - bignumber.js: 9.3.1 - web3-core-helpers: 1.2.11 - web3-core-method: 1.2.11 - web3-core-requestmanager: 1.2.11 - web3-utils: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - - web3-eth-abi@1.2.11: - dependencies: - '@ethersproject/abi': 5.0.0-beta.153 - underscore: 1.9.1 - web3-utils: 1.2.11 - optional: true - - web3-eth-accounts@1.2.11: - dependencies: - crypto-browserify: 3.12.0 - eth-lib: 0.2.8 - ethereumjs-common: 1.5.0 - ethereumjs-tx: 2.1.2 - scrypt-js: 3.0.1 - underscore: 1.9.1 - uuid: 3.3.2 - web3-core: 1.2.11 - web3-core-helpers: 1.2.11 - web3-core-method: 1.2.11 - web3-utils: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - - web3-eth-contract@1.2.11: - dependencies: - '@types/bn.js': 4.11.6 - underscore: 1.9.1 - web3-core: 1.2.11 - web3-core-helpers: 1.2.11 - web3-core-method: 1.2.11 - web3-core-promievent: 1.2.11 - web3-core-subscriptions: 1.2.11 - web3-eth-abi: 1.2.11 - web3-utils: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - - web3-eth-ens@1.2.11: - dependencies: - content-hash: 2.5.2 - eth-ens-namehash: 2.0.8 - underscore: 1.9.1 - web3-core: 1.2.11 - web3-core-helpers: 1.2.11 - web3-core-promievent: 1.2.11 - web3-eth-abi: 1.2.11 - web3-eth-contract: 1.2.11 - web3-utils: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - - web3-eth-iban@1.2.11: - dependencies: - bn.js: 4.12.2 - web3-utils: 1.2.11 - optional: true - - web3-eth-personal@1.2.11: - dependencies: - '@types/node': 20.19.14 - web3-core: 1.2.11 - web3-core-helpers: 1.2.11 - web3-core-method: 1.2.11 - web3-net: 1.2.11 - web3-utils: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - - web3-eth@1.2.11: - dependencies: - underscore: 1.9.1 - web3-core: 1.2.11 - web3-core-helpers: 1.2.11 - web3-core-method: 1.2.11 - web3-core-subscriptions: 1.2.11 - web3-eth-abi: 1.2.11 - web3-eth-accounts: 1.2.11 - web3-eth-contract: 1.2.11 - web3-eth-ens: 1.2.11 - web3-eth-iban: 1.2.11 - web3-eth-personal: 1.2.11 - web3-net: 1.2.11 - web3-utils: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - - web3-net@1.2.11: - dependencies: - web3-core: 1.2.11 - web3-core-method: 1.2.11 - web3-utils: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - - web3-provider-engine@14.2.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10): - dependencies: - async: 2.6.4 - backoff: 2.5.0 - clone: 2.1.2 - cross-fetch: 2.2.6(encoding@0.1.13) - eth-block-tracker: 3.0.1 - eth-json-rpc-infura: 3.2.1(encoding@0.1.13) - eth-sig-util: 1.4.2 - ethereumjs-block: 1.7.1 - ethereumjs-tx: 1.3.7 - ethereumjs-util: 5.2.1 - ethereumjs-vm: 2.6.0 - json-rpc-error: 2.0.0 - json-stable-stringify: 1.3.0 - promise-to-callback: 1.0.0 - readable-stream: 2.3.8 - request: 2.88.2 - semaphore: 1.1.0 - ws: 5.2.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) - xhr: 2.6.0 - xtend: 4.0.2 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - - web3-providers-http@1.2.11: - dependencies: - web3-core-helpers: 1.2.11 - xhr2-cookies: 1.1.0 - optional: true - - web3-providers-ipc@1.2.11: - dependencies: - oboe: 2.1.4 - underscore: 1.9.1 - web3-core-helpers: 1.2.11 - optional: true - - web3-providers-ws@1.2.11: - dependencies: - eventemitter3: 4.0.4 - underscore: 1.9.1 - web3-core-helpers: 1.2.11 - websocket: 1.0.32 - transitivePeerDependencies: - - supports-color - optional: true - - web3-shh@1.2.11: - dependencies: - web3-core: 1.2.11 - web3-core-method: 1.2.11 - web3-core-subscriptions: 1.2.11 - web3-net: 1.2.11 - transitivePeerDependencies: - - supports-color - optional: true - web3-utils@1.10.4: dependencies: '@ethereumjs/util': 8.1.0 @@ -28066,33 +22934,6 @@ snapshots: randombytes: 2.1.0 utf8: 3.0.0 - web3-utils@1.2.11: - dependencies: - bn.js: 4.12.2 - eth-lib: 0.2.8 - ethereum-bloom-filters: 1.2.0 - ethjs-unit: 0.1.6 - number-to-bn: 1.7.0 - randombytes: 2.1.0 - underscore: 1.9.1 - utf8: 3.0.0 - optional: true - - web3@1.2.11(bufferutil@4.0.9)(utf-8-validate@5.0.10): - dependencies: - web3-bzz: 1.2.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) - web3-core: 1.2.11 - web3-eth: 1.2.11 - web3-eth-personal: 1.2.11 - web3-net: 1.2.11 - web3-shh: 1.2.11 - web3-utils: 1.2.11 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - optional: true - webcrypto-core@1.8.1: dependencies: '@peculiar/asn1-schema': 2.5.0 @@ -28103,19 +22944,6 @@ snapshots: webidl-conversions@3.0.1: {} - websocket@1.0.32: - dependencies: - bufferutil: 4.0.9 - debug: 2.6.9 - es5-ext: 0.10.64 - typedarray-to-buffer: 3.1.5 - utf-8-validate: 5.0.10 - yaeti: 0.0.6 - transitivePeerDependencies: - - supports-color - - whatwg-fetch@2.0.4: {} - whatwg-fetch@3.6.20: {} whatwg-url@5.0.0: @@ -28265,23 +23093,6 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - ws@3.3.3(bufferutil@4.0.9)(utf-8-validate@5.0.10): - dependencies: - async-limiter: 1.0.1 - safe-buffer: 5.1.2 - ultron: 1.1.1 - optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 - optional: true - - ws@5.2.4(bufferutil@4.0.9)(utf-8-validate@5.0.10): - dependencies: - async-limiter: 1.0.1 - optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 - ws@6.2.3(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: async-limiter: 1.0.1 @@ -28319,38 +23130,6 @@ snapshots: bufferutil: 4.0.9 utf-8-validate: 5.0.10 - xhr-request-promise@0.1.3: - dependencies: - xhr-request: 1.1.0 - optional: true - - xhr-request@1.1.0: - dependencies: - buffer-to-arraybuffer: 0.0.5 - object-assign: 4.1.1 - query-string: 5.1.1 - simple-get: 2.8.2 - timed-out: 4.0.1 - url-set-query: 1.0.0 - xhr: 2.6.0 - optional: true - - xhr2-cookies@1.1.0: - dependencies: - cookiejar: 2.1.4 - optional: true - - xhr@2.6.0: - dependencies: - global: 4.4.0 - is-function: 1.0.2 - parse-headers: 2.0.6 - xtend: 4.0.2 - - xtend@2.1.2: - dependencies: - object-keys: 0.4.0 - xtend@4.0.2: {} y18n@3.2.2: {} @@ -28359,10 +23138,6 @@ snapshots: y18n@5.0.8: {} - yaeti@0.0.6: {} - - yallist@2.1.2: {} - yallist@3.1.1: {} yallist@4.0.0: {} @@ -28392,10 +23167,6 @@ snapshots: yargs-parser@21.1.1: {} - yargs-parser@8.1.0: - dependencies: - camelcase: 4.1.0 - yargs-unparser@2.0.0: dependencies: camelcase: 6.3.0 @@ -28403,21 +23174,6 @@ snapshots: flat: 5.0.2 is-plain-obj: 2.1.0 - yargs@10.1.2: - dependencies: - cliui: 4.1.0 - decamelize: 1.2.0 - find-up: 2.1.0 - get-caller-file: 1.0.3 - os-locale: 2.1.0 - require-directory: 2.1.1 - require-main-filename: 1.0.1 - set-blocking: 2.0.0 - string-width: 2.1.1 - which-module: 2.0.1 - y18n: 3.2.2 - yargs-parser: 8.1.0 - yargs@15.4.1: dependencies: cliui: 6.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0a60d1cce..fa5c80080 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -58,7 +58,7 @@ catalog: mocha: ^11.7.5 prettier: ^3.7.4 prettier-plugin-solidity: ^2.1.0 - solhint: ^6.0.3 + solhint: ^6.2.1 ts-node: ^10.9.2 typechain: ^8.3.2 typescript: ^5.9.3