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/package.json b/package.json index 45282aa78..972633896 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "lint:json": "prettier -w --cache --log-level warn 'packages/**/*.json' '.changeset/**/*.json' '*.json'", "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", "test:coverage": "pnpm build && pnpm -r run build:self:coverage && pnpm -r run test:coverage:self" }, "devDependencies": { @@ -55,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": { 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..152071cff 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.0", "publishConfig": { "access": "public" }, diff --git a/packages/address-book/scripts/copy-addresses-for-publish.js b/packages/address-book/scripts/copy-addresses-for-publish.js index 6335f7dc5..75a563d64 100755 --- a/packages/address-book/scripts/copy-addresses-for-publish.js +++ b/packages/address-book/scripts/copy-addresses-for-publish.js @@ -3,8 +3,8 @@ /** * 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. + * This script copies the actual addresses.json files from horizon, issuance, and + * subgraph-service packages to replace the symlinks before npm publish. * * Why we need this: * - Development uses symlinks (committed to git) for convenience 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-test/tests/unit/rewards/rewards-interface.test.ts b/packages/contracts-test/tests/unit/rewards/rewards-interface.test.ts index 7bbfebe6b..db77d5f9b 100644 --- a/packages/contracts-test/tests/unit/rewards/rewards-interface.test.ts +++ b/packages/contracts-test/tests/unit/rewards/rewards-interface.test.ts @@ -1,5 +1,10 @@ import { RewardsManager } from '@graphprotocol/contracts' -import { IERC165__factory, IIssuanceTarget__factory, IRewardsManager__factory } from '@graphprotocol/interfaces/types' +import { + IERC165__factory, + IIssuanceTarget__factory, + IProviderEligibilityManagement__factory, + IRewardsManager__factory, +} from '@graphprotocol/interfaces/types' import { GraphNetworkContracts, toGRT } from '@graphprotocol/sdk' import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' @@ -78,6 +83,11 @@ describe('RewardsManager interfaces', () => { expect(supports).to.be.true }) + it('should support IProviderEligibilityManagement interface', async function () { + const supports = await rewardsManager.supportsInterface(IProviderEligibilityManagement__factory.interfaceId) + expect(supports).to.be.true + }) + it('should return false for unsupported interfaces', async function () { // Test with an unknown interface ID const unknownInterfaceId = '0x12345678' // Random interface ID 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/hardhat.config.ts b/packages/data-edge/hardhat.config.ts index 807580f49..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', @@ -101,7 +86,7 @@ const config: HardhatUserConfig = { solidity: { compilers: [ { - version: '0.8.12', + version: '0.8.35', settings: { optimizer: { enabled: true, @@ -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..2819769c4 --- /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..5b3350e94 --- /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..c9dcd90db --- /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..ca465ae66 100644 --- a/packages/deployment/deploy/allocate/direct/01_impl.ts +++ b/packages/deployment/deploy/allocate/direct/01_impl.ts @@ -1,82 +1,78 @@ -import { getTargetChainIdFromEnv } from '@graphprotocol/deployment/lib/address-book-utils.js' -import { loadDirectAllocationArtifact } from '@graphprotocol/deployment/lib/artifact-loaders.js' +import { getLibraryResolver, loadDirectAllocationArtifact } from '@graphprotocol/deployment/lib/artifact-loaders.js' +import { computeBytecodeHash } from '@graphprotocol/deployment/lib/bytecode-utils.js' import { Contracts } from '@graphprotocol/deployment/lib/contract-registry.js' -import { SpecialTags, Tags } from '@graphprotocol/deployment/lib/deployment-tags.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) + // 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 resolver = getLibraryResolver('issuance') + const bytecodeHash = computeBytecodeHash( + artifact.deployedBytecode ?? '0x', + artifact.deployedLinkReferences, + resolver, + ) - 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`) - } + await graph.updateIssuanceAddressBook(env, { + name: Contracts.issuance.DirectAllocation_Implementation.name, + address: result.address, + deployment: { + txHash: result.transaction?.hash ?? '', + argsData: result.argsData, + bytecodeHash, + }, + }) } + + 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..f55ac1713 --- /dev/null +++ b/packages/deployment/deploy/gip/0088/10_status.ts @@ -0,0 +1,182 @@ +import { + IISSUANCE_TARGET_INTERFACE_ID, + ISSUANCE_TARGET_ABI, + PROVIDER_ELIGIBILITY_MANAGEMENT_ABI, + SUBGRAPH_SERVICE_CLOSE_GUARD_ABI, +} from '@graphprotocol/deployment/lib/abis.js' +import { 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 = + contract.addressBook === 'subgraph-service' + ? graph.getSubgraphServiceAddressBook(targetChainId) + : graph.getHorizonAddressBook(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..2dbc35825 --- /dev/null +++ b/packages/deployment/deploy/gip/0088/upgrade/04_upgrade.ts @@ -0,0 +1,453 @@ +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 type { AnyAddressBookOps } from '@graphprotocol/deployment/lib/address-book-ops.js' +import { 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: AnyAddressBookOps = + abType === 'subgraph-service' + ? graph.getSubgraphServiceAddressBook(targetChainId) + : abType === 'issuance' + ? graph.getIssuanceAddressBook(targetChainId) + : graph.getHorizonAddressBook(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..352b04d4f --- /dev/null +++ b/packages/deployment/deploy/gip/0088/upgrade/10_status.ts @@ -0,0 +1,334 @@ +import { IISSUANCE_TARGET_INTERFACE_ID } from '@graphprotocol/deployment/lib/abis.js' +import { 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 = + contract.addressBook === 'subgraph-service' + ? graph.getSubgraphServiceAddressBook(targetChainId) + : graph.getHorizonAddressBook(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..d6dbcdc09 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 (rem-local-network) + +The `localNetwork` network targets the Graph local network docker-compose stack (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 (symlinked to mounted config) +- 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 are populated by Phase 1 (hardhat-graph-protocol deploys Horizon + SubgraphService) +- Phase 2+ deployment scripts use this package to deploy additional contracts (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/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..2be1995ba 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 (rem-local-network, docker-compose stack) + 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,17 @@ const config: HardhatUserConfig = { } : undefined, }, + // Graph Local Network (rem-local-network docker-compose stack) + // Contracts deployed fresh with hardhat-graph-protocol (Phase 1) + // Address books use 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 +304,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-utils.ts b/packages/deployment/lib/address-book-utils.ts index 0de0db016..1ab196ac7 100644 --- a/packages/deployment/lib/address-book-utils.ts +++ b/packages/deployment/lib/address-book-utils.ts @@ -32,24 +32,150 @@ import { AddressBookOps } from './address-book-ops.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 (rem-local-network). + * + * The local network uses chainId 1337 and deploys contracts from scratch. + * Address books use addresses-local-network.json files which are symlinked + * to mounted config files in the Docker container (populated by Phase 1). + */ +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 +201,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 +243,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 +346,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 +354,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 +371,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 +388,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') } diff --git a/packages/deployment/lib/apply-configuration.ts b/packages/deployment/lib/apply-configuration.ts index b7615b844..8bc4e14a1 100644 --- a/packages/deployment/lib/apply-configuration.ts +++ b/packages/deployment/lib/apply-configuration.ts @@ -17,7 +17,7 @@ import { type RoleCondition, checkConditions, } from './contract-checks.js' -import { createGovernanceTxBuilder, executeTxBatchDirect, saveGovernanceTxAndExit } from './execute-governance.js' +import { createGovernanceTxBuilder, executeTxBatchDirect, saveGovernanceTx } from './execute-governance.js' /** * Options for applyConfiguration @@ -145,10 +145,8 @@ export async function applyConfiguration( 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..ef2608578 100644 --- a/packages/deployment/lib/deploy-implementation.ts +++ b/packages/deployment/lib/deploy-implementation.ts @@ -1,10 +1,13 @@ 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 { + getLibraryResolver, + linkArtifactLibraries, loadContractsArtifact, + loadHorizonBuildArtifact, loadIssuanceArtifact, loadOpenZeppelinArtifact, loadSubgraphServiceArtifact, @@ -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 @@ -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': @@ -236,6 +241,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,8 +276,11 @@ 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 @@ -284,16 +293,55 @@ export async function deployImplementation( : graph.getHorizonAddressBook(targetChainId) // Compute local artifact bytecode hash (for storing with deployment) - const localBytecodeHash = computeBytecodeHash(artifact.deployedBytecode ?? '0x') + const resolver = getLibraryResolver(artifactSource.type) + const localBytecodeHash = computeBytecodeHash( + artifact.deployedBytecode ?? '0x', + artifact.deployedLinkReferences, + resolver, + ) + + // 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`) @@ -335,7 +383,7 @@ 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', + argsData: impl.argsData, bytecodeHash: localBytecodeHash, ...(blockNumber !== undefined && { blockNumber }), ...(timestamp && { timestamp }), diff --git a/packages/deployment/lib/deploy-standalone.ts b/packages/deployment/lib/deploy-standalone.ts new file mode 100644 index 000000000..0b7b2c9fd --- /dev/null +++ b/packages/deployment/lib/deploy-standalone.ts @@ -0,0 +1,79 @@ +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}`) + } + + // Update address book based on which book the contract belongs to + if (contract.addressBook === 'horizon') { + await graph.updateHorizonAddressBook(env, { + name: contract.name, + address: result.address, + }) + } else if (contract.addressBook === 'issuance') { + await graph.updateIssuanceAddressBook(env, { + 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..7f845950d --- /dev/null +++ b/packages/deployment/lib/deployment-config.ts @@ -0,0 +1,138 @@ +import { readFileSync } from 'node:fs' +import { resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import type { Environment } from '@rocketh/core/types' + +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', + }, +} + +/** + * Strip single-line // comments from JSON5-style content so it can be parsed + * by JSON.parse. Preserves strings containing //. + */ +function stripComments(text: string): string { + return text.replace(/^\s*\/\/.*$/gm, '').replace(/,(\s*[}\]])/g, '$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 JSON.parse(stripComments(raw)) as DeploymentConfigFile + } 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-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..a7ac62727 100644 --- a/packages/deployment/lib/issuance-deploy-utils.ts +++ b/packages/deployment/lib/issuance-deploy-utils.ts @@ -3,6 +3,7 @@ import type { Environment } from '@rocketh/core/types' import type { PublicClient } from 'viem' import { encodeFunctionData } from 'viem' +import type { AnyAddressBookOps } from './address-book-ops.js' import { Contracts, type RegistryEntry } from './contract-registry.js' import { getGovernor } from './controller-utils.js' import { @@ -12,9 +13,10 @@ import { loadArtifactFromSource, } from './deploy-implementation.js' import { loadTransparentProxyArtifact } from './artifact-loaders.js' -import { INITIALIZE_GOVERNOR_ABI } from './abis.js' +import { INITIALIZE_GOVERNOR_ABI, OZ_PROXY_ADMIN_ABI } from './abis.js' import { computeBytecodeHash } from './bytecode-utils.js' -import { deploy, graph } from '../rocketh/deploy.js' +import { getTargetChainIdFromEnv } from './address-book-utils.js' +import { deploy, execute, graph } from '../rocketh/deploy.js' /** ERC1967 admin slot: keccak256("eip1967.proxy.admin") - 1 */ const ERC1967_ADMIN_SLOT = '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103' @@ -36,6 +38,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 +138,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 +166,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, @@ -156,14 +178,20 @@ export async function updateProxyAddressBook( proxyAdminAddress?: string, implementationDeployment?: DeploymentMetadata, ) { - await graphUtils.updateIssuanceAddressBook(env, { + const update = { name: contract.name, address: proxyAddress, - proxy: 'transparent', + proxy: 'transparent' as const, proxyAdmin: proxyAdminAddress, implementation: implAddress, implementationDeployment, - }) + } + + if (contract.addressBook === 'horizon') { + await graphUtils.updateHorizonAddressBook(env, update) + } else { + await graphUtils.updateIssuanceAddressBook(env, update) + } } /** @@ -234,7 +262,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 +299,58 @@ 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: AnyAddressBookOps = + contract.addressBook === 'horizon' + ? graph.getHorizonAddressBook(targetChainId) + : graph.getIssuanceAddressBook(targetChainId) + + // Get deployment metadata from the shared implementation's address book entry + const implMetadata = addressBook.getDeploymentMetadata(sharedImplementation.name) + addressBook.setPendingImplementationWithMetadata( + contract.name, + implDep.address, + implMetadata ?? { txHash: '', bytecodeHash: '' }, + ) + + 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 +390,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 +402,6 @@ export async function deployProxyContract( async function deployProxyWithOwnImpl( env: Environment, contract: RegistryEntry, - governor: string, constructorArgs: unknown[], initializeArgs: unknown[], deployer: string, @@ -348,16 +422,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 +440,7 @@ async function deployProxyWithOwnImpl( { account: deployer, artifact: proxyArtifact, - args: [implResult.address, governor, initCalldata], + args: [implResult.address, deployer, initCalldata], }, { skipIfAlreadyDeployed: true }, ) @@ -405,7 +480,7 @@ async function deployProxyWithOwnImpl( 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 +499,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 +521,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 +530,7 @@ async function deployProxyWithSharedImpl( { account: deployer, artifact: proxyArtifact, - args: [implDep.address, governor, initCalldata], + args: [implDep.address, deployer, initCalldata], }, { skipIfAlreadyDeployed: true }, ) @@ -475,7 +551,7 @@ async function deployProxyWithSharedImpl( 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 +562,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..b460271ce --- /dev/null +++ b/packages/deployment/lib/status-detail.ts @@ -0,0 +1,1139 @@ +/** + * 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 { 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 + : contract.addressBook === 'subgraph-service' + ? graph.getSubgraphServiceAddressBook(chainId) + : graph.getIssuanceAddressBook(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..a5022a177 100644 --- a/packages/deployment/lib/sync-utils.ts +++ b/packages/deployment/lib/sync-utils.ts @@ -1,8 +1,21 @@ +import { existsSync } from 'node:fs' + import type { Artifact, Environment } from '@rocketh/core/types' import type { DeploymentMetadata } from '@graphprotocol/toolshed/deployments' import { + autoDetectForkNetwork, + getForkNetwork, + getForkStateDir, + getForkTargetChainId, + getIssuanceAddressBookPath, + getTargetChainIdFromEnv, + isForkMode, +} from './address-book-utils.js' +import { + getLibraryResolver, loadContractsArtifact, + loadHorizonBuildArtifact, loadIssuanceArtifact, loadOpenZeppelinArtifact, loadSubgraphServiceArtifact, @@ -12,9 +25,12 @@ import { type AddressBookType, type ArtifactSource, type ContractMetadata, + type RegistryEntry, getAddressBookEntryName, getContractMetadata, + getContractsByAddressBook, } from './contract-registry.js' +import { SpecialTags } from './deployment-tags.js' import { getOnChainImplementation } from './deploy-implementation.js' import { graph } from '../rocketh/deploy.js' import type { AnyAddressBookOps } from './address-book-ops.js' @@ -22,11 +38,11 @@ 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 '' @@ -46,6 +62,8 @@ function loadArtifactFromSource(source: ArtifactSource): Artifact | undefined { switch (source.type) { case 'contracts': return loadContractsArtifact(source.path, source.name) + case 'horizon': + return loadHorizonBuildArtifact(source.path) case 'subgraph-service': return loadSubgraphServiceArtifact(source.name) case 'issuance': @@ -111,7 +129,12 @@ export function checkShouldSync( if (metadata?.bytecodeHash && artifact) { const loadedArtifact = loadArtifactFromSource(artifact) if (loadedArtifact?.deployedBytecode) { - const localHash = computeBytecodeHash(loadedArtifact.deployedBytecode) + const libResolver = getLibraryResolver(artifact.type) + const localHash = computeBytecodeHash( + loadedArtifact.deployedBytecode, + loadedArtifact.deployedLinkReferences, + libResolver, + ) if (metadata.bytecodeHash !== localHash) { return { shouldSync: false, @@ -170,7 +193,12 @@ export function reconstructDeploymentRecord( } if (deploymentMetadata.bytecodeHash && loadedArtifact.deployedBytecode) { - const localHash = computeBytecodeHash(loadedArtifact.deployedBytecode) + const libResolver = getLibraryResolver(artifact.type) + const localHash = computeBytecodeHash( + loadedArtifact.deployedBytecode, + loadedArtifact.deployedLinkReferences, + libResolver, + ) if (deploymentMetadata.bytecodeHash !== localHash) { // Bytecode has changed - cannot reconstruct reliably return undefined @@ -215,6 +243,89 @@ export function createDeploymentMetadata( } } +/** + * Check if local artifact bytecode differs from what was last deployed. + * + * 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). + * + * @returns codeChanged flag and the computed localHash (needed for hashMatches checks) + */ +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 = loadArtifactFromSource(artifactSource) + const resolver = getLibraryResolver(artifactSource.type) + const localHash = localArtifact?.deployedBytecode + ? computeBytecodeHash(localArtifact.deployedBytecode, localArtifact.deployedLinkReferences, resolver) + : undefined + + 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 +344,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 +383,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 +410,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 +462,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 +478,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 +525,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 +593,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,32 +630,25 @@ 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) 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) { @@ -544,10 +660,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 @@ -625,42 +741,52 @@ 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') { @@ -742,31 +868,47 @@ 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 = 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}` + } } + 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[] @@ -787,11 +929,99 @@ 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 = rockethRecord.receipt?.blockNumber + ? parseInt(rockethRecord.receipt.blockNumber as string) + : undefined + const addressBookBlockNumber = entry.deployment?.blockNumber + + const rockethIsNewer = + !entry.deployment?.argsData || + (rockethBlockNumber !== undefined && addressBookBlockNumber === undefined) || + (rockethBlockNumber !== undefined && + addressBookBlockNumber !== undefined && + rockethBlockNumber > addressBookBlockNumber) + + if (rockethIsNewer) { + const deploymentMetadata: DeploymentMetadata = { + txHash: rockethRecord.transaction?.hash ?? '', + argsData: rockethRecord.argsData, + bytecodeHash: rockethRecord.deployedBytecode ? computeBytecodeHash(rockethRecord.deployedBytecode) : '', + ...(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 +1030,248 @@ 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 } } +/** + * Resolve address book instance for a given address book type and chain ID + */ +function getAddressBookForType(addressBookType: AddressBookType, chainId: number) { + switch (addressBookType) { + case 'horizon': + return graph.getHorizonAddressBook(chainId) + case 'subgraph-service': + return graph.getSubgraphServiceAddressBook(chainId) + case 'issuance': + return graph.getIssuanceAddressBook(chainId) + } +} + +/** + * 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 +1282,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 +1347,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 +1364,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 +1380,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 +1420,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 +1456,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..72473073e --- /dev/null +++ b/packages/deployment/lib/task-utils.ts @@ -0,0 +1,139 @@ +/** + * 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 { type AddressBookType, CONTRACT_REGISTRY } from './contract-registry.js' +import { graph } from '../rocketh/deploy.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 = + addressBook === 'issuance' + ? graph.getIssuanceAddressBook(chainId) + : addressBook === 'horizon' + ? graph.getHorizonAddressBook(chainId) + : graph.getSubgraphServiceAddressBook(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..3b35d482d 100644 --- a/packages/deployment/lib/upgrade-implementation.ts +++ b/packages/deployment/lib/upgrade-implementation.ts @@ -5,9 +5,10 @@ import { getTargetChainIdFromEnv } from './address-book-utils.js' import type { AnyAddressBookOps } from './address-book-ops.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 +20,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 +32,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 +64,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 +112,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,17 +126,27 @@ 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' @@ -146,19 +158,49 @@ export async function upgradeImplementation( // 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) + addressBookInstance.setPendingImplementationWithMetadata( + contractName, + implDep.address, + implMetadata ?? { txHash: '', bytecodeHash: '' }, + ) + 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 +211,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 +229,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 +249,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..384150c88 100644 --- a/packages/deployment/rocketh/deploy.ts +++ b/packages/deployment/rocketh/deploy.ts @@ -6,6 +6,7 @@ import { execute, read, tx } from '@rocketh/read-execute' import { createPublicClient, custom } from 'viem' import { + autoDetectForkNetwork, getForkTargetChainId, getHorizonAddressBook, getIssuanceAddressBook, @@ -16,9 +17,9 @@ import { 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,10 +30,15 @@ export interface IssuanceDeploymentUpdate { implementation?: string /** Proxy type if this is a proxied contract */ proxy?: 'transparent' | 'graph' - /** Implementation deployment metadata (for verification) */ + /** Implementation deployment metadata (for verification of proxied contracts) */ implementationDeployment?: DeploymentMetadata + /** Deployment metadata (for verification of non-proxied contracts) */ + deployment?: DeploymentMetadata } +/** @deprecated Use DeploymentUpdate */ +export type IssuanceDeploymentUpdate = DeploymentUpdate + /** * Graph Protocol deployment helpers * @@ -56,6 +62,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,6 +103,42 @@ export const graph = { */ getIssuanceAddressBook: (chainId?: number) => getIssuanceAddressBook(chainId), + /** + * Update horizon address book after deploying a contract. + * Supports both standalone and proxied contracts. + * + * @param env - Rocketh environment (used to get chain ID from provider) + * @param update - Deployment update details + */ + updateHorizonAddressBook: async (env: Environment, update: DeploymentUpdate) => { + const chainId = await getTargetChainIdFromEnv(env) + const addressBook = getHorizonAddressBook(chainId) + + if (update.proxy) { + addressBook.setProxy( + update.name as Parameters[0], + update.address, + update.implementation!, + update.proxyAdmin!, + update.proxy, + ) + if (update.implementationDeployment) { + addressBook.setImplementationDeploymentMetadata( + update.name as Parameters[0], + update.implementationDeployment, + ) + } + } else { + addressBook.setContract(update.name as Parameters[0], update.address) + if (update.deployment) { + addressBook.setDeploymentMetadata( + update.name as Parameters[0], + update.deployment, + ) + } + } + }, + /** * Update issuance address book after deploying a contract. * Call this after rocketh's deployViaProxy or deploy to sync the address book. @@ -118,6 +167,12 @@ export const graph = { } } else { addressBook.setContract(update.name as Parameters[0], update.address) + if (update.deployment) { + addressBook.setDeploymentMetadata( + update.name as Parameters[0], + update.deployment, + ) + } } }, } 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..921465dae 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,6 +8,7 @@ import path from 'path' import { decodeAbiParameters } from 'viem' import type { AnyAddressBookOps } from '../lib/address-book-ops.js' +import { getLibraryResolver } from '../lib/artifact-loaders.js' import { computeBytecodeHash } from '../lib/bytecode-utils.js' import { type AddressBookType, @@ -17,7 +18,8 @@ import { getContractsByAddressBook, } from '../lib/contract-registry.js' import { loadArtifactFromSource } from '../lib/deploy-implementation.js' -import { verifyOZProxy } from '../lib/oz-proxy-verify.js' +import { checkEtherscanVerified, verifyOZProxy } from '../lib/oz-proxy-verify.js' +import { resolveConfigVar } from '../lib/task-utils.js' import { graph } from '../rocketh/deploy.js' const ADDRESS_BOOK_TYPES: AddressBookType[] = ['horizon', 'subgraph-service', 'issuance'] @@ -31,6 +33,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 +54,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 +86,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 +119,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 +128,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) @@ -348,7 +335,9 @@ function checkBytecodeMatch( } // Compare local artifact bytecodeHash with stored hash - const localBytecodeHash = computeBytecodeHash(artifact.deployedBytecode) + // Must pass linkReferences and resolver to match how hash was computed at deployment + const resolver = getLibraryResolver(metadata.artifact!.type) + const localBytecodeHash = computeBytecodeHash(artifact.deployedBytecode, artifact.deployedLinkReferences, resolver) if (localBytecodeHash !== deploymentMetadata.bytecodeHash) { return { matches: false, @@ -393,24 +382,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 +411,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 +553,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 +571,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/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/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/mocks/AfterCollectionGasReportingMock.sol b/packages/horizon/contracts/mocks/AfterCollectionGasReportingMock.sol new file mode 100644 index 000000000..cc2a2c788 --- /dev/null +++ b/packages/horizon/contracts/mocks/AfterCollectionGasReportingMock.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// solhint-disable no-unused-vars + +pragma solidity ^0.8.27; + +/** + * @title AfterCollectionGasReportingMock + * @author Edge & Node + * @notice Test-only mock used by the warm-path `CallbackGasProbe` boundary test. Returns + * `gasleft()` from `afterCollection` so the probe can read back the forwarded-gas value via + * returndata, and provides a no-op `isEligible` for the warm-up staticcall. + * + * @dev `afterCollection` shares its selector with `IAgreementOwner.afterCollection` but + * intentionally diverges on return type (returns `uint256` so the probe can decode the + * gasleft sample). Production dispatch in `RecurringCollector._postCollectCallback` discards + * returndata, so this divergence does not affect the gas accounting under measurement. + */ +contract AfterCollectionGasReportingMock { + /// @notice No-op warm-up target. Returning a value is irrelevant — the probe only runs + /// this to warm `payer`'s entry on the access list before the timed CALL. + /// @param payer Ignored. + /// @return Always true. + function isEligible(address payer) external pure returns (bool) { + payer; + return true; + } + + /// @notice Returns the `gasleft()` value observed at function entry. View so the probe + /// can be invoked via `eth_call` (Hardhat `staticCall`) without committing state. + /// @param agreementId Ignored. + /// @param tokens Ignored. + /// @return The result of `gasleft()` at function entry. + function afterCollection(bytes16 agreementId, uint256 tokens) external view returns (uint256) { + agreementId; + tokens; + return gasleft(); + } +} diff --git a/packages/horizon/contracts/mocks/CallbackGasProbe.sol b/packages/horizon/contracts/mocks/CallbackGasProbe.sol index 3bf76a5e6..48a29dc11 100644 --- a/packages/horizon/contracts/mocks/CallbackGasProbe.sol +++ b/packages/horizon/contracts/mocks/CallbackGasProbe.sol @@ -3,19 +3,22 @@ pragma solidity ^0.8.27; import { IProviderEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IProviderEligibility.sol"; +import { IAgreementOwner } from "@graphprotocol/interfaces/contracts/horizon/IAgreementOwner.sol"; /** * @title CallbackGasProbe * @author Edge & Node - * @notice Test-only contract that replicates the precheck + STATICCALL pattern used by - * `RecurringCollector._preCollectCallbacks` for the eligibility path. Exists so that + * @notice Test-only contract that replicates the precheck + STATICCALL/CALL patterns used by + * `RecurringCollector._preCollectCallbacks` (eligibility, cold path) and + * `RecurringCollector._postCollectCallback` (afterCollection, warm path). Exists so that * Hardhat-side tests can verify, on a real EIP-2929-applying EVM (foundry's REVM in this * project does not differentiate cold/warm in `gasleft()`-derived measurements), that - * `CALLBACK_GAS_OVERHEAD` covers the cold-account access cost on the staticcall. + * `CALLBACK_GAS_OVERHEAD` covers both the cold-account access cost on the staticcall and the + * warm-call dispatch overhead on the after-collection CALL. * - * @dev MUST be kept in sync with the equivalent block in `RecurringCollector.sol`. If the + * @dev MUST be kept in sync with the equivalent blocks in `RecurringCollector.sol`. If the * production constants (`MAX_PAYER_CALLBACK_GAS`, `CALLBACK_GAS_OVERHEAD`) or the precheck / - * staticcall sequence change, mirror the change here. This probe is not deployed to any + * staticcall / call sequence change, mirror the change here. This probe is not deployed to any * production network. */ contract CallbackGasProbe { @@ -24,6 +27,7 @@ contract CallbackGasProbe { error CallbackGasProbeInsufficientCallbackGas(); error CallbackGasProbeNotEligible(); + error CallbackGasProbeAfterCollectionFailed(); /** * @notice Re-runs the eligibility precheck + STATICCALL exactly as @@ -54,4 +58,59 @@ contract CallbackGasProbe { revert CallbackGasProbeNotEligible(); } } + + /** + * @notice Re-runs the after-collection precheck + CALL exactly as + * `RecurringCollector._postCollectCallback` does, against `payer`. Warms the payer first + * (mirroring the eligibility staticcall site that runs ahead of the after-callback in + * `_collect`) so the CALL itself measures warm-path δ. + * + * Returns the `gasleft()` the callee observed at function entry, captured from the + * callee's 32-byte return word. Boundary tests use this to assert that, at the lowest + * outer gas at which the precheck just clears, the forwarded gas stays within tolerance + * of `MAX_PAYER_CALLBACK_GAS` — i.e. `CALLBACK_GAS_OVERHEAD ≥ δ_warm`. Reverts with + * `CallbackGasProbeInsufficientCallbackGas` if the precheck blocks, or + * `CallbackGasProbeAfterCollectionFailed` if the CALL itself fails. + * + * @dev Diverges from production in exactly one respect: it reads back the callee's + * returndata so the test can observe the warm-path forwarded-gas value. Production + * dispatch in `_postCollectCallback` discards returndata via `call(..., 0, 0)`. The + * gas accounting up to and through the CALL opcode is identical. + * @param payer The contract whose `afterCollection` should be invoked. + * @return received The `gasleft()` value the callee saw at function entry. + */ + function probeAfterCollection(address payer) external returns (uint256 received) { + // Warm payer's account access list. In production, `_preCollectCallbacks` is the + // first to touch `payer` (eligibility staticcall), so by the time + // `_postCollectCallback` issues its CALL the account is warm. Replicate that here + // with a staticcall whose return value we don't care about. + bytes memory eligCd = abi.encodeCall(IProviderEligibility.isEligible, (address(0))); + // solhint-disable-next-line no-inline-assembly + assembly { + // Output buffer is irrelevant — we ignore the result; this exists only to warm payer. + pop(staticcall(MAX_PAYER_CALLBACK_GAS, payer, add(eligCd, 0x20), mload(eligCd), 0, 0)) + } + + // Precheck — same expression as `_postCollectCallback`. + if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) { + revert CallbackGasProbeInsufficientCallbackGas(); + } + + // CALL afterCollection — same opcode and gas limit as `_postCollectCallback`. + bytes memory cd = abi.encodeCall(IAgreementOwner.afterCollection, (bytes16(0), 0)); + bool ok; + // solhint-disable-next-line no-inline-assembly + assembly { + ok := call(MAX_PAYER_CALLBACK_GAS, payer, 0, add(cd, 0x20), mload(cd), 0, 0) + } + if (!ok) revert CallbackGasProbeAfterCollectionFailed(); + + // Capture the 32-byte gasleft value the callee returned. Note RC discards returndata + // here (call(..., 0, 0)); we read it back only so the test can assert on it. + // solhint-disable-next-line no-inline-assembly + assembly { + returndatacopy(0, 0, 32) + received := mload(0) + } + } } diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index d56d6580b..dd89dabad 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -135,11 +135,7 @@ contract RecurringCollector is } } - /** - * @notice List of pause guardians and their allowed status - * @param pauseGuardian The address to check - * @return Whether the address is a pause guardian - */ + /// @inheritdoc IRecurringCollector function pauseGuardians(address pauseGuardian) public view override returns (bool) { return _getStorage().pauseGuardians[pauseGuardian]; } @@ -208,12 +204,8 @@ contract RecurringCollector is emit PauseGuardianSet(_pauseGuardian, _allowed); } - /** - * @inheritdoc IPaymentsCollector - * @notice Initiate a payment collection through the payments protocol. - * See {IPaymentsCollector.collect}. - * @dev Caller must be the data service the RCA was issued to. - */ + /// @inheritdoc IPaymentsCollector + /// @dev Caller must be the data service the RCA was issued to. function collect( IGraphPayments.PaymentTypes paymentType, bytes calldata data @@ -225,11 +217,7 @@ contract RecurringCollector is } } - /** - * @inheritdoc IRecurringCollector - * @notice Accept a Recurring Collection Agreement. - * @dev Caller must be the data service the RCA was issued to. - */ + /// @inheritdoc IRecurringCollector function accept( RecurringCollectionAgreement calldata rca, bytes calldata signature @@ -321,12 +309,7 @@ contract RecurringCollector is agreement.updateNonce = 0; } - /** - * @inheritdoc IRecurringCollector - * @notice Cancel a Recurring Collection Agreement. - * See {IRecurringCollector.cancel}. - * @dev Caller must be the data service for the agreement. - */ + /// @inheritdoc IRecurringCollector function cancel(bytes16 agreementId, CancelAgreementBy by) external whenNotPaused { RecurringCollectorStorage storage $ = _getStorage(); AgreementData storage agreement = $.agreements[agreementId]; @@ -351,13 +334,9 @@ contract RecurringCollector is emit AgreementCanceled(agreement.dataService, agreement.payer, agreement.serviceProvider, agreementId, by); } - /** - * @inheritdoc IRecurringCollector - * @notice Update a Recurring Collection Agreement. - * @dev Caller must be the data service for the agreement. - * @dev Note: Updated pricing terms apply immediately and will affect the next collection - * for the entire period since lastCollectionAt. - */ + /// @inheritdoc IRecurringCollector + /// @dev Updated pricing terms apply immediately and affect the next collection for the + /// entire period since lastCollectionAt. function update(RecurringCollectionAgreementUpdate calldata rcau, bytes calldata signature) external whenNotPaused { AgreementData storage agreement = _requireValidUpdateTarget(rcau.agreementId); @@ -1351,8 +1330,11 @@ contract RecurringCollector is ); if (block.timestamp <= rcau.deadline) { + uint256 collectionStart = _a.state == AgreementState.Accepted + ? _agreementCollectionStartAt(_a) + : block.timestamp; uint256 maxPendingClaim = _maxClaim( - block.timestamp, + collectionStart, rcau.endsAt, rcau.maxSecondsPerCollection, rcau.maxOngoingTokensPerSecond, diff --git a/packages/horizon/foundry.toml b/packages/horizon/foundry.toml index c13f2ebe0..3f810e892 100644 --- a/packages/horizon/foundry.toml +++ b/packages/horizon/foundry.toml @@ -3,15 +3,30 @@ src = 'contracts' out = 'build' libs = ["node_modules"] test = 'test' -cache_path = 'cache_forge' -fs_permissions = [{ access = "read", path = "./"}] -optimizer = true -optimizer_runs = 100 +cache_path = 'cache_forge' +fs_permissions = [{ access = "read", path = "./" }] +solc_version = '0.8.35' +evm_version = 'cancun' +# Suppress solc warnings emitted from third-party code we don't control +# (e.g. @openzeppelin/foundry-upgrades' state-mutability suggestions). +ignored_warnings_from = ["node_modules"] # Exclude test files from coverage reports no_match_coverage = "(^test/|/mocks/)" +# Opt-in profile for production-matching bytecode (viaIR + optimizer). +# Use FOUNDRY_PROFILE=prod for CI / pre-merge runs that should mirror what hardhat +# compiles for deploy. Default profile stays optimizer-off / viaIR-off for fast iteration. +[profile.prod] +optimizer = true +optimizer_runs = 100 +via_ir = true + # Lint configuration [lint] +# Disable lint-on-build: forge build's auto-lint scans test/ files which use bytes32("...") +# literal IDs that trigger spurious unsafe-typecast warnings. `pnpm lint:forge` (scoped to +# contracts/) remains the canonical lint path for production code. +lint_on_build = false ignore = ["contracts/mocks/imports.sol"] exclude_lints = ["mixed-case-function", "mixed-case-variable", "block-timestamp"] diff --git a/packages/horizon/hardhat.config.ts b/packages/horizon/hardhat.config.ts index d9b1334e4..bbc804613 100644 --- a/packages/horizon/hardhat.config.ts +++ b/packages/horizon/hardhat.config.ts @@ -17,15 +17,6 @@ if (isProjectBuilt(__dirname)) { const baseConfig = hardhatBaseConfig(require) const config: HardhatUserConfig = { ...baseConfig, - solidity: { - version: '0.8.27', - settings: { - optimizer: { - enabled: true, - runs: 20, - }, - }, - }, etherscan: { ...baseConfig.etherscan, }, diff --git a/packages/horizon/ignition/configs/migrate.localNetwork.json5 b/packages/horizon/ignition/configs/migrate.localNetwork.json5 index 8b052634d..21f34880e 100644 --- a/packages/horizon/ignition/configs/migrate.localNetwork.json5 +++ b/packages/horizon/ignition/configs/migrate.localNetwork.json5 @@ -1,7 +1,7 @@ { "$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", diff --git a/packages/horizon/ignition/configs/protocol.localNetwork.json5 b/packages/horizon/ignition/configs/protocol.localNetwork.json5 index 2d3c08b39..5b5ea1e2c 100644 --- a/packages/horizon/ignition/configs/protocol.localNetwork.json5 +++ b/packages/horizon/ignition/configs/protocol.localNetwork.json5 @@ -1,8 +1,8 @@ { "$global": { // Accounts for new deployment - derived from hardhat default mnemonic - "pauseGuardian": "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d", // index 3 - "subgraphAvailabilityOracle": "0xd03ea8624C8C5987235048901fB614fDcA89b117", // index 4 + "pauseGuardian": "0x90F79bf6EB2c4f870365E785982E1f101E93b906", // index 3 + "subgraphAvailabilityOracle": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", // index 4 // Placeholder address for a standalone Horizon deployment, see README.md for more details "subgraphServiceAddress": "0x0000000000000000000000000000000000000000", 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/test/deployment/RecurringCollectorCallbackGas.test.ts b/packages/horizon/test/deployment/RecurringCollectorCallbackGas.test.ts index 9fb6b51ba..681405025 100644 --- a/packages/horizon/test/deployment/RecurringCollectorCallbackGas.test.ts +++ b/packages/horizon/test/deployment/RecurringCollectorCallbackGas.test.ts @@ -3,34 +3,40 @@ import hre from 'hardhat' import { ethers } from 'hardhat' /** - * Cold-path boundary check for `CALLBACK_GAS_OVERHEAD` in `RecurringCollector`. + * Boundary checks for `CALLBACK_GAS_OVERHEAD` in `RecurringCollector`. * * Foundry/REVM in this project does not differentiate cold/warm account access in * `gasleft()`-derived measurements (verified empirically: `vm.cool` produces no gas * differential, and a fresh-deployed contract's first staticcall costs the same as a - * subsequent one). The Foundry warm-path test - * (`test_Collect_Callbacks_ReceiveMaxPayerCallbackGas` in `afterCollection.t.sol`) bounds - * the warm δ; this Hardhat-side test exercises the cold δ that `CALLBACK_GAS_OVERHEAD` - * was actually sized for (EIP-2929 cold-account access ≈ 2_600 gas). + * subsequent one). Both directions — cold δ on the eligibility staticcall, warm δ on + * the after-collection CALL — therefore need a Hardhat-side test against an EIP-2929- + * applying EVM. Each `it(...)` block here covers one direction. * - * Each `await probe.probeEligibility(...)` is a fresh ethers transaction, so each one - * starts with an empty access list — the first access to `payer` inside the call genuinely - * incurs the cold-access cost on Hardhat Network's EVM (which does apply EIP-2929). - * - * Discriminator at the boundary: - * - `CallbackGasProbeInsufficientCallbackGas` at `hi - 1` → precheck is the gate, OVERHEAD - * covers cold δ. - * - `CallbackGasProbeNotEligible` at `hi - 1` → precheck passed but forwarded gas was - * below `MAX − tolerance`, i.e. OVERHEAD < cold δ. - * - * **If this test fails with `NotEligible`**, `CALLBACK_GAS_OVERHEAD` no longer covers the - * cold-access cost. Action: bump it in `RecurringCollector.sol` (and mirror in - * `CallbackGasProbe.sol`); do not raise `tolerance` here. + * The horizon Foundry tests retain the *negative* boundary + * (`test_Collect_Revert_WhenInsufficientCallbackGas*` in `afterCollection.t.sol`): + * `gasleft < threshold` reverts with `InsufficientCallbackGas`. Those exercises don't + * depend on cold/warm differentiation. The *positive* boundary — at lowest passing gas, + * the forwarded gas is within tolerance of MAX — does, and lives here. */ -describe('RecurringCollector callback gas overhead (cold path)', () => { +describe('RecurringCollector callback gas overhead', () => { const MAX_PAYER_CALLBACK_GAS = 1_500_000n const TOLERANCE = 500n + /** + * Each `await probe.probeEligibility(...)` is a fresh ethers transaction, so each one + * starts with an empty access list — the first access to `payer` inside the call genuinely + * incurs the cold-access cost on Hardhat Network's EVM (which does apply EIP-2929). + * + * Discriminator at the boundary: + * - `CallbackGasProbeInsufficientCallbackGas` at `hi - 1` → precheck is the gate, OVERHEAD + * covers cold δ. + * - `CallbackGasProbeNotEligible` at `hi - 1` → precheck passed but forwarded gas was + * below `MAX − tolerance`, i.e. OVERHEAD < cold δ. + * + * **If this test fails with `NotEligible`**, `CALLBACK_GAS_OVERHEAD` no longer covers the + * cold-access cost. Action: bump it in `RecurringCollector.sol` (and mirror in + * `CallbackGasProbe.sol`); do not raise `tolerance` here. + */ it('CALLBACK_GAS_OVERHEAD covers cold-access cost on the eligibility staticcall', async () => { // Deploy the probe and a gasleft-reporting eligibility mock. The mock returns // false from isEligible() if forwarded gas dropped below MAX_PAYER_CALLBACK_GAS - TOLERANCE. @@ -91,6 +97,75 @@ describe('RecurringCollector callback gas overhead (cold path)', () => { `boundary revert at hi-1 was ${failure.reason}, expected InsufficientCallbackGas — CALLBACK_GAS_OVERHEAD does not cover cold delta`, ).to.equal('InsufficientCallbackGas') }) + + /** + * Warm-path counterpart: the after-collection CALL in `_postCollectCallback`. The probe + * first warms `payer` via a staticcall (mirroring the eligibility site that runs ahead + * of the after-callback in `_collect`), then does the precheck + CALL pattern. + * + * At the lowest outer gas at which the precheck just clears, EIP-150's `gasleft * 63/64` + * cap is at its tightest — the forwarded gas is `MAX − δ_warm`, where δ_warm is the + * pre-CALL Solidity overhead between `gasleft()` and the CALL opcode plus the warm CALL + * fixed cost. Asserting `received >= MAX − tolerance` verifies that + * `CALLBACK_GAS_OVERHEAD ≥ δ_warm` at the production configuration. + * + * **If this test fails** look at *what changed*: + * - You added pre-CALL Solidity overhead (extra arg encoding, an SLOAD before the + * assembly block, code between `gasleft()` and CALL). + * Action: bump `CALLBACK_GAS_OVERHEAD` in `RecurringCollector.sol` (and mirror in + * `CallbackGasProbe.sol`); **don't** just raise `TOLERANCE` here. + * - You changed `MAX_PAYER_CALLBACK_GAS`. Update `MAX_PAYER_CALLBACK_GAS` here to follow. + */ + it('CALLBACK_GAS_OVERHEAD covers warm-path δ on the after-collection CALL', async () => { + const ProbeFactory = await ethers.getContractFactory('CallbackGasProbe') + const probe = await ProbeFactory.deploy() + await probe.waitForDeployment() + + const MockFactory = await ethers.getContractFactory('AfterCollectionGasReportingMock') + const mock = await MockFactory.deploy() + await mock.waitForDeployment() + + type Outcome = { ok: true; received: bigint } | { ok: false; reason: string } + + const callBoundary = async (gasLimit: bigint): Promise => { + try { + const received = await probe.probeAfterCollection.staticCall(await mock.getAddress(), { gasLimit }) + return { ok: true, received: BigInt(received) } + } catch (e: any) { + const data: string = e?.data ?? e?.info?.error?.data ?? '' + if (typeof data === 'string' && data.startsWith('0x')) { + const insufficientSel = probe.interface.getError('CallbackGasProbeInsufficientCallbackGas')!.selector + const failedSel = probe.interface.getError('CallbackGasProbeAfterCollectionFailed')!.selector + if (data.startsWith(insufficientSel)) return { ok: false, reason: 'InsufficientCallbackGas' } + if (data.startsWith(failedSel)) return { ok: false, reason: 'AfterCollectionFailed' } + } + return { ok: false, reason: 'oog-or-other' } + } + } + + // Binary search the lowest gas at which the probe succeeds. + let lo = 1_500_000n + let hi = 2_000_000n + while (hi - lo > 1n) { + const mid = (lo + hi) / 2n + const result = await callBoundary(mid) + if (result.ok) hi = mid + else lo = mid + } + + // Re-run at the lowest passing gas; this is where the precheck is just satisfied so the + // EIP-150 cap is tightest. The callee's recorded gasleft must stay within tolerance of MAX. + const settled = await callBoundary(hi) + expect(settled.ok, 'binary search settled on a gas value where probe should succeed').to.be.true + if (!settled.ok) return // narrowing for TS + + expect( + settled.received, + `afterCollection received ${settled.received} < MAX_PAYER_CALLBACK_GAS - TOLERANCE (${ + MAX_PAYER_CALLBACK_GAS - TOLERANCE + }) at boundary gas: CALLBACK_GAS_OVERHEAD margin eroded`, + ).to.be.gte(MAX_PAYER_CALLBACK_GAS - TOLERANCE) + }) }) // Suppress lint about unused hre import; some hardhat plugins require it for side effects. diff --git a/packages/horizon/test/unit/data-service/extensions/DataServiceFees.t.sol b/packages/horizon/test/unit/data-service/extensions/DataServiceFees.t.sol index 5692dd952..b5ab2442a 100644 --- a/packages/horizon/test/unit/data-service/extensions/DataServiceFees.t.sol +++ b/packages/horizon/test/unit/data-service/extensions/DataServiceFees.t.sol @@ -101,6 +101,15 @@ contract DataServiceFeesTest is HorizonStakingSharedTest { _assertReleaseStake(users.indexer, numClaimsToRelease); } + function test_ProcessStakeClaim_RevertWhen_ClaimNotFound() external { + StakeClaimsHarness harness = new StakeClaimsHarness(); + bytes32 unknownClaimId = keccak256("unknown"); + bytes memory acc = abi.encode(uint256(0), address(0)); + + vm.expectRevert(abi.encodeWithSelector(StakeClaims.StakeClaimsClaimNotFound.selector, unknownClaimId)); + harness.processStakeClaim(unknownClaimId, acc); + } + function test_Release_WhenNIsNotValid( uint256 tokens, uint256 steps @@ -238,3 +247,12 @@ contract DataServiceFeesTest is HorizonStakingSharedTest { assertEq(afterTail, calcValues.claimsCount == beforeCount ? bytes32(0) : beforeTail); } } + +contract StakeClaimsHarness { + mapping(address serviceProvider => uint256 tokens) public feesProvisionTracker; + mapping(bytes32 claimId => StakeClaims.StakeClaim claim) public claims; + + function processStakeClaim(bytes32 claimId, bytes memory acc) external returns (bool, bytes memory) { + return StakeClaims.processStakeClaim(feesProvisionTracker, claims, claimId, acc); + } +} diff --git a/packages/horizon/test/unit/payments/graph-tally-collector/GraphTallyCollector.t.sol b/packages/horizon/test/unit/payments/graph-tally-collector/GraphTallyCollector.t.sol index 4b05992f3..bd022f1d3 100644 --- a/packages/horizon/test/unit/payments/graph-tally-collector/GraphTallyCollector.t.sol +++ b/packages/horizon/test/unit/payments/graph-tally-collector/GraphTallyCollector.t.sol @@ -42,7 +42,7 @@ contract GraphTallyTest is HorizonStakingSharedTest, PaymentsEscrowSharedTest { * HELPERS */ - function _getSignerProof(uint256 _proofDeadline, uint256 _signer) internal returns (bytes memory) { + function _getSignerProof(uint256 _proofDeadline, uint256 _signer) internal view returns (bytes memory) { (, address msgSender, ) = vm.readCallers(); bytes32 messageHash = keccak256( abi.encodePacked( diff --git a/packages/horizon/test/unit/payments/recurring-collector/RecurringCollectorHelper.t.sol b/packages/horizon/test/unit/payments/recurring-collector/RecurringCollectorHelper.t.sol index a512d0321..29700d488 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/RecurringCollectorHelper.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/RecurringCollectorHelper.t.sol @@ -177,7 +177,7 @@ contract RecurringCollectorHelper is AuthorizableHelper, Bounder { function _sensibleMaxSecondsPerCollection( uint32 _seed, uint32 _minSecondsPerCollection - ) internal view returns (uint32) { + ) internal pure returns (uint32) { return uint32( bound( diff --git a/packages/horizon/test/unit/payments/recurring-collector/afterCollection.t.sol b/packages/horizon/test/unit/payments/recurring-collector/afterCollection.t.sol index 63d72d809..de2a01eb1 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/afterCollection.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/afterCollection.t.sol @@ -275,89 +275,6 @@ contract RecurringCollectorAfterCollectionTest is RecurringCollectorSharedTest { assertTrue(triggered, "eligibility precheck must trip InsufficientCallbackGas under tight gas"); } - /// @notice Boundary regression check on CALLBACK_GAS_OVERHEAD: at the lowest gas at which - /// `collect` succeeds, the after-callback precheck is just barely satisfied, so the EIP-150 - /// `gasleft * 63/64` cap is at its tightest. Asserting the callback's recorded gasleft stays - /// within `tolerance` of MAX_PAYER_CALLBACK_GAS verifies that `OVERHEAD ≥ δ` (the actual - /// pre-CALL Solidity overhead) at this collector configuration. Existing - /// `test_Collect_Revert_WhenInsufficientCallbackGas*` tests bracket the other side - /// (`gasleft < threshold` reverts with `InsufficientCallbackGas`). - /// - /// **If this test fails** look at *what changed*: - /// - You added pre-CALL Solidity overhead (extra arg encoding, an SLOAD before the assembly - /// block, or any code between the gasleft() precheck and the CALL opcode). - /// Action: bump `CALLBACK_GAS_OVERHEAD` in `RecurringCollector.sol` to cover the new δ, - /// or trim the new overhead. **Don't just raise `tolerance` here** — that silences the - /// alarm without restoring the production margin. - /// - You changed `MAX_PAYER_CALLBACK_GAS`. Update `expectedMin` to follow. - /// - You changed `MockAgreementOwner.afterCollection`'s prologue (added SLOADs, etc.) so the - /// `gasleft()` sample lands deeper into dispatch. Recorded baseline shifts. Adjust - /// `tolerance` only if you understand and accept the new overhead path. - /// - /// Numbers at the time of writing (CALLBACK_GAS_OVERHEAD = 3_000, MAX = 1_500_000, warm-call - /// δ ≈ 500, mock dispatch ≈ 300): recorded ≈ 1_499_700. tolerance 500 → assertion floor - /// 1_499_500, leaves ~200 gas of headroom. Reducing `OVERHEAD` to ~100 (sabotage check) - /// drops recorded to ~1_499_220 → fails. - function test_Collect_Callbacks_ReceiveMaxPayerCallbackGas() public { - // Skip under `forge coverage`: the optimizer is disabled there, so dispatch δ - // is materially larger than under production settings (optimizer + via_ir). The - // whole point of this test is asserting the production margin — measuring against - // unoptimized bytecode answers a different question. The package's coverage - // scripts set FOUNDRY_COVERAGE=1; running `forge coverage` directly without it - // will fail this test loudly, which is the correct outcome. - if (vm.envOr("FOUNDRY_COVERAGE", false)) { - vm.skip(true); - } - - uint256 expectedMin = 1_500_000; // mirrors MAX_PAYER_CALLBACK_GAS - uint256 tolerance = 500; - - MockAgreementOwner approver = _newApprover(); - (IRecurringCollector.RecurringCollectionAgreement memory rca, bytes16 agreementId) = _acceptUnsignedAgreement( - approver - ); - - skip(rca.minSecondsPerCollection); - bytes memory data = _generateCollectData( - _generateCollectParams(rca, agreementId, bytes32("col-budget"), 1 ether, 0) - ); - bytes memory callData = abi.encodeCall( - _recurringCollector.collect, - (IGraphPayments.PaymentTypes.IndexingFee, data) - ); - - // Binary-search the lowest external gas at which `collect` succeeds. With both - // CONDITION_AGREEMENT_OWNER callbacks live, the after-callback precheck is the - // bottleneck — at the lowest passing gas its precheck is just satisfied, so any - // shortfall in CALLBACK_GAS_OVERHEAD shows up in the after-callback's recorded gasleft. - uint256 lo = 1_500_000; // below threshold — collect reverts with InsufficientCallbackGas - uint256 hi = 2_000_000; // ample - while (hi - lo > 100) { - uint256 mid = (lo + hi) / 2; - uint256 snap = vm.snapshotState(); - vm.prank(rca.dataService); - (bool ok, ) = address(_recurringCollector).call{ gas: mid }(callData); - assertTrue(vm.revertToState(snap)); - if (ok) hi = mid; - else lo = mid; - } - - // Re-run at the lowest passing gas; this run's state we keep so the mock's recorded - // values are readable. - vm.prank(rca.dataService); - (bool finalOk, ) = address(_recurringCollector).call{ gas: hi }(callData); - assertTrue(finalOk, "collect failed at the gas value the binary search settled on"); - - // before-callback (cold first access) runs with plenty of remaining gas, so EIP-150 - // doesn't bite there even at the boundary. after-callback (warm) is where the cap - // tightens — that's the meaningful assertion. - assertGe( - approver.recordedAfterCollectionGasleft(), - expectedMin - tolerance, - "afterCollection received < MAX_PAYER_CALLBACK_GAS at boundary gas: CALLBACK_GAS_OVERHEAD margin eroded" - ); - } - function test_AfterCollection_NotCalledForEOAPayer(FuzzyTestCollect calldata fuzzy) public { // Use standard ECDSA-signed path (EOA payer, no contract) (IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, , , ) = _sensibleAuthorizeAndAccept( diff --git a/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol b/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol index 689cf5b48..36317702f 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol @@ -460,9 +460,6 @@ contract RecurringCollectorCoverageGapsTest is RecurringCollectorSharedTest { function test_GetCollectionInfo_ZeroCollectionSeconds(FuzzyTestAccept calldata fuzzy) public { (, , , bytes16 agreementId) = _sensibleAuthorizeAndAccept(fuzzy); - // Read agreement in the same block as accept - IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - (bool isCollectable, uint256 collectionSeconds, ) = _recurringCollector.getCollectionInfo(agreementId); assertFalse(isCollectable, "Should not be collectable with zero elapsed time"); diff --git a/packages/horizon/test/unit/payments/recurring-collector/eligibility.t.sol b/packages/horizon/test/unit/payments/recurring-collector/eligibility.t.sol index b507e522f..2b54d3f2b 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/eligibility.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/eligibility.t.sol @@ -128,12 +128,9 @@ contract RecurringCollectorEligibilityTest is RecurringCollectorSharedTest { function test_Collect_OK_WhenEOAPayer(FuzzyTestCollect calldata fuzzy) public { // Use standard ECDSA-signed path (EOA payer) - ( - IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, - , - , - bytes16 agreementId - ) = _sensibleAuthorizeAndAccept(fuzzy.fuzzyTestAccept); + (IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, , , ) = _sensibleAuthorizeAndAccept( + fuzzy.fuzzyTestAccept + ); (bytes memory data, uint256 collectionSeconds, uint256 tokens) = _generateValidCollection( acceptedRca, diff --git a/packages/horizon/test/unit/payments/recurring-collector/getMaxNextClaim.t.sol b/packages/horizon/test/unit/payments/recurring-collector/getMaxNextClaim.t.sol index fe792c059..679b582bc 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/getMaxNextClaim.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/getMaxNextClaim.t.sol @@ -367,7 +367,10 @@ contract RecurringCollectorGetMaxNextClaimTest is RecurringCollectorSharedTest { vm.prank(rca.dataService); _recurringCollector.collect(_paymentType(0), data); - uint256 lastCollectionAt = block.timestamp; + // vm.getBlockTimestamp() (external cheatcode CALL) instead of block.timestamp: + // under viaIR, the TIMESTAMP opcode is treated as pure and CSE-eliminates the + // local, re-reading block.timestamp at the use site post-warp. + uint256 lastCollectionAt = vm.getBlockTimestamp(); // Warp past endsAt vm.warp(rca.endsAt + 1000); @@ -391,7 +394,8 @@ contract RecurringCollectorGetMaxNextClaimTest is RecurringCollectorSharedTest { bytes16 agreementId ) = _sensibleAuthorizeAndAccept(fuzzy); - uint256 acceptedAt = block.timestamp; + // See sibling test for the viaIR/CSE rationale for vm.getBlockTimestamp(). + uint256 acceptedAt = vm.getBlockTimestamp(); // Warp past endsAt without ever collecting vm.warp(rca.endsAt + 1000); @@ -754,5 +758,240 @@ contract RecurringCollectorGetMaxNextClaimTest is RecurringCollectorSharedTest { assertGt(activeScope, 0, "sanity: active scope claim is non-zero"); } + /// @notice Envelope invariant across update(): with a pending RCAU stored, the amount any + /// subsequent collect() can pay out (after that RCAU is promoted via update()) must not + /// exceed the value getMaxNextClaim() returned beforehand. Exercised under conditions + /// where update() retroactively widens the per-collect cap relative to the remaining + /// `endsAt - now` window. + function test_GetMaxNextClaim_PendingScope_CoversPostUpdateCollection() public { + MockAgreementOwner approver = new MockAgreementOwner(); + + // Original RCA: rate=1, maxSec=3600, endsAt far in the future. maxInitialTokens=0 + // and minSec=0 to keep the arithmetic clean. + IRecurringCollector.RecurringCollectionAgreement memory rca = IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 0, + maxOngoingTokensPerSecond: 1, + minSecondsPerCollection: 0, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }); + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + _setupValidProvision(rca.serviceProvider, rca.dataService); + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + // Long quiet period: 24h elapses without any collection. lastCollectionAt stays 0, + // so the collection start (= acceptedAt) is now 86_400s in the past. + skip(86_400); + + // Pending RCAU offered: short remaining window (1_200s to endsAt) but generous + // maxSec (7_200s) and 10x rate. After update() promotes this, the per-collect cap + // is governed by maxSec_new (7_200), not the remaining-window view (1_200). + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = IRecurringCollector + .RecurringCollectionAgreementUpdate({ + agreementId: agreementId, + deadline: uint64(block.timestamp), + endsAt: uint64(block.timestamp + 1_200), + maxInitialTokens: 0, + maxOngoingTokensPerSecond: 10, + minSecondsPerCollection: 0, + maxSecondsPerCollection: 7_200, + conditions: 0, + nonce: 1, + metadata: "" + }); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + + // The reservation envelope RAM._reconcileAgreement uses to size escrow. + uint256 preUpdateEnvelope = _recurringCollector.getMaxNextClaim(agreementId); + + // Promote the RCAU on the same block. update() overwrites terms but does NOT advance + // lastCollectionAt (per the contract's update() docstring). + vm.prank(rca.dataService); + _recurringCollector.update(rcau, ""); + + // Warp to endsAt — worst-case elapsed for a single collect — and request unbounded + // tokens. The contract clamps to its internal maxClaim. With new terms applied + // retroactively from acceptedAt, this pays out rate_new * maxSec_new = 10 * 7_200. + vm.warp(rcau.endsAt); + bytes memory data = _generateCollectData( + _generateCollectParams(rca, agreementId, keccak256("collect-after-update"), type(uint256).max, 0) + ); + vm.prank(rca.dataService); + uint256 actuallyCollected = _recurringCollector.collect(_paymentType(0), data); + + // The post-update collect must fit within the pre-update envelope — otherwise RAM + // under-reserves and PaymentsEscrow.collect can revert on insufficient balance. + assertLe( + actuallyCollected, + preUpdateEnvelope, + "post-update collect amount must fit within the pre-update envelope" + ); + } + + /// @notice Same envelope invariant as above, but with `lastCollectionAt > 0` (a prior + /// collect has already advanced the collection-start anchor). Confirms the issue is not + /// specific to the never-collected path nor entangled with `maxInitialTokens` accounting. + function test_GetMaxNextClaim_PendingScope_CoversPostUpdateCollection_AfterPriorCollect() public { + MockAgreementOwner approver = new MockAgreementOwner(); + + IRecurringCollector.RecurringCollectionAgreement memory rca = IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 0, + maxOngoingTokensPerSecond: 1, + minSecondsPerCollection: 0, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }); + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + _setupValidProvision(rca.serviceProvider, rca.dataService); + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + // Anchor lastCollectionAt one hour past acceptedAt via a real collect(). + skip(1 hours); + bytes memory firstCollect = _generateCollectData( + _generateCollectParams(rca, agreementId, keccak256("first-collect"), 1, 0) + ); + vm.prank(rca.dataService); + _recurringCollector.collect(_paymentType(0), firstCollect); + + // Long quiet period after the prior collect — lastCollectionAt remains anchored. + skip(86_400); + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = IRecurringCollector + .RecurringCollectionAgreementUpdate({ + agreementId: agreementId, + deadline: uint64(block.timestamp), + endsAt: uint64(block.timestamp + 1_200), + maxInitialTokens: 0, + maxOngoingTokensPerSecond: 10, + minSecondsPerCollection: 0, + maxSecondsPerCollection: 7_200, + conditions: 0, + nonce: 1, + metadata: "" + }); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + + uint256 preUpdateEnvelope = _recurringCollector.getMaxNextClaim(agreementId); + + vm.prank(rca.dataService); + _recurringCollector.update(rcau, ""); + + vm.warp(rcau.endsAt); + bytes memory data = _generateCollectData( + _generateCollectParams(rca, agreementId, keccak256("collect-after-update"), type(uint256).max, 0) + ); + vm.prank(rca.dataService); + uint256 actuallyCollected = _recurringCollector.collect(_paymentType(0), data); + + assertLe( + actuallyCollected, + preUpdateEnvelope, + "post-update collect amount must fit within the pre-update envelope" + ); + } + + /// @notice Same envelope invariant under fuzzed elapsed-time, new rate, new maxSec, and + /// remaining-window inputs. Bounds picked to satisfy validator constraints + /// (`maxSec >= 600`, `endsAt - deadline >= 600`) while keeping arithmetic well within + /// uint256. + function testFuzz_GetMaxNextClaim_PendingScope_CoversPostUpdateCollection( + uint256 elapsedSinceAccept, + uint32 newMaxSec, + uint256 newRate, + uint64 remainingWindow + ) public { + elapsedSinceAccept = bound(elapsedSinceAccept, 60, 30 days); + newMaxSec = uint32(bound(newMaxSec, 600, 30 days)); + newRate = bound(newRate, 1, 1e18); + remainingWindow = uint64(bound(remainingWindow, 600, 30 days)); + + MockAgreementOwner approver = new MockAgreementOwner(); + + IRecurringCollector.RecurringCollectionAgreement memory rca = IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 3650 days), + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 0, + maxOngoingTokensPerSecond: 1, + minSecondsPerCollection: 0, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }); + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + _setupValidProvision(rca.serviceProvider, rca.dataService); + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + skip(elapsedSinceAccept); + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = IRecurringCollector + .RecurringCollectionAgreementUpdate({ + agreementId: agreementId, + deadline: uint64(block.timestamp), + endsAt: uint64(block.timestamp) + remainingWindow, + maxInitialTokens: 0, + maxOngoingTokensPerSecond: newRate, + minSecondsPerCollection: 0, + maxSecondsPerCollection: newMaxSec, + conditions: 0, + nonce: 1, + metadata: "" + }); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + + uint256 preUpdateEnvelope = _recurringCollector.getMaxNextClaim(agreementId); + + vm.prank(rca.dataService); + _recurringCollector.update(rcau, ""); + + vm.warp(rcau.endsAt); + bytes memory data = _generateCollectData( + _generateCollectParams( + rca, + agreementId, + keccak256(abi.encode(elapsedSinceAccept, newMaxSec, newRate, remainingWindow)), + type(uint256).max, + 0 + ) + ); + vm.prank(rca.dataService); + uint256 actuallyCollected = _recurringCollector.collect(_paymentType(0), data); + + assertLe( + actuallyCollected, + preUpdateEnvelope, + "post-update collect amount must fit within the pre-update envelope" + ); + } + /* solhint-enable graph/func-name-mixedcase */ } diff --git a/packages/horizon/test/unit/payments/recurring-collector/returndataBomb.t.sol b/packages/horizon/test/unit/payments/recurring-collector/returndataBomb.t.sol index 3ef69f430..e68b4882f 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/returndataBomb.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/returndataBomb.t.sol @@ -23,7 +23,7 @@ contract HugeReturnPayer is IAgreementOwner, IERC165 { return interfaceId == type(IERC165).interfaceId || interfaceId == type(IProviderEligibility).interfaceId; } - function beforeCollection(bytes16, uint256) external { + function beforeCollection(bytes16, uint256) external view { uint256 size = returnBytes; // solhint-disable-next-line no-inline-assembly assembly { @@ -31,7 +31,7 @@ contract HugeReturnPayer is IAgreementOwner, IERC165 { } } - function afterCollection(bytes16, uint256) external { + function afterCollection(bytes16, uint256) external view { uint256 size = returnBytes; // solhint-disable-next-line no-inline-assembly assembly { diff --git a/packages/horizon/test/unit/payments/recurring-collector/viewFunctions.t.sol b/packages/horizon/test/unit/payments/recurring-collector/viewFunctions.t.sol index 902572a29..4c39fd25c 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/viewFunctions.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/viewFunctions.t.sol @@ -38,8 +38,6 @@ contract RecurringCollectorViewFunctionsTest is RecurringCollectorSharedTest { // Cancel by service provider _cancel(rca, agreementId, IRecurringCollector.CancelAgreementBy.ServiceProvider); - IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - (bool isCollectable, , IRecurringCollector.AgreementNotCollectableReason reason) = _recurringCollector .getCollectionInfo(agreementId); @@ -81,8 +79,6 @@ contract RecurringCollectorViewFunctionsTest is RecurringCollectorSharedTest { // Cancel by payer in the same block as accept _cancel(rca, agreementId, IRecurringCollector.CancelAgreementBy.Payer); - IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - (bool isCollectable, uint256 collectionSeconds, ) = _recurringCollector.getCollectionInfo(agreementId); // Same block cancel means no time elapsed @@ -104,8 +100,6 @@ contract RecurringCollectorViewFunctionsTest is RecurringCollectorSharedTest { skip(rca.minSecondsPerCollection); _cancel(rca, agreementId, IRecurringCollector.CancelAgreementBy.Payer); - IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - (bool isCollectable, uint256 collectionSeconds, ) = _recurringCollector.getCollectionInfo(agreementId); assertTrue(isCollectable, "Payer cancel with elapsed time should be collectable"); diff --git a/packages/horizon/test/unit/shared/horizon-staking/HorizonStakingShared.t.sol b/packages/horizon/test/unit/shared/horizon-staking/HorizonStakingShared.t.sol index 1309de2b5..c7e50a133 100644 --- a/packages/horizon/test/unit/shared/horizon-staking/HorizonStakingShared.t.sol +++ b/packages/horizon/test/unit/shared/horizon-staking/HorizonStakingShared.t.sol @@ -1373,8 +1373,6 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest { } function _slash(address serviceProvider, address verifier, uint256 tokens, uint256 verifierCutAmount) internal { - bool isDelegationSlashingEnabled = staking.isDelegationSlashingEnabled(); - // staking contract knows the address of the legacy subgraph service // but we cannot read it as it's an immutable, we have to use the global var :/ bool legacy = verifier == subgraphDataServiceLegacyAddress; @@ -1393,6 +1391,18 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest { calcValues.providerTokensSlashed = Math.min(before.provision.tokens, calcValues.tokensToSlash); calcValues.delegationTokensSlashed = calcValues.tokensToSlash - calcValues.providerTokensSlashed; + _expectSlashEmits(serviceProvider, verifier, verifierCutAmount, calcValues); + staking.slash(serviceProvider, tokens, verifierCutAmount, verifier); + _assertSlashStateAfter(serviceProvider, verifier, verifierCutAmount, before, calcValues); + } + + function _expectSlashEmits( + address serviceProvider, + address verifier, + uint256 verifierCutAmount, + CalcValuesSlash memory calcValues + ) internal { + bool isDelegationSlashingEnabled = staking.isDelegationSlashingEnabled(); if (calcValues.tokensToSlash > 0) { if (verifierCutAmount > 0) { vm.expectEmit(address(token)); @@ -1427,9 +1437,17 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest { ); } } - staking.slash(serviceProvider, tokens, verifierCutAmount, verifier); + } - // after + function _assertSlashStateAfter( + address serviceProvider, + address verifier, + uint256 verifierCutAmount, + BeforeValuesSlash memory before, + CalcValuesSlash memory calcValues + ) internal view { + bool isDelegationSlashingEnabled = staking.isDelegationSlashingEnabled(); + bool legacy = verifier == subgraphDataServiceLegacyAddress; Provision memory afterProvision = staking.getProvision(serviceProvider, verifier); DelegationPoolInternalTest memory afterPool = _getStorageDelegationPoolInternal( serviceProvider, @@ -1440,56 +1458,50 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest { uint256 afterStakingBalance = token.balanceOf(address(staking)); uint256 afterVerifierBalance = token.balanceOf(verifier); - { - uint256 tokensSlashed = calcValues.providerTokensSlashed + - (isDelegationSlashingEnabled ? calcValues.delegationTokensSlashed : 0); - uint256 provisionThawingTokens = (before.provision.tokensThawing * - (before.provision.tokens - calcValues.providerTokensSlashed)) / before.provision.tokens; - - // assert - assertEq(afterProvision.tokens + calcValues.providerTokensSlashed, before.provision.tokens); - assertEq(afterProvision.tokensThawing, provisionThawingTokens); - assertEq( - afterProvision.sharesThawing, - afterProvision.tokensThawing == 0 ? 0 : before.provision.sharesThawing - ); - assertEq(afterProvision.maxVerifierCut, before.provision.maxVerifierCut); - assertEq(afterProvision.maxVerifierCutPending, before.provision.maxVerifierCutPending); - assertEq(afterProvision.thawingPeriod, before.provision.thawingPeriod); - assertEq(afterProvision.thawingPeriodPending, before.provision.thawingPeriodPending); + uint256 tokensSlashed = calcValues.providerTokensSlashed + + (isDelegationSlashingEnabled ? calcValues.delegationTokensSlashed : 0); + uint256 provisionThawingTokens = (before.provision.tokensThawing * + (before.provision.tokens - calcValues.providerTokensSlashed)) / before.provision.tokens; + + assertEq(afterProvision.tokens + calcValues.providerTokensSlashed, before.provision.tokens); + assertEq(afterProvision.tokensThawing, provisionThawingTokens); + assertEq(afterProvision.sharesThawing, afterProvision.tokensThawing == 0 ? 0 : before.provision.sharesThawing); + assertEq(afterProvision.maxVerifierCut, before.provision.maxVerifierCut); + assertEq(afterProvision.maxVerifierCutPending, before.provision.maxVerifierCutPending); + assertEq(afterProvision.thawingPeriod, before.provision.thawingPeriod); + assertEq(afterProvision.thawingPeriodPending, before.provision.thawingPeriodPending); + assertEq( + afterProvision.thawingNonce, + (before.provision.sharesThawing != 0 && afterProvision.sharesThawing == 0) + ? before.provision.thawingNonce + 1 + : before.provision.thawingNonce + ); + if (isDelegationSlashingEnabled) { + uint256 poolThawingTokens = (before.pool.tokensThawing * + (before.pool.tokens - calcValues.delegationTokensSlashed)) / before.pool.tokens; + assertEq(afterPool.tokens + calcValues.delegationTokensSlashed, before.pool.tokens); + assertEq(afterPool.shares, before.pool.shares); + assertEq(afterPool.tokensThawing, poolThawingTokens); + assertEq(afterPool.sharesThawing, afterPool.tokensThawing == 0 ? 0 : before.pool.sharesThawing); assertEq( - afterProvision.thawingNonce, - (before.provision.sharesThawing != 0 && afterProvision.sharesThawing == 0) - ? before.provision.thawingNonce + 1 - : before.provision.thawingNonce + afterPool.thawingNonce, + (before.pool.sharesThawing != 0 && afterPool.sharesThawing == 0) + ? before.pool.thawingNonce + 1 + : before.pool.thawingNonce ); - if (isDelegationSlashingEnabled) { - uint256 poolThawingTokens = (before.pool.tokensThawing * - (before.pool.tokens - calcValues.delegationTokensSlashed)) / before.pool.tokens; - assertEq(afterPool.tokens + calcValues.delegationTokensSlashed, before.pool.tokens); - assertEq(afterPool.shares, before.pool.shares); - assertEq(afterPool.tokensThawing, poolThawingTokens); - assertEq(afterPool.sharesThawing, afterPool.tokensThawing == 0 ? 0 : before.pool.sharesThawing); - assertEq( - afterPool.thawingNonce, - (before.pool.sharesThawing != 0 && afterPool.sharesThawing == 0) - ? before.pool.thawingNonce + 1 - : before.pool.thawingNonce - ); - } + } - assertEq(before.stakingBalance - tokensSlashed, afterStakingBalance); - assertEq(before.verifierBalance + verifierCutAmount, afterVerifierBalance); + assertEq(before.stakingBalance - tokensSlashed, afterStakingBalance); + assertEq(before.verifierBalance + verifierCutAmount, afterVerifierBalance); - assertEq( - afterServiceProvider.tokensStaked + calcValues.providerTokensSlashed, - before.serviceProvider.tokensStaked - ); - assertEq( - afterServiceProvider.tokensProvisioned + calcValues.providerTokensSlashed, - before.serviceProvider.tokensProvisioned - ); - } + assertEq( + afterServiceProvider.tokensStaked + calcValues.providerTokensSlashed, + before.serviceProvider.tokensStaked + ); + assertEq( + afterServiceProvider.tokensProvisioned + calcValues.providerTokensSlashed, + before.serviceProvider.tokensProvisioned + ); } /* diff --git a/packages/horizon/test/unit/staking/coverageGaps.t.sol b/packages/horizon/test/unit/staking/coverageGaps.t.sol index 07dfec2ed..72288d5af 100644 --- a/packages/horizon/test/unit/staking/coverageGaps.t.sol +++ b/packages/horizon/test/unit/staking/coverageGaps.t.sol @@ -31,7 +31,7 @@ contract HorizonStakingCoverageGapsTest is HorizonStakingTest { function test_GetIdleStake_WithStake( uint256 stakeAmount, - uint256 provisionAmount, + uint256 /* provisionAmount */, uint32 maxVerifierCut, uint64 thawingPeriod ) public useIndexer useProvision(stakeAmount, maxVerifierCut, thawingPeriod) { diff --git a/packages/interfaces/contracts/horizon/IRecurringCollector.sol b/packages/interfaces/contracts/horizon/IRecurringCollector.sol index 18f89f00b..48ad5ea64 100644 --- a/packages/interfaces/contracts/horizon/IRecurringCollector.sol +++ b/packages/interfaces/contracts/horizon/IRecurringCollector.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.22; import { IAgreementCollector } from "./IAgreementCollector.sol"; -import { IGraphPayments } from "./IGraphPayments.sol"; import { IAuthorizable } from "./IAuthorizable.sol"; /** @@ -244,12 +243,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { uint256 dataServiceCut ); - /** - * @notice Thrown when an agreement does not exist (no accepted state and no stored offer) - * @param agreementId The agreement ID that was not found - */ - error RecurringCollectorAgreementNotFound(bytes16 agreementId); - /** * @notice Thrown when accepting an agreement with a zero ID */ @@ -279,12 +272,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { */ error RecurringCollectorInvalidSigner(); - /** - * @notice Thrown when the payment type is not IndexingFee - * @param invalidPaymentType The invalid payment type - */ - error RecurringCollectorInvalidPaymentType(IGraphPayments.PaymentTypes invalidPaymentType); - /** * @notice Thrown when the caller is not the data service the RCA was issued to * @param unauthorizedCaller The address of the caller @@ -331,10 +318,11 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { error RecurringCollectorAgreementEndsBeforeDeadline(uint64 deadline, uint64 endsAt); /** - * @notice Thrown when accepting or upgrading an agreement with an elapsed endsAt - * @param allowedMinCollectionWindow The allowed minimum collection window - * @param minSecondsPerCollection The minimum seconds per collection - * @param maxSecondsPerCollection The maximum seconds per collection + * @notice Thrown when the collection window (max - min) is below the required minimum, + * or when max <= min. + * @param allowedMinCollectionWindow The minimum required collection window in seconds + * @param minSecondsPerCollection The provided minimum seconds per collection + * @param maxSecondsPerCollection The provided maximum seconds per collection */ error RecurringCollectorAgreementInvalidCollectionWindow( uint32 allowedMinCollectionWindow, @@ -349,19 +337,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { */ error RecurringCollectorAgreementInvalidDuration(uint32 requiredMinDuration, uint256 invalidDuration); - /** - * @notice Thrown when calling collect() with a zero collection seconds - * @param agreementId The agreement ID - * @param currentTimestamp The current timestamp - * @param lastCollectionAt The timestamp when the last collection was done - * - */ - error RecurringCollectorZeroCollectionSeconds( - bytes16 agreementId, - uint256 currentTimestamp, - uint64 lastCollectionAt - ); - /** * @notice Thrown when calling collect() too soon * @param agreementId The agreement ID @@ -490,10 +465,12 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { /** * @notice Accept a Recurring Collection Agreement. - * @dev Caller must be the data service the RCA was issued to. - * If `signature` is non-empty: checks `rca.deadline >= block.timestamp` and verifies the ECDSA signature. - * If `signature` is empty: the payer must be a contract implementing {IAgreementOwner.approveAgreement} - * and must return the magic value for the RCA's EIP712 hash. + * @dev Caller must be the data service the RCA was issued to. Idempotent: re-accepting an + * already-accepted agreement with the same RCA hash is a no-op (skips deadline and auth checks). + * Otherwise enforces `block.timestamp <= rca.deadline` and authorizes via one of: + * - `signature` non-empty: ECDSA signature recovery against the payer (or its authorized signer); + * - `signature` empty: the payer must have previously stored an offer for the same RCA hash via + * {IAgreementCollector.offer}; the stored hash is checked instead of a signature. * @param rca The Recurring Collection Agreement to accept * @param signature ECDSA signature bytes, or empty for contract-approved agreements * @return agreementId The deterministically generated agreement ID @@ -504,7 +481,11 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { ) external returns (bytes16 agreementId); /** - * @notice Cancel an indexing agreement. + * @notice Cancel a Recurring Collection Agreement. + * @dev Caller must be the data service for the agreement; the agreement must be in the + * Accepted state. Records `canceledAt = block.timestamp` and transitions to + * CanceledByPayer or CanceledByServiceProvider per `by`. Any pending RCAU offer whose + * hash differs from the active terms is dropped. * @param agreementId The agreement's ID. * @param by The party that is canceling the agreement. */ @@ -512,10 +493,12 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { /** * @notice Update a Recurring Collection Agreement. - * @dev Caller must be the data service for the agreement. - * If `signature` is non-empty: checks `rcau.deadline >= block.timestamp` and verifies the ECDSA signature. - * If `signature` is empty: the payer (stored in the agreement) must be a contract implementing - * {IAgreementOwner.approveAgreement} and must return the magic value for the RCAU's EIP712 hash. + * @dev Caller must be the data service for the agreement. Idempotent: re-applying an already-applied + * RCAU is a no-op (skips deadline and auth checks). Otherwise enforces + * `block.timestamp <= rcau.deadline` and authorizes via one of: + * - `signature` non-empty: ECDSA signature recovery against the payer (or its authorized signer); + * - `signature` empty: the payer must have previously stored an offer for the same RCAU hash via + * {IAgreementCollector.offer}; the stored hash is checked instead of a signature. * @param rcau The Recurring Collection Agreement Update to apply * @param signature ECDSA signature bytes, or empty for contract-approved updates */ diff --git a/packages/interfaces/contracts/issuance/agreement/IRecurringAgreementManagement.sol b/packages/interfaces/contracts/issuance/agreement/IRecurringAgreementManagement.sol index b6b02f1bc..bbb90564d 100644 --- a/packages/interfaces/contracts/issuance/agreement/IRecurringAgreementManagement.sol +++ b/packages/interfaces/contracts/issuance/agreement/IRecurringAgreementManagement.sol @@ -63,8 +63,9 @@ interface IRecurringAgreementManagement { /** * @notice Emitted when a (collector, provider) pair is removed from tracking - * @dev Emitted when the pair has no agreements AND escrow is fully recovered (balance zero). - * May cascade inline from agreement deletion or be triggered by {reconcileProvider}. + * @dev Emitted when the pair has no agreements AND escrow balance is below the residual + * threshold (2^minResidualEscrowFactor). May cascade inline from agreement deletion or be + * triggered by {reconcileProvider}. * @param collector The collector address * @param provider The provider address */ diff --git a/packages/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol b/packages/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol index 4c01aba27..9861e7d2c 100644 --- a/packages/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol +++ b/packages/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol @@ -83,10 +83,10 @@ interface IRecurringAgreements { function getSumMaxNextClaim() external view returns (uint256 tokens); /** - * @notice Get the total undeposited escrow across all providers - * @dev Maintained incrementally: sum of max(0, sumMaxNextClaim[p] - deposited[p]) - * for each provider p. Correctly accounts for per-provider deficits without - * allowing over-deposited providers to mask under-deposited ones. + * @notice Get the total undeposited escrow across all (collector, provider) pairs + * @dev Maintained incrementally: sum of max(0, sumMaxNextClaim[c][p] - escrowSnap[c][p]) + * across all pairs. Per-pair clamping prevents over-deposited pairs from masking + * under-deposited ones. * @return tokens The total unfunded amount */ function getTotalEscrowDeficit() external view returns (uint256 tokens); diff --git a/packages/interfaces/hardhat.config.ts b/packages/interfaces/hardhat.config.ts index 42001f299..be8d2feaa 100644 --- a/packages/interfaces/hardhat.config.ts +++ b/packages/interfaces/hardhat.config.ts @@ -3,7 +3,7 @@ import 'hardhat-ignore-warnings' const config = { solidity: { - compilers: [{ version: '0.8.27' }, { version: '0.7.6' }], + compilers: [{ version: '0.8.35' }, { version: '0.7.6' }], }, typechain: { outDir: 'types', diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json index 3b774337d..a25e678a1 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.1-dips.0", "publishConfig": { "access": "public" }, diff --git a/packages/interfaces/src/types/horizon.ts b/packages/interfaces/src/types/horizon.ts index c2a09abb6..afed43e2c 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, @@ -22,12 +23,14 @@ export { IGraphProxyAdmin as GraphProxyAdmin, IGraphTallyCollectorToolshed as GraphTallyCollector, IHorizonStakingToolshed as HorizonStaking, + IRecurringCollector, IL2CurationToolshed as L2Curation, IL2GNSToolshed as L2GNS, IGraphToken as L2GraphToken, ILegacyRewardsManager as LegacyRewardsManager, IStaking as LegacyStaking, IPaymentsEscrowToolshed as PaymentsEscrow, + IRecurringCollector as RecurringCollector, IRewardsManagerToolshed as RewardsManager, ISubgraphNFT as SubgraphNFT, } 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..28942d3e1 100644 --- a/packages/issuance/addresses.json +++ b/packages/issuance/addresses.json @@ -32,33 +32,139 @@ "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": "", "argsData": "0x000000000000000000000000f8c05dcf59e8b28bfd5eed176c562bebcfc7ac04", - "bytecodeHash": "0x94b490cdb340cdf9f601e618fdb7e21608969ba1a0dee05a3b017efa4ad36ad0", - "blockNumber": 250574005 + "bytecodeHash": "0xf11b102de39fbe66879f57214393c7ff7438e050f77802e2c08c71a000002003" } } } diff --git a/packages/issuance/contracts/allocate/IssuanceAllocator.sol b/packages/issuance/contracts/allocate/IssuanceAllocator.sol index 76ecf8792..dd45f611a 100644 --- a/packages/issuance/contracts/allocate/IssuanceAllocator.sol +++ b/packages/issuance/contracts/allocate/IssuanceAllocator.sol @@ -54,12 +54,9 @@ import { ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/int * @dev Pause Behavior: * - Allocator-minting: Completely suspended during pause. No tokens minted, lastDistributionBlock frozen. * When unpaused, distributes retroactively using current rates for entire undistributed period. (Distribution will be triggered by calling distributeIssuance() when not paused.) - * - Self-minting: Continues tracking via events and accumulation during pause. Accumulated self-minting - * reduces allocator-minting budget when distribution resumes, ensuring total issuance conservation. - * - Ongoing accumulation: Once accumulation starts (during pause), continues through any unpaused - * periods until distribution clears it, preventing loss of self-minting allowances across pause cycles. - * - Tracking divergence: lastSelfMintingBlock advances during pause (for allowance tracking) while - * lastDistributionBlock stays frozen (no allocator-minting). This is intentional and correct. + * - Self-minting: tracked unconditionally so the Self-Minting Accumulation invariant holds in + * every reachable state. Distribution reconciles the offset on catch-up. + * - lastSelfMintingBlock advances during pause; lastDistributionBlock stays frozen. * * @dev Issuance Accounting Invariants: * The contract maintains strict accounting to ensure total token issuance never exceeds the configured @@ -75,12 +72,12 @@ import { ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/int * where totalSelfMintingRate_b is the end-state rate for block b. * * 3. Rate Constraint: For all blocks b, totalSelfMintingRate_b ≤ issuancePerBlock_b - * This follows from invariant (1) since 0 ≤ totalAllocatorRate_b. + * Follows from Allocation Completeness since 0 ≤ totalAllocatorRate_b. * * 4. Issuance Upper Bound: For any distribution period with blocks = toBlock - fromBlock + 1: * Let issuancePerBlock_final = current issuancePerBlock at distribution time * - * From invariants (2) and (3): + * From Self-Minting Accumulation and Rate Constraint: * selfMintingOffset ≤ Σ(issuancePerBlock_b) * * Allocator-minting budget for period: @@ -97,9 +94,11 @@ import { ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/int * Any remaining offset when cleared represents self-minting that occurred beyond what the final * issuancePerBlock rate would allow for the period. This is acceptable because: * a) Self-minting targets were operating under rates that were valid at the time - * b) The total minted still respects the Σ(issuancePerBlock_b) bound (invariant 4) + * b) The total minted still respects the Issuance Upper Bound * c) Clearing the offset prevents it from affecting future distributions - * d) The SelfMintingOffsetReconciled event provides visibility into all offset adjustments + * d) Off-chain consumers can reconstruct any deviation from IssuanceDistributed, + * IssuanceSelfMintAllowance{,Aggregate}, IssuancePerBlockUpdated, and Pausable's + * Paused/Unpaused events * * This design ensures that even when issuancePerBlock or allocation rates change over time, and even * when self-minting targets mint independently, the total tokens minted never exceeds the sum of @@ -269,43 +268,6 @@ contract IssuanceAllocator is uint256 indexed toBlock ); // solhint-disable-line gas-indexed-events - /* solhint-disable gas-indexed-events */ - /// @notice Emitted when self-minting offset is reconciled during pending distribution - /// @param offsetBefore The self-minting offset before reconciliation - /// @param offsetAfter The self-minting offset after reconciliation (0 when caught up to current block) - /// @param totalForPeriod The total issuance budget for the distributed period - /// @param fromBlock First block in the distribution period (inclusive) - /// @param toBlock Last block in the distribution period (inclusive) - /// @dev This event provides visibility into the accounting reconciliation between self-minting - /// and allocator-minting budgets during pending distribution. When offsetAfter is 0, the contract - /// has fully caught up with distribution. When offsetAfter > 0, there remains accumulated offset - /// that will be applied to future distributions. - event SelfMintingOffsetReconciled( - uint256 offsetBefore, - uint256 offsetAfter, - uint256 totalForPeriod, - uint256 indexed fromBlock, - uint256 indexed toBlock - ); - /* solhint-enable gas-indexed-events */ - - /* solhint-disable gas-indexed-events */ - /// @notice Emitted when self-minting offset accumulates during pause or catch-up - /// @param offsetBefore The self-minting offset before accumulation - /// @param offsetAfter The self-minting offset after accumulation - /// @param fromBlock First block in the accumulation period (inclusive) - /// @param toBlock Last block in the accumulation period (inclusive) - /// @dev This event provides visibility into offset growth during pause periods or while catching up - /// after unpause. Together with SelfMintingOffsetReconciled, provides complete accounting of all - /// offset changes. - event SelfMintingOffsetAccumulated( - uint256 offsetBefore, - uint256 offsetAfter, - uint256 indexed fromBlock, - uint256 indexed toBlock - ); - /* solhint-enable gas-indexed-events */ - /// @notice Emitted when self-minting allowance is calculated in aggregate mode /// @param totalAmount The total amount of tokens available for self-minting across all targets /// @param fromBlock First block included in this allowance period (inclusive) @@ -416,19 +378,9 @@ contract IssuanceAllocator is uint256 blocks = block.number - previousBlock; uint256 fromBlock = previousBlock + 1; - // Accumulate if currently paused OR if there's existing accumulated balance. - // Once accumulation starts (during pause), continue through any unpaused periods - // until distribution clears the accumulation. This is conservative and allows - // better recovery when distribution is delayed through pause/unpause cycles. - uint256 offsetBefore = $.selfMintingOffset; - if (paused() || 0 < offsetBefore) { - $.selfMintingOffset += $.totalSelfMintingRate * blocks; - - // Emit accumulation event whenever offset changes - if (offsetBefore != $.selfMintingOffset) { - emit SelfMintingOffsetAccumulated(offsetBefore, $.selfMintingOffset, fromBlock, block.number); - } - } + // Maintains the Self-Minting Accumulation invariant unconditionally so the distribution + // path can safely bound allocator budget by selfMintingOffset. + $.selfMintingOffset += $.totalSelfMintingRate * blocks; $.lastSelfMintingBlock = block.number; // Emit self-minting allowance events based on mode @@ -455,12 +407,9 @@ contract IssuanceAllocator is /** * @notice Internal implementation for `distributeIssuance` - * @dev Handles the actual distribution logic. - * @dev Always calls _advanceSelfMintingBlock() first (advances lastSelfMintingBlock, tracks self-minting). - * @dev If paused: Returns lastDistributionBlock without distributing allocator-minting (frozen state). - * @dev If unpaused: Chooses distribution path based on accumulated self-minting: - * - With accumulation: retroactive distribution path (current rates, reduced allocator budget) - * - Without accumulation: normal distribution path (simple per-block minting) + * @dev Always advances self-minting first; returns frozen lastDistributionBlock if paused; + * otherwise delegates to _distributePendingIssuance. Self-Minting Accumulation lets a single + * distribution path serve every reachable state. * @return Block number distributed to */ function _distributeIssuance() private returns (uint256) { @@ -469,37 +418,7 @@ contract IssuanceAllocator is if (paused()) return $.lastDistributionBlock; - return 0 < $.selfMintingOffset ? _distributePendingIssuance(block.number) : _performNormalDistribution(); - } - - /** - * @notice Performs normal (non-pending) issuance distribution - * @dev Distributes allocator-minting issuance to all targets based on their rates - * @dev Assumes contract is not paused and pending issuance has already been distributed - * @return Block number distributed to - */ - function _performNormalDistribution() private returns (uint256) { - IssuanceAllocatorData storage $ = _getIssuanceAllocatorStorage(); - - uint256 blocks = block.number - $.lastDistributionBlock; - if (blocks == 0) return $.lastDistributionBlock; - - uint256 fromBlock = $.lastDistributionBlock + 1; - - for (uint256 i = 0; i < $.targetAddresses.length; ++i) { - address target = $.targetAddresses[i]; - if (target == address(0)) continue; - - AllocationTarget storage targetData = $.allocationTargets[target]; - if (0 < targetData.allocatorMintingRate) { - uint256 amount = targetData.allocatorMintingRate * blocks; - GRAPH_TOKEN.mint(target, amount); - emit IssuanceDistributed(target, amount, fromBlock, block.number); - } - } - - $.lastDistributionBlock = block.number; - return block.number; + return _distributePendingIssuance(block.number); } /** @@ -519,11 +438,10 @@ contract IssuanceAllocator is } /** - * @notice Internal implementation for distributing pending accumulated allocator-minting issuance + * @notice Internal implementation for distributing allocator-minting issuance up to a given block * @param toBlockNumber Block number to distribute up to - * @dev Distributes allocator-minting issuance for undistributed period using current rates, - * retroactively applied from lastDistributionBlock to toBlockNumber (inclusive). - * Called when 0 < selfMintingOffset, which occurs after pause periods or delayed distribution. + * @dev Distributes for [lastDistributionBlock+1, toBlockNumber]. selfMintingOffset bounds + * allocator budget so the Issuance Upper Bound holds across any rate variation in the range. * @dev Available budget = max(0, issuancePerBlock * blocks - selfMintingOffset). * Distribution cases: * (1) available < allocatedTotal: proportional distribution to non-default, default gets zero @@ -568,44 +486,13 @@ contract IssuanceAllocator is } $.lastDistributionBlock = toBlockNumber; - _reconcileSelfMintingOffset(toBlockNumber, blocks, totalForPeriod, selfMintingOffset); - return toBlockNumber; - } - - /** - * @notice Reconciles self-minting offset after distribution and emits event if changed - * @param toBlockNumber Block number distributed to - * @param blocks Number of blocks in the distribution period - * @param totalForPeriod Total issuance budget for the period - * @param selfMintingOffset Self-minting offset before reconciliation - * @dev Updates accumulated self-minting after distribution. - * Subtracts the period budget used (min of accumulated and totalForPeriod). - * When caught up to current block, clears all since nothing remains to distribute. - */ - function _reconcileSelfMintingOffset( - uint256 toBlockNumber, - uint256 blocks, - uint256 totalForPeriod, - uint256 selfMintingOffset - ) private { - IssuanceAllocatorData storage $ = _getIssuanceAllocatorStorage(); - - uint256 newOffset = toBlockNumber == block.number + // Reconcile offset: clear on full catch-up, otherwise subtract the period's budget + // (clamped at 0). Any clamped residue represents self-minting beyond what the final + // rate would allow for the period — the Issuance Upper Bound still holds. + $.selfMintingOffset = toBlockNumber == block.number ? 0 : (totalForPeriod < selfMintingOffset ? selfMintingOffset - totalForPeriod : 0); - - // Emit reconciliation event whenever offset changes during pending distribution - if (selfMintingOffset != newOffset) { - emit SelfMintingOffsetReconciled( - selfMintingOffset, - newOffset, - totalForPeriod, - toBlockNumber - blocks + 1, - toBlockNumber - ); - } - - $.selfMintingOffset = newOffset; + return toBlockNumber; } /** diff --git a/packages/issuance/contracts/eligibility/mocks/MockRewardsEligibilityOracle.sol b/packages/issuance/contracts/eligibility/mocks/MockRewardsEligibilityOracle.sol new file mode 100644 index 000000000..92e811ce5 --- /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 IRewardsEligibility (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/subgraph-service/test/unit/subgraphService/indexing-agreement/collect.t.sol b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/collect.t.sol index 46d3dac26..ebfef4ec0 100644 --- a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/collect.t.sol +++ b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/collect.t.sol @@ -283,12 +283,12 @@ contract SubgraphServiceIndexingAgreementCollectTest is SubgraphServiceIndexingA function test_SubgraphService_CollectIndexingFees_AfterCloseStaleAllocation_ResizesToZero( Seed memory seed, - uint256 entities, - bytes32 poi + uint256 /* entities */, + bytes32 /* poi */ ) public { Context storage ctx = _newCtx(seed); IndexerState memory indexerState = _withIndexer(ctx); - (, bytes16 acceptedAgreementId) = _withAcceptedIndexingAgreement(ctx, indexerState); + _withAcceptedIndexingAgreement(ctx, indexerState); skip(MAX_POI_STALENESS + 1); resetPrank(indexerState.addr); diff --git a/packages/testing/foundry.toml b/packages/testing/foundry.toml index 8b632102a..e81bd9bc3 100644 --- a/packages/testing/foundry.toml +++ b/packages/testing/foundry.toml @@ -16,12 +16,22 @@ remappings = [ "subgraph-service/=node_modules/@graphprotocol/subgraph-service/contracts/", "subgraph-service-test/=node_modules/@graphprotocol/subgraph-service/test/", ] +solc_version = '0.8.35' +evm_version = 'cancun' + +# Opt-in profile for production-matching bytecode (viaIR + optimizer). +# Use FOUNDRY_PROFILE=prod for CI / pre-merge runs that should mirror what hardhat +# compiles for deploy. Default profile stays optimizer-off / viaIR-off for fast iteration. +[profile.prod] optimizer = true optimizer_runs = 100 via_ir = true -solc_version = '0.8.34' -evm_version = 'cancun' [lint] +# Disable lint-on-build: forge's lint walker follows symlinks without cycle detection and +# infinite-loops on this package's pnpm-hoisted node_modules +# (e.g., @graphprotocol/contracts/task/node_modules/@graphprotocol/contracts/task/...). +# `forge lint contracts/` (scoped) still works as a separate command. +lint_on_build = false exclude_lints = ["mixed-case-function", "mixed-case-variable", "block-timestamp"] ignore = ["node_modules/**", "**/node_modules/**"] diff --git a/packages/testing/test/gas/CallbackGas.t.sol b/packages/testing/test/gas/CallbackGas.t.sol index ae703ad51..f4b6e5da1 100644 --- a/packages/testing/test/gas/CallbackGas.t.sol +++ b/packages/testing/test/gas/CallbackGas.t.sol @@ -85,7 +85,7 @@ contract CallbackGasTest is RealStackHarness { bytes16 agreementId = _offerAgreement(rca); // Accept on the real RecurringCollector using ContractApproval path (empty signature). - // RAM.approveAgreement returns the selector when the hash is authorized. + // The RCA hash was pre-registered via _offerAgreement, so the stored-hash check passes. vm.prank(dataService); recurringCollector.accept(rca, ""); diff --git a/packages/testing/test/harness/FullStackHarness.t.sol b/packages/testing/test/harness/FullStackHarness.t.sol index b02735288..efc2fef6a 100644 --- a/packages/testing/test/harness/FullStackHarness.t.sol +++ b/packages/testing/test/harness/FullStackHarness.t.sol @@ -151,33 +151,39 @@ abstract contract FullStackHarness is Test { // We use type(...).creationCode instead of vm.getCode to get the exact bytecode // that will be used by CREATE2, avoiding metadata hash mismatches across packages. bytes32 saltGP = keccak256("GraphPaymentsSalt"); - bytes memory gpCreation = type(GraphPayments).creationCode; - address predictedGP = vm.computeCreate2Address( - saltGP, - keccak256(bytes.concat(gpCreation, abi.encode(address(controller), PROTOCOL_PAYMENT_CUT))), - deployer - ); - bytes32 saltEscrow = keccak256("GraphEscrowSalt"); - bytes memory escrowCreation = type(PaymentsEscrow).creationCode; - address predictedEscrow = vm.computeCreate2Address( - saltEscrow, - keccak256(bytes.concat(escrowCreation, abi.encode(address(controller), WITHDRAW_ESCROW_THAWING_PERIOD))), - deployer - ); - - // Register in controller (GraphDirectory reads immutably at construction) - vm.startPrank(governor); - controller.setContractProxy(keccak256("GraphToken"), address(token)); - controller.setContractProxy(keccak256("Staking"), address(stakingProxy)); - controller.setContractProxy(keccak256("RewardsManager"), address(rewardsManager)); - controller.setContractProxy(keccak256("GraphPayments"), predictedGP); - controller.setContractProxy(keccak256("PaymentsEscrow"), predictedEscrow); - controller.setContractProxy(keccak256("EpochManager"), address(epochManager)); - controller.setContractProxy(keccak256("GraphTokenGateway"), makeAddr("GraphTokenGateway")); - controller.setContractProxy(keccak256("GraphProxyAdmin"), makeAddr("GraphProxyAdmin")); - controller.setContractProxy(keccak256("Curation"), address(curation)); - vm.stopPrank(); + // Block-scoped to release the creationCode/predicted-address locals before later code — + // legacy compiler pipeline runs out of stack slots in this function otherwise. + { + bytes memory gpCreation = type(GraphPayments).creationCode; + address predictedGP = vm.computeCreate2Address( + saltGP, + keccak256(bytes.concat(gpCreation, abi.encode(address(controller), PROTOCOL_PAYMENT_CUT))), + deployer + ); + + bytes memory escrowCreation = type(PaymentsEscrow).creationCode; + address predictedEscrow = vm.computeCreate2Address( + saltEscrow, + keccak256( + bytes.concat(escrowCreation, abi.encode(address(controller), WITHDRAW_ESCROW_THAWING_PERIOD)) + ), + deployer + ); + + // Register in controller (GraphDirectory reads immutably at construction) + vm.startPrank(governor); + controller.setContractProxy(keccak256("GraphToken"), address(token)); + controller.setContractProxy(keccak256("Staking"), address(stakingProxy)); + controller.setContractProxy(keccak256("RewardsManager"), address(rewardsManager)); + controller.setContractProxy(keccak256("GraphPayments"), predictedGP); + controller.setContractProxy(keccak256("PaymentsEscrow"), predictedEscrow); + controller.setContractProxy(keccak256("EpochManager"), address(epochManager)); + controller.setContractProxy(keccak256("GraphTokenGateway"), makeAddr("GraphTokenGateway")); + controller.setContractProxy(keccak256("GraphProxyAdmin"), makeAddr("GraphProxyAdmin")); + controller.setContractProxy(keccak256("Curation"), address(curation)); + vm.stopPrank(); + } // Deploy DisputeManager vm.startPrank(deployer); diff --git a/packages/testing/test/integration/AgreementLifecycle.t.sol b/packages/testing/test/integration/AgreementLifecycle.t.sol index 515450460..9e01afc77 100644 --- a/packages/testing/test/integration/AgreementLifecycle.t.sol +++ b/packages/testing/test/integration/AgreementLifecycle.t.sol @@ -83,46 +83,49 @@ contract AgreementLifecycleTest is FullStackHarness { assertEq(uint8(ssAgreement.collectorAgreement.state), uint8(IRecurringCollector.AgreementState.Accepted)); // -- Step 3: Advance time and collect -- - uint256 collectSeconds = 1800; // 30 minutes - skip(collectSeconds); - - // Add extra tokens to indexer's provision for stake locking - uint256 expectedTokens = tokensPerSecond * collectSeconds; - uint256 tokensToLock = expectedTokens * STAKE_TO_FEES_RATIO; - _mintTokens(indexer.addr, tokensToLock); - vm.startPrank(indexer.addr); - token.approve(address(staking), tokensToLock); - staking.stakeTo(indexer.addr, tokensToLock); - staking.addToProvision(indexer.addr, address(subgraphService), tokensToLock); - vm.stopPrank(); - - uint256 indexerBalanceBefore = token.balanceOf(indexer.addr); - (uint256 escrowBefore, , ) = escrow.escrowAccounts(address(ram), address(recurringCollector), indexer.addr); - - // Advance past allocation creation epoch so POI isn't "too young" - vm.roll(block.number + EPOCH_LENGTH); - - uint256 tokensCollected = _collectIndexingFees( - indexer, - agreementId, - 0, // entities - keccak256("poi1"), - block.number - 1 - ); - - // Verify tokens flowed correctly - assertTrue(tokensCollected > 0, "should collect tokens"); - uint256 indexerBalanceAfter = token.balanceOf(indexer.addr); - uint256 protocolBurn = tokensCollected.mulPPMRoundUp(PROTOCOL_PAYMENT_CUT); - assertEq( - indexerBalanceAfter - indexerBalanceBefore, - tokensCollected - protocolBurn, - "indexer received tokens minus protocol cut" - ); - - // Verify escrow changed (RAM's beforeCollection/afterCollection may adjust balance) - (uint256 escrowAfter, , ) = escrow.escrowAccounts(address(ram), address(recurringCollector), indexer.addr); - assertTrue(escrowAfter < escrowBefore, "escrow balance decreased after collection"); + // Block-scoped to release step-3 locals before step 4 — legacy compiler pipeline runs out of stack slots otherwise. + { + uint256 collectSeconds = 1800; // 30 minutes + skip(collectSeconds); + + // Add extra tokens to indexer's provision for stake locking + uint256 expectedTokens = tokensPerSecond * collectSeconds; + uint256 tokensToLock = expectedTokens * STAKE_TO_FEES_RATIO; + _mintTokens(indexer.addr, tokensToLock); + vm.startPrank(indexer.addr); + token.approve(address(staking), tokensToLock); + staking.stakeTo(indexer.addr, tokensToLock); + staking.addToProvision(indexer.addr, address(subgraphService), tokensToLock); + vm.stopPrank(); + + uint256 indexerBalanceBefore = token.balanceOf(indexer.addr); + (uint256 escrowBefore, , ) = escrow.escrowAccounts(address(ram), address(recurringCollector), indexer.addr); + + // Advance past allocation creation epoch so POI isn't "too young" + vm.roll(block.number + EPOCH_LENGTH); + + uint256 tokensCollected = _collectIndexingFees( + indexer, + agreementId, + 0, // entities + keccak256("poi1"), + block.number - 1 + ); + + // Verify tokens flowed correctly + assertTrue(tokensCollected > 0, "should collect tokens"); + uint256 indexerBalanceAfter = token.balanceOf(indexer.addr); + uint256 protocolBurn = tokensCollected.mulPPMRoundUp(PROTOCOL_PAYMENT_CUT); + assertEq( + indexerBalanceAfter - indexerBalanceBefore, + tokensCollected - protocolBurn, + "indexer received tokens minus protocol cut" + ); + + // Verify escrow changed (RAM's beforeCollection/afterCollection may adjust balance) + (uint256 escrowAfter, , ) = escrow.escrowAccounts(address(ram), address(recurringCollector), indexer.addr); + assertTrue(escrowAfter < escrowBefore, "escrow balance decreased after collection"); + } // -- Step 4: Reconcile RAM state -- ram.reconcileAgreement(IAgreementCollector(address(recurringCollector)), agreementId); diff --git a/packages/toolshed/package.json b/packages/toolshed/package.json index b60e2a86d..375bb36ff 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.1-dips.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 a97f9d29c..cf6fd0579 100644 --- a/packages/toolshed/src/hardhat/hardhat.base.config.ts +++ b/packages/toolshed/src/hardhat/hardhat.base.config.ts @@ -1,5 +1,5 @@ import { vars } from 'hardhat/config' -import type { HardhatUserConfig, NetworksUserConfig, ProjectPathsUserConfig, SolidityUserConfig } from 'hardhat/types' +import type { HardhatUserConfig, NetworksUserConfig, ProjectPathsUserConfig, SolcUserConfig } from 'hardhat/types' import { resolveAddressBook } from '../lib/resolve' @@ -41,13 +41,15 @@ const ARBITRUM_SEPOLIA_RPC = vars.get('ARBITRUM_SEPOLIA_RPC', 'https://sepolia-r const LOCAL_NETWORK_RPC = vars.get('LOCAL_NETWORK_RPC', 'http://chain:8545') const LOCALHOST_RPC = vars.get('LOCALHOST_RPC', 'http://localhost:8545') -export const solidityUserConfig: SolidityUserConfig = { - version: '0.8.27', +export const solidityUserConfig: SolcUserConfig = { + version: '0.8.35', settings: { optimizer: { enabled: true, runs: 100, }, + viaIR: true, + evmVersion: 'cancun', }, } @@ -58,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: @@ -81,6 +90,7 @@ export const networksUserConfig = function (callerRequire: typeof require): Base return { hardhat: { chainId: 31337, + hardfork: 'cancun', accounts: { mnemonic: 'myth like bonus scare over problem client lizard pioneer submit female collect', }, diff --git a/packages/toolshed/src/hardhat/index.ts b/packages/toolshed/src/hardhat/index.ts index 689ab47a6..629b971d1 100644 --- a/packages/toolshed/src/hardhat/index.ts +++ b/packages/toolshed/src/hardhat/index.ts @@ -1,7 +1,7 @@ export { isProjectBuilt, loadTasks } from './config' export { setERC20Balance, setGRTBalance } from './erc20' export { getEventData } from './event' -export { hardhatBaseConfig } from './hardhat.base.config' +export { hardhatBaseConfig, solidityUserConfig } from './hardhat.base.config' export { loadConfig, patchConfig, saveToAddressBook } from './ignition' export { requireLocalNetwork } from './local' export { diff --git a/packages/toolshed/test/recurring-collector.test.ts b/packages/toolshed/test/recurring-collector.test.ts new file mode 100644 index 000000000..9a8e7efd7 --- /dev/null +++ b/packages/toolshed/test/recurring-collector.test.ts @@ -0,0 +1,183 @@ +import assert from 'node:assert/strict' + +import { ethers } from 'ethers' + +import { + decodeAcceptIndexingAgreementMetadata, + decodeIndexingAgreementTermsV1, + decodeSignedRCA, + encodeCollectIndexingFeesData, +} from '../dist/core/index.js' + +const coder = ethers.AbiCoder.defaultAbiCoder() + +// -- decodeSignedRCA round-trip -- + +{ + const rca = { + deadline: 1000000n, + endsAt: 2000000n, + payer: '0x1111111111111111111111111111111111111111', + dataService: '0x2222222222222222222222222222222222222222', + serviceProvider: '0x3333333333333333333333333333333333333333', + maxInitialTokens: 500n * 10n ** 18n, + maxOngoingTokensPerSecond: 1n * 10n ** 15n, + minSecondsPerCollection: 3600n, + maxSecondsPerCollection: 86400n, + conditions: 0n, + nonce: 42n, + metadata: '0xdeadbeef', + } + const signature = '0x' + 'ab'.repeat(65) + + const encoded = coder.encode( + [ + '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) rca, bytes signature)', + ], + [{ rca, signature }], + ) + + const decoded = decodeSignedRCA(encoded) + + assert.equal(decoded.rca.deadline, rca.deadline) + assert.equal(decoded.rca.endsAt, rca.endsAt) + assert.equal(decoded.rca.payer, rca.payer) + assert.equal(decoded.rca.dataService, rca.dataService) + assert.equal(decoded.rca.serviceProvider, rca.serviceProvider) + assert.equal(decoded.rca.maxInitialTokens, rca.maxInitialTokens) + assert.equal(decoded.rca.maxOngoingTokensPerSecond, rca.maxOngoingTokensPerSecond) + assert.equal(decoded.rca.minSecondsPerCollection, rca.minSecondsPerCollection) + assert.equal(decoded.rca.maxSecondsPerCollection, rca.maxSecondsPerCollection) + assert.equal(decoded.rca.conditions, rca.conditions) + assert.equal(decoded.rca.nonce, rca.nonce) + assert.equal(decoded.rca.metadata, rca.metadata) + assert.equal(decoded.signature, signature) + console.log('PASS: decodeSignedRCA round-trip') +} + +// -- decodeSignedRCA with empty metadata -- + +{ + const rca = { + deadline: 100n, + endsAt: 200n, + payer: '0x' + '00'.repeat(20), + dataService: '0x' + '00'.repeat(20), + serviceProvider: '0x' + '00'.repeat(20), + maxInitialTokens: 0n, + maxOngoingTokensPerSecond: 0n, + minSecondsPerCollection: 0n, + maxSecondsPerCollection: 0n, + conditions: 0n, + nonce: 0n, + metadata: '0x', + } + const signature = '0x' + + const encoded = coder.encode( + [ + '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) rca, bytes signature)', + ], + [{ rca, signature }], + ) + + const decoded = decodeSignedRCA(encoded) + assert.equal(decoded.rca.metadata, '0x') + assert.equal(decoded.signature, '0x') + console.log('PASS: decodeSignedRCA with empty metadata') +} + +// -- decodeAcceptIndexingAgreementMetadata round-trip -- + +{ + const subgraphDeploymentId = ethers.id('my-subgraph') + const version = 0n // V1 = 0 in the enum + const terms = coder.encode(['uint256', 'uint256'], [1000n, 2000n]) + + const encoded = coder.encode( + ['tuple(bytes32 subgraphDeploymentId, uint8 version, bytes terms)'], + [{ subgraphDeploymentId, version, terms }], + ) + + const decoded = decodeAcceptIndexingAgreementMetadata(encoded) + + assert.equal(decoded.subgraphDeploymentId, subgraphDeploymentId) + assert.equal(decoded.version, version) + assert.equal(decoded.terms, terms) + console.log('PASS: decodeAcceptIndexingAgreementMetadata round-trip') +} + +// -- decodeAcceptIndexingAgreementMetadata with empty terms -- + +{ + const encoded = coder.encode( + ['tuple(bytes32 subgraphDeploymentId, uint8 version, bytes terms)'], + [{ subgraphDeploymentId: ethers.ZeroHash, version: 0, terms: '0x' }], + ) + + const decoded = decodeAcceptIndexingAgreementMetadata(encoded) + assert.equal(decoded.terms, '0x') + console.log('PASS: decodeAcceptIndexingAgreementMetadata with empty terms') +} + +// -- decodeAcceptIndexingAgreementMetadata with unknown version -- + +{ + const encoded = coder.encode( + ['tuple(bytes32 subgraphDeploymentId, uint8 version, bytes terms)'], + [{ subgraphDeploymentId: ethers.ZeroHash, version: 255, terms: '0x' }], + ) + + const decoded = decodeAcceptIndexingAgreementMetadata(encoded) + assert.equal(decoded.version, 255n) + console.log('PASS: decodeAcceptIndexingAgreementMetadata with unknown version') +} + +// -- decodeIndexingAgreementTermsV1 round-trip -- + +{ + const tokensPerSecond = 1000n * 10n ** 18n + const tokensPerEntityPerSecond = 5n * 10n ** 15n + + const encoded = coder.encode( + ['tuple(uint256 tokensPerSecond, uint256 tokensPerEntityPerSecond)'], + [{ tokensPerSecond, tokensPerEntityPerSecond }], + ) + + const decoded = decodeIndexingAgreementTermsV1(encoded) + + assert.equal(decoded.tokensPerSecond, tokensPerSecond) + assert.equal(decoded.tokensPerEntityPerSecond, tokensPerEntityPerSecond) + console.log('PASS: decodeIndexingAgreementTermsV1 round-trip') +} + +// -- encodeCollectIndexingFeesData round-trip -- + +{ + const agreementId = '0x' + 'ab'.repeat(16) + const entities = 1000n + const poi = ethers.id('test-poi') + const poiBlockNumber = 12345n + const metadata = '0xdeadbeef' + const maxSlippage = 100n + + const encoded = encodeCollectIndexingFeesData(agreementId, entities, poi, poiBlockNumber, metadata, maxSlippage) + + // Decode outer: (bytes16, bytes) + const [decodedAgreementId, innerData] = coder.decode(['bytes16', 'bytes'], encoded) + assert.equal(decodedAgreementId, agreementId) + + // Decode inner: CollectIndexingFeeDataV1 + const [decodedEntities, decodedPoi, decodedPoiBlockNumber, decodedMetadata, decodedMaxSlippage] = coder.decode( + ['uint256', 'bytes32', 'uint256', 'bytes', 'uint256'], + innerData, + ) + assert.equal(decodedEntities, entities) + assert.equal(decodedPoi, poi) + assert.equal(decodedPoiBlockNumber, poiBlockNumber) + assert.equal(decodedMetadata, metadata) + assert.equal(decodedMaxSlippage, maxSlippage) + console.log('PASS: encodeCollectIndexingFeesData round-trip') +} + +console.log('\nAll tests passed.') 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 0554d28f1..c2f67fdae 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 @@ -64,8 +76,8 @@ catalogs: specifier: ^16.4.0 version: 16.4.0 hardhat: - specifier: ^2.26.0 - version: 2.26.3 + specifier: ^2.28.0 + version: 2.28.6 hardhat-contract-sizer: specifier: ^2.10.0 version: 2.10.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 @@ -99,6 +114,9 @@ catalogs: 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 @@ -204,7 +227,7 @@ importers: version: 3.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@defi-wonderland/smock': specifier: ^2.4.1 - version: 2.4.1(@ethersproject/abi@5.8.0)(@ethersproject/abstract-provider@5.8.0)(@ethersproject/abstract-signer@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.26.3(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.26.3(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.4.1(@ethersproject/abi@5.8.0)(@ethersproject/abstract-provider@5.8.0)(@ethersproject/abstract-signer@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)))(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)) '@ethersproject/abi': specifier: ^5.8.0 version: 5.8.0 @@ -228,19 +251,19 @@ importers: version: link:../interfaces '@nomicfoundation/hardhat-network-helpers': specifier: ^1.0.0 - version: 1.1.0(hardhat@2.26.3(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: 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)) '@nomicfoundation/hardhat-verify': specifier: 2.1.1 - version: 2.1.1(hardhat@2.26.3(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.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)) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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.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.0 - version: 3.1.8(hardhat@2.26.3(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: 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.26.3(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.26.3(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 @@ -249,13 +272,13 @@ importers: version: 3.4.2 '@openzeppelin/hardhat-upgrades': specifier: ^1.22.1 - 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.26.3(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.26.3(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.26.3(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: 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)) '@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) '@typechain/hardhat': specifier: ^6.1.2 - 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.26.3(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)) + 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)) '@types/chai': specifier: ^4.2.0 version: 4.3.20 @@ -300,22 +323,22 @@ importers: version: 2.12.6(graphql@16.11.0) hardhat: specifier: 'catalog:' - version: 2.26.3(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.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-abi-exporter: specifier: ^2.11.0 - version: 2.11.0(hardhat@2.26.3(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.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: 'catalog:' - version: 2.10.1(hardhat@2.26.3(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.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: 'catalog:' - version: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) + 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-ignore-warnings: specifier: 'catalog:' version: 0.2.12 hardhat-storage-layout: specifier: 'catalog:' - version: 0.1.7(hardhat@2.26.3(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: 0.1.7(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)) prettier: specifier: 'catalog:' version: 3.8.1 @@ -327,7 +350,7 @@ importers: version: 6.0.3(typescript@5.9.3) solidity-coverage: specifier: ^0.8.16 - version: 0.8.16(hardhat@2.26.3(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: 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)) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) @@ -364,7 +387,7 @@ importers: version: 3.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@defi-wonderland/smock': specifier: ^2.4.1 - version: 2.4.1(@ethersproject/abi@5.8.0)(@ethersproject/abstract-provider@5.8.0)(@ethersproject/abstract-signer@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.26.3(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.26.3(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.4.1(@ethersproject/abi@5.8.0)(@ethersproject/abstract-provider@5.8.0)(@ethersproject/abstract-signer@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)))(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)) '@ethersproject/abi': specifier: ^5.8.0 version: 5.8.0 @@ -385,16 +408,16 @@ importers: version: 1.8.7(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@nomicfoundation/hardhat-network-helpers': specifier: ^1.0.0 - version: 1.1.0(hardhat@2.26.3(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: 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': specifier: ^2.2.3 - version: 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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.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.0 - version: 3.1.8(hardhat@2.26.3(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: 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.26.3(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.26.3(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 @@ -403,13 +426,13 @@ importers: version: 3.4.2 '@openzeppelin/hardhat-upgrades': specifier: ^1.22.1 - 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.26.3(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.26.3(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.26.3(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: 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)) '@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) '@typechain/hardhat': specifier: ^6.1.2 - 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.26.3(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)) + 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)) '@types/chai': specifier: ^4.2.0 version: 4.3.20 @@ -454,19 +477,19 @@ importers: version: 2.12.6(graphql@16.11.0) hardhat: specifier: 'catalog:' - version: 2.26.3(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.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-abi-exporter: specifier: ^2.11.0 - version: 2.11.0(hardhat@2.26.3(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.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.10.0 - version: 2.10.1(hardhat@2.26.3(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.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.8 - version: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) + 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-storage-layout: specifier: ^0.1.7 - version: 0.1.7(hardhat@2.26.3(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: 0.1.7(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)) prettier: specifier: 'catalog:' version: 3.8.1 @@ -475,7 +498,7 @@ importers: version: 2.1.0(prettier@3.8.1) solidity-coverage: specifier: ^0.8.16 - version: 0.8.16(hardhat@2.26.3(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: 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)) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) @@ -533,13 +556,13 @@ importers: version: 1.8.7(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@nomicfoundation/hardhat-network-helpers': specifier: ^1.0.0 - version: 1.1.0(hardhat@2.26.3(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: 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': specifier: ^2.2.3 - version: 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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.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.0 - version: 3.1.8(hardhat@2.26.3(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: 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/contracts': specifier: 3.4.2 version: 3.4.2 @@ -548,13 +571,13 @@ importers: version: 3.4.2 '@openzeppelin/hardhat-upgrades': specifier: ^1.22.1 - 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.26.3(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.26.3(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.26.3(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: 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)) '@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) '@typechain/hardhat': specifier: ^6.1.2 - 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.26.3(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)) + 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)) '@types/glob': specifier: ^8.1.0 version: 8.1.0 @@ -587,19 +610,19 @@ importers: version: 2.12.6(graphql@16.11.0) hardhat: specifier: 'catalog:' - version: 2.26.3(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.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-abi-exporter: specifier: ^2.11.0 - version: 2.11.0(hardhat@2.26.3(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.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.10.0 - version: 2.10.1(hardhat@2.26.3(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.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.8 - version: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) + 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-storage-layout: specifier: ^0.1.7 - version: 0.1.7(hardhat@2.26.3(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: 0.1.7(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)) prettier: specifier: 'catalog:' version: 3.8.1 @@ -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.26.3(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.26.3(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.26.3(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.26.3(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.26.3(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.26.3(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.26.3(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.26.3(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.26.3(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,50 +678,35 @@ 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.26.3(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.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-abi-exporter: specifier: ^2.2.0 - version: 2.11.0(hardhat@2.26.3(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.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 - version: 2.10.1(hardhat@2.26.3(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)) + 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 - version: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) + 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.26.3(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.26.3(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 @@ -731,15 +718,12 @@ importers: version: 6.0.3(typescript@5.9.3) solidity-coverage: specifier: ^0.8.16 - version: 0.8.16(hardhat@2.26.3(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 + 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)) 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) @@ -858,17 +848,17 @@ importers: version: link:../toolshed '@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.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + 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 devDependencies: '@nomicfoundation/hardhat-verify': specifier: ^2.0.12 - version: 2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + version: 2.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)) '@types/chai': specifier: ^4.0.0 version: 4.3.20 @@ -889,10 +879,10 @@ importers: version: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: specifier: 'catalog:' - version: 2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + version: 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) hardhat-secure-accounts: specifier: ^1.0.4 - 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.26.3(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.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + 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@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)) mocha: specifier: ^10.8.2 version: 10.8.2 @@ -916,28 +906,28 @@ importers: version: link:../toolshed '@nomicfoundation/hardhat-chai-matchers': specifier: ^2.0.0 - 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.26.3(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.26.3(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.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.26.3(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: 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-foundry': specifier: ^1.1.1 - version: 1.2.0(hardhat@2.26.3(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: 1.2.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)) '@nomicfoundation/hardhat-ignition': specifier: ^0.15.9 - version: 0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(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)))(bufferutil@4.0.9)(hardhat@2.26.3(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) + version: 0.15.13(@nomicfoundation/hardhat-verify@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)))(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) '@nomicfoundation/hardhat-ignition-ethers': specifier: ^0.15.9 - version: 0.15.14(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(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)))(bufferutil@4.0.9)(hardhat@2.26.3(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))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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: 0.15.14(@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)))(@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@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)))(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))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(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)) '@nomicfoundation/hardhat-network-helpers': specifier: ^1.0.0 - version: 1.1.0(hardhat@2.26.3(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: 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)) '@nomicfoundation/hardhat-toolbox': specifier: ^4.0.0 - version: 4.0.0(8d521f1e2e60e049232a7f203ff6170d) + version: 4.0.0(c97007b62875fc8ad4d8d85b595e6fa7) '@nomicfoundation/hardhat-verify': specifier: ^2.1.1 - version: 2.1.1(hardhat@2.26.3(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.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)) '@nomicfoundation/ignition-core': specifier: ^0.15.9 version: 0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -952,13 +942,13 @@ importers: version: 0.4.0(@openzeppelin/defender-deploy-client-cli@0.0.1-alpha.10(encoding@0.1.13))(@openzeppelin/upgrades-core@1.44.1) '@tenderly/hardhat-tenderly': specifier: ^1.11.0 - version: 1.11.0(@types/node@20.19.14)(bufferutil@4.0.9)(encoding@0.1.13)(hardhat@2.26.3(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) + 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-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: ^9.0.0 - 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.26.3(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)) + 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/chai': specifier: ^4.2.0 version: 4.3.20 @@ -985,19 +975,19 @@ importers: version: 11.0.3 hardhat: specifier: 'catalog:' - version: 2.26.3(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.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.10.0 - version: 2.10.1(hardhat@2.26.3(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.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.8 - version: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) + 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-graph-protocol: specifier: workspace:^ version: link:../hardhat-graph-protocol hardhat-secure-accounts: specifier: ^1.0.5 - 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.26.3(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.26.3(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: 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)) lint-staged: specifier: 'catalog:' version: 16.2.7 @@ -1012,7 +1002,7 @@ importers: version: 6.0.3(typescript@5.9.3) solidity-coverage: specifier: ^0.8.0 - version: 0.8.16(hardhat@2.26.3(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: 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)) ts-node: specifier: '>=8.0.0' version: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) @@ -1033,13 +1023,13 @@ importers: version: 5.7.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@nomicfoundation/hardhat-ethers': specifier: ^3.0.0 - version: 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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: 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-toolbox': specifier: ^4.0.0 - version: 4.0.0(841324e874603666491d4961f5a3314c) + version: 4.0.0(387217c56543caeacafa191f98890669) '@nomicfoundation/hardhat-verify': specifier: ^2.0.0 - version: 2.1.1(hardhat@2.26.3(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.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)) '@openzeppelin/contracts': specifier: 3.4.2 version: 3.4.2 @@ -1060,7 +1050,7 @@ importers: version: ethers@5.7.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: specifier: 'catalog:' - version: 2.26.3(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.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-ignore-warnings: specifier: 'catalog:' version: 0.2.12 @@ -1190,28 +1180,28 @@ importers: version: link:../toolshed '@nomicfoundation/hardhat-chai-matchers': specifier: ^2.0.0 - 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.26.3(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.26.3(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.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.26.3(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: 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-foundry': specifier: ^1.1.1 - version: 1.2.0(hardhat@2.26.3(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: 1.2.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)) '@nomicfoundation/hardhat-ignition': specifier: ^0.15.9 - version: 0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(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)))(bufferutil@4.0.9)(hardhat@2.26.3(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) + version: 0.15.13(@nomicfoundation/hardhat-verify@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)))(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) '@nomicfoundation/hardhat-ignition-ethers': specifier: ^0.15.9 - version: 0.15.14(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(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)))(bufferutil@4.0.9)(hardhat@2.26.3(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))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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: 0.15.14(@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)))(@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@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)))(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))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(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)) '@nomicfoundation/hardhat-network-helpers': specifier: ^1.0.0 - version: 1.1.0(hardhat@2.26.3(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: 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)) '@nomicfoundation/hardhat-toolbox': specifier: ^4.0.0 - version: 4.0.0(8d521f1e2e60e049232a7f203ff6170d) + version: 4.0.0(c97007b62875fc8ad4d8d85b595e6fa7) '@nomicfoundation/hardhat-verify': specifier: ^2.0.10 - version: 2.1.1(hardhat@2.26.3(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.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)) '@nomicfoundation/ignition-core': specifier: ^0.15.9 version: 0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -1226,13 +1216,13 @@ importers: version: 0.4.0(@openzeppelin/defender-deploy-client-cli@0.0.1-alpha.10(encoding@0.1.13))(@openzeppelin/upgrades-core@1.44.1) '@tenderly/hardhat-tenderly': specifier: ^1.11.0 - version: 1.11.0(@types/node@20.19.14)(bufferutil@4.0.9)(encoding@0.1.13)(hardhat@2.26.3(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) + 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-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: ^9.0.0 - 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.26.3(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)) + 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/chai': specifier: ^4.2.0 version: 4.3.20 @@ -1259,19 +1249,19 @@ importers: version: 11.0.3 hardhat: specifier: 'catalog:' - version: 2.26.3(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.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.10.0 - version: 2.10.1(hardhat@2.26.3(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.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.8 - version: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) + 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-graph-protocol: specifier: workspace:^ version: link:../hardhat-graph-protocol hardhat-secure-accounts: specifier: ^1.0.5 - 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.26.3(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.26.3(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: 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)) json5: specifier: ^2.2.3 version: 2.2.3 @@ -1289,10 +1279,10 @@ importers: version: 6.0.3(typescript@5.9.3) solidity-coverage: specifier: ^0.8.0 - version: 0.8.16(hardhat@2.26.3(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: 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)) solidity-docgen: specifier: ^0.6.0-beta.36 - version: 0.6.0-beta.36(hardhat@2.26.3(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: 0.6.0-beta.36(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)) ts-node: specifier: '>=8.0.0' version: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) @@ -1365,13 +1355,13 @@ importers: version: 3.15.2(graphql-yoga@5.15.2(graphql@16.11.0))(graphql@16.11.0) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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.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.0 - version: 3.1.8(hardhat@2.26.3(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: 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.26.3(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.26.3(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 @@ -1380,13 +1370,13 @@ importers: version: 3.4.2 '@openzeppelin/hardhat-upgrades': specifier: ^1.22.1 - 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.26.3(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.26.3(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.26.3(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: 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)) '@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) '@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.26.3(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)) + 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)) '@types/mocha': specifier: ^9.1.0 version: 9.1.1 @@ -1425,19 +1415,19 @@ importers: version: 5.15.2(graphql@16.11.0) hardhat: specifier: 'catalog:' - version: 2.26.3(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.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-abi-exporter: specifier: ^2.0.1 - version: 2.11.0(hardhat@2.26.3(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.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.1 - version: 2.10.1(hardhat@2.26.3(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.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-deploy: specifier: ^0.7.0-beta.9 - version: 0.7.11(@ethersproject/hardware-wallets@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(hardhat@2.26.3(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) + version: 0.7.11(@ethersproject/hardware-wallets@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.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-gas-reporter: specifier: ^1.0.1 - version: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) + 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-ignore-warnings: specifier: 'catalog:' version: 0.2.12 @@ -1464,7 +1454,7 @@ importers: version: 6.0.3(typescript@5.9.3) solidity-coverage: specifier: ^0.8.16 - version: 0.8.16(hardhat@2.26.3(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: 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)) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) @@ -1491,10 +1481,10 @@ importers: version: link:../issuance '@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.26.3(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: 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) @@ -1503,7 +1493,7 @@ importers: version: 11.0.3 hardhat: specifier: 'catalog:' - version: 2.26.3(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.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: specifier: ^2.2.3 version: 2.2.3 @@ -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==} @@ -3609,70 +3568,70 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nomicfoundation/edr-darwin-arm64@0.11.3': - resolution: {integrity: sha512-w0tksbdtSxz9nuzHKsfx4c2mwaD0+l5qKL2R290QdnN9gi9AV62p9DHkOgfBdyg6/a6ZlnQqnISi7C9avk/6VA==} - engines: {node: '>= 18'} - '@nomicfoundation/edr-darwin-arm64@0.12.0-next.22': resolution: {integrity: sha512-TpEBSKyMZJEPvYwBPYclC2b+qobKjn1YhVa7aJ1R7RMPy5dJ/PqsrUK5UuUFFybBqoIorru5NTcsyCMWP5T/Fg==} engines: {node: '>= 20'} - '@nomicfoundation/edr-darwin-x64@0.11.3': - resolution: {integrity: sha512-QR4jAFrPbOcrO7O2z2ESg+eUeIZPe2bPIlQYgiJ04ltbSGW27FblOzdd5+S3RoOD/dsZGKAvvy6dadBEl0NgoA==} - engines: {node: '>= 18'} + '@nomicfoundation/edr-darwin-arm64@0.12.0-next.23': + resolution: {integrity: sha512-Amh7mRoDzZyJJ4efqoePqdoZOzharmSOttZuJDlVE5yy07BoE8hL6ZRpa5fNYn0LCqn/KoWs8OHANWxhKDGhvQ==} + engines: {node: '>= 20'} '@nomicfoundation/edr-darwin-x64@0.12.0-next.22': resolution: {integrity: sha512-aK/+m8xUkR4u+czTVGU06nSFVH43AY6XCBoR2YjO8SglAAjCSTWK3WAfVb6FcsriMmKv4PrvoyHLMbMP+fXcGA==} engines: {node: '>= 20'} - '@nomicfoundation/edr-linux-arm64-gnu@0.11.3': - resolution: {integrity: sha512-Ktjv89RZZiUmOFPspuSBVJ61mBZQ2+HuLmV67InNlh9TSUec/iDjGIwAn59dx0bF/LOSrM7qg5od3KKac4LJDQ==} - engines: {node: '>= 18'} + '@nomicfoundation/edr-darwin-x64@0.12.0-next.23': + resolution: {integrity: sha512-9wn489FIQm7m0UCD+HhktjWx6vskZzeZD9oDc2k9ZvbBzdXwPp5tiDqUBJ+eQpByAzCDfteAJwRn2lQCE0U+Iw==} + engines: {node: '>= 20'} '@nomicfoundation/edr-linux-arm64-gnu@0.12.0-next.22': resolution: {integrity: sha512-W5vXMleG14hVzRYGPEwlHLJ6iiQE8Qh63Uj538nAz4YUI6wWSgUOZE7K2Gt1EdujZGnrt7kfDslgJ96n4nKQZw==} engines: {node: '>= 20'} - '@nomicfoundation/edr-linux-arm64-musl@0.11.3': - resolution: {integrity: sha512-B3sLJx1rL2E9pfdD4mApiwOZSrX0a/KQSBWdlq1uAhFKqkl00yZaY4LejgZndsJAa4iKGQJlGnw4HCGeVt0+jA==} - engines: {node: '>= 18'} + '@nomicfoundation/edr-linux-arm64-gnu@0.12.0-next.23': + resolution: {integrity: sha512-nlk5EejSzEUfEngv0Jkhqq3/wINIfF2ED9wAofc22w/V1DV99ASh9l3/e/MIHOQFecIZ9MDqt0Em9/oDyB1Uew==} + engines: {node: '>= 20'} '@nomicfoundation/edr-linux-arm64-musl@0.12.0-next.22': resolution: {integrity: sha512-VDp7EB3iY8MH/fFVcgEzLDGYmtS6j2honNc0RNUCFECKPrdsngGrTG8p+YFxyVjq2m5GEsdyKo4e+BKhaUNPdg==} engines: {node: '>= 20'} - '@nomicfoundation/edr-linux-x64-gnu@0.11.3': - resolution: {integrity: sha512-D/4cFKDXH6UYyKPu6J3Y8TzW11UzeQI0+wS9QcJzjlrrfKj0ENW7g9VihD1O2FvXkdkTjcCZYb6ai8MMTCsaVw==} - engines: {node: '>= 18'} + '@nomicfoundation/edr-linux-arm64-musl@0.12.0-next.23': + resolution: {integrity: sha512-SJuPBp3Rc6vM92UtVTUxZQ/QlLhLfwTftt2XUiYohmGKB3RjGzpgduEFMCA0LEnucUckU6UHrJNFHiDm77C4PQ==} + engines: {node: '>= 20'} '@nomicfoundation/edr-linux-x64-gnu@0.12.0-next.22': resolution: {integrity: sha512-XL6oA3ymRSQYyvg6hF1KIax6V/9vlWr5gJ8GPHVVODk1a/YfuEEY1osN5Zmo6aztUkSGKwSuac/3Ax7rfDDiSg==} engines: {node: '>= 20'} - '@nomicfoundation/edr-linux-x64-musl@0.11.3': - resolution: {integrity: sha512-ergXuIb4nIvmf+TqyiDX5tsE49311DrBky6+jNLgsGDTBaN1GS3OFwFS8I6Ri/GGn6xOaT8sKu3q7/m+WdlFzg==} - engines: {node: '>= 18'} + '@nomicfoundation/edr-linux-x64-gnu@0.12.0-next.23': + resolution: {integrity: sha512-NU+Qs3u7Qt6t3bJFdmmjd5CsvgI2bPPzO31KifM2Ez96/jsXYho5debtTQnimlb5NAqiHTSlxjh/F8ROcptmeQ==} + engines: {node: '>= 20'} '@nomicfoundation/edr-linux-x64-musl@0.12.0-next.22': resolution: {integrity: sha512-hmkRIXxWa9P0PwfXOAO6WUw11GyV5gpxcMunqWBTkwZ4QW/hi/CkXmlLo6VHd6ceCwpUNLhCGndBtrOPrNRi4A==} engines: {node: '>= 20'} - '@nomicfoundation/edr-win32-x64-msvc@0.11.3': - resolution: {integrity: sha512-snvEf+WB3OV0wj2A7kQ+ZQqBquMcrozSLXcdnMdEl7Tmn+KDCbmFKBt3Tk0X3qOU4RKQpLPnTxdM07TJNVtung==} - engines: {node: '>= 18'} + '@nomicfoundation/edr-linux-x64-musl@0.12.0-next.23': + resolution: {integrity: sha512-F78fZA2h6/ssiCSZOovlgIu0dUeI7ItKPsDDF3UUlIibef052GCXmliMinC90jVPbrjUADMd1BUwjfI0Z8OllQ==} + engines: {node: '>= 20'} '@nomicfoundation/edr-win32-x64-msvc@0.12.0-next.22': resolution: {integrity: sha512-X7f+7KUMm00trsXAHCHJa+x1fc3QAbk2sBctyOgpET+GLrfCXbxqrccKi7op8f0zTweAVGg1Hsc8SjjC7kwFLw==} engines: {node: '>= 20'} - '@nomicfoundation/edr@0.11.3': - resolution: {integrity: sha512-kqILRkAd455Sd6v8mfP3C1/0tCOynJWY+Ir+k/9Boocu2kObCrsFgG+ZWB7fSBVdd9cPVSNrnhWS+V+PEo637g==} - engines: {node: '>= 18'} + '@nomicfoundation/edr-win32-x64-msvc@0.12.0-next.23': + resolution: {integrity: sha512-IfJZQJn7d/YyqhmguBIGoCKjE9dKjbu6V6iNEPApfwf5JyyjHYyyfkLU4rf7hygj57bfH4sl1jtQ6r8HnT62lw==} + engines: {node: '>= 20'} '@nomicfoundation/edr@0.12.0-next.22': resolution: {integrity: sha512-JigYWf2stjpDxSndBsxRoobQHK8kz4SAVaHtTIKQLIHbsBwymE8i120Ejne6Jk+Ndc5CsNINXB8/bK6vLPe9jA==} engines: {node: '>= 20'} + '@nomicfoundation/edr@0.12.0-next.23': + resolution: {integrity: sha512-F2/6HZh8Q9RsgkOIkRrckldbhPjIZY7d4mT9LYuW68miwGQ5l7CkAgcz9fRRiurA0+YJhtsbx/EyrD9DmX9BOw==} + engines: {node: '>= 20'} + '@nomicfoundation/ethereumjs-rlp@5.0.4': resolution: {integrity: sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==} engines: {node: '>=18'} @@ -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==} @@ -4884,9 +4770,6 @@ 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==} @@ -4938,10 +4821,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 +4840,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 +4864,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 +4894,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 +4906,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 +4913,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 +4931,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 +4938,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 +4957,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 +4970,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 +4997,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 +5011,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,32 +5033,6 @@ 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==} @@ -5393,10 +5048,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 +5075,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 +5085,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 +5122,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,14 +5135,6 @@ 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'} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -5510,33 +5142,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 +5168,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 +5210,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 +5218,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 +5226,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 +5261,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 +5312,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 +5361,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 +5395,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 +5423,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 +5434,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 +5444,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 +5451,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 +5487,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 +5511,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 +5532,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 +5562,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 +5579,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 +5593,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 +5641,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 +5653,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 +5666,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 +5673,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 +5713,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 +5741,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 +5760,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 +5767,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 +5788,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 +5816,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 +5823,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 +5835,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 +5843,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 +5857,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 +5872,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 +5883,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 +5937,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 +5964,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 +5981,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 +6005,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 +6119,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 +6157,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 +6168,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 +6180,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 +6187,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 +6202,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 +6230,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 +6247,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 +6262,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 +6272,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 +6280,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 +6351,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 +6365,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 +6377,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 +6385,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 +6397,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 +6411,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 +6430,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 +6471,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 +6489,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 +6501,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 +6515,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 +6533,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 +6584,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 +6595,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 +6610,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'} @@ -7515,13 +6631,9 @@ packages: 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 - 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==} @@ -7544,9 +6656,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 +6664,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 +6680,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 +6736,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'} @@ -7710,8 +6803,8 @@ packages: peerDependencies: hardhat: ^2.0.3 - hardhat@2.26.3: - resolution: {integrity: sha512-gBfjbxCCEaRgMCRgTpjo1CEoJwqNPhyGMMVHYZJxoQ3LLftp2erSVf8ZF6hTQC0r2wst4NcqNmLWqMnHg1quTw==} + hardhat@2.28.6: + resolution: {integrity: sha512-zQze7qe+8ltwHvhX5NQ8sN1N37WWZGw8L63y+2XcPxGwAjc/SMF829z3NS6o1krX0sryhAsVBK/xrwUqlsot4Q==} hasBin: true peerDependencies: ts-node: '*' @@ -7726,10 +6819,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 +6827,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 +6853,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 +6870,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 +6877,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 +6897,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 +6922,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 +6933,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 +6949,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 +7092,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 +7116,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 +7124,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 +7143,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 +7152,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 +7160,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 +7172,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 +7209,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 +7221,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 +7237,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 +7301,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 +7310,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 +7390,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 +7411,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 +7422,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 +7434,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 +7450,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 +7457,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 +7478,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 +7505,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 +7534,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 +7544,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 +7594,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 +7620,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 +7689,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 +7696,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 +7704,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 +7720,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 +7731,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 +7745,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 +7762,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 +7797,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 +7811,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 +7830,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 +7837,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 +7995,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 +8016,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 +8024,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 +8036,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==} @@ -9346,9 +8061,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 +8084,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 +8096,13 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@1.3.3: - resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==} - 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 +8125,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 +8145,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 +8163,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 +8199,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 +8224,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 +8292,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 +8304,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 +8330,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 +8349,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 +8360,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 +8411,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 +8442,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 +8454,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 +8466,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 +8498,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 +8522,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 +8532,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 +8551,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 +8561,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 +8573,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'} @@ -10065,9 +8600,6 @@ packages: 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 +8625,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 +8690,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 +8733,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 +8753,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 +8763,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 +8771,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 +8791,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 +8798,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 +8813,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 +8842,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 +8877,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 +8896,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 +8909,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 +8985,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 +8992,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 +9020,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,34 +9035,15 @@ 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'} + req-cwd@2.0.0: + resolution: {integrity: sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==} + engines: {node: '>=4'} req-from@2.0.0: resolution: {integrity: sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==} @@ -10690,10 +9090,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 +9105,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 +9117,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 +9200,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 +9208,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 +9225,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 +9232,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 +9239,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 +9358,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 +9369,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 +9390,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 +9436,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 +9448,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 +9456,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 +9479,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 +9487,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'} @@ -11228,24 +9531,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] solidity-comments-linux-arm64-musl@0.0.2: resolution: {integrity: sha512-guCDbHArcjE+JDXYkxx5RZzY1YF6OnAKCo+sTC5fstyW/KGKaQJNPyBNWuwYsQiaEHpvhW1ha537IvlGek8GqA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] solidity-comments-linux-x64-gnu@0.0.2: resolution: {integrity: sha512-zIqLehBK/g7tvrFmQljrfZXfkEeLt2v6wbe+uFu6kH/qAHZa7ybt8Vc0wYcmjo2U0PeBm15d79ee3AkwbIjFdQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] solidity-comments-linux-x64-musl@0.0.2: resolution: {integrity: sha512-R9FeDloVlFGTaVkOlELDVC7+1Tjx5WBPI5L8r0AGOPHK3+jOcRh6sKYpI+VskSPDc3vOO46INkpDgUXrKydlIw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] solidity-comments-win32-arm64-msvc@0.0.2: resolution: {integrity: sha512-QnWJoCQcJj+rnutULOihN9bixOtYWDdF5Rfz9fpHejL1BtNjdLW1om55XNVHGAHPqBxV4aeQQ6OirKnp9zKsug==} @@ -11286,39 +9593,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'} @@ -11346,10 +9626,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==} @@ -11386,10 +9662,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'} @@ -11405,17 +9677,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'} @@ -11459,9 +9724,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==} @@ -11496,14 +9758,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'} @@ -11519,18 +9773,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'} @@ -11543,10 +9789,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'} @@ -11554,9 +9796,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'} @@ -11572,10 +9811,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==} @@ -11583,11 +9818,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'} @@ -11609,10 +9839,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. @@ -11637,9 +9863,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==} @@ -11649,10 +9872,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'} @@ -11671,10 +9890,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==} @@ -11682,30 +9897,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'} @@ -11720,18 +9915,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==} @@ -11745,23 +9932,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 @@ -11820,15 +9995,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'} @@ -11861,13 +10030,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 @@ -11890,9 +10052,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==} @@ -11908,18 +10067,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'} @@ -11943,9 +10090,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'} @@ -11957,9 +10101,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==} @@ -11978,10 +10119,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} @@ -12002,18 +10139,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 @@ -12029,17 +10158,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'} @@ -12054,10 +10172,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'} @@ -12072,19 +10186,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: 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. - 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. @@ -12092,10 +10197,12 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + 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@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + 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 v8-compile-cache-lib@3.0.1: @@ -12116,9 +10223,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'} @@ -12153,111 +10257,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==} @@ -12369,28 +10378,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: @@ -12474,22 +10461,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'} @@ -12504,14 +10475,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==} @@ -12546,16 +10509,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'} @@ -13054,7 +11011,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 @@ -13413,7 +11370,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 @@ -13695,16 +11652,16 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 - '@defi-wonderland/smock@2.4.1(@ethersproject/abi@5.8.0)(@ethersproject/abstract-provider@5.8.0)(@ethersproject/abstract-signer@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.26.3(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.26.3(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))': + '@defi-wonderland/smock@2.4.1(@ethersproject/abi@5.8.0)(@ethersproject/abstract-provider@5.8.0)(@ethersproject/abstract-signer@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)))(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: '@ethersproject/abi': 5.8.0 '@ethersproject/abstract-provider': 5.8.0 '@ethersproject/abstract-signer': 5.8.0 '@nomicfoundation/ethereumjs-util': 9.0.4 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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)) diff: 5.2.0 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 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 lodash.isequalwith: 4.4.0 rxjs: 7.8.2 @@ -13936,7 +11893,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 @@ -13952,7 +11909,7 @@ 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 @@ -13972,20 +11929,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: @@ -13993,26 +11940,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 @@ -14032,51 +11959,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)': + '@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: - '@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))': - 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) + '@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@8.1.1) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) ganache: 7.4.3 transitivePeerDependencies: @@ -14096,7 +11993,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 @@ -14164,19 +12061,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 @@ -15247,12 +13131,12 @@ snapshots: '@ethersproject/providers': 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@graphprotocol/common-ts': 2.0.11(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@graphprotocol/contracts': 7.2.1 - '@nomicfoundation/hardhat-network-helpers': 1.1.0(hardhat@2.26.3(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.26.3(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) + '@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@8.1.1) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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.26.3(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.26.3(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: 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)) inquirer: 8.0.0 lodash: 4.17.21 yaml: 1.10.2 @@ -15662,7 +13546,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 @@ -15677,7 +13561,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 @@ -16180,14 +14064,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 @@ -16250,43 +14126,33 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@nomicfoundation/edr-darwin-arm64@0.11.3': {} - '@nomicfoundation/edr-darwin-arm64@0.12.0-next.22': {} - '@nomicfoundation/edr-darwin-x64@0.11.3': {} + '@nomicfoundation/edr-darwin-arm64@0.12.0-next.23': {} '@nomicfoundation/edr-darwin-x64@0.12.0-next.22': {} - '@nomicfoundation/edr-linux-arm64-gnu@0.11.3': {} + '@nomicfoundation/edr-darwin-x64@0.12.0-next.23': {} '@nomicfoundation/edr-linux-arm64-gnu@0.12.0-next.22': {} - '@nomicfoundation/edr-linux-arm64-musl@0.11.3': {} + '@nomicfoundation/edr-linux-arm64-gnu@0.12.0-next.23': {} '@nomicfoundation/edr-linux-arm64-musl@0.12.0-next.22': {} - '@nomicfoundation/edr-linux-x64-gnu@0.11.3': {} + '@nomicfoundation/edr-linux-arm64-musl@0.12.0-next.23': {} '@nomicfoundation/edr-linux-x64-gnu@0.12.0-next.22': {} - '@nomicfoundation/edr-linux-x64-musl@0.11.3': {} + '@nomicfoundation/edr-linux-x64-gnu@0.12.0-next.23': {} '@nomicfoundation/edr-linux-x64-musl@0.12.0-next.22': {} - '@nomicfoundation/edr-win32-x64-msvc@0.11.3': {} + '@nomicfoundation/edr-linux-x64-musl@0.12.0-next.23': {} '@nomicfoundation/edr-win32-x64-msvc@0.12.0-next.22': {} - '@nomicfoundation/edr@0.11.3': - dependencies: - '@nomicfoundation/edr-darwin-arm64': 0.11.3 - '@nomicfoundation/edr-darwin-x64': 0.11.3 - '@nomicfoundation/edr-linux-arm64-gnu': 0.11.3 - '@nomicfoundation/edr-linux-arm64-musl': 0.11.3 - '@nomicfoundation/edr-linux-x64-gnu': 0.11.3 - '@nomicfoundation/edr-linux-x64-musl': 0.11.3 - '@nomicfoundation/edr-win32-x64-msvc': 0.11.3 + '@nomicfoundation/edr-win32-x64-msvc@0.12.0-next.23': {} '@nomicfoundation/edr@0.12.0-next.22': dependencies: @@ -16298,6 +14164,16 @@ snapshots: '@nomicfoundation/edr-linux-x64-musl': 0.12.0-next.22 '@nomicfoundation/edr-win32-x64-msvc': 0.12.0-next.22 + '@nomicfoundation/edr@0.12.0-next.23': + dependencies: + '@nomicfoundation/edr-darwin-arm64': 0.12.0-next.23 + '@nomicfoundation/edr-darwin-x64': 0.12.0-next.23 + '@nomicfoundation/edr-linux-arm64-gnu': 0.12.0-next.23 + '@nomicfoundation/edr-linux-arm64-musl': 0.12.0-next.23 + '@nomicfoundation/edr-linux-x64-gnu': 0.12.0-next.23 + '@nomicfoundation/edr-linux-x64-musl': 0.12.0-next.23 + '@nomicfoundation/edr-win32-x64-msvc': 0.12.0-next.23 + '@nomicfoundation/ethereumjs-rlp@5.0.4': {} '@nomicfoundation/ethereumjs-util@9.0.4': @@ -16305,26 +14181,26 @@ snapshots: '@nomicfoundation/ethereumjs-rlp': 5.0.4 ethereum-cryptography: 0.1.3 - '@nomicfoundation/hardhat-chai-matchers@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.26.3(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.26.3(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-chai-matchers@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))': dependencies: - '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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': 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)) '@types/chai-as-promised': 7.1.8 chai: 4.5.0 chai-as-promised: 7.1.2(chai@4.5.0) deep-eql: 4.1.4 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 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) ordinal: 1.0.3 - '@nomicfoundation/hardhat-chai-matchers@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.26.3(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@5.3.3)(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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-chai-matchers@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@5.3.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: - '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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': 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)) '@types/chai-as-promised': 7.1.8 chai: 5.3.3 chai-as-promised: 7.1.2(chai@5.3.3) deep-eql: 4.1.4 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 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) ordinal: 1.0.3 '@nomicfoundation/hardhat-errors@3.0.6': @@ -16347,20 +14223,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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@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.26.3(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: 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 transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': + '@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.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(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 transitivePeerDependencies: - supports-color @@ -16369,7 +14245,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) @@ -16378,28 +14254,28 @@ snapshots: - supports-color - utf-8-validate - '@nomicfoundation/hardhat-foundry@1.2.0(hardhat@2.26.3(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-foundry@1.2.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))': dependencies: - hardhat: 2.26.3(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: 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) picocolors: 1.1.1 - '@nomicfoundation/hardhat-ignition-ethers@0.15.14(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(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)))(bufferutil@4.0.9)(hardhat@2.26.3(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))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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-ignition-ethers@0.15.14(@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)))(@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@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)))(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))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(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.26.3(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-ignition': 0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(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)))(bufferutil@4.0.9)(hardhat@2.26.3(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) + '@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)) + '@nomicfoundation/hardhat-ignition': 0.15.13(@nomicfoundation/hardhat-verify@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)))(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) '@nomicfoundation/ignition-core': 0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 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-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(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)))(bufferutil@4.0.9)(hardhat@2.26.3(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)': + '@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@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)))(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)': dependencies: - '@nomicfoundation/hardhat-verify': 2.1.1(hardhat@2.26.3(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': 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)) '@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.26.3(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: 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 prompts: 2.4.2 transitivePeerDependencies: @@ -16415,7 +14291,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: @@ -16434,16 +14310,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-network-helpers@1.1.0(hardhat@2.26.3(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-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))': dependencies: ethereumjs-util: 7.1.5 - hardhat: 2.26.3(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: 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-network-helpers@3.0.3(hardhat@2.26.3(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-network-helpers@3.0.3(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-errors': 3.0.6 '@nomicfoundation/hardhat-utils': 3.0.6 - hardhat: 2.26.3(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: 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: - supports-color @@ -16455,42 +14331,42 @@ snapshots: transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-toolbox@4.0.0(841324e874603666491d4961f5a3314c)': + '@nomicfoundation/hardhat-toolbox@4.0.0(387217c56543caeacafa191f98890669)': dependencies: - '@nomicfoundation/hardhat-chai-matchers': 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.26.3(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@5.3.3)(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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': 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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-network-helpers': 3.0.3(hardhat@2.26.3(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': 2.1.1(hardhat@2.26.3(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-chai-matchers': 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@5.3.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)) + '@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)) + '@nomicfoundation/hardhat-network-helpers': 3.0.3(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': 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': 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': 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.26.3(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)) + '@typechain/hardhat': 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/chai': 4.3.20 '@types/mocha': 10.0.10 '@types/node': 20.19.14 chai: 5.3.3 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) - solidity-coverage: 0.8.17(hardhat@2.26.3(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: 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: 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) + solidity-coverage: 0.8.17(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)) ts-node: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) typescript: 5.9.3 - '@nomicfoundation/hardhat-toolbox@4.0.0(8d521f1e2e60e049232a7f203ff6170d)': + '@nomicfoundation/hardhat-toolbox@4.0.0(c97007b62875fc8ad4d8d85b595e6fa7)': dependencies: - '@nomicfoundation/hardhat-chai-matchers': 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.26.3(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.26.3(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': 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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-network-helpers': 1.1.0(hardhat@2.26.3(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': 2.1.1(hardhat@2.26.3(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-chai-matchers': 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': 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-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)) + '@nomicfoundation/hardhat-verify': 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': 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': 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.26.3(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)) + '@typechain/hardhat': 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/chai': 4.3.20 '@types/mocha': 9.1.1 '@types/node': 20.19.14 chai: 4.5.0 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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) - solidity-coverage: 0.8.16(hardhat@2.26.3(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: 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: 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) + solidity-coverage: 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)) ts-node: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) typescript: 5.9.3 @@ -16498,7 +14374,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 @@ -16510,13 +14386,13 @@ snapshots: '@nomicfoundation/hardhat-vendored@3.0.0': {} - '@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(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@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))': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/address': 5.8.0 cbor: 8.1.0 - debug: 4.4.3(supports-color@9.4.0) - hardhat: 2.26.3(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@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 semver: 6.3.1 @@ -16525,13 +14401,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': + '@nomicfoundation/hardhat-verify@2.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))': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/address': 5.8.0 cbor: 8.1.0 - debug: 4.4.3(supports-color@9.4.0) - hardhat: 2.26.3(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@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 semver: 6.3.1 @@ -16548,7 +14424,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 @@ -16568,7 +14444,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 @@ -16620,20 +14496,25 @@ snapshots: '@nomicfoundation/solidity-analyzer-linux-x64-musl': 0.1.2 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.2 - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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))': dependencies: ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 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.26.3(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.26.3(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: 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 semver: 6.3.1 table: 6.9.0 @@ -16641,21 +14522,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.26.3(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.26.3(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.26.3(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.26.3(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.26.3(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.26.3(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: - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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)) + '@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.26.3(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: 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: @@ -16681,8 +14560,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)': @@ -16742,17 +14619,17 @@ snapshots: '@openzeppelin/defender-deploy-client-cli': 0.0.1-alpha.10(encoding@0.1.13) '@openzeppelin/upgrades-core': 1.44.1 - '@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.26.3(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.26.3(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.26.3(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))': dependencies: - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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.26.3(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)) + '@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/defender-base-client': 1.54.6(debug@4.4.3)(encoding@0.1.13) '@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.26.3(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: 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 transitivePeerDependencies: - encoding @@ -16776,7 +14653,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 @@ -16833,7 +14710,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) @@ -16853,7 +14730,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 @@ -16881,13 +14758,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 @@ -16896,13 +14766,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 @@ -16910,14 +14773,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 @@ -16926,14 +14781,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 @@ -16984,10 +14831,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 @@ -17000,24 +14847,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 @@ -17028,7 +14875,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: @@ -17065,10 +14912,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 @@ -17174,12 +15021,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': @@ -17510,16 +15351,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 @@ -17540,13 +15371,13 @@ snapshots: transitivePeerDependencies: - debug - '@tenderly/hardhat-integration@1.1.1(@types/node@20.19.14)(bufferutil@4.0.9)(hardhat@2.26.3(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)': + '@tenderly/hardhat-integration@1.1.1(@types/node@20.19.14)(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)': dependencies: '@tenderly/api-client': 1.1.0(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3) axios: 1.12.2(debug@4.4.3) dotenv: 16.6.1 fs-extra: 10.1.0 - hardhat: 2.26.3(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: 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 @@ -17562,14 +15393,14 @@ snapshots: - supports-color - utf-8-validate - '@tenderly/hardhat-tenderly@1.11.0(@types/node@20.19.14)(bufferutil@4.0.9)(encoding@0.1.13)(hardhat@2.26.3(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)': + '@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.26.3(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.26.3(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.26.3(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.26.3(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.26.3(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 - '@tenderly/hardhat-integration': 1.1.1(@types/node@20.19.14)(bufferutil@4.0.9)(hardhat@2.26.3(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) + '@tenderly/hardhat-integration': 1.1.1(@types/node@20.19.14)(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) dotenv: 16.6.1 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: @@ -17617,11 +15448,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) @@ -17630,22 +15456,22 @@ snapshots: typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) typescript: 5.9.3 - '@typechain/hardhat@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.26.3(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))': + '@typechain/hardhat@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))': dependencies: '@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) fs-extra: 9.1.0 - hardhat: 2.26.3(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: 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) - '@typechain/hardhat@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.26.3(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))': + '@typechain/hardhat@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))': dependencies: '@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) fs-extra: 9.1.0 - hardhat: 2.26.3(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: 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/abstract-leveldown@7.2.5': {} @@ -17679,14 +15505,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 @@ -17760,11 +15578,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': @@ -17777,7 +15590,7 @@ snapshots: '@types/minimatch@6.0.0': dependencies: - minimatch: 10.0.3 + minimatch: 10.1.1 '@types/mkdirp@0.5.2': dependencies: @@ -17811,15 +15624,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 @@ -17877,7 +15681,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: @@ -17887,7 +15691,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 @@ -17906,7 +15710,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 @@ -17921,7 +15725,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 @@ -18058,8 +15862,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 @@ -18081,22 +15883,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 @@ -18132,14 +15918,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 @@ -18158,13 +15941,6 @@ snapshots: 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 @@ -18218,8 +15994,6 @@ snapshots: ansi-regex@6.2.2: {} - ansi-styles@2.2.1: {} - ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -18234,11 +16008,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 @@ -18249,7 +16018,7 @@ snapshots: arbos-precompiles@1.0.2(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: - hardhat: 2.26.3(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: 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: - bufferutil - supports-color @@ -18271,24 +16040,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: {} @@ -18317,10 +16068,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 @@ -18345,17 +16092,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 @@ -18368,13 +16104,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 @@ -18391,14 +16120,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 @@ -18417,10 +16142,6 @@ snapshots: async@1.5.2: {} - async@2.6.2: - dependencies: - lodash: 4.17.21 - async@2.6.4: dependencies: lodash: 4.17.21 @@ -18431,8 +16152,6 @@ snapshots: at-least-node@1.0.0: {} - atob@2.1.2: {} - atomic-sleep@1.0.0: {} auto-bind@4.0.0: {} @@ -18466,140 +16185,6 @@ 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: - dependencies: - babel-runtime: 6.26.0 - babel-traverse: 6.26.0 - babel-types: 6.26.0 - transitivePeerDependencies: - - supports-color - - babel-helper-function-name@6.24.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 - 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: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - - babel-helper-optimise-call-expression@6.24.1: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - - babel-helper-regex@6.26.0: - dependencies: - babel-runtime: 6.26.0 - babel-types: 6.26.0 - lodash: 4.17.21 - - 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 @@ -18613,14 +16198,6 @@ snapshots: 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 @@ -18638,188 +16215,13 @@ snapshots: '@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) @@ -18838,41 +16240,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 @@ -18912,67 +16279,6 @@ 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: {} base-64@0.1.0: {} @@ -18985,16 +16291,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: @@ -19022,8 +16318,6 @@ snapshots: bignumber.js@9.3.1: {} - binary-extensions@1.13.1: {} - binary-extensions@2.3.0: {} bindings@1.5.0: @@ -19033,14 +16327,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 @@ -19129,24 +16415,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: @@ -19169,35 +16437,12 @@ 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: - 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 - braces@3.0.3: dependencies: fill-range: 7.1.1 brorand@1.1.0: {} - browser-stdout@1.3.0: {} - browser-stdout@1.3.1: {} browserify-aes@1.2.0: @@ -19209,47 +16454,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 @@ -19278,9 +16482,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: {} @@ -19308,6 +16509,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: @@ -19322,15 +16524,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: @@ -19348,21 +16541,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: @@ -19375,33 +16553,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 @@ -19438,8 +16589,6 @@ snapshots: camelcase@3.0.0: {} - camelcase@4.1.0: {} - camelcase@5.3.1: {} camelcase@6.3.0: {} @@ -19501,14 +16650,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 @@ -19583,25 +16724,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 @@ -19651,30 +16773,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: {} @@ -19700,16 +16803,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 @@ -19723,12 +16816,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 @@ -19747,24 +16834,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 @@ -19802,12 +16875,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 @@ -19830,12 +16897,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: {} @@ -19849,8 +16912,6 @@ snapshots: compare-versions@6.1.1: {} - component-emitter@1.3.1: {} - concat-map@0.0.1: {} concat-stream@1.6.2: @@ -19893,13 +16954,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: @@ -19917,8 +16971,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: {} @@ -19927,18 +16979,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: {} @@ -19965,7 +17007,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: @@ -19975,19 +17017,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 @@ -20014,13 +17050,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) @@ -20043,20 +17072,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 @@ -20065,26 +17080,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: @@ -20121,16 +17116,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 @@ -20141,12 +17126,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: {} @@ -20155,13 +17134,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 @@ -20179,33 +17151,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 @@ -20225,21 +17176,6 @@ snapshots: 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-property@2.0.2: - dependencies: - is-descriptor: 1.0.3 - isobject: 3.0.1 - - defined@1.0.1: {} - delayed-stream@1.0.0: {} delegates@1.0.0: @@ -20260,20 +17196,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: @@ -20283,21 +17209,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 @@ -20315,8 +17230,6 @@ snapshots: dependencies: esutils: 2.0.3 - dom-walk@0.1.2: {} - dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -20330,10 +17243,6 @@ snapshots: dotenv@16.6.1: {} - dotignore@0.1.2: - dependencies: - minimatch: 3.1.2 - dottie@2.0.6: {} dset@3.1.4: {} @@ -20344,9 +17253,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 @@ -20408,14 +17314,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 @@ -20426,6 +17324,7 @@ snapshots: encoding@0.1.13: dependencies: iconv-lite: 0.6.3 + optional: true end-of-stream@1.4.5: dependencies: @@ -20442,8 +17341,6 @@ snapshots: environment@1.1.0: {} - eol@0.9.1: {} - err-code@2.0.3: {} errno@0.1.8: @@ -20515,8 +17412,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: {} @@ -20542,24 +17437,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 @@ -20726,7 +17603,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 @@ -20750,13 +17627,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 @@ -20783,18 +17653,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 @@ -20820,102 +17678,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 @@ -20948,20 +17714,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)) @@ -20981,92 +17733,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 @@ -21093,51 +17764,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 @@ -21318,35 +17944,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: {} @@ -21358,48 +17957,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 @@ -21476,56 +18033,6 @@ snapshots: 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 - extend@3.0.2: {} extendable-error@0.1.7: {} @@ -21536,37 +18043,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: {} @@ -21635,10 +18119,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) @@ -21665,23 +18145,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 @@ -21710,24 +18173,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 @@ -21737,10 +18182,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 @@ -21757,17 +18198,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 @@ -21779,8 +18209,6 @@ snapshots: flow-enums-runtime@0.0.6: {} - flow-stoplight@1.0.0: {} - fmix@0.1.0: dependencies: imul: 1.0.1 @@ -21789,18 +18217,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: @@ -21849,10 +18271,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: @@ -21878,12 +18296,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 @@ -21903,11 +18315,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 @@ -21920,12 +18327,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 @@ -21944,44 +18345,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 @@ -22031,18 +18394,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: @@ -22055,8 +18406,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-value@2.0.6: {} - getpass@0.1.7: dependencies: assert-plus: 1.0.0 @@ -22075,15 +18424,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 @@ -22118,15 +18458,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@7.1.2: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - glob@7.1.7: dependencies: fs.realpath: 1.0.0 @@ -22167,17 +18498,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 @@ -22205,21 +18529,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 @@ -22234,23 +18543,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: {} @@ -22321,8 +18613,6 @@ snapshots: graphql@16.8.0: {} - growl@1.10.3: {} - handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -22339,17 +18629,17 @@ snapshots: ajv: 6.12.6 har-schema: 2.0.0 - hardhat-abi-exporter@2.11.0(hardhat@2.26.3(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-abi-exporter@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)): dependencies: '@ethersproject/abi': 5.8.0 delete-empty: 3.0.0 - hardhat: 2.26.3(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: 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@2.10.1(hardhat@2.26.3(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@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)): dependencies: chalk: 4.1.2 cli-table3: 0.6.5 - hardhat: 2.26.3(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: 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) strip-ansi: 6.0.1 hardhat-deploy@0.11.45(bufferutil@4.0.9)(utf-8-validate@5.0.10): @@ -22369,7 +18659,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 @@ -22383,7 +18673,7 @@ snapshots: - supports-color - utf-8-validate - hardhat-deploy@0.7.11(@ethersproject/hardware-wallets@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(hardhat@2.26.3(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-deploy@0.7.11(@ethersproject/hardware-wallets@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.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): dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/abstract-signer': 5.8.0 @@ -22400,10 +18690,10 @@ 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.26.3(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: 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) match-all: 1.2.7 murmur-128: 0.2.1 qs: 6.14.0 @@ -22412,12 +18702,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 @@ -22425,11 +18715,11 @@ snapshots: transitivePeerDependencies: - supports-color - hardhat-gas-reporter@1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(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-gas-reporter@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): dependencies: array-uniq: 1.0.3 eth-gas-reporter: 0.2.27(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 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) sha1: 1.1.1 transitivePeerDependencies: - '@codechecks/client' @@ -22443,52 +18733,52 @@ snapshots: node-interval-tree: 2.1.2 solidity-comments: 0.0.2 - 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.26.3(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.26.3(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)): dependencies: - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(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) + '@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@8.1.1) enquirer: 2.4.1 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 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 prompt-sync: 4.2.0 transitivePeerDependencies: - supports-color - 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.26.3(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.26.3(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@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.26.3(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) + '@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@8.1.1) enquirer: 2.4.1 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(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: 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 prompt-sync: 4.2.0 transitivePeerDependencies: - supports-color - 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.26.3(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.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): + 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.26.3(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) + '@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@8.1.1) enquirer: 2.4.1 ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(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.clonedeep: 4.5.0 prompt-sync: 4.2.0 transitivePeerDependencies: - supports-color - hardhat-storage-layout@0.1.7(hardhat@2.26.3(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-storage-layout@0.1.7(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: console-table-printer: 2.14.6 - hardhat: 2.26.3(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: 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@2.26.3(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@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: '@ethereumjs/util': 9.1.0 '@ethersproject/abi': 5.8.0 - '@nomicfoundation/edr': 0.11.3 + '@nomicfoundation/edr': 0.12.0-next.23 '@nomicfoundation/solidity-analyzer': 0.1.2 '@sentry/node': 5.30.0 adm-zip: 0.4.16 @@ -22497,7 +18787,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 @@ -22533,11 +18823,11 @@ snapshots: - supports-color - utf-8-validate - hardhat@2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(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: '@ethereumjs/util': 9.1.0 '@ethersproject/abi': 5.8.0 - '@nomicfoundation/edr': 0.11.3 + '@nomicfoundation/edr': 0.12.0-next.23 '@nomicfoundation/solidity-analyzer': 0.1.2 '@sentry/node': 5.30.0 adm-zip: 0.4.16 @@ -22546,7 +18836,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 @@ -22594,7 +18884,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 @@ -22609,16 +18899,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: {} @@ -22640,37 +18924,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 @@ -22688,8 +18945,6 @@ snapshots: dependencies: function-bind: 1.1.2 - he@1.1.1: {} - he@1.2.0: {} header-case@2.0.4: @@ -22697,8 +18952,6 @@ snapshots: capital-case: 1.0.4 tslib: 2.8.1 - heap@0.2.6: {} - heap@0.2.7: {} helmet@5.0.2: {} @@ -22717,11 +18970,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: @@ -22755,13 +19003,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 @@ -22775,12 +19020,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 @@ -22789,23 +19028,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: @@ -22815,6 +19050,7 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true iconv-lite@0.7.0: dependencies: @@ -22926,10 +19162,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: @@ -22937,11 +19169,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 @@ -22964,10 +19191,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 @@ -22977,22 +19200,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 @@ -23006,44 +19219,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 @@ -23052,14 +19237,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 @@ -23067,10 +19248,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 @@ -23094,35 +19271,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 @@ -23140,8 +19294,6 @@ snapshots: dependencies: call-bound: 1.0.4 - is-stream@1.1.0: {} - is-stream@2.0.1: {} is-string@1.1.1: @@ -23200,20 +19352,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) @@ -23337,10 +19481,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: @@ -23360,10 +19500,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: {} @@ -23372,9 +19508,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: {} @@ -23385,31 +19518,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: {} @@ -23418,20 +19532,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 @@ -23454,8 +19558,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonify@0.0.1: {} - jsonparse@1.3.1: {} jsonpointer@5.0.1: {} @@ -23484,29 +19586,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 @@ -23528,122 +19613,42 @@ 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 - level-iterator-stream@4.0.2: dependencies: inherits: 2.0.4 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 @@ -23675,33 +19680,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 @@ -23712,19 +19696,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 @@ -23748,11 +19719,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 @@ -23801,8 +19767,6 @@ snapshots: lodash.upperfirst@4.3.1: {} - lodash@4.17.20: {} - lodash@4.17.21: {} log-symbols@4.1.0: @@ -23810,13 +19774,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 @@ -23834,10 +19791,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 @@ -23856,27 +19809,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 @@ -23889,8 +19827,6 @@ snapshots: lru_map@0.3.3: {} - ltgt@2.1.3: {} - ltgt@2.2.1: {} make-error@1.3.6: {} @@ -23918,10 +19854,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 @@ -23999,8 +19931,6 @@ snapshots: math-intrinsics@1.1.0: {} - math-random@1.0.4: {} - mcl-wasm@0.7.9: {} md5.js@1.3.5: @@ -24013,28 +19943,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 @@ -24052,34 +19960,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 @@ -24140,7 +20024,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 @@ -24236,7 +20120,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 @@ -24435,7 +20319,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 @@ -24454,40 +20338,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 @@ -24506,15 +20356,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 @@ -24522,10 +20367,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: {} @@ -24550,8 +20391,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist@0.0.8: {} - minimist@1.2.8: {} minipass-collect@2.0.1: @@ -24578,12 +20417,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 @@ -24592,33 +20425,14 @@ snapshots: minipass@7.1.2: {} - minizlib@1.3.3: - dependencies: - minipass: 2.9.0 - optional: true - 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 @@ -24654,31 +20468,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 @@ -24701,36 +20490,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 @@ -24750,27 +20509,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 @@ -24803,12 +20543,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 @@ -24833,11 +20569,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 @@ -24900,12 +20631,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: @@ -24928,14 +20653,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 @@ -24961,31 +20678,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 @@ -25002,31 +20700,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 @@ -25036,11 +20715,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: @@ -25102,18 +20776,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: {} @@ -25154,12 +20820,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: @@ -25168,10 +20828,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 @@ -25184,10 +20840,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 @@ -25217,8 +20869,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: {} @@ -25245,16 +20895,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: @@ -25273,15 +20913,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 @@ -25305,42 +20936,6 @@ snapshots: 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 - - patch-package@6.5.1: - 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 - path-browserify@1.0.1: {} path-case@3.0.4: @@ -25352,16 +20947,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: {} @@ -25384,9 +20975,6 @@ snapshots: path-starts-with@2.0.1: {} - path-to-regexp@0.1.12: - optional: true - path-to-regexp@0.1.7: {} path-type@1.1.0: @@ -25412,8 +21000,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: @@ -25479,8 +21065,6 @@ snapshots: picomatch@4.0.3: {} - pidtree@0.5.0: {} - pidtree@0.6.0: {} pify@2.3.0: {} @@ -25521,8 +21105,6 @@ snapshots: pluralize@8.0.0: {} - posix-character-classes@0.1.1: {} - possible-typed-array-names@1.1.0: {} postgres-array@2.0.0: {} @@ -25535,8 +21117,6 @@ snapshots: dependencies: xtend: 4.0.2 - postinstall-postinstall@2.1.0: {} - prebuild-install@5.3.6: dependencies: detect-libc: 1.0.3 @@ -25573,17 +21153,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 @@ -25601,14 +21174,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 @@ -25624,11 +21193,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 @@ -25663,49 +21227,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 @@ -25735,11 +21260,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 @@ -25752,13 +21272,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: @@ -25769,22 +21282,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: @@ -25907,20 +21408,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 @@ -25937,14 +21424,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 @@ -25974,27 +21453,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 @@ -26004,12 +21464,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 @@ -26018,12 +21472,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 @@ -26034,14 +21482,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 @@ -26093,8 +21533,6 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve-url@0.2.1: {} - resolve.exports@2.0.3: {} resolve@1.1.7: {} @@ -26109,16 +21547,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 @@ -26133,8 +21561,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: {} @@ -26177,7 +21603,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) @@ -26228,10 +21654,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 @@ -26243,10 +21665,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: {} @@ -26272,11 +21690,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 @@ -26285,16 +21698,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: {} @@ -26369,7 +21776,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 @@ -26393,7 +21800,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 @@ -26446,17 +21853,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: @@ -26475,21 +21871,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: {} @@ -26507,16 +21894,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: {} @@ -26564,13 +21945,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 @@ -26586,31 +21960,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 @@ -26627,33 +21986,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 @@ -26663,10 +21999,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 @@ -26675,17 +22007,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 @@ -26780,7 +22101,7 @@ snapshots: solidity-comments-win32-ia32-msvc: 0.0.2 solidity-comments-win32-x64-msvc: 0.0.2 - solidity-coverage@0.8.16(hardhat@2.26.3(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-coverage@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)): dependencies: '@ethersproject/abi': 5.8.0 '@solidity-parser/parser': 0.20.2 @@ -26791,7 +22112,7 @@ snapshots: ghost-testrpc: 0.0.2 global-modules: 2.0.0 globby: 10.0.2 - hardhat: 2.26.3(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: 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) jsonschema: 1.5.0 lodash: 4.17.21 mocha: 10.8.2 @@ -26803,7 +22124,7 @@ snapshots: shelljs: 0.8.5 web3-utils: 1.10.4 - solidity-coverage@0.8.17(hardhat@2.26.3(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-coverage@0.8.17(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 '@solidity-parser/parser': 0.20.2 @@ -26814,7 +22135,7 @@ snapshots: ghost-testrpc: 0.0.2 global-modules: 2.0.0 globby: 10.0.2 - hardhat: 2.26.3(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: 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) jsonschema: 1.5.0 lodash: 4.17.21 mocha: 10.8.2 @@ -26826,68 +22147,21 @@ snapshots: shelljs: 0.8.5 web3-utils: 1.10.4 - solidity-docgen@0.6.0-beta.36(hardhat@2.26.3(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-docgen@0.6.0-beta.36(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: handlebars: 4.7.8 - hardhat: 2.26.3(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: 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 @@ -26916,10 +22190,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 @@ -26960,11 +22230,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: {} @@ -26976,16 +22241,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: {} @@ -27047,8 +22304,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 @@ -27083,10 +22338,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 @@ -27097,16 +22348,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 @@ -27119,33 +22364,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,25 +22395,6 @@ snapshots: 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 @@ -27207,17 +22412,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 @@ -27246,11 +22440,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: {} @@ -27279,11 +22468,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 @@ -27295,9 +22479,6 @@ snapshots: through@2.3.8: {} - timed-out@4.0.1: - optional: true - tiny-lru@8.0.2: {} tinyexec@1.0.1: {} @@ -27315,10 +22496,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: @@ -27327,31 +22504,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: {} @@ -27363,20 +22519,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): @@ -27390,28 +22534,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 @@ -27477,12 +22603,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 @@ -27506,25 +22628,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 @@ -27570,10 +22677,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): @@ -27589,16 +22692,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: {} @@ -27612,9 +22705,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 @@ -27626,9 +22716,6 @@ snapshots: underscore@1.13.7: {} - underscore@1.9.1: - optional: true - undici-types@6.21.0: {} undici@5.29.0: @@ -27641,13 +22728,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 @@ -27664,15 +22744,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 @@ -27691,16 +22764,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 @@ -27716,11 +22779,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: @@ -27731,26 +22793,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: {} @@ -27770,9 +22814,6 @@ snapshots: value-or-promise@1.0.12: {} - varint@5.0.2: - optional: true - vary@1.1.2: {} verror@1.10.0: @@ -27823,232 +22864,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 @@ -28060,33 +22875,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 @@ -28097,19 +22885,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: @@ -28259,23 +23034,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 @@ -28313,38 +23071,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: {} @@ -28353,10 +23079,6 @@ snapshots: y18n@5.0.8: {} - yaeti@0.0.6: {} - - yallist@2.1.2: {} - yallist@3.1.1: {} yallist@4.0.0: {} @@ -28386,10 +23108,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 @@ -28397,21 +23115,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 725f20b89..0a60d1cce 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -44,7 +44,7 @@ catalog: ethers: ^6.16.0 glob: ^11.0.2 globals: ^16.4.0 - hardhat: ^2.26.0 + hardhat: ^2.28.0 hardhat-contract-sizer: ^2.10.0 hardhat-dependency-compiler: ^1.2.1 hardhat-gas-reporter: ^1.0.8