diff --git a/.github/workflows/contracts-automaton-prod-image.yml b/.github/workflows/contracts-automaton-prod-image.yml new file mode 100644 index 0000000000..186a9b7437 --- /dev/null +++ b/.github/workflows/contracts-automaton-prod-image.yml @@ -0,0 +1,42 @@ +name: Contracts Actions Image + +on: + push: + branches: + - origin-automaton-production + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Prepare image metadata + id: prep + run: | + IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/contracts-automaton-prod" + IMAGE_NAME="$(echo "${IMAGE_NAME}" | tr '[:upper:]' '[:lower:]')" + echo "image_name=${IMAGE_NAME}" >> "${GITHUB_OUTPUT}" + + - name: Build and push image + uses: docker/build-push-action@v5 + with: + context: ./contracts + file: ./contracts/dockerfile-actions + push: true + tags: | + ${{ steps.prep.outputs.image_name }}:latest + ${{ steps.prep.outputs.image_name }}:${{ github.sha }} diff --git a/.github/workflows/defi.yml b/.github/workflows/defi.yml index e13b9fd17d..12b5635327 100644 --- a/.github/workflows/defi.yml +++ b/.github/workflows/defi.yml @@ -1,12 +1,12 @@ name: DeFi -on: +on: pull_request: types: [opened, reopened, synchronize] push: branches: - - 'master' - - 'staging' - - 'stable' + - "master" + - "staging" + - "stable" workflow_dispatch: concurrency: @@ -32,7 +32,7 @@ jobs: node-version: "20.x" cache: "pnpm" cache-dependency-path: contracts/pnpm-lock.yaml - + - name: Configure Git to use HTTPS for GitHub run: git config --global url."https://github.com/".insteadOf "git@github.com:" @@ -63,14 +63,14 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: node-version: "20.x" cache: "pnpm" cache-dependency-path: contracts/pnpm-lock.yaml - + - name: Configure Git to use HTTPS for GitHub run: git config --global url."https://github.com/".insteadOf "git@github.com:" @@ -85,7 +85,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: unit-test-coverage-${{ github.sha }} - path: | + path: | ./contracts/coverage.json ./contracts/coverage/**/* retention-days: 1 @@ -101,7 +101,7 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -125,7 +125,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: base-unit-test-coverage-${{ github.sha }} - path: | + path: | ./contracts/coverage.json ./contracts/coverage/**/* retention-days: 1 @@ -141,7 +141,7 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -165,17 +165,67 @@ jobs: - uses: actions/upload-artifact@v4 with: name: sonic-unit-test-coverage-${{ github.sha }} - path: | + path: | ./contracts/coverage.json ./contracts/coverage/**/* retention-days: 1 + contracts-nonce-queue-test: + name: "Nonce Queue Integration" + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_DB: nonce_test + POSTGRES_USER: test + POSTGRES_PASSWORD: test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U test -d nonce_test" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + steps: + - uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.x" + cache: "pnpm" + cache-dependency-path: contracts/pnpm-lock.yaml + + - name: Configure Git to use HTTPS for GitHub + run: git config --global url."https://github.com/".insteadOf "git@github.com:" + + - name: Install deps + working-directory: ./contracts + run: pnpm install --frozen-lockfile + + - name: Run nonce queue integration test + env: + DATABASE_URL: postgresql://test:test@localhost:5432/nonce_test + working-directory: ./contracts + run: | + pnpm exec ts-node tasks/lib/nonceQueue.test.ts + pnpm exec ts-node tasks/lib/nonceQueueTxLifecycle.test.ts + pnpm exec ts-node tasks/lib/nonceQueueTxHistory.test.ts + pnpm exec ts-node cron/api.test.ts + contracts-forktest: name: "Mainnet Fork Tests ${{ matrix.chunk_id }}" runs-on: ubuntu-latest strategy: matrix: - chunk_id: [0,1,2,3] + chunk_id: [0, 1, 2, 3] continue-on-error: true env: HARDHAT_CACHE_DIR: ./cache @@ -192,7 +242,7 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -221,7 +271,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: fork-test-coverage-${{ github.sha }}-runner${{ matrix.chunk_id }} - path: | + path: | ./contracts/coverage.json ./contracts/coverage/**/* retention-days: 1 @@ -242,7 +292,7 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -271,7 +321,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: fork-test-arb-coverage-${{ github.sha }} - path: | + path: | ./contracts/coverage.json ./contracts/coverage/**/* retention-days: 1 @@ -292,7 +342,7 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -321,11 +371,11 @@ jobs: - uses: actions/upload-artifact@v4 with: name: fork-test-base-coverage-${{ github.sha }} - path: | + path: | ./contracts/coverage.json ./contracts/coverage/**/* retention-days: 1 - + contracts-sonic-forktest: name: "Sonic Fork Tests" runs-on: ubuntu-latest @@ -342,7 +392,7 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -371,11 +421,11 @@ jobs: - uses: actions/upload-artifact@v4 with: name: fork-test-sonic-coverage-${{ github.sha }} - path: | + path: | ./contracts/coverage.json ./contracts/coverage/**/* retention-days: 1 - + contracts-plume-forktest: name: "Plume Fork Tests" runs-on: ubuntu-latest @@ -392,7 +442,7 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -501,7 +551,7 @@ jobs: key: ${{ runner.os }}-hardhat-${{ hashFiles('contracts/cache/*.json') }} restore-keys: | ${{ runner.os }}-hardhat-cache - + - name: Download all reports uses: actions/download-artifact@v4 @@ -519,7 +569,7 @@ jobs: - name: Set up Python 3.10 uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: "3.10" - name: Install dependencies run: | @@ -534,7 +584,7 @@ jobs: with: version: 10 run_install: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -564,4 +614,4 @@ jobs: env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: - args: --severity-threshold=high --all-projects \ No newline at end of file + args: --severity-threshold=high --all-projects diff --git a/contracts/.eslintrc.js b/contracts/.eslintrc.js index eaf475fb2c..953d510ee9 100644 --- a/contracts/.eslintrc.js +++ b/contracts/.eslintrc.js @@ -24,4 +24,25 @@ module.exports = { "no-only-tests/no-only-tests": "error", "no-unused-vars": [2, { vars: "all", args: "after-used" }], }, + overrides: [ + { + files: ["**/*.ts"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint"], + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + 2, + { vars: "all", args: "after-used" }, + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-require-imports": "off", + }, + }, + ], }; diff --git a/contracts/README.md b/contracts/README.md index 069f9a0aec..1612c7b9da 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -246,8 +246,9 @@ If enabled, the gas usage will be output in a table after the tests have execute When using Hardhat tasks, there are a few options for specifying the wallet to send transactions from. 1. Primary key -2. Impersonate -3. Defender Relayer +2. AWS KMS signer +3. Impersonate +4. Defender Relayer ### Primary Key @@ -262,6 +263,24 @@ unset DEPLOYER_PK unset GOVERNOR_PK ``` +### AWS KMS Signer + +Hardhat tasks can sign transactions with AWS KMS when both `AWS_ACCESS_KEY_ID` and +`AWS_SECRET_ACCESS_KEY` are set. + +The default `relayer-id` is `origin-relayer-production-evm`. Some tasks can be mapped +to different defaults in code, and a user-provided task parameter always wins: + +``` +npx hardhat --network --relayer-id +``` + +The relayer resolution precedence is: + +1. `--relayer-id` +2. task-name based override map +3. global default (`origin-relayer-production-evm`) + ### Impersonate If using a fork test or node, you can impersonate any externally owned account or contract. Export `IMPERSONATE` with the address of the account you want to impersonate. The account will be funded with some Ether. For example diff --git a/contracts/cron/.gitignore b/contracts/cron/.gitignore new file mode 100644 index 0000000000..885df5d5c5 --- /dev/null +++ b/contracts/cron/.gitignore @@ -0,0 +1 @@ +cronjob \ No newline at end of file diff --git a/contracts/cron/OBSERVABILITY.md b/contracts/cron/OBSERVABILITY.md new file mode 100644 index 0000000000..bf29b8a4b4 --- /dev/null +++ b/contracts/cron/OBSERVABILITY.md @@ -0,0 +1,126 @@ +# Automaton observability cookbook + +How to answer operational questions about the Automaton (see [`README.md`](./README.md)) using Loki + Grafana. The same schema is emitted by the sibling Automaton in `arm-oeth` — swap `app="origin-dollar"` for `app="arm-oeth"` and every query below works there too. + +## Event model + +Every scheduled invocation produces: + +1. **Exactly one `action.start`** from the supervisor, the moment the child is spawned. +2. **Exactly one terminal event** from the supervisor: `action.success` (exit 0) or `action.failure` (non-zero exit, signal, or spawn error). +3. **Optionally, one `action.error`** from the task wrapper if it threw — carries the error class, message, stack, chain, and network. +4. Any number of in-flight `info`/`debug` lines the task itself emitted. + +All events for one run share the same `run_id`. Use it as the join key when investigating. + +The supervisor owns events 1 and 2. **Even if the child crashes, OOMs, segfaults, or never starts**, the supervisor still produces both — counts and rates stay correct on the worst days. + +## Field schema + +| Field | Type | Label? | Appears on | Notes | +|---|---|---|---|---| +| `app` | string | **label** | all | Always `origin-dollar` (or `arm-oeth` in the sibling repo). | +| `action` | string | **label** | all | The job name from `cron-jobs.ts`. | +| `event` | string | **label** | start/success/failure/error | One of `action.start`, `action.success`, `action.failure`, `action.error`. | +| `source` | string | **label** | start/success/failure/error | `supervisor` for run-lifecycle events, `task` for `action.error`. | +| `run_id` | UUID | field | all | Correlation key. Field, not label, to keep cardinality bounded. | +| `duration_ms` | number | field | success/failure/error | Wall-clock from supervisor spawn (or task wrapper start) to terminal event. | +| `exit_code` | number\|null | field | success/failure | From the child process. `null` on spawn failure. | +| `signal` | string\|null | field | success/failure | e.g. `SIGKILL` (often = OOM-killed when paired with code 137). | +| `spawn_failed` | bool | field | failure | Set when the child couldn't be spawned at all. | +| `schedule` | string | field | start | The cron expression for the job. | +| `command` | string | field | start | The shell command being executed. | +| `chain_id` | number | field | error | Resolved chain id, if signer setup got that far. | +| `network` | string | field | error | Human network name (`mainnet`, `base`, `sonic`, …). | +| `error_name` | string | field | error | Error class (`Error`, `ProviderError`, …). | +| `error_message` | string | field | error | The thrown message. | +| `error_stack` | string | field | error | Full JS stack trace. | + +**Why labels are restricted to `action`, `event`, `source`:** Loki indexes labels, so high-cardinality labels blow up the index. `run_id` (UUIDs), `error_message` (free text), and numeric fields like `duration_ms` would all be cardinality bombs. They're still queryable via `| json` extraction; you just can't use them in stream selectors. + +## Common questions → queries + +### How many runs of X happened this week? + +```logql +sum(count_over_time({app="origin-dollar", action="healthcheck", event="action.start"}[7d])) +``` + +### How many of those failed? + +```logql +sum(count_over_time({app="origin-dollar", action="healthcheck", event="action.failure"}[7d])) +``` + +### Why did a particular run fail? + +Find recent failures, then pivot on `run_id`: + +```logql +{app="origin-dollar", event="action.error"} + | json + | line_format "{{.action}} [{{.run_id}}] {{.error_name}}: {{.error_message}}" +``` + +Pick a `run_id` from the result, then: + +```logql +{app="origin-dollar"} | json | run_id="" +``` + +That returns the full breadcrumb in time order: `action.start` → in-flight info lines from the task → `action.error` (with stack) → supervisor's `action.failure` (with exit code/signal). Usually enough to fix without rerunning locally. + +### Average runtime per action + +```logql +avg by (action) ( + avg_over_time( + {app="origin-dollar", event=~"action.success|action.failure", source="supervisor"} + | json | unwrap duration_ms [7d] + ) +) +``` + +p95 instead: + +```logql +quantile_over_time(0.95, + {app="origin-dollar", event=~"action.success|action.failure", source="supervisor"} + | json | unwrap duration_ms [7d] +) by (action) +``` + +### Success rate per action + +```logql +sum by (action) (count_over_time({app="origin-dollar", event="action.success"}[7d])) +/ +sum by (action) (count_over_time({app="origin-dollar", event="action.start"}[7d])) +``` + +Using `action.start` as the denominator (rather than success+failure) catches the pathological case where a run started but no terminal event was ever emitted — the ratio drops below 1.0 and you notice. + +### Crashed-without-explanation runs + +Runs that produced an `action.failure` with **no** preceding `action.error`. These are the ones where the child died before reaching the task wrapper (OOM, parse error, missing env var, signal). Fewest moving parts: + +```logql +{app="origin-dollar", event="action.failure", source="supervisor"} | json | exit_code=137 +``` + +Code 137 + `SIGKILL` is almost always OOM. Code 1 with no `action.error` for the same `run_id` usually means the hardhat process failed before importing the task wrapper. + +## Known limitation: log loss on hard kill + +Log shipping is direct from the supervisor process to Loki via winston-loki's HTTP transport, batched on a 5ms interval. On graceful `SIGTERM` we `await flushLogger()` before exiting. On `SIGKILL` (OOM-killer, `kill -9`, container force-stop) the in-flight batch dies with the process and those events are lost. Fully fixing this would require a stdout-scraping sidecar (promtail / grafana-agent / alloy) — out of scope. In practice the gap is small: supervisor lifecycle events are emitted at job boundaries, not mid-job, so the window for loss is narrow. + +## When to graduate to Prometheus + +Symptoms that mean log queries have outgrown their usefulness: + +- Grafana panels are slow to render because they re-scan the log stream. +- You want recording rules so dashboards don't recompute on every refresh. +- You want alerts on "no successful run in the last N minutes" with cheap evaluation. +- You want long-retention metrics (months) without paying log-storage costs. + +When that happens, add a `/metrics` endpoint to `cron-supervisor.ts` exposing `automaton_runs_total{action,status}`, `automaton_run_duration_seconds{action}` (histogram), and `automaton_last_success_timestamp_seconds{action}` (gauge). The log schema stays as-is for debugging — metrics complement it, they don't replace it. diff --git a/contracts/cron/README.md b/contracts/cron/README.md new file mode 100644 index 0000000000..0ebbb18317 --- /dev/null +++ b/contracts/cron/README.md @@ -0,0 +1,88 @@ +# Cron Actions (Automaton) + +Containerized scheduler for running hardhat tasks on a schedule. Replaces OpenZeppelin Defender actions. Internally referred to as **Automaton**; a sibling Automaton with the same shape and log schema lives in the `arm-oeth` repo. Keep field names (`event`, `source`, `action`, `run_id`, `duration_ms`, `error_*`) in sync across both so a single Grafana dashboard can serve both. + +## How it works + +1. **`cron-jobs.ts`** — Defines all scheduled jobs (name, cron schedule, command, enabled flag) with full TypeScript typing +2. **`render-crontab.ts`** — Imports jobs from `cron-jobs.ts`, filters to enabled jobs, writes a supercronic-compatible crontab file +3. **`cron-supervisor.ts`** — Starts supercronic with the generated crontab, runs an HTTP API for triggering actions on-demand and checking run status +4. **`cron-entrypoint.sh`** — Docker entrypoint that boots the supervisor + +Each job runs a hardhat task (e.g. `pnpm hardhat healthcheck`). Signing uses the existing KMS signer from `utils/signers.js` when `AWS_ACCESS_KEY_ID` is set. + +## Running locally + +```bash +cd contracts +docker compose up actions +``` + +## API + +The supervisor exposes an HTTP API (default port 8080): + +```bash +# Health check (no auth) +curl http://localhost:8080/healthz + +# List all actions (requires bearer token) +curl -H 'Authorization: Bearer $ACTION_API_BEARER_TOKEN' \ + http://localhost:8080/api/v1/actions + +# Trigger an action +curl -X POST -H 'Authorization: Bearer $ACTION_API_BEARER_TOKEN' \ + http://localhost:8080/api/v1/actions/healthcheck/runs + +# Check run status +curl -H 'Authorization: Bearer $ACTION_API_BEARER_TOKEN' \ + http://localhost:8080/api/v1/runs/ + +# List recent nonce-queue managed transactions (default limit=50, max=500) +curl -H 'Authorization: Bearer $ACTION_API_BEARER_TOKEN' \ + 'http://localhost:8080/api/v1/transactions?limit=100&address=0xabc...&chainId=1' +``` + +## Adding a new job + +Add an entry to the `cronJobs` array in `cron-jobs.ts`: + +```ts +{ + name: "my_new_job", + schedule: "0 */6 * * *", + enabled: true, + command: "cd /app && pnpm hardhat myTask --network mainnet", +} +``` + +Set `enabled: false` to define a job that can only be triggered via the API. + +## Environment variables + +| Variable | Description | +| -------------------------- | -------------------------------------------------------------------- | +| `ACTION_API_BEARER_TOKEN` | Required. Auth token for the HTTP API | +| `PROVIDER_URL` | Mainnet RPC endpoint | +| `HARDHAT_NETWORK` | Default network for tasks | +| `WINSTON_LOG_MODE_ENABLED` | Enable structured winston logging with run/action correlation fields | +| `LOKI_URL` | Grafana Loki push endpoint (optional) | +| `LOKI_USER` | Loki basic auth user (optional) | +| `LOKI_API_KEY` | Loki basic auth key (optional) | +| `AWS_ACCESS_KEY_ID` | For KMS signer (optional) | +| `AWS_SECRET_ACCESS_KEY` | For KMS signer (optional) | + +When `WINSTON_LOG_MODE_ENABLED` is unset/false, task modules continue to use the +legacy `debug` logger flow (`DEBUG=origin:*` wildcard filtering). + +## Observability + +Each action generates its own `run_id` (UUID) and emits structured events via the winston/Loki logger in `tasks/lib/action.ts`: + +- `action.start` — emitted after resolving the signer and chain, before running the action +- `action.success` — emitted on successful completion, includes `duration_ms` +- `action.error` — emitted on failure, includes `duration_ms`, `error_name`, `error_message`, `error_stack` + +All events for a single run share the same `run_id` for correlation in Grafana. + +See [`OBSERVABILITY.md`](./OBSERVABILITY.md) for the field schema and LogQL cookbook. diff --git a/contracts/cron/TODO.md b/contracts/cron/TODO.md new file mode 100644 index 0000000000..ad08019cbf --- /dev/null +++ b/contracts/cron/TODO.md @@ -0,0 +1,21 @@ +# TODO List + +## Defender Actions which use compiled code: + +https://defender.openzeppelin.com/#/actions/automatic/076c59e4-4150-42c7-9ba0-9962069ac353 +https://defender.openzeppelin.com/#/actions/automatic/0b852456-96a0-4f1d-9d6c-39e1c6ae9dfc +https://defender.openzeppelin.com/#/actions/automatic/65b53496-e426-4850-8349-059e63eb2120 +https://defender.openzeppelin.com/#/actions/automatic/65f04f74-8da7-4fc5-94b3-96be31bac03b +https://defender.openzeppelin.com/#/actions/automatic/6a633bb0-aff8-4b37-aaae-b4c6f244ed87 +https://defender.openzeppelin.com/#/actions/automatic/6e4f764d-4126-45a5-b7d9-1ab90cd3ffd6 +https://defender.openzeppelin.com/#/actions/automatic/84988850-6816-4074-8e7b-c11cb2b32e7e +https://defender.openzeppelin.com/#/actions/automatic/a4f8ca5f-7144-469b-b84a-58b30fed72ce +https://defender.openzeppelin.com/#/actions/automatic/aa194c13-0dbf-49d2-8e87-70e61f3d71a8 +https://defender.openzeppelin.com/#/actions/automatic/b1d831f1-29d4-4943-bb2e-8e625b76e82c +https://defender.openzeppelin.com/#/actions/automatic/bb43e5da-f936-4185-84da-253394583665 +https://defender.openzeppelin.com/#/actions/automatic/e2929f53-db56-49b2-b054-35f7df7fc4fb +https://defender.openzeppelin.com/#/actions/automatic/e571409b-5399-48e4-bfb2-50b7af9903aa +https://defender.openzeppelin.com/#/actions/automatic/f74f24b4-d98b-4181-89cc-6608369b6f91 +https://defender.openzeppelin.com/#/actions/automatic/f92ea662-fc34-433b-8beb-b34e9ab74685 + +- [ ] Check hyper EVM network jobs for correct network usage diff --git a/contracts/cron/api.test.ts b/contracts/cron/api.test.ts new file mode 100644 index 0000000000..bd5d162689 --- /dev/null +++ b/contracts/cron/api.test.ts @@ -0,0 +1,330 @@ +import { once } from "node:events"; +import { Pool } from "pg"; + +import { createApi } from "./api"; +import { + _resetNonceQueueTxHistoryForTesting, + recordNonceQueueTxLifecycleEvent, +} from "../tasks/lib/nonceQueueTxHistory"; + +if (!process.env.DATABASE_URL) { + console.error( + "Set DATABASE_URL to run this test, e.g.:\n" + + " DATABASE_URL=postgresql://test:test@localhost:5433/nonce_test npx ts-node cron/api.test.ts" + ); + process.exit(1); +} + +const TEST_TOKEN = "test-token"; +const ADDRESS_A = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +const ADDRESS_B = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + +function assert(condition: any, message: string): asserts condition { + if (!condition) throw new Error(message); +} + +async function resetAndSeed(pool: Pool) { + await pool.query("DROP TABLE IF EXISTS nonce_queue_transactions"); + _resetNonceQueueTxHistoryForTesting(); + + await recordNonceQueueTxLifecycleEvent({ + pool, + event: { + type: "send_accepted", + txHash: "0xseed", + stage: "initial", + signerAddress: ADDRESS_A, + chainId: 1, + nonce: 0, + }, + }); + await pool.query("TRUNCATE TABLE nonce_queue_transactions"); + + await pool.query( + ` + INSERT INTO nonce_queue_transactions ( + tx_hash, + signer_address, + chain_id, + nonce, + stage, + status, + lifecycle_state, + explorer_url, + send_count, + block_number, + error_message, + submitted_at, + finalized_at, + updated_at + ) + SELECT + '0xmain' || lpad(gs::text, 6, '0'), + $1, + 1, + gs, + 'initial', + CASE WHEN gs % 2 = 0 THEN 'completed' ELSE 'pending' END, + CASE WHEN gs % 2 = 0 THEN 'confirmed' ELSE 'submitted_initial' END, + 'https://etherscan.io/tx/' || '0xmain' || lpad(gs::text, 6, '0'), + 1, + CASE WHEN gs % 2 = 0 THEN gs ELSE NULL END, + NULL, + NOW() - (gs::text || ' seconds')::interval, + CASE WHEN gs % 2 = 0 THEN NOW() - ((gs - 1)::text || ' seconds')::interval ELSE NULL END, + NOW() + FROM generate_series(1, 510) AS gs + `, + [ADDRESS_A] + ); + + await pool.query( + ` + INSERT INTO nonce_queue_transactions ( + tx_hash, + signer_address, + chain_id, + nonce, + stage, + status, + lifecycle_state, + explorer_url, + send_count, + block_number, + error_message, + submitted_at, + finalized_at, + updated_at + ) + SELECT + '0xb' || lpad(gs::text, 6, '0'), + $1, + 1, + gs, + 'initial', + 'completed', + 'confirmed', + 'https://etherscan.io/tx/' || '0xb' || lpad(gs::text, 6, '0'), + 1, + gs, + NULL, + NOW() - ((510 + gs)::text || ' seconds')::interval, + NOW() - ((510 + gs - 1)::text || ' seconds')::interval, + NOW() + FROM generate_series(1, 5) AS gs + `, + [ADDRESS_B] + ); + + await pool.query( + ` + INSERT INTO nonce_queue_transactions ( + tx_hash, + signer_address, + chain_id, + nonce, + stage, + status, + lifecycle_state, + explorer_url, + send_count, + block_number, + error_message, + submitted_at, + finalized_at, + updated_at + ) + SELECT + '0xbase' || lpad(gs::text, 6, '0'), + $1, + 8453, + gs, + 'initial', + 'completed', + 'confirmed', + 'https://basescan.org/tx/' || '0xbase' || lpad(gs::text, 6, '0'), + 1, + gs, + NULL, + NOW() - ((520 + gs)::text || ' seconds')::interval, + NOW() - ((520 + gs - 1)::text || ' seconds')::interval, + NOW() + FROM generate_series(1, 5) AS gs + `, + [ADDRESS_A] + ); +} + +async function startTestServer() { + const server = createApi({ + host: "127.0.0.1", + port: 0, + apiToken: TEST_TOKEN, + jobs: [], + jobsByName: new Map(), + workdir: process.cwd(), + historyLimit: 100, + healthCheck: () => ({ running: true, pid: 1 }), + log: console as any, + }); + + server.listen(0, "127.0.0.1"); + await once(server, "listening"); + const addressInfo = server.address(); + if (!addressInfo || typeof addressInfo === "string") { + throw new Error("Could not determine test server port"); + } + + return { + server, + baseUrl: `http://127.0.0.1:${addressInfo.port}`, + }; +} + +async function getJson( + baseUrl: string, + path: string, + opts: { auth?: boolean } = {} +) { + const headers: Record = {}; + if (opts.auth !== false) { + headers.Authorization = `Bearer ${TEST_TOKEN}`; + } + + const res = await fetch(`${baseUrl}${path}`, { headers }); + const payload = await res.json(); + return { status: res.status, payload }; +} + +async function testApiTransactionsEndpoint(pool: Pool) { + console.log("--- API Test: /api/v1/transactions ---"); + await resetAndSeed(pool); + + const { server, baseUrl } = await startTestServer(); + try { + const unauthorized = await getJson(baseUrl, "/api/v1/transactions", { + auth: false, + }); + assert( + unauthorized.status === 401, + `Expected 401 for unauthorized /api/v1/transactions, got ${unauthorized.status}` + ); + + const unauthorizedActions = await getJson(baseUrl, "/api/v1/actions", { + auth: false, + }); + assert( + unauthorizedActions.status === 401, + `Expected 401 for unauthorized /api/v1/actions, got ${unauthorizedActions.status}` + ); + + const defaultLimit = await getJson(baseUrl, "/api/v1/transactions"); + assert(defaultLimit.status === 200, "Expected 200 for default list"); + assert( + defaultLimit.payload.transactions.length === 50, + `Expected default limit 50, got ${defaultLimit.payload.transactions.length}` + ); + + const clamped = await getJson(baseUrl, "/api/v1/transactions?limit=999"); + assert(clamped.status === 200, "Expected 200 for clamped list"); + assert( + clamped.payload.transactions.length === 500, + `Expected clamped limit 500, got ${clamped.payload.transactions.length}` + ); + + const byAddress = await getJson( + baseUrl, + `/api/v1/transactions?limit=50&address=${ADDRESS_B}` + ); + assert(byAddress.status === 200, "Expected 200 for address filter"); + assert( + byAddress.payload.transactions.length === 5, + `Expected 5 rows for address filter, got ${byAddress.payload.transactions.length}` + ); + assert( + byAddress.payload.transactions.every( + (row: any) => row.signerAddress === ADDRESS_B + ), + "Address filter returned rows for different address" + ); + + const byChain = await getJson( + baseUrl, + "/api/v1/transactions?limit=50&chainId=8453" + ); + assert(byChain.status === 200, "Expected 200 for chain filter"); + assert( + byChain.payload.transactions.length === 5, + `Expected 5 rows for chain filter, got ${byChain.payload.transactions.length}` + ); + assert( + byChain.payload.transactions.every((row: any) => row.chainId === 8453), + "Chain filter returned rows for different chain" + ); + + const byAddressAndChain = await getJson( + baseUrl, + `/api/v1/transactions?limit=50&address=${ADDRESS_A}&chainId=8453` + ); + assert( + byAddressAndChain.status === 200, + "Expected 200 for address+chain filter" + ); + assert( + byAddressAndChain.payload.transactions.length === 5, + `Expected 5 rows for address+chain filter, got ${byAddressAndChain.payload.transactions.length}` + ); + assert( + byAddressAndChain.payload.transactions.every( + (row: any) => row.signerAddress === ADDRESS_A && row.chainId === 8453 + ), + "Address+chain filter returned unexpected rows" + ); + + const sample = defaultLimit.payload.transactions[0]; + for (const key of [ + "txHash", + "signerAddress", + "chainId", + "nonce", + "stage", + "status", + "lifecycleState", + "explorerUrl", + "submittedAt", + "finalizedAt", + "blockNumber", + "errorMessage", + "sendCount", + ]) { + assert( + sample[key] !== undefined, + `Expected field ${key} on response row` + ); + } + + console.log("PASS: transactions endpoint auth, limits, and filters\n"); + } finally { + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) reject(err); + else resolve(); + }); + }); + } +} + +async function test() { + const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + try { + await testApiTransactionsEndpoint(pool); + console.log("All cron api tests passed!"); + } finally { + await pool.end(); + } +} + +test().catch((err) => { + console.error("TEST FAILED:", err); + process.exit(1); +}); diff --git a/contracts/cron/api.ts b/contracts/cron/api.ts new file mode 100644 index 0000000000..9158f394e7 --- /dev/null +++ b/contracts/cron/api.ts @@ -0,0 +1,363 @@ +import { spawn } from "node:child_process"; +import { randomUUID, timingSafeEqual } from "node:crypto"; +import http from "node:http"; +import type { Logger } from "winston"; +import { getNoncePool } from "../tasks/lib/nonceQueue"; +import { listNonceQueueTransactions } from "../tasks/lib/nonceQueueTxHistory"; +import type { CronJob } from "./render-crontab"; + +// --- Run tracking --- + +interface ActionRun { + runId: string; + action: string; + schedule: string; + enabled: boolean; + command?: string; + status: "queued" | "running" | "succeeded" | "failed"; + queuedAt: string; + startedAt: string; + finishedAt: string | null; + exitCode: number | null; + signal: string | null; + pid: number | null; + error?: string; +} + +// --- API --- + +export interface ApiOpts { + host: string; + port: number; + apiToken: string; + jobs: CronJob[]; + jobsByName: Map; + workdir: string; + historyLimit: number; + healthCheck: () => { running: boolean; pid: number | null }; + log: Logger; +} + +function parsePositiveIntParam( + value: string | null, + { + fallback, + minimum, + maximum, + }: { fallback: number; minimum: number; maximum: number } +): number | null { + if (value === null || value.trim() === "") return fallback; + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < minimum) return null; + return Math.min(parsed, maximum); +} + +export function createApi(opts: ApiOpts): http.Server { + const { + host, + port, + apiToken, + jobs, + jobsByName, + workdir, + historyLimit, + healthCheck, + log, + } = opts; + + const nowIso = () => new Date().toISOString(); + + // --- Run store --- + + const runStore = new Map(); + const runOrder: string[] = []; + + function storeRun(run: ActionRun) { + runStore.set(run.runId, run); + runOrder.push(run.runId); + while (runOrder.length > historyLimit) { + runStore.delete(runOrder.shift()!); + } + } + + // --- Action execution --- + + function runAction(action: CronJob, run: ActionRun) { + run.status = "running"; + run.startedAt = nowIso(); + run.command = action.command; + const startedAtMs = Date.now(); + let terminalEventEmitted = false; + + log.info(`Starting run ${run.runId} for "${action.name}"`, { + event: "action.start", + source: "supervisor", + action: action.name, + run_id: run.runId, + schedule: action.schedule, + command: action.command, + }); + + const child = spawn("/bin/sh", ["-lc", action.command], { + cwd: workdir, + env: { + ...process.env, + ACTION_RUN_ID: run.runId, + ACTION_NAME: action.name, + }, + stdio: "inherit", + }); + run.pid = child.pid ?? null; + + child.on("error", (err) => { + run.status = "failed"; + run.finishedAt = nowIso(); + run.exitCode = null; + run.signal = null; + run.error = err.message; + if (terminalEventEmitted) return; + terminalEventEmitted = true; + log.error(`Run ${run.runId} failed to spawn: ${err.message}`, { + event: "action.failure", + source: "supervisor", + action: action.name, + run_id: run.runId, + schedule: action.schedule, + command: action.command, + duration_ms: Date.now() - startedAtMs, + exit_code: null, + signal: null, + spawn_failed: true, + error_name: err?.name ?? "Error", + error_message: err?.message ?? String(err), + }); + }); + + child.on("exit", (code, signal) => { + run.finishedAt = nowIso(); + run.exitCode = code; + run.signal = signal; + run.status = code === 0 ? "succeeded" : "failed"; + if (terminalEventEmitted) return; + terminalEventEmitted = true; + const terminalMeta = { + event: code === 0 ? "action.success" : "action.failure", + source: "supervisor", + action: action.name, + run_id: run.runId, + schedule: action.schedule, + command: action.command, + duration_ms: Date.now() - startedAtMs, + exit_code: code, + signal, + spawn_failed: false, + }; + if (code === 0) { + log.info(`Run ${run.runId} completed successfully`, terminalMeta); + } else { + log.error( + `Run ${run.runId} failed (exit_code=${code}, signal=${signal})`, + terminalMeta + ); + } + }); + } + + function triggerAction(actionName: string): ActionRun | undefined { + const action = jobsByName.get(actionName); + if (!action) return undefined; + + const run: ActionRun = { + runId: randomUUID(), + action: action.name, + schedule: action.schedule, + enabled: action.enabled, + status: "queued", + queuedAt: nowIso(), + startedAt: nowIso(), + finishedAt: null, + exitCode: null, + signal: null, + pid: null, + }; + + storeRun(run); + setImmediate(() => runAction(action, run)); + return run; + } + + // --- HTTP helpers --- + + function json( + res: http.ServerResponse, + statusCode: number, + payload: any, + extraHeaders: Record = {} + ) { + res.writeHead(statusCode, { + "Content-Type": "application/json", + ...extraHeaders, + }); + res.end(JSON.stringify(payload)); + } + + const expectedTokenBuffer = Buffer.from(apiToken); + function isAuthorized(headerValue: string | string[] | undefined): boolean { + if (typeof headerValue !== "string") return false; + if (!headerValue.startsWith("Bearer ")) return false; + const providedBuffer = Buffer.from(headerValue.slice(7).trim()); + if (providedBuffer.length !== expectedTokenBuffer.length) return false; + return timingSafeEqual(providedBuffer, expectedTokenBuffer); + } + + // --- Server --- + + const server = http.createServer((req, res) => { + const method = req.method || "GET"; + const forwardedProto = req.headers["x-forwarded-proto"]; + const proto = + typeof forwardedProto === "string" && forwardedProto.length > 0 + ? forwardedProto.split(",")[0].trim() + : "http"; + const reqHost = req.headers.host || `${host}:${port}`; + const origin = `${proto}://${reqHost}`; + const url = new URL(req.url || "/", origin); + + // Health check (unauthenticated) + if (method === "GET" && url.pathname === "/healthz") { + const health = healthCheck(); + return json(res, 200, { + status: "ok", + api: "up", + supercronic: { running: health.running, pid: health.pid }, + }); + } + + // Auth gate for /api/v1/* + if ( + url.pathname.startsWith("/api/v1/") && + !isAuthorized(req.headers.authorization) + ) { + return json(res, 401, { error: "Unauthorized" }); + } + + // List actions + if (method === "GET" && url.pathname === "/api/v1/actions") { + return json(res, 200, { + actions: jobs.map((job) => ({ + name: job.name, + schedule: job.schedule, + enabled: job.enabled, + })), + }); + } + + if (method === "GET" && url.pathname === "/api/v1/transactions") { + const limit = parsePositiveIntParam(url.searchParams.get("limit"), { + fallback: 50, + minimum: 1, + maximum: 500, + }); + if (limit === null) { + return json(res, 400, { + error: "Invalid limit (expected integer >= 1)", + }); + } + + const addressParam = url.searchParams.get("address"); + const normalizedAddress = + addressParam && addressParam.trim().length > 0 + ? addressParam.trim().toLowerCase() + : undefined; + const chainIdParam = url.searchParams.get("chainId"); + let parsedChainId: number | undefined; + if (chainIdParam !== null && chainIdParam.trim().length > 0) { + const candidate = Number(chainIdParam); + if (!Number.isInteger(candidate) || candidate < 0) { + return json(res, 400, { + error: "Invalid chainId (expected integer >= 0)", + }); + } + parsedChainId = candidate; + } + + const noncePool = getNoncePool(); + if (!noncePool) { + return json(res, 200, { transactions: [] }); + } + + void listNonceQueueTransactions({ + pool: noncePool, + params: { + limit, + address: normalizedAddress, + chainId: parsedChainId, + }, + }) + .then((transactions) => { + json(res, 200, { transactions }); + }) + .catch((err: any) => { + log.error( + `Failed to list nonce queue transactions: ${ + err?.message ?? String(err) + }` + ); + json(res, 500, { error: "Internal server error" }); + }); + return; + } + + // Trigger action run + const triggerMatch = url.pathname.match( + /^\/api\/v1\/actions\/([^/]+)\/runs$/ + ); + if (method === "POST" && triggerMatch) { + const run = triggerAction(decodeURIComponent(triggerMatch[1])); + if (!run) + return json(res, 404, { + error: `Unknown action "${triggerMatch[1]}"`, + }); + const statusUrl = `${origin}/api/v1/runs/${encodeURIComponent( + run.runId + )}`; + return json( + res, + 202, + { + runId: run.runId, + action: run.action, + status: run.status, + statusUrl, + startedAt: run.startedAt, + }, + { Location: statusUrl } + ); + } + + // Get run status + const statusMatch = url.pathname.match(/^\/api\/v1\/runs\/([^/]+)$/); + if (method === "GET" && statusMatch) { + const run = runStore.get(decodeURIComponent(statusMatch[1])); + if (!run) return json(res, 404, { error: "Run not found" }); + return json(res, 200, { + runId: run.runId, + action: run.action, + status: run.status, + startedAt: run.startedAt, + finishedAt: run.finishedAt, + exitCode: run.exitCode, + signal: run.signal, + }); + } + + json(res, 404, { error: "Not found" }); + }); + + server.on("error", (err) => { + log.error(`HTTP server error: ${err.message}`); + process.exit(1); + }); + + return server; +} diff --git a/contracts/cron/cron-entrypoint.sh b/contracts/cron/cron-entrypoint.sh new file mode 100644 index 0000000000..60c072e9f4 --- /dev/null +++ b/contracts/cron/cron-entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +exec node -r ts-node/register /app/cron/cron-supervisor.ts diff --git a/contracts/cron/cron-jobs.ts b/contracts/cron/cron-jobs.ts new file mode 100644 index 0000000000..07cbfde338 --- /dev/null +++ b/contracts/cron/cron-jobs.ts @@ -0,0 +1,239 @@ +import type { CronJob } from "./render-crontab"; + +export const cronJobs: CronJob[] = [ + { + name: "manage_merkle_morpho_bribe", + schedule: "30 13 * * 3", // weekly (Wednesday) + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat manageMerklBribes --network mainnet", + }, + { + name: "manage_curve_pb_mainnet", + schedule: "30 09 * * 5", // weekly (Friday) + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat manageBribes --network mainnet", + }, + { + name: "update_votemarket_epochs", + schedule: "0 6 * * 5", // weekly (Friday) + enabled: false, + permmissioned: true, + command: + "cd /app && pnpm hardhat updateVotemarketEpochs --network arbitrumOne", + }, + { + name: "OETHandOUSD_harvest_CRV_MOPRHO_native_staking", + schedule: "25 11,23 * * *", // twice daily + enabled: false, + permmissioned: false, + command: "cd /app && pnpm hardhat harvest --network mainnet", + }, + { + name: "OETH_native_staking_accounting", + schedule: "30 23 * * *", // daily + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat doAccounting --network mainnet", + }, + { + name: "manage_pass_through", + schedule: "30 12 * * 0", // weekly (Sunday) + enabled: false, + permmissioned: false, + command: "cd /app && pnpm hardhat managePassThrough --network mainnet", + }, + { + name: "claim_bribes_base", + schedule: "30 10 * * 4", // weekly (Thursday) + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat claimBribes --network base", + }, + { + name: "manage_bribes_base", + schedule: "35 13 * * 3", // weekly (Wednesday) + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat manageMerklBribes --network base", + }, + { + name: "sonic_staking_request_withdraw", + schedule: "35 3,9,15,21 * * *", // 4 times daily + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat sonicUndelegate --network sonic", + }, + { + name: "sonic_staking_claim_withdraw", + schedule: "58 */2 * * *", // every 2 hours (12 times daily) + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat sonicClaimWithdrawals --network sonic", + }, + { + name: "healthcheck", + schedule: "*/5 * * * *", // every 5 minutes + enabled: true, + command: + "cd /app && pnpm hardhat healthcheck --network mainnet", + }, + { + name: "daily_snap_balances", + schedule: "2 0 * * *", // daily + enabled: false, + comment: "Remove --consol true once the consolidation is finished", + command: + "cd /app && pnpm hardhat snapBalances --network mainnet --consol true", + }, + { + name: "daily_verify_balances", + schedule: "6 0 * * *", // daily + enabled: false, + comment: "Remove --consol true once the consolidation is finished", + command: + "cd /app && pnpm hardhat verifyBalances --network mainnet --consol true", + }, + { + name: "daily_verify_deposits", + schedule: "11 */4 * * *", // every 4 hours (6 times daily) + enabled: false, + comment: "This is disabled until we are finished with consolidations", + command: "cd /app && pnpm hardhat verifyDeposits --network mainnet", + }, + { + name: "daily_auto_validator_deposits", + schedule: "14 1 * * *", // daily + enabled: false, + comment: + "Don't enable this in the near future as deposit queue is 50 days long.", + command: "cd /app && pnpm hardhat autoValidatorDeposits --network mainnet", + }, + { + name: "daily_auto_validator_withdrawals", + schedule: "24 1 * * *", // daily + enabled: false, + comment: + "Don't enable this in the near future as deposit queue is 50 days long and we rather use AMO for the liquidity", + command: + "cd /app && pnpm hardhat autoValidatorWithdrawals --network mainnet", + }, + { + name: "otoken_os_collectAndRelease", + schedule: "55 23 * * *", // daily + enabled: false, + permmissioned: false, + command: + "cd /app && pnpm hardhat otokenOsCollectAndRelease --network sonic", + }, + { + name: "otoken_ousd_autoWithdrawal", + schedule: "35 11,23 * * *", // twice daily + enabled: false, + command: + "cd /app && pnpm hardhat otokenOusdAutoWithdrawal --network mainnet", + }, + { + name: "otoken_oethb_updateWoethPrice", + schedule: "30 21 * * *", // daily + enabled: false, + permmissioned: false, + command: + "cd /app && pnpm hardhat otokenOethbUpdateWoethPrice --network base", + }, + { + name: "otoken_oethp_addWithdrawalQueueLiquidity", + schedule: "25 0 * * *", // daily + enabled: false, + permmissioned: false, + command: + "cd /app && pnpm hardhat otokenOethpAddWithdrawalQueueLiquidity --network plume", + }, + { + name: "otoken_oethb_rebase", + schedule: "25 9,21 * * *", // twice daily + enabled: false, + permmissioned: false, + command: "cd /app && pnpm hardhat otokenOethbRebase --network base", + }, + { + name: "otoken_os_sonicRestakeRewards", + schedule: "52 22 * * *", // daily + enabled: false, + command: + "cd /app && pnpm hardhat otokenOsSonicRestakeRewards --network sonic", + }, + { + name: "cross_chain_balance_update_base", + schedule: "40 7,15,23 * * *", // 3 times daily + enabled: false, + permmissioned: true, + command: + "cd /app && pnpm hardhat crossChainBalanceUpdateBase --network base", + }, + { + name: "cross_chain_balance_update_hyperevm", + schedule: "50 7,15,23 * * *", // 3 times daily + enabled: false, + permmissioned: true, + command: + "cd /app && pnpm hardhat crossChainBalanceUpdateHyperevm --network hyperevm", + }, + { + name: "cross_chain_base_mainnet", + schedule: "27 2,8,14,20 * * *", // 4 times daily + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat relayCCTPMessage --network base", + }, + { + name: "cross_chain_mainnet_base", + schedule: "43 4,10,16,22 * * *", // 4 times daily + enabled: false, + permmissioned: true, + command: "cd /app && pnpm hardhat relayCCTPMessage --network mainnet", + }, + { + name: "cross_chain_hyper_mainnet", + schedule: "17 1,6,11,16,21 * * *", // 5 times daily + enabled: false, + permmissioned: true, + command: + "cd /app && pnpm hardhat crossChainRelayHyperEVM --network hyperevm", + }, + { + name: "cross_chain_mainnet_hyper", + schedule: "7 3,8,13,18,23 * * *", // 5 times daily + enabled: false, + permmissioned: true, + command: + "cd /app && pnpm hardhat crossChainRelayHyperEVM --network mainnet", + }, + { + name: "claim_ssv_rewards", + schedule: "45 0 1 * *", // monthly (1st day) + enabled: false, + command: "cd /app && pnpm hardhat claimSSVRewards --network mainnet", + }, + { + name: "otoken_ousd_oeth_rebase", + schedule: "45 11,23 * * *", // twice daily + enabled: false, + command: "cd /app && pnpm hardhat otokenOusdOethRebase --network mainnet", + }, + { + name: "ogn_claimAndForwardRewards", + schedule: "50 0 * * 2", // weekly (Tuesday) + enabled: false, + command: + "cd /app && pnpm hardhat ognClaimAndForwardRewards --network mainnet", + }, + { + name: "otoken_oethb_harvest", + schedule: "55 11 * * *", // daily + enabled: false, + permmissioned: false, + command: "cd /app && pnpm hardhat otokenOethbHarvest --network base", + }, +]; diff --git a/contracts/cron/cron-supervisor.ts b/contracts/cron/cron-supervisor.ts new file mode 100644 index 0000000000..37a620fdb7 --- /dev/null +++ b/contracts/cron/cron-supervisor.ts @@ -0,0 +1,160 @@ +import { spawn } from "node:child_process"; +import fs from "node:fs"; +import baseLogger, { flushLogger } from "../tasks/lib/logger"; +import { getNoncePool } from "../tasks/lib/nonceQueue"; +import { createApi } from "./api"; +import { cronJobs } from "./cron-jobs"; +import { renderCrontab } from "./render-crontab"; + +const log = baseLogger.child({ source: "supervisor" }); + +// --- Configuration --- + +const host = process.env.HOST || "0.0.0.0"; +const port = Number.parseInt(process.env.PORT || "8080", 10); +const cronOutputPath = process.env.CRON_OUTPUT_PATH || "/app/cron/cronjob"; +const supercronicBin = process.env.SUPERCRONIC_BIN || "supercronic"; +const runHistoryLimit = Number.parseInt( + process.env.ACTION_RUN_HISTORY_LIMIT || "500", + 10, +); +const actionApiToken = process.env.ACTION_API_BEARER_TOKEN; +const configuredActionWorkdir = process.env.ACTION_WORKDIR || "/app"; + +if (!Number.isInteger(port) || port < 1 || port > 65535) { + log.error(`Invalid PORT value "${process.env.PORT}"`); + process.exit(1); +} +if (!actionApiToken || actionApiToken.trim().length === 0) { + log.error("ACTION_API_BEARER_TOKEN must be set and non-empty"); + process.exit(1); +} +if (!Number.isInteger(runHistoryLimit) || runHistoryLimit < 1) { + log.error( + `Invalid ACTION_RUN_HISTORY_LIMIT value "${process.env.ACTION_RUN_HISTORY_LIMIT}"`, + ); + process.exit(1); +} + +const actionWorkdir = fs.existsSync(configuredActionWorkdir) + ? configuredActionWorkdir + : process.cwd(); + +if (!fs.existsSync(configuredActionWorkdir)) { + log.warn( + `ACTION_WORKDIR "${configuredActionWorkdir}" does not exist, using "${actionWorkdir}" instead`, + ); +} + +// --- Cron setup --- + +function initCron() { + try { + return renderCrontab({ jobs: cronJobs, outputPath: cronOutputPath }); + } catch (e: any) { + log.error(e.message); + process.exit(1); + } +} + +const { jobs: allJobs, enabledJobs } = initCron(); + +log.info( + `Generated ${enabledJobs.length} enabled cron jobs at ${cronOutputPath}`, +); +log.info( + `Generated ${cronOutputPath}:\n${fs.readFileSync(cronOutputPath, "utf8")}`, +); + +// --- Supercronic --- + +const supercronic = spawn(supercronicBin, [cronOutputPath], { + env: process.env, + stdio: "inherit", +}); +let supercronicAlive = true; + +supercronic.on("error", (err) => { + log.error(`supercronic start error: ${err.message}`); + process.exit(1); +}); + +// --- API server --- + +const jobsByName = new Map(allJobs.map((job) => [job.name, job])); +const server = createApi({ + host, + port, + apiToken: actionApiToken, + jobs: allJobs, + jobsByName, + workdir: actionWorkdir, + historyLimit: runHistoryLimit, + healthCheck: () => ({ + running: supercronicAlive, + pid: supercronic.pid ?? null, + }), + log, +}); + +server.listen(port, host, () => { + log.info(`API listening on ${host}:${port}`); +}); + +// --- Graceful shutdown --- + +let shuttingDown = false; +let serverClosed = false; +let supercronicClosed = false; + +function maybeExit() { + if (shuttingDown && serverClosed && supercronicClosed) process.exit(0); +} + +supercronic.on("exit", (code, signal) => { + supercronicAlive = false; + supercronicClosed = true; + if (!shuttingDown) { + log.error( + `supercronic exited unexpectedly (code=${code}, signal=${signal})`, + ); + process.exit(typeof code === "number" ? code : 1); + } + maybeExit(); +}); + +server.on("close", () => { + serverClosed = true; + maybeExit(); +}); + +async function shutdown(signal: string) { + if (shuttingDown) return; + shuttingDown = true; + + log.info(`Shutting down (signal=${signal})`); + try { + await flushLogger(); + } catch (err: any) { + log.error(`flushLogger failed: ${err?.message}`); + } + try { + const pool = getNoncePool(); + if (pool) await pool.end(); + } catch (err: any) { + log.error(`noncePool close failed: ${err?.message}`); + } + server.close(); + + if (supercronicAlive && supercronic.exitCode === null) { + supercronic.kill("SIGTERM"); + setTimeout(() => { + if (supercronic.exitCode === null) supercronic.kill("SIGKILL"); + }, 10_000).unref(); + } + + setTimeout(() => process.exit(0), 12_000).unref(); +} + +process.on("SIGTERM", () => void shutdown("SIGTERM")); +process.on("SIGINT", () => void shutdown("SIGINT")); diff --git a/contracts/cron/render-crontab.ts b/contracts/cron/render-crontab.ts new file mode 100644 index 0000000000..989fabfdc3 --- /dev/null +++ b/contracts/cron/render-crontab.ts @@ -0,0 +1,54 @@ +import fs from "node:fs"; +import path from "node:path"; + +import { cronJobs } from "./cron-jobs"; + +const DEFAULT_OUTPUT_PATH = process.env.CRON_OUTPUT_PATH || "./cron/cronjob"; + +export class RenderCrontabError extends Error {} + +export interface CronJob { + name: string; + schedule: string; + enabled: boolean; + permmissioned?: boolean; + command: string; + comment?: string; +} + +export interface CronConfig { + jobs: CronJob[]; +} + +export function renderCrontab({ + jobs = cronJobs, + outputPath = DEFAULT_OUTPUT_PATH, +}: { jobs?: CronJob[]; outputPath?: string } = {}) { + const enabledJobs = jobs.filter((job) => job.enabled); + if (enabledJobs.length === 0) { + throw new RenderCrontabError("config has zero enabled jobs"); + } + + const lines: string[] = []; + for (const job of enabledJobs) { + lines.push(`# ${job.name}`); + lines.push(`${job.schedule} ${job.command}`); + } + + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, `${lines.join("\n")}\n`, "utf8"); + + return { jobs, enabledJobs, outputPath }; +} + +if (require.main === module) { + try { + const result = renderCrontab(); + console.log( + `[render-crontab] wrote ${result.enabledJobs.length} enabled jobs to ${result.outputPath}` + ); + } catch (e: any) { + console.error(`[render-crontab] ${e.message}`); + process.exit(1); + } +} diff --git a/contracts/deployments/mainnet/OETHDripper.json b/contracts/deployments/mainnet/OETHDripper.json deleted file mode 100644 index 065627a40a..0000000000 --- a/contracts/deployments/mainnet/OETHDripper.json +++ /dev/null @@ -1,374 +0,0 @@ -{ - "address": "0x0929C0fbFF88e129ACaA51Bba0C959491325b4aD", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "_vault", - "type": "address" - }, - { - "internalType": "address", - "name": "_token", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousGovernor", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newGovernor", - "type": "address" - } - ], - "name": "GovernorshipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousGovernor", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newGovernor", - "type": "address" - } - ], - "name": "PendingGovernorshipTransfer", - "type": "event" - }, - { - "inputs": [], - "name": "availableFunds", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "claimGovernance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "collect", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "collectAndRebase", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "drip", - "outputs": [ - { - "internalType": "uint64", - "name": "lastCollect", - "type": "uint64" - }, - { - "internalType": "uint192", - "name": "perSecond", - "type": "uint192" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "dripDuration", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "governor", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "isGovernor", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_durationSeconds", - "type": "uint256" - } - ], - "name": "setDripDuration", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_asset", - "type": "address" - }, - { - "internalType": "address", - "name": "_receiver", - "type": "address" - } - ], - "name": "transferAllToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newGovernor", - "type": "address" - } - ], - "name": "transferGovernance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_asset", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "transferToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "transactionHash": "0xe3caa74ee0d37c4878982cd7a492cec7140463f445462fc31a2cf34290808b1d", - "receipt": { - "to": null, - "from": "0x074105fdD39e982B2ffE749A193c942db1046AB9", - "contractAddress": "0x0929C0fbFF88e129ACaA51Bba0C959491325b4aD", - "transactionIndex": 8, - "gasUsed": "850925", - "logsBloom": "0x00000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000004000000000000000000000000000000000000000000000010000000000000000000000080000000000000000000000000000000010000000000000000000000000000000000000010000000000000000000000000400000000000000020000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x55db8223966a91b9441d9c5720994c4c0bef3c30e1e74b0fccaf30e748092548", - "transactionHash": "0xe3caa74ee0d37c4878982cd7a492cec7140463f445462fc31a2cf34290808b1d", - "logs": [ - { - "transactionIndex": 8, - "blockNumber": 21624125, - "transactionHash": "0xe3caa74ee0d37c4878982cd7a492cec7140463f445462fc31a2cf34290808b1d", - "address": "0x0929C0fbFF88e129ACaA51Bba0C959491325b4aD", - "topics": [ - "0xc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x000000000000000000000000074105fdd39e982b2ffe749a193c942db1046ab9" - ], - "data": "0x", - "logIndex": 38, - "blockHash": "0x55db8223966a91b9441d9c5720994c4c0bef3c30e1e74b0fccaf30e748092548" - } - ], - "blockNumber": 21624125, - "cumulativeGasUsed": "1821309", - "status": 1, - "byzantium": true - }, - "args": [ - "0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab", - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" - ], - "numDeployments": 2, - "solcInputHash": "a79054f017f110f64fdd0ebd33e29bb2", - "metadata": "{\"compiler\":{\"version\":\"0.8.7+commit.e28d00a7\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_vault\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"GovernorshipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"PendingGovernorshipTransfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"availableFunds\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"collect\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"collectAndRebase\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"drip\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"lastCollect\",\"type\":\"uint64\"},{\"internalType\":\"uint192\",\"name\":\"perSecond\",\"type\":\"uint192\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"dripDuration\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"governor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isGovernor\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_durationSeconds\",\"type\":\"uint256\"}],\"name\":\"setDripDuration\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_receiver\",\"type\":\"address\"}],\"name\":\"transferAllToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newGovernor\",\"type\":\"address\"}],\"name\":\"transferGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"transferToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Origin Protocol Inc\",\"kind\":\"dev\",\"methods\":{\"availableFunds()\":{\"returns\":{\"_0\":\"The amount that would be sent if a collect was called\"}},\"setDripDuration(uint256)\":{\"details\":\"Change the drip duration. Governor only.\",\"params\":{\"_durationSeconds\":\"the number of seconds to drip out the entire balance over if no collects were called during that time.\"}},\"transferAllToken(address,address)\":{\"details\":\"Transfer out all ERC20 held by the contract. Governor only.\",\"params\":{\"_asset\":\"ERC20 token address\"}},\"transferGovernance(address)\":{\"params\":{\"_newGovernor\":\"Address of the new Governor\"}},\"transferToken(address,uint256)\":{\"details\":\"Transfer out ERC20 tokens held by the contract. Governor only.\",\"params\":{\"_amount\":\"amount to transfer\",\"_asset\":\"ERC20 token address\"}}},\"title\":\"OETH Dripper Contract\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"claimGovernance()\":{\"notice\":\"Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor.\"},\"collect()\":{\"notice\":\"Collect all dripped funds and send to vault. Recalculate new drip rate.\"},\"collectAndRebase()\":{\"notice\":\"Collect all dripped funds, send to vault, recalculate new drip rate, and rebase OUSD.\"},\"governor()\":{\"notice\":\"Returns the address of the current Governor.\"},\"isGovernor()\":{\"notice\":\"Returns true if the caller is the current Governor.\"},\"transferGovernance(address)\":{\"notice\":\"Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/harvest/OETHDripper.sol\":\"OETHDripper\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x61437cb513a887a1bbad006e7b1c8b414478427d33de47c5600af3c748f108da\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc3d946432c0ddbb1f846a0d3985be71299df331b91d06732152117f62f0be2b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x51b758a8815ecc9596c66c37d56b1d33883a444631a3f916b9fe65cb863ef7c4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/SafeCast.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\\n * checks.\\n *\\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\\n * easily result in undesired exploitation or bugs, since developers usually\\n * assume that overflows raise errors. `SafeCast` restores this intuition by\\n * reverting the transaction when such an operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n *\\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\\n * all math on `uint256` and `int256` and then downcasting.\\n */\\nlibrary SafeCast {\\n /**\\n * @dev Returns the downcasted uint224 from uint256, reverting on\\n * overflow (when the input is greater than largest uint224).\\n *\\n * Counterpart to Solidity's `uint224` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 224 bits\\n */\\n function toUint224(uint256 value) internal pure returns (uint224) {\\n require(value <= type(uint224).max, \\\"SafeCast: value doesn't fit in 224 bits\\\");\\n return uint224(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint128 from uint256, reverting on\\n * overflow (when the input is greater than largest uint128).\\n *\\n * Counterpart to Solidity's `uint128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n */\\n function toUint128(uint256 value) internal pure returns (uint128) {\\n require(value <= type(uint128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return uint128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint96 from uint256, reverting on\\n * overflow (when the input is greater than largest uint96).\\n *\\n * Counterpart to Solidity's `uint96` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 96 bits\\n */\\n function toUint96(uint256 value) internal pure returns (uint96) {\\n require(value <= type(uint96).max, \\\"SafeCast: value doesn't fit in 96 bits\\\");\\n return uint96(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint64 from uint256, reverting on\\n * overflow (when the input is greater than largest uint64).\\n *\\n * Counterpart to Solidity's `uint64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n */\\n function toUint64(uint256 value) internal pure returns (uint64) {\\n require(value <= type(uint64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return uint64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint32 from uint256, reverting on\\n * overflow (when the input is greater than largest uint32).\\n *\\n * Counterpart to Solidity's `uint32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n */\\n function toUint32(uint256 value) internal pure returns (uint32) {\\n require(value <= type(uint32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return uint32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint16 from uint256, reverting on\\n * overflow (when the input is greater than largest uint16).\\n *\\n * Counterpart to Solidity's `uint16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n */\\n function toUint16(uint256 value) internal pure returns (uint16) {\\n require(value <= type(uint16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return uint16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint8 from uint256, reverting on\\n * overflow (when the input is greater than largest uint8).\\n *\\n * Counterpart to Solidity's `uint8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n */\\n function toUint8(uint256 value) internal pure returns (uint8) {\\n require(value <= type(uint8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return uint8(value);\\n }\\n\\n /**\\n * @dev Converts a signed int256 into an unsigned uint256.\\n *\\n * Requirements:\\n *\\n * - input must be greater than or equal to 0.\\n */\\n function toUint256(int256 value) internal pure returns (uint256) {\\n require(value >= 0, \\\"SafeCast: value must be positive\\\");\\n return uint256(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int128 from int256, reverting on\\n * overflow (when the input is less than smallest int128 or\\n * greater than largest int128).\\n *\\n * Counterpart to Solidity's `int128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt128(int256 value) internal pure returns (int128) {\\n require(value >= type(int128).min && value <= type(int128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return int128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int64 from int256, reverting on\\n * overflow (when the input is less than smallest int64 or\\n * greater than largest int64).\\n *\\n * Counterpart to Solidity's `int64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt64(int256 value) internal pure returns (int64) {\\n require(value >= type(int64).min && value <= type(int64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return int64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int32 from int256, reverting on\\n * overflow (when the input is less than smallest int32 or\\n * greater than largest int32).\\n *\\n * Counterpart to Solidity's `int32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt32(int256 value) internal pure returns (int32) {\\n require(value >= type(int32).min && value <= type(int32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return int32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int16 from int256, reverting on\\n * overflow (when the input is less than smallest int16 or\\n * greater than largest int16).\\n *\\n * Counterpart to Solidity's `int16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt16(int256 value) internal pure returns (int16) {\\n require(value >= type(int16).min && value <= type(int16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return int16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int8 from int256, reverting on\\n * overflow (when the input is less than smallest int8 or\\n * greater than largest int8).\\n *\\n * Counterpart to Solidity's `int8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n *\\n * _Available since v3.1._\\n */\\n function toInt8(int256 value) internal pure returns (int8) {\\n require(value >= type(int8).min && value <= type(int8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return int8(value);\\n }\\n\\n /**\\n * @dev Converts an unsigned uint256 into a signed int256.\\n *\\n * Requirements:\\n *\\n * - input must be less than or equal to maxInt256.\\n */\\n function toInt256(uint256 value) internal pure returns (int256) {\\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\\n require(value <= uint256(type(int256).max), \\\"SafeCast: value doesn't fit in an int256\\\");\\n return int256(value);\\n }\\n}\\n\",\"keccak256\":\"0x5c6caab697d302ad7eb59c234a4d2dbc965c1bae87709bd2850060b7695b28c7\",\"license\":\"MIT\"},\"contracts/governance/Governable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\\n * from owner to governor and renounce methods removed. Does not use\\n * Context.sol like Ownable.sol does for simplification.\\n * @author Origin Protocol Inc\\n */\\ncontract Governable {\\n // Storage position of the owner and pendingOwner of the contract\\n // keccak256(\\\"OUSD.governor\\\");\\n bytes32 private constant governorPosition =\\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\\n\\n // keccak256(\\\"OUSD.pending.governor\\\");\\n bytes32 private constant pendingGovernorPosition =\\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\\n\\n // keccak256(\\\"OUSD.reentry.status\\\");\\n bytes32 private constant reentryStatusPosition =\\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\\n\\n // See OpenZeppelin ReentrancyGuard implementation\\n uint256 constant _NOT_ENTERED = 1;\\n uint256 constant _ENTERED = 2;\\n\\n event PendingGovernorshipTransfer(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n event GovernorshipTransferred(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial Governor.\\n */\\n constructor() {\\n _setGovernor(msg.sender);\\n emit GovernorshipTransferred(address(0), _governor());\\n }\\n\\n /**\\n * @notice Returns the address of the current Governor.\\n */\\n function governor() public view returns (address) {\\n return _governor();\\n }\\n\\n /**\\n * @dev Returns the address of the current Governor.\\n */\\n function _governor() internal view returns (address governorOut) {\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n governorOut := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Returns the address of the pending Governor.\\n */\\n function _pendingGovernor()\\n internal\\n view\\n returns (address pendingGovernor)\\n {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n pendingGovernor := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the Governor.\\n */\\n modifier onlyGovernor() {\\n require(isGovernor(), \\\"Caller is not the Governor\\\");\\n _;\\n }\\n\\n /**\\n * @notice Returns true if the caller is the current Governor.\\n */\\n function isGovernor() public view returns (bool) {\\n return msg.sender == _governor();\\n }\\n\\n function _setGovernor(address newGovernor) internal {\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and make it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n bytes32 position = reentryStatusPosition;\\n uint256 _reentry_status;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n _reentry_status := sload(position)\\n }\\n\\n // On the first call to nonReentrant, _notEntered will be true\\n require(_reentry_status != _ENTERED, \\\"Reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _ENTERED)\\n }\\n\\n _;\\n\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _NOT_ENTERED)\\n }\\n }\\n\\n function _setPendingGovernor(address newGovernor) internal {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the current Governor. Must be claimed for this to complete\\n * @param _newGovernor Address of the new Governor\\n */\\n function transferGovernance(address _newGovernor) external onlyGovernor {\\n _setPendingGovernor(_newGovernor);\\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\\n }\\n\\n /**\\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the new Governor.\\n */\\n function claimGovernance() external {\\n require(\\n msg.sender == _pendingGovernor(),\\n \\\"Only the pending Governor can complete the claim\\\"\\n );\\n _changeGovernor(msg.sender);\\n }\\n\\n /**\\n * @dev Change Governance of the contract to a new account (`newGovernor`).\\n * @param _newGovernor Address of the new Governor\\n */\\n function _changeGovernor(address _newGovernor) internal {\\n require(_newGovernor != address(0), \\\"New Governor is address(0)\\\");\\n emit GovernorshipTransferred(_governor(), _newGovernor);\\n _setGovernor(_newGovernor);\\n }\\n}\\n\",\"keccak256\":\"0xb7133d6ce7a9e673ff79fcedb3fd41ae6e58e251f94915bb65731abe524270b4\",\"license\":\"MIT\"},\"contracts/harvest/Dripper.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\n\\n/**\\n * @title OUSD Dripper\\n *\\n * The dripper contract smooths out the yield from point-in-time yield events\\n * and spreads the yield out over a configurable time period. This ensures a\\n * continuous per block yield to makes users happy as their next rebase\\n * amount is always moving up. Also, this makes historical day to day yields\\n * smooth, rather than going from a near zero day, to a large APY day, then\\n * back to a near zero day again.\\n *\\n *\\n * Design notes\\n * - USDT has a smaller resolution than the number of seconds\\n * in a week, which can make per second payouts have a rounding error. However\\n * the total effect is not large - cents per day, and this money is\\n * not lost, just distributed in the future. While we could use a higher\\n * decimal precision for the drip perSecond, we chose simpler code.\\n * - By calculating the changing drip rates on collects only, harvests and yield\\n * events don't have to call anything on this contract or pay any extra gas.\\n * Collect() is already be paying for a single write, since it has to reset\\n * the lastCollect time.\\n * - By having a collectAndRebase method, and having our external systems call\\n * that, the OUSD vault does not need any changes, not even to know the address\\n * of the dripper.\\n * - A rejected design was to retro-calculate the drip rate on each collect,\\n * based on the balance at the time of the collect. While this would have\\n * required less state, and would also have made the contract respond more quickly\\n * to new income, it would break the predictability that is this contract's entire\\n * purpose. If we did this, the amount of fundsAvailable() would make sharp increases\\n * when funds were deposited.\\n * - When the dripper recalculates the rate, it targets spending the balance over\\n * the duration. This means that every time that collect is called, if no\\n * new funds have been deposited the duration is being pushed back and the\\n * rate decreases. This is expected, and ends up following a smoother but\\n * longer curve the more collect() is called without incoming yield.\\n *\\n */\\n\\ncontract Dripper is Governable {\\n using SafeERC20 for IERC20;\\n\\n struct Drip {\\n uint64 lastCollect; // overflows 262 billion years after the sun dies\\n uint192 perSecond; // drip rate per second\\n }\\n\\n address immutable vault; // OUSD vault\\n address immutable token; // token to drip out\\n uint256 public dripDuration; // in seconds\\n Drip public drip; // active drip parameters\\n\\n constructor(address _vault, address _token) {\\n vault = _vault;\\n token = _token;\\n }\\n\\n /// @notice How much funds have dripped out already and are currently\\n // available to be sent to the vault.\\n /// @return The amount that would be sent if a collect was called\\n function availableFunds() external view returns (uint256) {\\n uint256 balance = IERC20(token).balanceOf(address(this));\\n return _availableFunds(balance, drip);\\n }\\n\\n /// @notice Collect all dripped funds and send to vault.\\n /// Recalculate new drip rate.\\n function collect() external {\\n _collect();\\n }\\n\\n /// @notice Collect all dripped funds, send to vault, recalculate new drip\\n /// rate, and rebase OUSD.\\n function collectAndRebase() external {\\n _collect();\\n IVault(vault).rebase();\\n }\\n\\n /// @dev Change the drip duration. Governor only.\\n /// @param _durationSeconds the number of seconds to drip out the entire\\n /// balance over if no collects were called during that time.\\n function setDripDuration(uint256 _durationSeconds)\\n external\\n virtual\\n onlyGovernor\\n {\\n require(_durationSeconds > 0, \\\"duration must be non-zero\\\");\\n dripDuration = _durationSeconds;\\n _collect(); // duration change take immediate effect\\n }\\n\\n /// @dev Transfer out ERC20 tokens held by the contract. Governor only.\\n /// @param _asset ERC20 token address\\n /// @param _amount amount to transfer\\n function transferToken(address _asset, uint256 _amount)\\n external\\n onlyGovernor\\n {\\n IERC20(_asset).safeTransfer(governor(), _amount);\\n }\\n\\n /// @dev Calculate available funds by taking the lower of either the\\n /// currently dripped out funds or the balance available.\\n /// Uses passed in parameters to calculate with for gas savings.\\n /// @param _balance current balance in contract\\n /// @param _drip current drip parameters\\n function _availableFunds(uint256 _balance, Drip memory _drip)\\n internal\\n view\\n returns (uint256)\\n {\\n uint256 elapsed = block.timestamp - _drip.lastCollect;\\n uint256 allowed = (elapsed * _drip.perSecond);\\n return (allowed > _balance) ? _balance : allowed;\\n }\\n\\n /// @dev Sends the currently dripped funds to be vault, and sets\\n /// the new drip rate based on the new balance.\\n function _collect() internal virtual {\\n // Calculate send\\n uint256 balance = IERC20(token).balanceOf(address(this));\\n uint256 amountToSend = _availableFunds(balance, drip);\\n uint256 remaining = balance - amountToSend;\\n // Calculate new drip perSecond\\n // Gas savings by setting entire struct at one time\\n drip = Drip({\\n perSecond: uint192(remaining / dripDuration),\\n lastCollect: uint64(block.timestamp)\\n });\\n // Send funds\\n IERC20(token).safeTransfer(vault, amountToSend);\\n }\\n\\n /// @dev Transfer out all ERC20 held by the contract. Governor only.\\n /// @param _asset ERC20 token address\\n function transferAllToken(address _asset, address _receiver)\\n external\\n onlyGovernor\\n {\\n IERC20(_asset).safeTransfer(\\n _receiver,\\n IERC20(_asset).balanceOf(address(this))\\n );\\n }\\n}\\n\",\"keccak256\":\"0xc654ba3cdfd6763a1ba393d2ecd0aed776032dddafd2a3f916d1ba7497ea459b\",\"license\":\"MIT\"},\"contracts/harvest/OETHDripper.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\nimport { Dripper } from \\\"./Dripper.sol\\\";\\n\\n/**\\n * @title OETH Dripper Contract\\n * @author Origin Protocol Inc\\n */\\ncontract OETHDripper is Dripper {\\n constructor(address _vault, address _token) Dripper(_vault, _token) {}\\n}\\n\",\"keccak256\":\"0x793bb189f2419450b11a386f7a0e42cc0fdbf272624c8f93441046d9df6b8c83\",\"license\":\"MIT\"},\"contracts/interfaces/IBasicToken.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IBasicToken {\\n function symbol() external view returns (string memory);\\n\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xa562062698aa12572123b36dfd2072f1a39e44fed2031cc19c2c9fd522f96ec2\",\"license\":\"MIT\"},\"contracts/interfaces/IStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\\n */\\ninterface IStrategy {\\n /**\\n * @dev Deposit the given asset to platform\\n * @param _asset asset address\\n * @param _amount Amount to deposit\\n */\\n function deposit(address _asset, uint256 _amount) external;\\n\\n /**\\n * @dev Deposit the entire balance of all supported assets in the Strategy\\n * to the platform\\n */\\n function depositAll() external;\\n\\n /**\\n * @dev Withdraw given asset from Lending platform\\n */\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external;\\n\\n /**\\n * @dev Liquidate all assets in strategy and return them to Vault.\\n */\\n function withdrawAll() external;\\n\\n /**\\n * @dev Returns the current balance of the given asset.\\n */\\n function checkBalance(address _asset)\\n external\\n view\\n returns (uint256 balance);\\n\\n /**\\n * @dev Returns bool indicating whether strategy supports asset.\\n */\\n function supportsAsset(address _asset) external view returns (bool);\\n\\n /**\\n * @dev Collect reward tokens from the Strategy.\\n */\\n function collectRewardTokens() external;\\n\\n /**\\n * @dev The address array of the reward tokens for the Strategy.\\n */\\n function getRewardTokenAddresses() external view returns (address[] memory);\\n}\\n\",\"keccak256\":\"0xb291e409a9b95527f9ed19cd6bff8eeb9921a21c1f5194a48c0bb9ce6613959a\",\"license\":\"MIT\"},\"contracts/interfaces/IVault.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\nimport { VaultStorage } from \\\"../vault/VaultStorage.sol\\\";\\n\\ninterface IVault {\\n event AssetSupported(address _asset);\\n event AssetDefaultStrategyUpdated(address _asset, address _strategy);\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event RedeemFeeUpdated(uint256 _redeemFeeBps);\\n event PriceProviderUpdated(address _priceProvider);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event SwapperChanged(address _address);\\n event SwapAllowedUndervalueChanged(uint256 _basis);\\n event SwapSlippageChanged(address _asset, uint256 _basis);\\n event Swapped(\\n address indexed _fromAsset,\\n address indexed _toAsset,\\n uint256 _fromAssetAmount,\\n uint256 _toAssetAmount\\n );\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event DripperChanged(address indexed _dripper);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n\\n // Governable.sol\\n function transferGovernance(address _newGovernor) external;\\n\\n function claimGovernance() external;\\n\\n function governor() external view returns (address);\\n\\n // VaultAdmin.sol\\n function setPriceProvider(address _priceProvider) external;\\n\\n function priceProvider() external view returns (address);\\n\\n function setRedeemFeeBps(uint256 _redeemFeeBps) external;\\n\\n function redeemFeeBps() external view returns (uint256);\\n\\n function setVaultBuffer(uint256 _vaultBuffer) external;\\n\\n function vaultBuffer() external view returns (uint256);\\n\\n function setAutoAllocateThreshold(uint256 _threshold) external;\\n\\n function autoAllocateThreshold() external view returns (uint256);\\n\\n function setRebaseThreshold(uint256 _threshold) external;\\n\\n function rebaseThreshold() external view returns (uint256);\\n\\n function setStrategistAddr(address _address) external;\\n\\n function strategistAddr() external view returns (address);\\n\\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\\n\\n function maxSupplyDiff() external view returns (uint256);\\n\\n function setTrusteeAddress(address _address) external;\\n\\n function trusteeAddress() external view returns (address);\\n\\n function setTrusteeFeeBps(uint256 _basis) external;\\n\\n function trusteeFeeBps() external view returns (uint256);\\n\\n function ousdMetaStrategy() external view returns (address);\\n\\n function setSwapper(address _swapperAddr) external;\\n\\n function setSwapAllowedUndervalue(uint16 _percentageBps) external;\\n\\n function setOracleSlippage(address _asset, uint16 _allowedOracleSlippageBps)\\n external;\\n\\n function supportAsset(address _asset, uint8 _supportsAsset) external;\\n\\n function approveStrategy(address _addr) external;\\n\\n function removeStrategy(address _addr) external;\\n\\n function setAssetDefaultStrategy(address _asset, address _strategy)\\n external;\\n\\n function assetDefaultStrategies(address _asset)\\n external\\n view\\n returns (address);\\n\\n function pauseRebase() external;\\n\\n function unpauseRebase() external;\\n\\n function rebasePaused() external view returns (bool);\\n\\n function pauseCapital() external;\\n\\n function unpauseCapital() external;\\n\\n function capitalPaused() external view returns (bool);\\n\\n function transferToken(address _asset, uint256 _amount) external;\\n\\n function priceUnitMint(address asset) external view returns (uint256);\\n\\n function priceUnitRedeem(address asset) external view returns (uint256);\\n\\n function withdrawAllFromStrategy(address _strategyAddr) external;\\n\\n function withdrawAllFromStrategies() external;\\n\\n function withdrawFromStrategy(\\n address _strategyFromAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n function depositToStrategy(\\n address _strategyToAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n // VaultCore.sol\\n function mint(\\n address _asset,\\n uint256 _amount,\\n uint256 _minimumOusdAmount\\n ) external;\\n\\n function mintForStrategy(uint256 _amount) external;\\n\\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\\n\\n function burnForStrategy(uint256 _amount) external;\\n\\n function redeemAll(uint256 _minimumUnitAmount) external;\\n\\n function allocate() external;\\n\\n function rebase() external;\\n\\n function swapCollateral(\\n address fromAsset,\\n address toAsset,\\n uint256 fromAssetAmount,\\n uint256 minToAssetAmount,\\n bytes calldata data\\n ) external returns (uint256 toAssetAmount);\\n\\n function totalValue() external view returns (uint256 value);\\n\\n function checkBalance(address _asset) external view returns (uint256);\\n\\n function calculateRedeemOutputs(uint256 _amount)\\n external\\n view\\n returns (uint256[] memory);\\n\\n function getAssetCount() external view returns (uint256);\\n\\n function getAssetConfig(address _asset)\\n external\\n view\\n returns (VaultStorage.Asset memory config);\\n\\n function getAllAssets() external view returns (address[] memory);\\n\\n function getStrategyCount() external view returns (uint256);\\n\\n function swapper() external view returns (address);\\n\\n function allowedSwapUndervalue() external view returns (uint256);\\n\\n function getAllStrategies() external view returns (address[] memory);\\n\\n function isSupportedAsset(address _asset) external view returns (bool);\\n\\n function netOusdMintForStrategyThreshold() external view returns (uint256);\\n\\n function setOusdMetaStrategy(address _ousdMetaStrategy) external;\\n\\n function setNetOusdMintForStrategyThreshold(uint256 _threshold) external;\\n\\n function netOusdMintedForStrategy() external view returns (int256);\\n\\n function setDripper(address _dripper) external;\\n\\n function dripper() external view returns (address);\\n\\n function weth() external view returns (address);\\n\\n function cacheWETHAssetIndex() external;\\n\\n function wethAssetIndex() external view returns (uint256);\\n\\n function initialize(address, address) external;\\n\\n function setAdminImpl(address) external;\\n\\n function removeAsset(address _asset) external;\\n\\n // These are OETH specific functions\\n function addWithdrawalQueueLiquidity() external;\\n\\n function requestWithdrawal(uint256 _amount)\\n external\\n returns (uint256 requestId, uint256 queued);\\n\\n function claimWithdrawal(uint256 requestId)\\n external\\n returns (uint256 amount);\\n\\n function claimWithdrawals(uint256[] memory requestIds)\\n external\\n returns (uint256[] memory amounts, uint256 totalAmount);\\n\\n function withdrawalQueueMetadata()\\n external\\n view\\n returns (VaultStorage.WithdrawalQueueMetadata memory);\\n\\n function withdrawalRequests(uint256 requestId)\\n external\\n view\\n returns (VaultStorage.WithdrawalRequest memory);\\n\\n // OETHb specific functions\\n function addStrategyToMintWhitelist(address strategyAddr) external;\\n\\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\\n\\n function isMintWhitelistedStrategy(address strategyAddr)\\n external\\n view\\n returns (bool);\\n\\n function withdrawalClaimDelay() external view returns (uint256);\\n\\n function setWithdrawalClaimDelay(uint256 newDelay) external;\\n}\\n\",\"keccak256\":\"0x4bf8eb02eb5fc4d46cb792bfd84ccb7970ee6a240157472f1120f32a63813d36\",\"license\":\"MIT\"},\"contracts/token/OUSD.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OUSD Token Contract\\n * @dev ERC20 compatible contract for OUSD\\n * @dev Implements an elastic supply\\n * @author Origin Protocol Inc\\n */\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { SafeCast } from \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\n\\ncontract OUSD is Governable {\\n using SafeCast for int256;\\n using SafeCast for uint256;\\n\\n /// @dev Event triggered when the supply changes\\n /// @param totalSupply Updated token total supply\\n /// @param rebasingCredits Updated token rebasing credits\\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\\n event TotalSupplyUpdatedHighres(\\n uint256 totalSupply,\\n uint256 rebasingCredits,\\n uint256 rebasingCreditsPerToken\\n );\\n /// @dev Event triggered when an account opts in for rebasing\\n /// @param account Address of the account\\n event AccountRebasingEnabled(address account);\\n /// @dev Event triggered when an account opts out of rebasing\\n /// @param account Address of the account\\n event AccountRebasingDisabled(address account);\\n /// @dev Emitted when `value` tokens are moved from one account `from` to\\n /// another `to`.\\n /// @param from Address of the account tokens are moved from\\n /// @param to Address of the account tokens are moved to\\n /// @param value Amount of tokens transferred\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n /// a call to {approve}. `value` is the new allowance.\\n /// @param owner Address of the owner approving allowance\\n /// @param spender Address of the spender allowance is granted to\\n /// @param value Amount of tokens spender can transfer\\n event Approval(\\n address indexed owner,\\n address indexed spender,\\n uint256 value\\n );\\n /// @dev Yield resulting from {changeSupply} that a `source` account would\\n /// receive is directed to `target` account.\\n /// @param source Address of the source forwarding the yield\\n /// @param target Address of the target receiving the yield\\n event YieldDelegated(address source, address target);\\n /// @dev Yield delegation from `source` account to the `target` account is\\n /// suspended.\\n /// @param source Address of the source suspending yield forwarding\\n /// @param target Address of the target no longer receiving yield from `source`\\n /// account\\n event YieldUndelegated(address source, address target);\\n\\n enum RebaseOptions {\\n NotSet,\\n StdNonRebasing,\\n StdRebasing,\\n YieldDelegationSource,\\n YieldDelegationTarget\\n }\\n\\n uint256[154] private _gap; // Slots to align with deployed contract\\n uint256 private constant MAX_SUPPLY = type(uint128).max;\\n /// @dev The amount of tokens in existence\\n uint256 public totalSupply;\\n mapping(address => mapping(address => uint256)) private allowances;\\n /// @dev The vault with privileges to execute {mint}, {burn}\\n /// and {changeSupply}\\n address public vaultAddress;\\n mapping(address => uint256) internal creditBalances;\\n // the 2 storage variables below need trailing underscores to not name collide with public functions\\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\\n uint256 private rebasingCreditsPerToken_;\\n /// @dev The amount of tokens that are not rebasing - receiving yield\\n uint256 public nonRebasingSupply;\\n mapping(address => uint256) internal alternativeCreditsPerToken;\\n /// @dev A map of all addresses and their respective RebaseOptions\\n mapping(address => RebaseOptions) public rebaseState;\\n mapping(address => uint256) private __deprecated_isUpgraded;\\n /// @dev A map of addresses that have yields forwarded to. This is an\\n /// inverse mapping of {yieldFrom}\\n /// Key Account forwarding yield\\n /// Value Account receiving yield\\n mapping(address => address) public yieldTo;\\n /// @dev A map of addresses that are receiving the yield. This is an\\n /// inverse mapping of {yieldTo}\\n /// Key Account receiving yield\\n /// Value Account forwarding yield\\n mapping(address => address) public yieldFrom;\\n\\n uint256 private constant RESOLUTION_INCREASE = 1e9;\\n uint256[34] private __gap; // including below gap totals up to 200\\n\\n /// @dev Initializes the contract and sets necessary variables.\\n /// @param _vaultAddress Address of the vault contract\\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\\n external\\n onlyGovernor\\n {\\n require(_vaultAddress != address(0), \\\"Zero vault address\\\");\\n require(vaultAddress == address(0), \\\"Already initialized\\\");\\n\\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\\n vaultAddress = _vaultAddress;\\n }\\n\\n /// @dev Returns the symbol of the token, a shorter version\\n /// of the name.\\n function symbol() external pure virtual returns (string memory) {\\n return \\\"OUSD\\\";\\n }\\n\\n /// @dev Returns the name of the token.\\n function name() external pure virtual returns (string memory) {\\n return \\\"Origin Dollar\\\";\\n }\\n\\n /// @dev Returns the number of decimals used to get its user representation.\\n function decimals() external pure virtual returns (uint8) {\\n return 18;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault contract\\n */\\n modifier onlyVault() {\\n require(vaultAddress == msg.sender, \\\"Caller is not the Vault\\\");\\n _;\\n }\\n\\n /**\\n * @return High resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\\n return rebasingCreditsPerToken_;\\n }\\n\\n /**\\n * @return Low resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerToken() external view returns (uint256) {\\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @return High resolution total number of rebasing credits\\n */\\n function rebasingCreditsHighres() external view returns (uint256) {\\n return rebasingCredits_;\\n }\\n\\n /**\\n * @return Low resolution total number of rebasing credits\\n */\\n function rebasingCredits() external view returns (uint256) {\\n return rebasingCredits_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @notice Gets the balance of the specified address.\\n * @param _account Address to query the balance of.\\n * @return A uint256 representing the amount of base units owned by the\\n * specified address.\\n */\\n function balanceOf(address _account) public view returns (uint256) {\\n RebaseOptions state = rebaseState[_account];\\n if (state == RebaseOptions.YieldDelegationSource) {\\n // Saves a slot read when transferring to or from a yield delegating source\\n // since we know creditBalances equals the balance.\\n return creditBalances[_account];\\n }\\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\\n _creditsPerToken(_account);\\n if (state == RebaseOptions.YieldDelegationTarget) {\\n // creditBalances of yieldFrom accounts equals token balances\\n return baseBalance - creditBalances[yieldFrom[_account]];\\n }\\n return baseBalance;\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @dev Backwards compatible with old low res credits per token.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256) Credit balance and credits per token of the\\n * address\\n */\\n function creditsBalanceOf(address _account)\\n external\\n view\\n returns (uint256, uint256)\\n {\\n uint256 cpt = _creditsPerToken(_account);\\n if (cpt == 1e27) {\\n // For a period before the resolution upgrade, we created all new\\n // contract accounts at high resolution. Since they are not changing\\n // as a result of this upgrade, we will return their true values\\n return (creditBalances[_account], cpt);\\n } else {\\n return (\\n creditBalances[_account] / RESOLUTION_INCREASE,\\n cpt / RESOLUTION_INCREASE\\n );\\n }\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\\n * address, and isUpgraded\\n */\\n function creditsBalanceOfHighres(address _account)\\n external\\n view\\n returns (\\n uint256,\\n uint256,\\n bool\\n )\\n {\\n return (\\n creditBalances[_account],\\n _creditsPerToken(_account),\\n true // all accounts have their resolution \\\"upgraded\\\"\\n );\\n }\\n\\n // Backwards compatible view\\n function nonRebasingCreditsPerToken(address _account)\\n external\\n view\\n returns (uint256)\\n {\\n return alternativeCreditsPerToken[_account];\\n }\\n\\n /**\\n * @notice Transfer tokens to a specified address.\\n * @param _to the address to transfer to.\\n * @param _value the amount to be transferred.\\n * @return true on success.\\n */\\n function transfer(address _to, uint256 _value) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n\\n _executeTransfer(msg.sender, _to, _value);\\n\\n emit Transfer(msg.sender, _to, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Transfer tokens from one address to another.\\n * @param _from The address you want to send tokens from.\\n * @param _to The address you want to transfer to.\\n * @param _value The amount of tokens to be transferred.\\n * @return true on success.\\n */\\n function transferFrom(\\n address _from,\\n address _to,\\n uint256 _value\\n ) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n uint256 userAllowance = allowances[_from][msg.sender];\\n require(_value <= userAllowance, \\\"Allowance exceeded\\\");\\n\\n unchecked {\\n allowances[_from][msg.sender] = userAllowance - _value;\\n }\\n\\n _executeTransfer(_from, _to, _value);\\n\\n emit Transfer(_from, _to, _value);\\n return true;\\n }\\n\\n function _executeTransfer(\\n address _from,\\n address _to,\\n uint256 _value\\n ) internal {\\n (\\n int256 fromRebasingCreditsDiff,\\n int256 fromNonRebasingSupplyDiff\\n ) = _adjustAccount(_from, -_value.toInt256());\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_to, _value.toInt256());\\n\\n _adjustGlobals(\\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\\n );\\n }\\n\\n function _adjustAccount(address _account, int256 _balanceChange)\\n internal\\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\\n {\\n RebaseOptions state = rebaseState[_account];\\n int256 currentBalance = balanceOf(_account).toInt256();\\n if (currentBalance + _balanceChange < 0) {\\n revert(\\\"Transfer amount exceeds balance\\\");\\n }\\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\\n\\n if (state == RebaseOptions.YieldDelegationSource) {\\n address target = yieldTo[_account];\\n uint256 targetOldBalance = balanceOf(target);\\n uint256 targetNewCredits = _balanceToRebasingCredits(\\n targetOldBalance + newBalance\\n );\\n rebasingCreditsDiff =\\n targetNewCredits.toInt256() -\\n creditBalances[target].toInt256();\\n\\n creditBalances[_account] = newBalance;\\n creditBalances[target] = targetNewCredits;\\n } else if (state == RebaseOptions.YieldDelegationTarget) {\\n uint256 newCredits = _balanceToRebasingCredits(\\n newBalance + creditBalances[yieldFrom[_account]]\\n );\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n } else {\\n _autoMigrate(_account);\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem > 0) {\\n nonRebasingSupplyDiff = _balanceChange;\\n if (alternativeCreditsPerTokenMem != 1e18) {\\n alternativeCreditsPerToken[_account] = 1e18;\\n }\\n creditBalances[_account] = newBalance;\\n } else {\\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n }\\n }\\n }\\n\\n function _adjustGlobals(\\n int256 _rebasingCreditsDiff,\\n int256 _nonRebasingSupplyDiff\\n ) internal {\\n if (_rebasingCreditsDiff != 0) {\\n rebasingCredits_ = (rebasingCredits_.toInt256() +\\n _rebasingCreditsDiff).toUint256();\\n }\\n if (_nonRebasingSupplyDiff != 0) {\\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\\n _nonRebasingSupplyDiff).toUint256();\\n }\\n }\\n\\n /**\\n * @notice Function to check the amount of tokens that _owner has allowed\\n * to `_spender`.\\n * @param _owner The address which owns the funds.\\n * @param _spender The address which will spend the funds.\\n * @return The number of tokens still available for the _spender.\\n */\\n function allowance(address _owner, address _spender)\\n external\\n view\\n returns (uint256)\\n {\\n return allowances[_owner][_spender];\\n }\\n\\n /**\\n * @notice Approve the passed address to spend the specified amount of\\n * tokens on behalf of msg.sender.\\n * @param _spender The address which will spend the funds.\\n * @param _value The amount of tokens to be spent.\\n * @return true on success.\\n */\\n function approve(address _spender, uint256 _value) external returns (bool) {\\n allowances[msg.sender][_spender] = _value;\\n emit Approval(msg.sender, _spender, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Creates `_amount` tokens and assigns them to `_account`,\\n * increasing the total supply.\\n */\\n function mint(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Mint to the zero address\\\");\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, _amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply + _amount;\\n\\n require(totalSupply < MAX_SUPPLY, \\\"Max supply\\\");\\n emit Transfer(address(0), _account, _amount);\\n }\\n\\n /**\\n * @notice Destroys `_amount` tokens from `_account`,\\n * reducing the total supply.\\n */\\n function burn(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Burn from the zero address\\\");\\n if (_amount == 0) {\\n return;\\n }\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, -_amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply - _amount;\\n\\n emit Transfer(_account, address(0), _amount);\\n }\\n\\n /**\\n * @dev Get the credits per token for an account. Returns a fixed amount\\n * if the account is non-rebasing.\\n * @param _account Address of the account.\\n */\\n function _creditsPerToken(address _account)\\n internal\\n view\\n returns (uint256)\\n {\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem != 0) {\\n return alternativeCreditsPerTokenMem;\\n } else {\\n return rebasingCreditsPerToken_;\\n }\\n }\\n\\n /**\\n * @dev Auto migrate contracts to be non rebasing,\\n * unless they have opted into yield.\\n * @param _account Address of the account.\\n */\\n function _autoMigrate(address _account) internal {\\n bool isContract = _account.code.length > 0;\\n // In previous code versions, contracts would not have had their\\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\\n // therefore we check the actual accounting used on the account instead.\\n if (\\n isContract &&\\n rebaseState[_account] == RebaseOptions.NotSet &&\\n alternativeCreditsPerToken[_account] == 0\\n ) {\\n _rebaseOptOut(_account);\\n }\\n }\\n\\n /**\\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\\n * also balance that corresponds to those credits. The latter is important\\n * when adjusting the contract's global nonRebasingSupply to circumvent any\\n * possible rounding errors.\\n *\\n * @param _balance Balance of the account.\\n */\\n function _balanceToRebasingCredits(uint256 _balance)\\n internal\\n view\\n returns (uint256 rebasingCredits)\\n {\\n // Rounds up, because we need to ensure that accounts always have\\n // at least the balance that they should have.\\n // Note this should always be used on an absolute account value,\\n // not on a possibly negative diff, because then the rounding would be wrong.\\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n * @param _account Address of the account.\\n */\\n function governanceRebaseOptIn(address _account) external onlyGovernor {\\n require(_account != address(0), \\\"Zero address not allowed\\\");\\n _rebaseOptIn(_account);\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n */\\n function rebaseOptIn() external {\\n _rebaseOptIn(msg.sender);\\n }\\n\\n function _rebaseOptIn(address _account) internal {\\n uint256 balance = balanceOf(_account);\\n\\n // prettier-ignore\\n require(\\n alternativeCreditsPerToken[_account] > 0 ||\\n // Accounts may explicitly `rebaseOptIn` regardless of\\n // accounting if they have a 0 balance.\\n creditBalances[_account] == 0\\n ,\\n \\\"Account must be non-rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n // prettier-ignore\\n require(\\n state == RebaseOptions.StdNonRebasing ||\\n state == RebaseOptions.NotSet,\\n \\\"Only standard non-rebasing accounts can opt in\\\"\\n );\\n\\n uint256 newCredits = _balanceToRebasingCredits(balance);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdRebasing;\\n alternativeCreditsPerToken[_account] = 0;\\n creditBalances[_account] = newCredits;\\n // Globals\\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\\n\\n emit AccountRebasingEnabled(_account);\\n }\\n\\n /**\\n * @notice The calling account will no longer receive yield\\n */\\n function rebaseOptOut() external {\\n _rebaseOptOut(msg.sender);\\n }\\n\\n function _rebaseOptOut(address _account) internal {\\n require(\\n alternativeCreditsPerToken[_account] == 0,\\n \\\"Account must be rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n require(\\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\\n \\\"Only standard rebasing accounts can opt out\\\"\\n );\\n\\n uint256 oldCredits = creditBalances[_account];\\n uint256 balance = balanceOf(_account);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\\n alternativeCreditsPerToken[_account] = 1e18;\\n creditBalances[_account] = balance;\\n // Globals\\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\\n\\n emit AccountRebasingDisabled(_account);\\n }\\n\\n /**\\n * @notice Distribute yield to users. This changes the exchange rate\\n * between \\\"credits\\\" and OUSD tokens to change rebasing user's balances.\\n * @param _newTotalSupply New total supply of OUSD.\\n */\\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\\n require(totalSupply > 0, \\\"Cannot increase 0 supply\\\");\\n\\n if (totalSupply == _newTotalSupply) {\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n return;\\n }\\n\\n totalSupply = _newTotalSupply > MAX_SUPPLY\\n ? MAX_SUPPLY\\n : _newTotalSupply;\\n\\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\\n // round up in the favour of the protocol\\n rebasingCreditsPerToken_ =\\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\\n rebasingSupply;\\n\\n require(rebasingCreditsPerToken_ > 0, \\\"Invalid change in supply\\\");\\n\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n }\\n\\n /*\\n * @notice Send the yield from one account to another account.\\n * Each account keeps its own balances.\\n */\\n function delegateYield(address _from, address _to) external onlyGovernor {\\n require(_from != address(0), \\\"Zero from address not allowed\\\");\\n require(_to != address(0), \\\"Zero to address not allowed\\\");\\n\\n require(_from != _to, \\\"Cannot delegate to self\\\");\\n require(\\n yieldFrom[_to] == address(0) &&\\n yieldTo[_to] == address(0) &&\\n yieldFrom[_from] == address(0) &&\\n yieldTo[_from] == address(0),\\n \\\"Blocked by existing yield delegation\\\"\\n );\\n RebaseOptions stateFrom = rebaseState[_from];\\n RebaseOptions stateTo = rebaseState[_to];\\n\\n require(\\n stateFrom == RebaseOptions.NotSet ||\\n stateFrom == RebaseOptions.StdNonRebasing ||\\n stateFrom == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState from\\\"\\n );\\n\\n require(\\n stateTo == RebaseOptions.NotSet ||\\n stateTo == RebaseOptions.StdNonRebasing ||\\n stateTo == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState to\\\"\\n );\\n\\n if (alternativeCreditsPerToken[_from] == 0) {\\n _rebaseOptOut(_from);\\n }\\n if (alternativeCreditsPerToken[_to] > 0) {\\n _rebaseOptIn(_to);\\n }\\n\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(_to);\\n uint256 oldToCredits = creditBalances[_to];\\n uint256 newToCredits = _balanceToRebasingCredits(\\n fromBalance + toBalance\\n );\\n\\n // Set up the bidirectional links\\n yieldTo[_from] = _to;\\n yieldFrom[_to] = _from;\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\\n alternativeCreditsPerToken[_from] = 1e18;\\n creditBalances[_from] = fromBalance;\\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\\n creditBalances[_to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\\n emit YieldDelegated(_from, _to);\\n }\\n\\n /*\\n * @notice Stop sending the yield from one account to another account.\\n */\\n function undelegateYield(address _from) external onlyGovernor {\\n // Require a delegation, which will also ensure a valid delegation\\n require(yieldTo[_from] != address(0), \\\"Zero address not allowed\\\");\\n\\n address to = yieldTo[_from];\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(to);\\n uint256 oldToCredits = creditBalances[to];\\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\\n\\n // Remove the bidirectional links\\n yieldFrom[to] = address(0);\\n yieldTo[_from] = address(0);\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\\n creditBalances[_from] = fromBalance;\\n rebaseState[to] = RebaseOptions.StdRebasing;\\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\\n creditBalances[to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, fromBalance.toInt256());\\n emit YieldUndelegated(_from, to);\\n }\\n}\\n\",\"keccak256\":\"0x5741d16e48d6031ec92c1e189ea023c78a34956b6bc9642ea9dfc9a6dafe49ca\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Helpers.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\nimport { IBasicToken } from \\\"../interfaces/IBasicToken.sol\\\";\\n\\nlibrary Helpers {\\n /**\\n * @notice Fetch the `symbol()` from an ERC20 token\\n * @dev Grabs the `symbol()` from a contract\\n * @param _token Address of the ERC20 token\\n * @return string Symbol of the ERC20 token\\n */\\n function getSymbol(address _token) internal view returns (string memory) {\\n string memory symbol = IBasicToken(_token).symbol();\\n return symbol;\\n }\\n\\n /**\\n * @notice Fetch the `decimals()` from an ERC20 token\\n * @dev Grabs the `decimals()` from a contract and fails if\\n * the decimal value does not live within a certain range\\n * @param _token Address of the ERC20 token\\n * @return uint256 Decimals of the ERC20 token\\n */\\n function getDecimals(address _token) internal view returns (uint256) {\\n uint256 decimals = IBasicToken(_token).decimals();\\n require(\\n decimals >= 4 && decimals <= 18,\\n \\\"Token must have sufficient decimal places\\\"\\n );\\n\\n return decimals;\\n }\\n}\\n\",\"keccak256\":\"0x108b7a69e0140da0072ca18f90a03a3340574400f81aa6076cd2cccdf13699c2\",\"license\":\"MIT\"},\"contracts/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base contract any contracts that need to initialize state after deployment.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n */\\n bool private initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private initializing;\\n\\n /**\\n * @dev Modifier to protect an initializer function from being invoked twice.\\n */\\n modifier initializer() {\\n require(\\n initializing || !initialized,\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n\\n bool isTopLevelCall = !initializing;\\n if (isTopLevelCall) {\\n initializing = true;\\n initialized = true;\\n }\\n\\n _;\\n\\n if (isTopLevelCall) {\\n initializing = false;\\n }\\n }\\n\\n uint256[50] private ______gap;\\n}\\n\",\"keccak256\":\"0xaadbcc138114afed4af4f353c2ced2916e6ee14be91434789187f192caf0d786\",\"license\":\"MIT\"},\"contracts/vault/VaultStorage.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OToken VaultStorage contract\\n * @notice The VaultStorage contract defines the storage for the Vault contracts\\n * @author Origin Protocol Inc\\n */\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { Address } from \\\"@openzeppelin/contracts/utils/Address.sol\\\";\\n\\nimport { IStrategy } from \\\"../interfaces/IStrategy.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { OUSD } from \\\"../token/OUSD.sol\\\";\\nimport { Initializable } from \\\"../utils/Initializable.sol\\\";\\nimport \\\"../utils/Helpers.sol\\\";\\n\\ncontract VaultStorage is Initializable, Governable {\\n using SafeERC20 for IERC20;\\n\\n event AssetSupported(address _asset);\\n event AssetRemoved(address _asset);\\n event AssetDefaultStrategyUpdated(address _asset, address _strategy);\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event OusdMetaStrategyUpdated(address _ousdMetaStrategy);\\n event RedeemFeeUpdated(uint256 _redeemFeeBps);\\n event PriceProviderUpdated(address _priceProvider);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event NetOusdMintForStrategyThresholdChanged(uint256 _threshold);\\n event SwapperChanged(address _address);\\n event SwapAllowedUndervalueChanged(uint256 _basis);\\n event SwapSlippageChanged(address _asset, uint256 _basis);\\n event Swapped(\\n address indexed _fromAsset,\\n address indexed _toAsset,\\n uint256 _fromAssetAmount,\\n uint256 _toAssetAmount\\n );\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event DripperChanged(address indexed _dripper);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Assets supported by the Vault, i.e. Stablecoins\\n enum UnitConversion {\\n DECIMALS,\\n GETEXCHANGERATE\\n }\\n // Changed to fit into a single storage slot so the decimals needs to be recached\\n struct Asset {\\n // Note: OETHVaultCore doesn't use `isSupported` when minting,\\n // redeeming or checking balance of assets.\\n bool isSupported;\\n UnitConversion unitConversion;\\n uint8 decimals;\\n // Max allowed slippage from the Oracle price when swapping collateral assets in basis points.\\n // For example 40 == 0.4% slippage\\n uint16 allowedOracleSlippageBps;\\n }\\n\\n /// @dev mapping of supported vault assets to their configuration\\n // slither-disable-next-line uninitialized-state\\n mapping(address => Asset) internal assets;\\n /// @dev list of all assets supported by the vault.\\n // slither-disable-next-line uninitialized-state\\n address[] internal allAssets;\\n\\n // Strategies approved for use by the Vault\\n struct Strategy {\\n bool isSupported;\\n uint256 _deprecated; // Deprecated storage slot\\n }\\n /// @dev mapping of strategy contracts to their configuration\\n // slither-disable-next-line uninitialized-state\\n mapping(address => Strategy) internal strategies;\\n /// @dev list of all vault strategies\\n address[] internal allStrategies;\\n\\n /// @notice Address of the Oracle price provider contract\\n // slither-disable-next-line uninitialized-state\\n address public priceProvider;\\n /// @notice pause rebasing if true\\n bool public rebasePaused = false;\\n /// @notice pause operations that change the OToken supply.\\n /// eg mint, redeem, allocate, mint/burn for strategy\\n bool public capitalPaused = true;\\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\\n uint256 public redeemFeeBps;\\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\\n uint256 public vaultBuffer;\\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\\n uint256 public autoAllocateThreshold;\\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\\n uint256 public rebaseThreshold;\\n\\n /// @dev Address of the OToken token. eg OUSD or OETH.\\n // slither-disable-next-line uninitialized-state\\n OUSD internal oUSD;\\n\\n //keccak256(\\\"OUSD.vault.governor.admin.impl\\\");\\n bytes32 constant adminImplPosition =\\n 0xa2bd3d3cf188a41358c8b401076eb59066b09dec5775650c0de4c55187d17bd9;\\n\\n // Address of the contract responsible for post rebase syncs with AMMs\\n address private _deprecated_rebaseHooksAddr = address(0);\\n\\n // Deprecated: Address of Uniswap\\n // slither-disable-next-line constable-states\\n address private _deprecated_uniswapAddr = address(0);\\n\\n /// @notice Address of the Strategist\\n address public strategistAddr = address(0);\\n\\n /// @notice Mapping of asset address to the Strategy that they should automatically\\n // be allocated to\\n // slither-disable-next-line uninitialized-state\\n mapping(address => address) public assetDefaultStrategies;\\n\\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\\n // slither-disable-next-line uninitialized-state\\n uint256 public maxSupplyDiff;\\n\\n /// @notice Trustee contract that can collect a percentage of yield\\n address public trusteeAddress;\\n\\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\\n uint256 public trusteeFeeBps;\\n\\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\\n address[] private _deprecated_swapTokens;\\n\\n uint256 constant MINT_MINIMUM_UNIT_PRICE = 0.998e18;\\n\\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\\n\\n // slither-disable-start constable-states\\n // slither-disable-next-line uninitialized-state\\n address public ousdMetaStrategy;\\n\\n /// @notice How much OTokens are currently minted by the strategy\\n // slither-disable-next-line uninitialized-state\\n int256 public netOusdMintedForStrategy;\\n\\n /// @notice How much net total OTokens are allowed to be minted by all strategies\\n // slither-disable-next-line uninitialized-state\\n uint256 public netOusdMintForStrategyThreshold;\\n\\n // slither-disable-end constable-states\\n\\n uint256 constant MIN_UNIT_PRICE_DRIFT = 0.7e18;\\n uint256 constant MAX_UNIT_PRICE_DRIFT = 1.3e18;\\n\\n /// @notice Collateral swap configuration.\\n /// @dev is packed into a single storage slot to save gas.\\n struct SwapConfig {\\n // Contract that swaps the vault's collateral assets\\n address swapper;\\n // Max allowed percentage the total value can drop below the total supply in basis points.\\n // For example 100 == 1%\\n uint16 allowedUndervalueBps;\\n }\\n SwapConfig internal swapConfig = SwapConfig(address(0), 0);\\n\\n // List of strategies that can mint oTokens directly\\n // Used in OETHBaseVaultCore\\n // slither-disable-next-line uninitialized-state\\n mapping(address => bool) public isMintWhitelistedStrategy;\\n\\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\\n // slither-disable-start constable-states\\n // slither-disable-next-line uninitialized-state\\n address public dripper;\\n // slither-disable-end constable-states\\n\\n /// Withdrawal Queue Storage /////\\n\\n struct WithdrawalQueueMetadata {\\n // cumulative total of all withdrawal requests included the ones that have already been claimed\\n uint128 queued;\\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\\n uint128 claimable;\\n // total of all the requests that have been claimed\\n uint128 claimed;\\n // index of the next withdrawal request starting at 0\\n uint128 nextWithdrawalIndex;\\n }\\n\\n /// @notice Global metadata for the withdrawal queue including:\\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\\n /// claimed - total of all the requests that have been claimed\\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\\n // slither-disable-next-line uninitialized-state\\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\\n\\n struct WithdrawalRequest {\\n address withdrawer;\\n bool claimed;\\n uint40 timestamp; // timestamp of the withdrawal request\\n // Amount of oTokens to redeem. eg OETH\\n uint128 amount;\\n // cumulative total of all withdrawal requests including this one.\\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\\n uint128 queued;\\n }\\n\\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\\n\\n /// @notice Sets a minimum delay that is required to elapse between\\n /// requesting async withdrawals and claiming the request.\\n /// When set to 0 async withdrawals are disabled.\\n // slither-disable-start constable-states\\n // slither-disable-next-line uninitialized-state\\n uint256 public withdrawalClaimDelay;\\n // slither-disable-end constable-states\\n\\n // For future use\\n uint256[44] private __gap;\\n\\n /**\\n * @notice set the implementation for the admin, this needs to be in a base class else we cannot set it\\n * @param newImpl address of the implementation\\n */\\n function setAdminImpl(address newImpl) external onlyGovernor {\\n require(\\n Address.isContract(newImpl),\\n \\\"new implementation is not a contract\\\"\\n );\\n bytes32 position = adminImplPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newImpl)\\n }\\n }\\n}\\n\",\"keccak256\":\"0xf5007e2dcad1b8162ad934a3991c35497b32b924449cf052107ecafd5406dbd8\",\"license\":\"MIT\"}},\"version\":1}", - "bytecode": "0x60c060405234801561001057600080fd5b50604051610f4a380380610f4a83398101604081905261002f916100cb565b818161004733600080516020610f2a83398151915255565b600080516020610f2a833981519152546040516001600160a01b03909116906000907fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a908290a36001600160601b0319606092831b8116608052911b1660a052506100fe9050565b80516001600160a01b03811681146100c657600080fd5b919050565b600080604083850312156100de57600080fd5b6100e7836100af565b91506100f5602084016100af565b90509250929050565b60805160601c60a05160601c610dec61013e6000396000818161038b0152818161065c01526107910152600081816104f801526107b30152610dec6000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c8063737962971161007157806373796297146101375780639f678cca1461013f578063bb7a632e1461018d578063c7af335214610196578063d38bfff4146101ae578063e5225381146101c157600080fd5b80630493a0fa146100b95780630921e7ef146100ce5780630c340a24146100e15780631072cbea1461010657806346fcff4c146101195780635d36b1901461012f575b600080fd5b6100cc6100c7366004610c44565b6101c9565b005b6100cc6100dc366004610bc5565b610256565b6100e961030c565b6040516001600160a01b0390911681526020015b60405180910390f35b6100cc610114366004610bf8565b610329565b610121610369565b6040519081526020016100fd565b6100cc610448565b6100cc6104ee565b6001546101659067ffffffffffffffff811690600160401b90046001600160c01b031682565b6040805167ffffffffffffffff90931683526001600160c01b039091166020830152016100fd565b61012160005481565b61019e61056b565b60405190151581526020016100fd565b6100cc6101bc366004610baa565b61059c565b6100cc610640565b6101d161056b565b6101f65760405162461bcd60e51b81526004016101ed90610cc5565b60405180910390fd5b600081116102465760405162461bcd60e51b815260206004820152601960248201527f6475726174696f6e206d757374206265206e6f6e2d7a65726f0000000000000060448201526064016101ed565b6000819055610253610644565b50565b61025e61056b565b61027a5760405162461bcd60e51b81526004016101ed90610cc5565b6040516370a0823160e01b81523060048201526103089082906001600160a01b038516906370a082319060240160206040518083038186803b1580156102bf57600080fd5b505afa1580156102d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102f79190610c5d565b6001600160a01b03851691906107dd565b5050565b6000610324600080516020610d978339815191525490565b905090565b61033161056b565b61034d5760405162461bcd60e51b81526004016101ed90610cc5565b61030861035861030c565b6001600160a01b03841690836107dd565b6040516370a0823160e01b815230600482015260009081906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a082319060240160206040518083038186803b1580156103cd57600080fd5b505afa1580156103e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104059190610c5d565b6040805180820190915260015467ffffffffffffffff81168252600160401b90046001600160c01b0316602082015290915061044290829061082f565b91505090565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b0316146104e35760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b60648201526084016101ed565b6104ec33610881565b565b6104f6610644565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663af14052c6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561055157600080fd5b505af1158015610565573d6000803e3d6000fd5b50505050565b6000610583600080516020610d978339815191525490565b6001600160a01b0316336001600160a01b031614905090565b6105a461056b565b6105c05760405162461bcd60e51b81526004016101ed90610cc5565b6105e8817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b0316610608600080516020610d978339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b6104ec5b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a082319060240160206040518083038186803b1580156106a657600080fd5b505afa1580156106ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106de9190610c5d565b6040805180820190915260015467ffffffffffffffff81168252600160401b90046001600160c01b0316602082015290915060009061071e90839061082f565b9050600061072c8284610d3d565b905060405180604001604052804267ffffffffffffffff168152602001600054836107579190610cfc565b6001600160c01b03908116909152815160209092015116600160401b0267ffffffffffffffff909116176001556107d86001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f0000000000000000000000000000000000000000000000000000000000000000846107dd565b505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526107d8908490610942565b8051600090819061084a9067ffffffffffffffff1642610d3d565b9050600083602001516001600160c01b0316826108679190610d1e565b90508481116108765780610878565b845b95945050505050565b6001600160a01b0381166108d75760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f72206973206164647265737328302900000000000060448201526064016101ed565b806001600160a01b03166108f7600080516020610d978339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a361025381600080516020610d9783398151915255565b6000610997826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610a149092919063ffffffff16565b8051909150156107d857808060200190518101906109b59190610c22565b6107d85760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016101ed565b6060610a238484600085610a2d565b90505b9392505050565b606082471015610a8e5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016101ed565b843b610adc5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101ed565b600080866001600160a01b03168587604051610af89190610c76565b60006040518083038185875af1925050503d8060008114610b35576040519150601f19603f3d011682016040523d82523d6000602084013e610b3a565b606091505b5091509150610b4a828286610b55565b979650505050505050565b60608315610b64575081610a26565b825115610b745782518084602001fd5b8160405162461bcd60e51b81526004016101ed9190610c92565b80356001600160a01b0381168114610ba557600080fd5b919050565b600060208284031215610bbc57600080fd5b610a2682610b8e565b60008060408385031215610bd857600080fd5b610be183610b8e565b9150610bef60208401610b8e565b90509250929050565b60008060408385031215610c0b57600080fd5b610c1483610b8e565b946020939093013593505050565b600060208284031215610c3457600080fd5b81518015158114610a2657600080fd5b600060208284031215610c5657600080fd5b5035919050565b600060208284031215610c6f57600080fd5b5051919050565b60008251610c88818460208701610d54565b9190910192915050565b6020815260008251806020840152610cb1816040850160208701610d54565b601f01601f19169190910160400192915050565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b600082610d1957634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615610d3857610d38610d80565b500290565b600082821015610d4f57610d4f610d80565b500390565b60005b83811015610d6f578181015183820152602001610d57565b838111156105655750506000910152565b634e487b7160e01b600052601160045260246000fdfe7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa264697066735822122034ebfe843435bb5a61f84bffcbc17dcc3078caae536dc60073bab008693ddf5564736f6c634300080700337bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100b45760003560e01c8063737962971161007157806373796297146101375780639f678cca1461013f578063bb7a632e1461018d578063c7af335214610196578063d38bfff4146101ae578063e5225381146101c157600080fd5b80630493a0fa146100b95780630921e7ef146100ce5780630c340a24146100e15780631072cbea1461010657806346fcff4c146101195780635d36b1901461012f575b600080fd5b6100cc6100c7366004610c44565b6101c9565b005b6100cc6100dc366004610bc5565b610256565b6100e961030c565b6040516001600160a01b0390911681526020015b60405180910390f35b6100cc610114366004610bf8565b610329565b610121610369565b6040519081526020016100fd565b6100cc610448565b6100cc6104ee565b6001546101659067ffffffffffffffff811690600160401b90046001600160c01b031682565b6040805167ffffffffffffffff90931683526001600160c01b039091166020830152016100fd565b61012160005481565b61019e61056b565b60405190151581526020016100fd565b6100cc6101bc366004610baa565b61059c565b6100cc610640565b6101d161056b565b6101f65760405162461bcd60e51b81526004016101ed90610cc5565b60405180910390fd5b600081116102465760405162461bcd60e51b815260206004820152601960248201527f6475726174696f6e206d757374206265206e6f6e2d7a65726f0000000000000060448201526064016101ed565b6000819055610253610644565b50565b61025e61056b565b61027a5760405162461bcd60e51b81526004016101ed90610cc5565b6040516370a0823160e01b81523060048201526103089082906001600160a01b038516906370a082319060240160206040518083038186803b1580156102bf57600080fd5b505afa1580156102d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102f79190610c5d565b6001600160a01b03851691906107dd565b5050565b6000610324600080516020610d978339815191525490565b905090565b61033161056b565b61034d5760405162461bcd60e51b81526004016101ed90610cc5565b61030861035861030c565b6001600160a01b03841690836107dd565b6040516370a0823160e01b815230600482015260009081906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a082319060240160206040518083038186803b1580156103cd57600080fd5b505afa1580156103e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104059190610c5d565b6040805180820190915260015467ffffffffffffffff81168252600160401b90046001600160c01b0316602082015290915061044290829061082f565b91505090565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b0316146104e35760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b60648201526084016101ed565b6104ec33610881565b565b6104f6610644565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663af14052c6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561055157600080fd5b505af1158015610565573d6000803e3d6000fd5b50505050565b6000610583600080516020610d978339815191525490565b6001600160a01b0316336001600160a01b031614905090565b6105a461056b565b6105c05760405162461bcd60e51b81526004016101ed90610cc5565b6105e8817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b0316610608600080516020610d978339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b6104ec5b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a082319060240160206040518083038186803b1580156106a657600080fd5b505afa1580156106ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106de9190610c5d565b6040805180820190915260015467ffffffffffffffff81168252600160401b90046001600160c01b0316602082015290915060009061071e90839061082f565b9050600061072c8284610d3d565b905060405180604001604052804267ffffffffffffffff168152602001600054836107579190610cfc565b6001600160c01b03908116909152815160209092015116600160401b0267ffffffffffffffff909116176001556107d86001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f0000000000000000000000000000000000000000000000000000000000000000846107dd565b505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526107d8908490610942565b8051600090819061084a9067ffffffffffffffff1642610d3d565b9050600083602001516001600160c01b0316826108679190610d1e565b90508481116108765780610878565b845b95945050505050565b6001600160a01b0381166108d75760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f72206973206164647265737328302900000000000060448201526064016101ed565b806001600160a01b03166108f7600080516020610d978339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a361025381600080516020610d9783398151915255565b6000610997826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610a149092919063ffffffff16565b8051909150156107d857808060200190518101906109b59190610c22565b6107d85760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016101ed565b6060610a238484600085610a2d565b90505b9392505050565b606082471015610a8e5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016101ed565b843b610adc5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101ed565b600080866001600160a01b03168587604051610af89190610c76565b60006040518083038185875af1925050503d8060008114610b35576040519150601f19603f3d011682016040523d82523d6000602084013e610b3a565b606091505b5091509150610b4a828286610b55565b979650505050505050565b60608315610b64575081610a26565b825115610b745782518084602001fd5b8160405162461bcd60e51b81526004016101ed9190610c92565b80356001600160a01b0381168114610ba557600080fd5b919050565b600060208284031215610bbc57600080fd5b610a2682610b8e565b60008060408385031215610bd857600080fd5b610be183610b8e565b9150610bef60208401610b8e565b90509250929050565b60008060408385031215610c0b57600080fd5b610c1483610b8e565b946020939093013593505050565b600060208284031215610c3457600080fd5b81518015158114610a2657600080fd5b600060208284031215610c5657600080fd5b5035919050565b600060208284031215610c6f57600080fd5b5051919050565b60008251610c88818460208701610d54565b9190910192915050565b6020815260008251806020840152610cb1816040850160208701610d54565b601f01601f19169190910160400192915050565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b600082610d1957634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615610d3857610d38610d80565b500290565b600082821015610d4f57610d4f610d80565b500390565b60005b83811015610d6f578181015183820152602001610d57565b838111156105655750506000910152565b634e487b7160e01b600052601160045260246000fdfe7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa264697066735822122034ebfe843435bb5a61f84bffcbc17dcc3078caae536dc60073bab008693ddf5564736f6c63430008070033", - "libraries": {}, - "devdoc": { - "author": "Origin Protocol Inc", - "kind": "dev", - "methods": { - "availableFunds()": { - "returns": { - "_0": "The amount that would be sent if a collect was called" - } - }, - "setDripDuration(uint256)": { - "details": "Change the drip duration. Governor only.", - "params": { - "_durationSeconds": "the number of seconds to drip out the entire balance over if no collects were called during that time." - } - }, - "transferAllToken(address,address)": { - "details": "Transfer out all ERC20 held by the contract. Governor only.", - "params": { - "_asset": "ERC20 token address" - } - }, - "transferGovernance(address)": { - "params": { - "_newGovernor": "Address of the new Governor" - } - }, - "transferToken(address,uint256)": { - "details": "Transfer out ERC20 tokens held by the contract. Governor only.", - "params": { - "_amount": "amount to transfer", - "_asset": "ERC20 token address" - } - } - }, - "title": "OETH Dripper Contract", - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": { - "claimGovernance()": { - "notice": "Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor." - }, - "collect()": { - "notice": "Collect all dripped funds and send to vault. Recalculate new drip rate." - }, - "collectAndRebase()": { - "notice": "Collect all dripped funds, send to vault, recalculate new drip rate, and rebase OUSD." - }, - "governor()": { - "notice": "Returns the address of the current Governor." - }, - "isGovernor()": { - "notice": "Returns true if the caller is the current Governor." - }, - "transferGovernance(address)": { - "notice": "Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete" - } - }, - "version": 1 - }, - "storageLayout": { - "storage": [ - { - "astId": 10477, - "contract": "contracts/harvest/OETHDripper.sol:OETHDripper", - "label": "dripDuration", - "offset": 0, - "slot": "0", - "type": "t_uint256" - }, - { - "astId": 10480, - "contract": "contracts/harvest/OETHDripper.sol:OETHDripper", - "label": "drip", - "offset": 0, - "slot": "1", - "type": "t_struct(Drip)10471_storage" - } - ], - "types": { - "t_struct(Drip)10471_storage": { - "encoding": "inplace", - "label": "struct Dripper.Drip", - "members": [ - { - "astId": 10468, - "contract": "contracts/harvest/OETHDripper.sol:OETHDripper", - "label": "lastCollect", - "offset": 0, - "slot": "0", - "type": "t_uint64" - }, - { - "astId": 10470, - "contract": "contracts/harvest/OETHDripper.sol:OETHDripper", - "label": "perSecond", - "offset": 8, - "slot": "0", - "type": "t_uint192" - } - ], - "numberOfBytes": "32" - }, - "t_uint192": { - "encoding": "inplace", - "label": "uint192", - "numberOfBytes": "24" - }, - "t_uint256": { - "encoding": "inplace", - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint64": { - "encoding": "inplace", - "label": "uint64", - "numberOfBytes": "8" - } - } - } -} \ No newline at end of file diff --git a/contracts/deployments/mainnet/OETHDripperProxy.json b/contracts/deployments/mainnet/OETHDripperProxy.json deleted file mode 100644 index e8cd26d434..0000000000 --- a/contracts/deployments/mainnet/OETHDripperProxy.json +++ /dev/null @@ -1,284 +0,0 @@ -{ - "address": "0xc0F42F73b8f01849a2DD99753524d4ba14317EB3", - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousGovernor", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newGovernor", - "type": "address" - } - ], - "name": "GovernorshipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousGovernor", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newGovernor", - "type": "address" - } - ], - "name": "PendingGovernorshipTransfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "implementation", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, - { - "stateMutability": "payable", - "type": "fallback" - }, - { - "inputs": [], - "name": "admin", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "claimGovernance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "governor", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "implementation", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_logic", - "type": "address" - }, - { - "internalType": "address", - "name": "_initGovernor", - "type": "address" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "isGovernor", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newGovernor", - "type": "address" - } - ], - "name": "transferGovernance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - } - ], - "name": "upgradeTo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "upgradeToAndCall", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } - ], - "transactionHash": "0x8e4217c5883891816b9035100b0b1342492f8e618029bf022bdc85bf9aa330f2", - "receipt": { - "to": null, - "from": "0xFD9E6005187F448957a0972a7d0C0A6dA2911236", - "contractAddress": "0xc0F42F73b8f01849a2DD99753524d4ba14317EB3", - "transactionIndex": 69, - "gasUsed": "600505", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000004000000010000000000000002000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000020000000000040000000000000000000000000000000000000000000000000000000", - "blockHash": "0xd95d69a333a712b2c7df35a958f2ddad6606cf00f1cf889bd364f38eef3747fc", - "transactionHash": "0x8e4217c5883891816b9035100b0b1342492f8e618029bf022bdc85bf9aa330f2", - "logs": [ - { - "transactionIndex": 69, - "blockNumber": 17067704, - "transactionHash": "0x8e4217c5883891816b9035100b0b1342492f8e618029bf022bdc85bf9aa330f2", - "address": "0xc0F42F73b8f01849a2DD99753524d4ba14317EB3", - "topics": [ - "0xc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x000000000000000000000000fd9e6005187f448957a0972a7d0c0a6da2911236" - ], - "data": "0x", - "logIndex": 132, - "blockHash": "0xd95d69a333a712b2c7df35a958f2ddad6606cf00f1cf889bd364f38eef3747fc" - } - ], - "blockNumber": 17067704, - "cumulativeGasUsed": "5383677", - "status": 1, - "byzantium": true - }, - "args": [], - "solcInputHash": "8564b351f4bb5da3f43a5b9c5739eec4", - "metadata": "{\"compiler\":{\"version\":\"0.8.7+commit.e28d00a7\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"GovernorshipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"PendingGovernorshipTransfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"governor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_initGovernor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isGovernor\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newGovernor\",\"type\":\"address\"}],\"name\":\"transferGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"admin()\":{\"returns\":{\"_0\":\"The address of the proxy admin/it's also the governor.\"}},\"claimGovernance()\":{\"details\":\"Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor.\"},\"governor()\":{\"details\":\"Returns the address of the current Governor.\"},\"implementation()\":{\"returns\":{\"_0\":\"The address of the implementation.\"}},\"initialize(address,address,bytes)\":{\"details\":\"Contract initializer with Governor enforcement\",\"params\":{\"_data\":\"Data to send as msg.data to the implementation to initialize the proxied contract. It should include the signature and the parameters of the function to be called, as described in https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.\",\"_initGovernor\":\"Address of the initial Governor.\",\"_logic\":\"Address of the initial implementation.\"}},\"isGovernor()\":{\"details\":\"Returns true if the caller is the current Governor.\"},\"transferGovernance(address)\":{\"details\":\"Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete\",\"params\":{\"_newGovernor\":\"Address of the new Governor\"}},\"upgradeTo(address)\":{\"details\":\"Upgrade the backing implementation of the proxy. Only the admin can call this function.\",\"params\":{\"newImplementation\":\"Address of the new implementation.\"}},\"upgradeToAndCall(address,bytes)\":{\"details\":\"Upgrade the backing implementation of the proxy and call a function on the new implementation. This is useful to initialize the proxied contract.\",\"params\":{\"data\":\"Data to send as msg.data in the low level call. It should include the signature and the parameters of the function to be called, as described in https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\",\"newImplementation\":\"Address of the new implementation.\"}}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"OETHDripperProxy delegates calls to a OETHDripper implementation\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/proxies/Proxies.sol\":\"OETHDripperProxy\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x51b758a8815ecc9596c66c37d56b1d33883a444631a3f916b9fe65cb863ef7c4\",\"license\":\"MIT\"},\"contracts/governance/Governable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OUSD Governable Contract\\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\\n * from owner to governor and renounce methods removed. Does not use\\n * Context.sol like Ownable.sol does for simplification.\\n * @author Origin Protocol Inc\\n */\\ncontract Governable {\\n // Storage position of the owner and pendingOwner of the contract\\n // keccak256(\\\"OUSD.governor\\\");\\n bytes32 private constant governorPosition =\\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\\n\\n // keccak256(\\\"OUSD.pending.governor\\\");\\n bytes32 private constant pendingGovernorPosition =\\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\\n\\n // keccak256(\\\"OUSD.reentry.status\\\");\\n bytes32 private constant reentryStatusPosition =\\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\\n\\n // See OpenZeppelin ReentrancyGuard implementation\\n uint256 constant _NOT_ENTERED = 1;\\n uint256 constant _ENTERED = 2;\\n\\n event PendingGovernorshipTransfer(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n event GovernorshipTransferred(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial Governor.\\n */\\n constructor() {\\n _setGovernor(msg.sender);\\n emit GovernorshipTransferred(address(0), _governor());\\n }\\n\\n /**\\n * @dev Returns the address of the current Governor.\\n */\\n function governor() public view returns (address) {\\n return _governor();\\n }\\n\\n /**\\n * @dev Returns the address of the current Governor.\\n */\\n function _governor() internal view returns (address governorOut) {\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n governorOut := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Returns the address of the pending Governor.\\n */\\n function _pendingGovernor()\\n internal\\n view\\n returns (address pendingGovernor)\\n {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n pendingGovernor := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the Governor.\\n */\\n modifier onlyGovernor() {\\n require(isGovernor(), \\\"Caller is not the Governor\\\");\\n _;\\n }\\n\\n /**\\n * @dev Returns true if the caller is the current Governor.\\n */\\n function isGovernor() public view returns (bool) {\\n return msg.sender == _governor();\\n }\\n\\n function _setGovernor(address newGovernor) internal {\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and make it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n bytes32 position = reentryStatusPosition;\\n uint256 _reentry_status;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n _reentry_status := sload(position)\\n }\\n\\n // On the first call to nonReentrant, _notEntered will be true\\n require(_reentry_status != _ENTERED, \\\"Reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _ENTERED)\\n }\\n\\n _;\\n\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _NOT_ENTERED)\\n }\\n }\\n\\n function _setPendingGovernor(address newGovernor) internal {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @dev Transfers Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the current Governor. Must be claimed for this to complete\\n * @param _newGovernor Address of the new Governor\\n */\\n function transferGovernance(address _newGovernor) external onlyGovernor {\\n _setPendingGovernor(_newGovernor);\\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\\n }\\n\\n /**\\n * @dev Claim Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the new Governor.\\n */\\n function claimGovernance() external {\\n require(\\n msg.sender == _pendingGovernor(),\\n \\\"Only the pending Governor can complete the claim\\\"\\n );\\n _changeGovernor(msg.sender);\\n }\\n\\n /**\\n * @dev Change Governance of the contract to a new account (`newGovernor`).\\n * @param _newGovernor Address of the new Governor\\n */\\n function _changeGovernor(address _newGovernor) internal {\\n require(_newGovernor != address(0), \\\"New Governor is address(0)\\\");\\n emit GovernorshipTransferred(_governor(), _newGovernor);\\n _setGovernor(_newGovernor);\\n }\\n}\\n\",\"keccak256\":\"0x1b2af4d111ebd49acdbdfb4817b90bff752a453576d4e0b03dd5e5954f236c1b\",\"license\":\"MIT\"},\"contracts/proxies/InitializeGovernedUpgradeabilityProxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\nimport { Address } from \\\"@openzeppelin/contracts/utils/Address.sol\\\";\\n\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\n\\n/**\\n * @title BaseGovernedUpgradeabilityProxy\\n * @dev This contract combines an upgradeability proxy with our governor system.\\n * It is based on an older version of OpenZeppelins BaseUpgradeabilityProxy\\n * with Solidity ^0.8.0.\\n * @author Origin Protocol Inc\\n */\\ncontract InitializeGovernedUpgradeabilityProxy is Governable {\\n /**\\n * @dev Emitted when the implementation is upgraded.\\n * @param implementation Address of the new implementation.\\n */\\n event Upgraded(address indexed implementation);\\n\\n /**\\n * @dev Contract initializer with Governor enforcement\\n * @param _logic Address of the initial implementation.\\n * @param _initGovernor Address of the initial Governor.\\n * @param _data Data to send as msg.data to the implementation to initialize\\n * the proxied contract.\\n * It should include the signature and the parameters of the function to be\\n * called, as described in\\n * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\\n * This parameter is optional, if no data is given the initialization call\\n * to proxied contract will be skipped.\\n */\\n function initialize(\\n address _logic,\\n address _initGovernor,\\n bytes memory _data\\n ) public payable onlyGovernor {\\n require(_implementation() == address(0));\\n assert(\\n IMPLEMENTATION_SLOT ==\\n bytes32(uint256(keccak256(\\\"eip1967.proxy.implementation\\\")) - 1)\\n );\\n _changeGovernor(_initGovernor);\\n _setImplementation(_logic);\\n if (_data.length > 0) {\\n (bool success, ) = _logic.delegatecall(_data);\\n require(success);\\n }\\n }\\n\\n /**\\n * @return The address of the proxy admin/it's also the governor.\\n */\\n function admin() external view returns (address) {\\n return _governor();\\n }\\n\\n /**\\n * @return The address of the implementation.\\n */\\n function implementation() external view returns (address) {\\n return _implementation();\\n }\\n\\n /**\\n * @dev Upgrade the backing implementation of the proxy.\\n * Only the admin can call this function.\\n * @param newImplementation Address of the new implementation.\\n */\\n function upgradeTo(address newImplementation) external onlyGovernor {\\n _upgradeTo(newImplementation);\\n }\\n\\n /**\\n * @dev Upgrade the backing implementation of the proxy and call a function\\n * on the new implementation.\\n * This is useful to initialize the proxied contract.\\n * @param newImplementation Address of the new implementation.\\n * @param data Data to send as msg.data in the low level call.\\n * It should include the signature and the parameters of the function to be called, as described in\\n * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\\n */\\n function upgradeToAndCall(address newImplementation, bytes calldata data)\\n external\\n payable\\n onlyGovernor\\n {\\n _upgradeTo(newImplementation);\\n (bool success, ) = newImplementation.delegatecall(data);\\n require(success);\\n }\\n\\n /**\\n * @dev Fallback function.\\n * Implemented entirely in `_fallback`.\\n */\\n fallback() external payable {\\n _fallback();\\n }\\n\\n /**\\n * @dev Delegates execution to an implementation contract.\\n * This is a low level function that doesn't return to its internal call site.\\n * It will return to the external caller whatever the implementation returns.\\n * @param _impl Address to delegate.\\n */\\n function _delegate(address _impl) internal {\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n // Copy msg.data. We take full control of memory in this inline assembly\\n // block because it will not return to Solidity code. We overwrite the\\n // Solidity scratch pad at memory position 0.\\n calldatacopy(0, 0, calldatasize())\\n\\n // Call the implementation.\\n // out and outsize are 0 because we don't know the size yet.\\n let result := delegatecall(gas(), _impl, 0, calldatasize(), 0, 0)\\n\\n // Copy the returned data.\\n returndatacopy(0, 0, returndatasize())\\n\\n switch result\\n // delegatecall returns 0 on error.\\n case 0 {\\n revert(0, returndatasize())\\n }\\n default {\\n return(0, returndatasize())\\n }\\n }\\n }\\n\\n /**\\n * @dev Function that is run as the first thing in the fallback function.\\n * Can be redefined in derived contracts to add functionality.\\n * Redefinitions must call super._willFallback().\\n */\\n function _willFallback() internal {}\\n\\n /**\\n * @dev fallback implementation.\\n * Extracted to enable manual triggering.\\n */\\n function _fallback() internal {\\n _willFallback();\\n _delegate(_implementation());\\n }\\n\\n /**\\n * @dev Storage slot with the address of the current implementation.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.implementation\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant IMPLEMENTATION_SLOT =\\n 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n\\n /**\\n * @dev Returns the current implementation.\\n * @return impl Address of the current implementation\\n */\\n function _implementation() internal view returns (address impl) {\\n bytes32 slot = IMPLEMENTATION_SLOT;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n impl := sload(slot)\\n }\\n }\\n\\n /**\\n * @dev Upgrades the proxy to a new implementation.\\n * @param newImplementation Address of the new implementation.\\n */\\n function _upgradeTo(address newImplementation) internal {\\n _setImplementation(newImplementation);\\n emit Upgraded(newImplementation);\\n }\\n\\n /**\\n * @dev Sets the implementation address of the proxy.\\n * @param newImplementation Address of the new implementation.\\n */\\n function _setImplementation(address newImplementation) internal {\\n require(\\n Address.isContract(newImplementation),\\n \\\"Cannot set a proxy implementation to a non-contract address\\\"\\n );\\n\\n bytes32 slot = IMPLEMENTATION_SLOT;\\n\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(slot, newImplementation)\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc5a7922350e0d94b54cf70c0a9971bdf11dfc9aa61cd7b5ed027a6670151d852\",\"license\":\"MIT\"},\"contracts/proxies/Proxies.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\nimport { InitializeGovernedUpgradeabilityProxy } from \\\"./InitializeGovernedUpgradeabilityProxy.sol\\\";\\n\\n/**\\n * @notice OUSDProxy delegates calls to an OUSD implementation\\n */\\ncontract OUSDProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice WrappedOUSDProxy delegates calls to a WrappedOUSD implementation\\n */\\ncontract WrappedOUSDProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice VaultProxy delegates calls to a Vault implementation\\n */\\ncontract VaultProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice CompoundStrategyProxy delegates calls to a CompoundStrategy implementation\\n */\\ncontract CompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice AaveStrategyProxy delegates calls to a AaveStrategy implementation\\n */\\ncontract AaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice ThreePoolStrategyProxy delegates calls to a ThreePoolStrategy implementation\\n */\\ncontract ThreePoolStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice ConvexStrategyProxy delegates calls to a ConvexStrategy implementation\\n */\\ncontract ConvexStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice HarvesterProxy delegates calls to a Harvester implementation\\n */\\ncontract HarvesterProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice DripperProxy delegates calls to a Dripper implementation\\n */\\ncontract DripperProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice MorphoCompoundStrategyProxy delegates calls to a MorphoCompoundStrategy implementation\\n */\\ncontract MorphoCompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice ConvexOUSDMetaStrategyProxy delegates calls to a ConvexOUSDMetaStrategy implementation\\n */\\ncontract ConvexOUSDMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice ConvexLUSDMetaStrategyProxy delegates calls to a ConvexalGeneralizedMetaStrategy implementation\\n */\\ncontract ConvexLUSDMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice MorphoAaveStrategyProxy delegates calls to a MorphoCompoundStrategy implementation\\n */\\ncontract MorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice OETHProxy delegates calls to nowhere for now\\n */\\ncontract OETHProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice WOETHProxy delegates calls to nowhere for now\\n */\\ncontract WOETHProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice OETHVaultProxy delegates calls to a Vault implementation\\n */\\ncontract OETHVaultProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice OETHDripperProxy delegates calls to a OETHDripper implementation\\n */\\ncontract OETHDripperProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\\n/**\\n * @notice FraxETHStrategyProxy delegates calls to a FraxETHStrategy implementation\\n */\\ncontract FraxETHStrategyProxy is InitializeGovernedUpgradeabilityProxy {\\n\\n}\\n\",\"keccak256\":\"0x57d0526966c94a04e60d4fe2f0f15e83e0a6a9055ccf1753e762961bae9af642\",\"license\":\"MIT\"}},\"version\":1}", - "bytecode": "0x608060405234801561001057600080fd5b50610027336000805160206109ed83398151915255565b6000805160206109ed833981519152546040516001600160a01b03909116906000907fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a908290a36109708061007d6000396000f3fe6080604052600436106100865760003560e01c80635d36b190116100595780635d36b1901461010a578063c7af33521461011f578063cf7a1d7714610144578063d38bfff414610157578063f851a4401461009057610086565b80630c340a24146100905780633659cfe6146100c25780634f1ef286146100e25780635c60da1b146100f5575b61008e610177565b005b34801561009c57600080fd5b506100a5610197565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100ce57600080fd5b5061008e6100dd3660046106b0565b6101b4565b61008e6100f03660046107a4565b6101ed565b34801561010157600080fd5b506100a561028a565b34801561011657600080fd5b5061008e6102a2565b34801561012b57600080fd5b50610134610346565b60405190151581526020016100b9565b61008e6101523660046106d2565b610377565b34801561016357600080fd5b5061008e6101723660046106b0565b610445565b6101956101906000805160206108fb8339815191525490565b6104e9565b565b60006101af60008051602061091b8339815191525490565b905090565b6101bc610346565b6101e15760405162461bcd60e51b81526004016101d890610872565b60405180910390fd5b6101ea8161050d565b50565b6101f5610346565b6102115760405162461bcd60e51b81526004016101d890610872565b61021a8361050d565b6000836001600160a01b03168383604051610236929190610827565b600060405180830381855af49150503d8060008114610271576040519150601f19603f3d011682016040523d82523d6000602084013e610276565b606091505b505090508061028457600080fd5b50505050565b60006101af6000805160206108fb8339815191525490565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b03161461033d5760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b60648201526084016101d8565b6101953361054d565b600061035e60008051602061091b8339815191525490565b6001600160a01b0316336001600160a01b031614905090565b61037f610346565b61039b5760405162461bcd60e51b81526004016101d890610872565b60006103b36000805160206108fb8339815191525490565b6001600160a01b0316146103c657600080fd5b6103f160017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd6108a9565b6000805160206108fb8339815191521461040d5761040d6108ce565b6104168261054d565b61041f8361060e565b805115610440576000836001600160a01b0316826040516102369190610837565b505050565b61044d610346565b6104695760405162461bcd60e51b81526004016101d890610872565b610491817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b03166104b160008051602061091b8339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b3660008037600080366000845af43d6000803e808015610508573d6000f35b3d6000fd5b6105168161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105a35760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f72206973206164647265737328302900000000000060448201526064016101d8565b806001600160a01b03166105c360008051602061091b8339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a36101ea8160008051602061091b83398151915255565b803b6106825760405162461bcd60e51b815260206004820152603b60248201527f43616e6e6f742073657420612070726f787920696d706c656d656e746174696f60448201527f6e20746f2061206e6f6e2d636f6e74726163742061646472657373000000000060648201526084016101d8565b6000805160206108fb83398151915255565b80356001600160a01b03811681146106ab57600080fd5b919050565b6000602082840312156106c257600080fd5b6106cb82610694565b9392505050565b6000806000606084860312156106e757600080fd5b6106f084610694565b92506106fe60208501610694565b9150604084013567ffffffffffffffff8082111561071b57600080fd5b818601915086601f83011261072f57600080fd5b813581811115610741576107416108e4565b604051601f8201601f19908116603f01168101908382118183101715610769576107696108e4565b8160405282815289602084870101111561078257600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b6000806000604084860312156107b957600080fd5b6107c284610694565b9250602084013567ffffffffffffffff808211156107df57600080fd5b818601915086601f8301126107f357600080fd5b81358181111561080257600080fd5b87602082850101111561081457600080fd5b6020830194508093505050509250925092565b8183823760009101908152919050565b6000825160005b81811015610858576020818601810151858301520161083e565b81811115610867576000828501525b509190910192915050565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b6000828210156108c957634e487b7160e01b600052601160045260246000fd5b500390565b634e487b7160e01b600052600160045260246000fd5b634e487b7160e01b600052604160045260246000fdfe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa264697066735822122081694d3c77de5fe7cb1ff5c5211f05fefa37d7abb916a77671dd14f803d50a3564736f6c634300080700337bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a", - "deployedBytecode": "0x6080604052600436106100865760003560e01c80635d36b190116100595780635d36b1901461010a578063c7af33521461011f578063cf7a1d7714610144578063d38bfff414610157578063f851a4401461009057610086565b80630c340a24146100905780633659cfe6146100c25780634f1ef286146100e25780635c60da1b146100f5575b61008e610177565b005b34801561009c57600080fd5b506100a5610197565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100ce57600080fd5b5061008e6100dd3660046106b0565b6101b4565b61008e6100f03660046107a4565b6101ed565b34801561010157600080fd5b506100a561028a565b34801561011657600080fd5b5061008e6102a2565b34801561012b57600080fd5b50610134610346565b60405190151581526020016100b9565b61008e6101523660046106d2565b610377565b34801561016357600080fd5b5061008e6101723660046106b0565b610445565b6101956101906000805160206108fb8339815191525490565b6104e9565b565b60006101af60008051602061091b8339815191525490565b905090565b6101bc610346565b6101e15760405162461bcd60e51b81526004016101d890610872565b60405180910390fd5b6101ea8161050d565b50565b6101f5610346565b6102115760405162461bcd60e51b81526004016101d890610872565b61021a8361050d565b6000836001600160a01b03168383604051610236929190610827565b600060405180830381855af49150503d8060008114610271576040519150601f19603f3d011682016040523d82523d6000602084013e610276565b606091505b505090508061028457600080fd5b50505050565b60006101af6000805160206108fb8339815191525490565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b03161461033d5760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b60648201526084016101d8565b6101953361054d565b600061035e60008051602061091b8339815191525490565b6001600160a01b0316336001600160a01b031614905090565b61037f610346565b61039b5760405162461bcd60e51b81526004016101d890610872565b60006103b36000805160206108fb8339815191525490565b6001600160a01b0316146103c657600080fd5b6103f160017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd6108a9565b6000805160206108fb8339815191521461040d5761040d6108ce565b6104168261054d565b61041f8361060e565b805115610440576000836001600160a01b0316826040516102369190610837565b505050565b61044d610346565b6104695760405162461bcd60e51b81526004016101d890610872565b610491817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b03166104b160008051602061091b8339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b3660008037600080366000845af43d6000803e808015610508573d6000f35b3d6000fd5b6105168161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105a35760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f72206973206164647265737328302900000000000060448201526064016101d8565b806001600160a01b03166105c360008051602061091b8339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a36101ea8160008051602061091b83398151915255565b803b6106825760405162461bcd60e51b815260206004820152603b60248201527f43616e6e6f742073657420612070726f787920696d706c656d656e746174696f60448201527f6e20746f2061206e6f6e2d636f6e74726163742061646472657373000000000060648201526084016101d8565b6000805160206108fb83398151915255565b80356001600160a01b03811681146106ab57600080fd5b919050565b6000602082840312156106c257600080fd5b6106cb82610694565b9392505050565b6000806000606084860312156106e757600080fd5b6106f084610694565b92506106fe60208501610694565b9150604084013567ffffffffffffffff8082111561071b57600080fd5b818601915086601f83011261072f57600080fd5b813581811115610741576107416108e4565b604051601f8201601f19908116603f01168101908382118183101715610769576107696108e4565b8160405282815289602084870101111561078257600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b6000806000604084860312156107b957600080fd5b6107c284610694565b9250602084013567ffffffffffffffff808211156107df57600080fd5b818601915086601f8301126107f357600080fd5b81358181111561080257600080fd5b87602082850101111561081457600080fd5b6020830194508093505050509250925092565b8183823760009101908152919050565b6000825160005b81811015610858576020818601810151858301520161083e565b81811115610867576000828501525b509190910192915050565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b6000828210156108c957634e487b7160e01b600052601160045260246000fd5b500390565b634e487b7160e01b600052600160045260246000fd5b634e487b7160e01b600052604160045260246000fdfe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa264697066735822122081694d3c77de5fe7cb1ff5c5211f05fefa37d7abb916a77671dd14f803d50a3564736f6c63430008070033", - "devdoc": { - "kind": "dev", - "methods": { - "admin()": { - "returns": { - "_0": "The address of the proxy admin/it's also the governor." - } - }, - "claimGovernance()": { - "details": "Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor." - }, - "governor()": { - "details": "Returns the address of the current Governor." - }, - "implementation()": { - "returns": { - "_0": "The address of the implementation." - } - }, - "initialize(address,address,bytes)": { - "details": "Contract initializer with Governor enforcement", - "params": { - "_data": "Data to send as msg.data to the implementation to initialize the proxied contract. It should include the signature and the parameters of the function to be called, as described in https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.", - "_initGovernor": "Address of the initial Governor.", - "_logic": "Address of the initial implementation." - } - }, - "isGovernor()": { - "details": "Returns true if the caller is the current Governor." - }, - "transferGovernance(address)": { - "details": "Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete", - "params": { - "_newGovernor": "Address of the new Governor" - } - }, - "upgradeTo(address)": { - "details": "Upgrade the backing implementation of the proxy. Only the admin can call this function.", - "params": { - "newImplementation": "Address of the new implementation." - } - }, - "upgradeToAndCall(address,bytes)": { - "details": "Upgrade the backing implementation of the proxy and call a function on the new implementation. This is useful to initialize the proxied contract.", - "params": { - "data": "Data to send as msg.data in the low level call. It should include the signature and the parameters of the function to be called, as described in https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.", - "newImplementation": "Address of the new implementation." - } - } - }, - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": {}, - "notice": "OETHDripperProxy delegates calls to a OETHDripper implementation", - "version": 1 - }, - "storageLayout": { - "storage": [], - "types": null - } -} \ No newline at end of file diff --git a/contracts/dev.env b/contracts/dev.env index 3548a2a64c..dc283d23de 100644 --- a/contracts/dev.env +++ b/contracts/dev.env @@ -5,6 +5,7 @@ PROVIDER_URL=[SET PROVIDER URL HERE] SONIC_PROVIDER_URL=https://rpc.soniclabs.com PLUME_PROVIDER_URL=https://rpc.plume.org HOODI_PROVIDER_URL=https://rpc.hoodi.ethpandaops.io +HYPEREVM_PROVIDER_URL=https://lb.drpc.org/hyperliquid/.... # Set it to latest block number or leave it empty # BLOCK_NUMBER= @@ -70,4 +71,39 @@ VALIDATOR_KEYS_S3_BUCKET_NAME=[validator-keys-test | validator-keys] VALIDATOR_MASTER_ENCRYPTED_PRIVATE_KEY= # Tenderly access token required to upload newly deployed and verified contracts to Tenderly -TENDERLY_ACCESS_TOKEN= \ No newline at end of file +TENDERLY_ACCESS_TOKEN= + +# Loki Logging +LOKI_URL= +LOKI_USER= +LOKI_API_KEY= + +# Automated actions: Postgres nonce queue lock timeout in seconds (0 = wait forever). +# If lock acquisition exceeds this timeout, the tx send fails and the action exits non-zero. +NONCE_QUEUE_LOCK_TIMEOUT_S=0 + +# Automated actions: max wait for tx confirmation while holding nonce lock (seconds). +# 0 = wait forever. +NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S=600 +# Poll interval for receipt checks (seconds). +NONCE_QUEUE_RECEIPT_POLL_S=5 +# Attempt raw rebroadcast cadence for pending tx (seconds). 0 = disabled. +NONCE_QUEUE_REBROADCAST_INTERVAL_S=30 +# Attempt same-nonce fee-bumped replacement cadence (seconds). 0 = disabled. +NONCE_QUEUE_REPLACE_INTERVAL_S=90 +# Maximum number of replacement tx attempts per action run. +NONCE_QUEUE_MAX_REPLACEMENTS=3 +# Fee bump percent applied for replacement transactions. +NONCE_QUEUE_FEE_BUMP_PCT=15 +# Percent headroom added to estimated gas limit for nonce-queued submissions. +# 0 = disabled (use provider estimate / caller-specified gasLimit as-is). +NONCE_QUEUE_GAS_LIMIT_BUFFER_PCT=0 +# Chain-specific max gas caps (gwei) for initial/rebroadcast/replacement. +# Format: NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_. 0 = disabled. +NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_1=0 +NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_8453=0 +NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_146=0 +NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_560048=0 +NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_999=0 +NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_17000=0 +NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_42161=0 diff --git a/contracts/docker-compose.yml b/contracts/docker-compose.yml new file mode 100644 index 0000000000..6527eb38e4 --- /dev/null +++ b/contracts/docker-compose.yml @@ -0,0 +1,58 @@ +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: nonce_queue + POSTGRES_USER: nonce + POSTGRES_PASSWORD: nonce + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U nonce -d nonce_queue"] + interval: 5s + timeout: 3s + retries: 5 + + actions: + build: + context: . + dockerfile: dockerfile-actions + depends_on: + postgres: + condition: service_healthy + ports: + - "8080:8080" + environment: + - DATABASE_URL=${DATABASE_URL:-postgresql://nonce:nonce@postgres:5432/nonce_queue} + - NONCE_QUEUE_LOCK_TIMEOUT_S=${NONCE_QUEUE_LOCK_TIMEOUT_S:-0} + - NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S=${NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S:-600} + - NONCE_QUEUE_RECEIPT_POLL_S=${NONCE_QUEUE_RECEIPT_POLL_S:-5} + - NONCE_QUEUE_REBROADCAST_INTERVAL_S=${NONCE_QUEUE_REBROADCAST_INTERVAL_S:-30} + - NONCE_QUEUE_REPLACE_INTERVAL_S=${NONCE_QUEUE_REPLACE_INTERVAL_S:-90} + - NONCE_QUEUE_MAX_REPLACEMENTS=${NONCE_QUEUE_MAX_REPLACEMENTS:-3} + - NONCE_QUEUE_FEE_BUMP_PCT=${NONCE_QUEUE_FEE_BUMP_PCT:-15} + - NONCE_QUEUE_GAS_LIMIT_BUFFER_PCT=${NONCE_QUEUE_GAS_LIMIT_BUFFER_PCT:-0} + - NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_1=${NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_1:-40} + - NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_8453=${NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_8453:-0} + - NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_146=${NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_146:-0} + - NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_560048=${NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_560048:-0} + - NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_999=${NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_999:-0} + - NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_17000=${NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_17000:-0} + - NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_42161=${NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_42161:-0} + - PROVIDER_URL=${PROVIDER_URL:-} + - SONIC_PROVIDER_URL=${SONIC_PROVIDER_URL:-} + - PLUME_PROVIDER_URL=${PLUME_PROVIDER_URL:-} + - HOODI_PROVIDER_URL=${HOODI_PROVIDER_URL:-} + - BEACON_PROVIDER_URL=${BEACON_PROVIDER_URL:-} + - HARDHAT_NETWORK=${HARDHAT_NETWORK:-mainnet} + - ACTION_API_BEARER_TOKEN=${ACTION_API_BEARER_TOKEN:-test-token} + - LOKI_URL=${LOKI_URL:-} + - LOKI_USER=${LOKI_USER:-} + - LOKI_API_KEY=${LOKI_API_KEY:-} + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-} + - CRON_OUTPUT_PATH=./cron/cronjob + - DEBUG=origin* + +volumes: + pgdata: diff --git a/contracts/dockerfile b/contracts/dockerfile deleted file mode 100644 index c1cc49c2dd..0000000000 --- a/contracts/dockerfile +++ /dev/null @@ -1,65 +0,0 @@ -FROM node:22 - -ENV DEBIAN_FRONTEND=noninteractive \ - DEBUG=origin* \ - DEBUG_HIDE_DATE=true - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - git \ - openssh-client \ - build-essential \ - python3 \ - bsdutils \ - && rm -rf /var/lib/apt/lists/*◄ - -# Preload GitHub host key for SSH-based dependencies. -RUN mkdir -p /root/.ssh \ - && ssh-keyscan -t rsa github.com >> /root/.ssh/known_hosts - -RUN git config --global url."https://github.com/".insteadOf "git@github.com:" - -ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.39/supercronic-linux-amd64 \ - SUPERCRONIC_SHA1SUM=c98bbf82c5f648aaac8708c182cc83046fe48423 \ - SUPERCRONIC=supercronic-linux-amd64 - -RUN curl -fsSLO "$SUPERCRONIC_URL" \ - && echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \ - && chmod +x "$SUPERCRONIC" \ - && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \ - && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic - -WORKDIR /app - -# Enable pnpm via corepack and install dependencies first for better caching. -COPY pnpm-lock.yaml package.json pnpm-workspace.yaml ./ -RUN corepack enable \ - && pnpm install --frozen-lockfile - -# Copy the rest of the contracts workspace. -COPY . . - -RUN pnpm hardhat compile - -ENV PROVIDER_URL="" \ - SONIC_PROVIDER_URL="" \ - PLUME_PROVIDER_URL="" \ - HOODI_PROVIDER_URL="" \ - BEACON_PROVIDER_URL="" \ - DEFENDER_API_KEY="" \ - DEFENDER_API_SECRET="" \ - HARDHAT_NETWORK="" - -# Cron configuration for supercronic. -# Each Hardhat task runs with a 7 minute offset, ensuring sequential execution. -RUN cat <<'EOF' > /etc/cronjob -0 * * * * cd /app && pnpm hardhat snapBalances --network ${HARDHAT_NETWORK:-mainnet} -8 * * * * cd /app && pnpm hardhat verifyBalances --network ${HARDHAT_NETWORK:-mainnet} -10 * * * * cd /app && pnpm hardhat verifyDeposits --network ${HARDHAT_NETWORK:-mainnet} -12 * * * * cd /app && pnpm hardhat autoValidatorDeposits --network ${HARDHAT_NETWORK:-mainnet} -14 * * * * cd /app && pnpm hardhat autoValidatorWithdrawals --network ${HARDHAT_NETWORK:-mainnet} -EOF - -ENTRYPOINT ["supercronic", "/etc/cronjob"] diff --git a/contracts/dockerfile-actions b/contracts/dockerfile-actions new file mode 100644 index 0000000000..8050333809 --- /dev/null +++ b/contracts/dockerfile-actions @@ -0,0 +1,64 @@ +FROM node:20 + +ENV DEBIAN_FRONTEND=noninteractive \ + DEBUG=origin* \ + DEBUG_HIDE_DATE=true + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + git \ + openssh-client \ + build-essential \ + python3 \ + bsdutils \ + && rm -rf /var/lib/apt/lists/* + +# Preload GitHub host key for SSH-based dependencies. +RUN mkdir -p /root/.ssh \ + && ssh-keyscan -t rsa github.com >> /root/.ssh/known_hosts + +RUN git config --global url."https://github.com/".insteadOf "git@github.com:" + +ARG TARGETARCH +ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.39/supercronic-linux-${TARGETARCH} \ + SUPERCRONIC=supercronic-linux-${TARGETARCH} + +# sha1sums from https://github.com/aptible/supercronic/releases/tag/v0.2.39 +COPY <<'EOF' /tmp/supercronic-checksums.txt +c98bbf82c5f648aaac8708c182cc83046fe48423 supercronic-linux-amd64 +5ef4ccc3d43f12d0f6c3763758bc37cc4e5af76e supercronic-linux-arm64 +EOF + +RUN curl -fsSLO "$SUPERCRONIC_URL" \ + && grep "$SUPERCRONIC" /tmp/supercronic-checksums.txt | sha1sum -c - \ + && chmod +x "$SUPERCRONIC" \ + && mv "$SUPERCRONIC" /usr/local/bin/supercronic \ + && rm /tmp/supercronic-checksums.txt + +WORKDIR /app + +# Enable pnpm via corepack and install dependencies first for better caching. +COPY pnpm-lock.yaml package.json pnpm-workspace.yaml ./ +RUN corepack enable \ + && pnpm install --frozen-lockfile + +# Copy the rest of the contracts workspace. +COPY . . + +# Compile contracts on amd64 (production). Skip on arm64 (Mac) where solcjs WASM crashes. +RUN if [ "$TARGETARCH" = "amd64" ]; then pnpm hardhat compile; else echo "Skipping compile on $TARGETARCH"; fi + +ENV PROVIDER_URL="" \ + SONIC_PROVIDER_URL="" \ + PLUME_PROVIDER_URL="" \ + HOODI_PROVIDER_URL="" \ + BEACON_PROVIDER_URL="" \ + DEFENDER_API_KEY="" \ + DEFENDER_API_SECRET="" \ + HARDHAT_NETWORK="" + +RUN chmod +x /app/cron/cron-entrypoint.sh + +ENTRYPOINT ["/app/cron/cron-entrypoint.sh"] diff --git a/contracts/hardhat.config.js b/contracts/hardhat.config.js index da3bbf1f5d..29d4a30f3d 100644 --- a/contracts/hardhat.config.js +++ b/contracts/hardhat.config.js @@ -1,3 +1,18 @@ +require("ts-node").register({ + transpileOnly: true, + // Use inline compilerOptions instead of tsconfig.json to avoid + // interfering with ESM-only packages (e.g. @lodestar/types). + skipProject: true, + compilerOptions: { + module: "CommonJS", + target: "ES2020", + esModuleInterop: true, + allowSyntheticDefaultImports: true, + resolveJsonModule: true, + strict: false, + }, +}); + const ethers = require("ethers"); const { task } = require("hardhat/config"); const { @@ -50,6 +65,19 @@ require("solidity-coverage"); require("@openzeppelin/hardhat-upgrades"); require("./tasks/tasks"); + +// Auto-load TypeScript action files — each self-registers as a hardhat task +const fs = require("fs"); +const path = require("path"); +const actionsDir = path.join(__dirname, "tasks", "actions"); +if (fs.existsSync(actionsDir)) { + for (const file of fs.readdirSync(actionsDir).sort()) { + if (file.endsWith(".ts")) { + require(path.join(actionsDir, file)); + } + } +} + const { accounts } = require("./tasks/account"); const addresses = require("./utils/addresses.js"); diff --git a/contracts/package.json b/contracts/package.json index b57127a4f6..bfc42252bb 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -30,13 +30,16 @@ "node:anvil:sonic": "anvil --fork-url $SONIC_PROVIDER_URL --port 8545 --block-base-fee-per-gas 0 --auto-impersonate --disable-block-gas-limit", "node:anvil:hoodi": "anvil --fork-url $HOODI_PROVIDER_URL --port 8545 --block-base-fee-per-gas 0 --auto-impersonate --disable-block-gas-limit", "node:anvil:hyperevm": "anvil --fork-url $HYPEREVM_PROVIDER_URL --port 8545 --block-base-fee-per-gas 0 --auto-impersonate --disable-block-gas-limit", - "lint": "yarn run lint:js && yarn run lint:sol", + "lint": "yarn run lint:js && yarn run lint:ts && yarn run lint:sol", "lint:js": "eslint \"test/**/*.js\" \"tasks/**/*.js\" \"deploy/**/*.js\"", "lint:sol": "solhint \"contracts/**/*.sol\"", - "prettier": "yarn run prettier:js && yarn run prettier:sol", + "lint:ts": "eslint \"tasks/actions/**/*.ts\" \"tasks/lib/**/*.ts\" \"cron/**/*.ts\"", + "prettier": "yarn run prettier:js && yarn run prettier:ts && yarn run prettier:sol", "prettier:check": "prettier -c \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", "prettier:js": "prettier --write \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", "prettier:sol": "prettier --write --plugin=prettier-plugin-solidity \"contracts/**/*.sol\"", + "prettier:ts": "prettier --write \"tasks/actions/**/*.ts\" \"tasks/lib/**/*.ts\" \"cron/**/*.ts\"", + "typecheck": "tsc --noEmit", "test": "rm -rf deployments/hardhat && IS_TEST=true npx hardhat test", "test:base": "rm -rf deployments/hardhat && UNIT_TESTS_NETWORK=base IS_TEST=true npx hardhat test", "test:sonic": "rm -rf deployments/hardhat && UNIT_TESTS_NETWORK=sonic IS_TEST=true npx hardhat test", @@ -72,9 +75,10 @@ "author": "Origin Protocol Inc ", "license": "MIT", "devDependencies": { - "@aws-sdk/client-kms": "3.598.0", + "@aws-sdk/client-kms": "^3.675.0", "@aws-sdk/client-s3": "3.600.0", "@chainsafe/persistent-merkle-tree": "^1.2.1", + "@lastdotnet/purrikey": "^1.0.0", "@layerzerolabs/oft-evm": "3.1.4", "@nomicfoundation/hardhat-network-helpers": "1.0.9", "@nomicfoundation/hardhat-verify": "2.1.1", @@ -88,6 +92,8 @@ "@rollup/plugin-commonjs": "29.0.0", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "16.0.3 ", + "@typescript-eslint/eslint-plugin": "^8.57.2", + "@typescript-eslint/parser": "^8.57.2", "@uniswap/v3-core": "1.0.1", "@uniswap/v3-periphery": "1.4.3", "axios": "1.4.0", @@ -118,9 +124,14 @@ "solidity-bytes-utils": "0.8.4", "solidity-coverage": "0.8.14", "ssv-keys": "1.1.0", - "ssv-scanner": "github:bloxapp/ssv-scanner#v1.0.5", + "ssv-scanner": "github:ssvlabs/ssv-scanner#v1.0.5", "sync-fetch": "0.5.2", - "uuid": "9.0.1" + "ts-node": "^10.9.2", + "typescript": "^5.7.0", + "uuid": "9.0.1", + "viem": "^2.27.0", + "winston": "^3.19.0", + "winston-loki": "^6.1.4" }, "husky": { "hooks": { @@ -140,7 +151,9 @@ "@lodestar/params": "^1.41.0", "@lodestar/state-transition": "^1.41.0", "@lodestar/types": "^1.41.0", - "@lodestar/utils": "^1.41.0" + "@lodestar/utils": "^1.41.0", + "@types/pg": "^8.20.0", + "pg": "^8.20.0" }, "resolutions": { "@openzeppelin/contracts": "4.4.2" diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index 95c333434f..7fe6016d7a 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -50,34 +50,43 @@ importers: '@lodestar/utils': specifier: ^1.41.0 version: 1.41.0 + '@types/pg': + specifier: ^8.20.0 + version: 8.20.0 + pg: + specifier: ^8.20.0 + version: 8.20.0 devDependencies: '@aws-sdk/client-kms': - specifier: 3.598.0 - version: 3.598.0 + specifier: ^3.675.0 + version: 3.1013.0 '@aws-sdk/client-s3': specifier: 3.600.0 version: 3.600.0 '@chainsafe/persistent-merkle-tree': specifier: ^1.2.1 version: 1.2.1 + '@lastdotnet/purrikey': + specifier: ^1.0.0 + version: 1.0.0(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@layerzerolabs/oft-evm': specifier: 3.1.4 version: 3.1.4(c88efacc9580dc620f57bdff8a2cf4a7) '@nomicfoundation/hardhat-network-helpers': specifier: 1.0.9 - version: 1.0.9(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 1.0.9(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 2.1.1(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@nomiclabs/hardhat-solhint': specifier: 2.0.1 - version: 2.0.1(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 2.0.1(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@4.9.5))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@openzeppelin/contracts': specifier: 4.4.2 version: 4.4.2 @@ -86,7 +95,7 @@ importers: version: 2.7.0(bufferutil@4.1.0)(debug@4.3.4)(utf-8-validate@5.0.10)(web3-core@1.10.4)(web3-utils@1.10.4)(web3@1.10.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@openzeppelin/hardhat-upgrades': specifier: 1.27.0 - version: 1.27.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 1.27.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@rigidity/bls-signatures': specifier: 2.0.5 version: 2.0.5 @@ -99,6 +108,12 @@ importers: '@rollup/plugin-node-resolve': specifier: '16.0.3 ' version: 16.0.3(rollup@4.54.0) + '@typescript-eslint/eslint-plugin': + specifier: ^8.57.2 + version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.57.2 + version: 8.57.2(eslint@8.57.1)(typescript@5.9.3) '@uniswap/v3-core': specifier: 1.0.1 version: 1.0.1 @@ -125,28 +140,28 @@ importers: version: 3.3.0 ethereum-waffle: specifier: 4.0.10 - version: 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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@4.9.5) + version: 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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@5.9.3) ethers: specifier: 5.7.2 version: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) hardhat: specifier: 2.26.2 - version: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + version: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) hardhat-contract-sizer: specifier: 2.10.0 - version: 2.10.0(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 2.10.0(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) hardhat-deploy: specifier: 0.11.30 version: 0.11.30(bufferutil@4.1.0)(utf-8-validate@5.0.10) hardhat-deploy-ethers: specifier: 0.3.0-beta.13 - version: 0.3.0-beta.13(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 0.3.0-beta.13(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) hardhat-gas-reporter: specifier: 2.3.0 - version: 2.3.0(bufferutil@4.1.0)(debug@4.3.4)(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))(typescript@4.9.5)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 2.3.0(bufferutil@4.1.0)(debug@4.3.4)(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) hardhat-tracer: specifier: 3.2.1 - version: 3.2.1(bufferutil@4.1.0)(chai@4.3.7)(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + version: 3.2.1(bufferutil@4.1.0)(chai@4.3.7)(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) husky: specifier: 7.0.4 version: 7.0.4 @@ -176,7 +191,7 @@ importers: version: 0.8.6(debug@4.3.4) solhint: specifier: 3.4.1 - version: 3.4.1(typescript@4.9.5) + version: 3.4.1(typescript@5.9.3) solidifier: specifier: 2.2.3 version: 2.2.3 @@ -185,19 +200,34 @@ importers: version: 0.8.4 solidity-coverage: specifier: 0.8.14 - version: 0.8.14(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + version: 0.8.14(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) ssv-keys: specifier: 1.1.0 version: 1.1.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) ssv-scanner: - specifier: github:bloxapp/ssv-scanner#v1.0.5 - version: git+https://git@github.com:bloxapp/ssv-scanner.git#45068c93b3de84495f6cfad2767787c91d98aa8f(bufferutil@4.1.0)(utf-8-validate@5.0.10) + specifier: github:ssvlabs/ssv-scanner#v1.0.5 + version: https://codeload.github.com/ssvlabs/ssv-scanner/tar.gz/45068c93b3de84495f6cfad2767787c91d98aa8f(bufferutil@4.1.0)(utf-8-validate@5.0.10) sync-fetch: specifier: 0.5.2 version: 0.5.2 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@25.0.3)(typescript@5.9.3) + typescript: + specifier: ^5.7.0 + version: 5.9.3 uuid: specifier: 9.0.1 version: 9.0.1 + viem: + specifier: ^2.27.0 + version: 2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + winston: + specifier: ^3.19.0 + version: 3.19.0 + winston-loki: + specifier: ^6.1.4 + version: 6.1.4 packages: @@ -236,9 +266,9 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-kms@3.598.0': - resolution: {integrity: sha512-rTvtEMeS4p3NlA0oxSgpl4N9sg2fuJ23nbpMU1vIk8fo0+i8/72BfE5Z0vWknv3u1lvgh+umKOv8g0iyOaGPfw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/client-kms@3.1013.0': + resolution: {integrity: sha512-cDrwc6s7RLcRDR990383FdgpvkpjWhTrD1YyNYo1xZMmtgZ3aghAq2HBhs3adlxToXxEWmGCZn4HWcwPi+CjoQ==} + engines: {node: '>=20.0.0'} '@aws-sdk/client-lambda@3.958.0': resolution: {integrity: sha512-gwEqpDkgPLbFfewQkRRgnqn9iCfnd5BUVFUZpUoyq8DxzPmNn/lEVMkBaNCqwIXx07jd46+qd1neWBrH2UYi2Q==} @@ -248,10 +278,6 @@ packages: resolution: {integrity: sha512-iYoKbJTputbf+ubkX6gSK/y/4uJEBRaXZ18jykLdBQ8UJuGrk2gqvV8h7OlGAhToCeysmmMqM0vDWyLt6lP8nw==} engines: {node: '>=16.0.0'} - '@aws-sdk/client-sso-oidc@3.598.0': - resolution: {integrity: sha512-jfdH1pAO9Tt8Nkta/JJLoUnwl7jaRdxToQTJfUtE+o3+0JP5sA4LfC2rBkJSWcU5BdAA+kyOs5Lv776DlN04Vg==} - engines: {node: '>=16.0.0'} - '@aws-sdk/client-sso-oidc@3.600.0': resolution: {integrity: sha512-7+I8RWURGfzvChyNQSyj5/tKrqRbzRl7H+BnTOf/4Vsw1nFOi5ROhlhD4X/Y0QCTacxnaoNcIrqnY7uGGvVRzw==} engines: {node: '>=16.0.0'} @@ -264,10 +290,6 @@ packages: resolution: {integrity: sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sts@3.598.0': - resolution: {integrity: sha512-bXhz/cHL0iB9UH9IFtMaJJf4F8mV+HzncETCRFzZ9SyUMt5rP9j8A7VZknqGYSx/6mI8SsB1XJQkWSbhn6FiSQ==} - engines: {node: '>=16.0.0'} - '@aws-sdk/client-sts@3.600.0': resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} engines: {node: '>=16.0.0'} @@ -280,6 +302,10 @@ packages: resolution: {integrity: sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==} engines: {node: '>=18.0.0'} + '@aws-sdk/core@3.973.22': + resolution: {integrity: sha512-lY6g5L95jBNgOUitUhfV2N/W+i08jHEl3xuLODYSQH5Sf50V+LkVYBSyZRLtv2RyuXZXiV7yQ+acpswK1tlrOA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-env@3.598.0': resolution: {integrity: sha512-vi1khgn7yXzLCcgSIzQrrtd2ilUM0dWodxj3PQ6BLfP0O+q1imO3hG1nq7DVyJtq7rFHs6+9N8G4mYvTkxby2w==} engines: {node: '>=16.0.0'} @@ -288,6 +314,10 @@ packages: resolution: {integrity: sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-env@3.972.20': + resolution: {integrity: sha512-vI0QN96DFx3g9AunfOWF3CS4cMkqFiR/WM/FyP9QHr5rZ2dKPkYwP3tCgAOvGuu9CXI7dC1vU2FVUuZ+tfpNvQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.598.0': resolution: {integrity: sha512-N7cIafi4HVlQvEgvZSo1G4T9qb/JMLGMdBsDCT5XkeJrF0aptQWzTFH0jIdZcLrMYvzPcuEyO3yCBe6cy/ba0g==} engines: {node: '>=16.0.0'} @@ -296,6 +326,10 @@ packages: resolution: {integrity: sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-http@3.972.22': + resolution: {integrity: sha512-aS/81smalpe7XDnuQfOq4LIPuaV2PRKU2aMTrHcqO5BD4HwO5kESOHNcec2AYfBtLtIDqgF6RXisgBnfK/jt0w==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-ini@3.598.0': resolution: {integrity: sha512-/ppcIVUbRwDIwJDoYfp90X3+AuJo2mvE52Y1t2VSrvUovYn6N4v95/vXj6LS8CNDhz2jvEJYmu+0cTMHdhI6eA==} engines: {node: '>=16.0.0'} @@ -306,13 +340,17 @@ packages: resolution: {integrity: sha512-u7twvZa1/6GWmPBZs6DbjlegCoNzNjBsMS/6fvh5quByYrcJr/uLd8YEr7S3UIq4kR/gSnHqcae7y2nL2bqZdg==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-ini@3.972.22': + resolution: {integrity: sha512-rpF8fBT0LllMDp78s62aL2A/8MaccjyJ0ORzqu+ZADeECLSrrCWIeeXsuRam+pxiAMkI1uIyDZJmgLGdadkPXw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-login@3.958.0': resolution: {integrity: sha512-sDwtDnBSszUIbzbOORGh5gmXGl9aK25+BHb4gb1aVlqB+nNL2+IUEJA62+CE55lXSH8qXF90paivjK8tOHTwPA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.598.0': - resolution: {integrity: sha512-sXTlqL5I/awlF9Dg2MQ17SfrEaABVnsj2mf4jF5qQrIRhfbvQOIYdEqdy8Rn1AWlJMz/N450SGzc0XJ5owxxqw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/credential-provider-login@3.972.22': + resolution: {integrity: sha512-u33CO9zeNznlVSg9tWTCRYxaGkqr1ufU6qeClpmzAabXZa8RZxQoVXxL5T53oZJFzQYj+FImORCSsi7H7B77gQ==} + engines: {node: '>=20.0.0'} '@aws-sdk/credential-provider-node@3.600.0': resolution: {integrity: sha512-1pC7MPMYD45J7yFjA90SxpR0yaSvy+yZiq23aXhAPZLYgJBAxHLu0s0mDCk/piWGPh8+UGur5K0bVdx4B1D5hw==} @@ -322,6 +360,10 @@ packages: resolution: {integrity: sha512-vdoZbNG2dt66I7EpN3fKCzi6fp9xjIiwEA/vVVgqO4wXCGw8rKPIdDUus4e13VvTr330uQs2W0UNg/7AgtquEQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-node@3.972.23': + resolution: {integrity: sha512-U8tyLbLOZItuVWTH0ay9gWo4xMqZwqQbg1oMzdU4FQSkTpqXemm4X0uoKBR6llqAStgBp30ziKFJHTA43l4qMw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.598.0': resolution: {integrity: sha512-rM707XbLW8huMk722AgjVyxu2tMZee++fNA8TJVNgs1Ma02Wx6bBrfIvlyK0rCcIRb0WdQYP6fe3Xhiu4e8IBA==} engines: {node: '>=16.0.0'} @@ -330,6 +372,10 @@ packages: resolution: {integrity: sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-process@3.972.20': + resolution: {integrity: sha512-QRfk7GbA4/HDRjhP3QYR6QBr/QKreVoOzvvlRHnOuGgYJkeoPgPY3LAI1kK1ZMgZ4hH9KiGp757/ntol+INAig==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.598.0': resolution: {integrity: sha512-5InwUmrAuqQdOOgxTccRayMMkSmekdLk6s+az9tmikq0QFAHUCtofI+/fllMXSR9iL6JbGYi1940+EUmS4pHJA==} engines: {node: '>=16.0.0'} @@ -338,6 +384,10 @@ packages: resolution: {integrity: sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-sso@3.972.22': + resolution: {integrity: sha512-4vqlSaUbBj4aNPVKfB6yXuIQ2Z2mvLfIGba2OzzF6zUkN437/PGWsxBU2F8QPSFHti6seckvyCXidU3H+R8NvQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.598.0': resolution: {integrity: sha512-GV5GdiMbz5Tz9JO4NJtRoFXjW0GPEujA0j+5J/B723rTN+REHthJu48HdBKouHGhdzkDWkkh1bu52V02Wprw8w==} engines: {node: '>=16.0.0'} @@ -348,6 +398,10 @@ packages: resolution: {integrity: sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.22': + resolution: {integrity: sha512-/wN1CYg2rVLhW8/jLxMWacQrkpaynnL+4j/Z+e6X1PfoE6NiC0BeOw3i0JmtZrKun85wNV5GmspvuWJihfeeUw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-bucket-endpoint@3.598.0': resolution: {integrity: sha512-PM7BcFfGUSkmkT6+LU9TyJiB4S8yI7dfuKQDwK5ZR3P7MKaK4Uj4yyDiv0oe5xvkF6+O2+rShj+eh8YuWkOZ/Q==} engines: {node: '>=16.0.0'} @@ -368,6 +422,10 @@ packages: resolution: {integrity: sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-host-header@3.972.8': + resolution: {integrity: sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-location-constraint@3.598.0': resolution: {integrity: sha512-8oybQxN3F1ISOMULk7JKJz5DuAm5hCUcxMW9noWShbxTJuStNvuHf/WLUzXrf8oSITyYzIHPtf8VPlKR7I3orQ==} engines: {node: '>=16.0.0'} @@ -380,6 +438,10 @@ packages: resolution: {integrity: sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-logger@3.972.8': + resolution: {integrity: sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-recursion-detection@3.598.0': resolution: {integrity: sha512-vjT9BeFY9FeN0f8hm2l6F53tI0N5bUq6RcDkQXKNabXBnQxKptJRad6oP2X5y3FoVfBLOuDkQgiC2940GIPxtQ==} engines: {node: '>=16.0.0'} @@ -388,6 +450,10 @@ packages: resolution: {integrity: sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-recursion-detection@3.972.8': + resolution: {integrity: sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-sdk-s3@3.598.0': resolution: {integrity: sha512-5AGtLAh9wyK6ANPYfaKTqJY1IFJyePIxsEbxa7zS6REheAqyVmgJFaGu3oQ5XlxfGr5Uq59tFTRkyx26G1HkHA==} engines: {node: '>=16.0.0'} @@ -408,10 +474,18 @@ packages: resolution: {integrity: sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-user-agent@3.972.23': + resolution: {integrity: sha512-HQu8QoqGZZTvg0Spl9H39QTsSMFwgu+8yz/QGKndXFLk9FZMiCiIgBCVlTVKMDvVbgqIzD9ig+/HmXsIL2Rb+g==} + engines: {node: '>=20.0.0'} + '@aws-sdk/nested-clients@3.958.0': resolution: {integrity: sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==} engines: {node: '>=18.0.0'} + '@aws-sdk/nested-clients@3.996.12': + resolution: {integrity: sha512-KLdQGJPSm98uLINolQ0Tol8OAbk7g0Y7zplHJ1K83vbMIH13aoCvR6Tho66xueW4l4aZlEgVGLWBnD8ifUMsGQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/region-config-resolver@3.598.0': resolution: {integrity: sha512-oYXhmTokSav4ytmWleCr3rs/1nyvZW/S0tdi6X7u+dLNL5Jee+uMxWGzgOrWK6wrQOzucLVjS4E/wA11Kv2GTw==} engines: {node: '>=16.0.0'} @@ -420,10 +494,18 @@ packages: resolution: {integrity: sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==} engines: {node: '>=18.0.0'} + '@aws-sdk/region-config-resolver@3.972.8': + resolution: {integrity: sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4-multi-region@3.598.0': resolution: {integrity: sha512-1r/EyTrO1gSa1FirnR8V7mabr7gk+l+HkyTI0fcTSr8ucB7gmYyW6WjkY8JCz13VYHFK62usCEDS7yoJoJOzTA==} engines: {node: '>=16.0.0'} + '@aws-sdk/token-providers@3.1013.0': + resolution: {integrity: sha512-IL1c54UvbuERrs9oLm5rvkzMciwhhpn1FL0SlC3XUMoLlFhdBsWJgQKK8O5fsQLxbFVqjbjFx9OBkrn44X9PHw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.598.0': resolution: {integrity: sha512-TKY1EVdHVBnZqpyxyTHdpZpa1tUpb6nxVeRNn1zWG8QB5MvH4ALLd/jR+gtmWDNQbIG4cVuBOZFVL8hIYicKTA==} engines: {node: '>=16.0.0'} @@ -442,6 +524,10 @@ packages: resolution: {integrity: sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==} engines: {node: '>=18.0.0'} + '@aws-sdk/types@3.973.6': + resolution: {integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-arn-parser@3.568.0': resolution: {integrity: sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==} engines: {node: '>=16.0.0'} @@ -454,6 +540,10 @@ packages: resolution: {integrity: sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==} engines: {node: '>=18.0.0'} + '@aws-sdk/util-endpoints@3.996.5': + resolution: {integrity: sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-locate-window@3.957.0': resolution: {integrity: sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw==} engines: {node: '>=18.0.0'} @@ -464,6 +554,9 @@ packages: '@aws-sdk/util-user-agent-browser@3.957.0': resolution: {integrity: sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==} + '@aws-sdk/util-user-agent-browser@3.972.8': + resolution: {integrity: sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==} + '@aws-sdk/util-user-agent-node@3.598.0': resolution: {integrity: sha512-oyWGcOlfTdzkC6SVplyr0AGh54IMrDxbhg5RxJ5P+V4BKfcDoDcZV9xenUk9NsOi9MuUjxMumb9UJGkDhM1m0A==} engines: {node: '>=16.0.0'} @@ -482,6 +575,15 @@ packages: aws-crt: optional: true + '@aws-sdk/util-user-agent-node@3.973.9': + resolution: {integrity: sha512-jeFqqp8KD/P5O+qeKxyGeu7WEVIZFNprnkaDjGmBOjwxYwafCBhpxTgV1TlW6L8e76Vh/siNylNmN/OmSIFBUQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + '@aws-sdk/util-utf8-browser@3.259.0': resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} @@ -493,6 +595,10 @@ packages: resolution: {integrity: sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==} engines: {node: '>=18.0.0'} + '@aws-sdk/xml-builder@3.972.14': + resolution: {integrity: sha512-G/Yd8Bnnyh8QrqLf8jWJbixEnScUFW24e/wOBGYdw1Cl4r80KX/DvHyM2GVZ2vTp7J4gTEr8IXJlTadA8+UfuQ==} + engines: {node: '>=20.0.0'} + '@aws/lambda-invoke-store@0.2.2': resolution: {integrity: sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==} engines: {node: '>=18.0.0'} @@ -756,9 +862,22 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@dabh/diagnostics@2.0.8': resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + '@emnapi/core@1.9.1': + resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@ensdomains/ens@0.4.5': resolution: {integrity: sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw==} deprecated: Please use @ensdomains/ens-contracts @@ -773,6 +892,12 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.2': resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -1099,9 +1224,22 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@lastdotnet/purrikey@1.0.0': + resolution: {integrity: sha512-Y9ycl83T1ZrLSFZYT6V/QMFZjwc4fB+/WjuQygLAVSZAJNAaEtSRm9l4lMKazV/AE30qC/uKzyY9s/zoAtNx8w==} + engines: {node: '>=14.0.0'} + peerDependencies: + ethers: ^5.7.2 + '@layerzerolabs/devtools@0.4.10': resolution: {integrity: sha512-Y9kjUQuyNfm9Vs07+Mk0+KkqHPwHN2cLSzKhe5Tp+52R7d4fI5zsn33IaJsqqGWxSDL1sKq7gFMTdtglTdsA8A==} peerDependencies: @@ -1214,6 +1352,123 @@ packages: '@lodestar/utils@1.41.0': resolution: {integrity: sha512-D/KtN7qlQZe3sNDdSaCyD0bmwEHBmW9PYhROSsM2/JjIABOERH+1zJqj76a22igp3WcvrMCAkEs71mF9oLzWOQ==} + '@napi-rs/snappy-android-arm-eabi@7.3.3': + resolution: {integrity: sha512-d4vUFFzNBvazGfB/KU8MnEax6itTIgRWXodPdZDnWKHy9HwVBndpCiedQDcSNHcZNYV36rx034rpn7SAuTL2NA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/snappy-android-arm64@7.3.3': + resolution: {integrity: sha512-Uh+w18dhzjVl85MGhRnojb7OLlX2ErvMsYIunO/7l3Frvc2zQvfqsWsFJanu2dwqlE2YDooeNP84S+ywgN9sxg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/snappy-darwin-arm64@7.3.3': + resolution: {integrity: sha512-AmJn+6yOu/0V0YNHLKmRUNYkn93iv/1wtPayC7O1OHtfY6YqHQ31/MVeeRBiEYtQW9TwVZxXrDirxSB1PxRdtw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/snappy-darwin-x64@7.3.3': + resolution: {integrity: sha512-biLTXBmPjPmO7HIpv+5BaV9Gy/4+QJSUNJW8Pjx1UlWAVnocPy7um+zbvAWStZssTI5sfn/jOClrAegD4w09UA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/snappy-freebsd-x64@7.3.3': + resolution: {integrity: sha512-E3R3ewm8Mrjm0yL2TC3VgnphDsQaCPixNJqBbGiz3NTshVDhlPlOgPKF0NGYqKiKaDGdD9PKtUgOR4vagUtn7g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/snappy-linux-arm-gnueabihf@7.3.3': + resolution: {integrity: sha512-ZuNgtmk9j0KyT7TfLyEnvZJxOhbkyNR761nk04F0Q4NTHMICP28wQj0xgEsnCHUsEeA9OXrRL4R7waiLn+rOQA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/snappy-linux-arm64-gnu@7.3.3': + resolution: {integrity: sha512-KIzwtq0dAzshzpqZWjg0Q9lUx93iZN7wCCUzCdLYIQ+mvJZKM10VCdn0RcuQze1R3UJTPwpPLXQIVskNMBYyPA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/snappy-linux-arm64-musl@7.3.3': + resolution: {integrity: sha512-AAED4cQS74xPvktsyVmz5sy8vSxG/+3d7Rq2FDBZzj3Fv6v5vux6uZnECPCAqpALCdTtJ61unqpOyqO7hZCt1Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/snappy-linux-ppc64-gnu@7.3.3': + resolution: {integrity: sha512-pofO5eSLg8ZTBwVae4WHHwJxJGZI8NEb4r5Mppvq12J/1/Hq1HecClXmfY3A7bdT2fsS2Td+Q7CI9VdBOj2sbA==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@napi-rs/snappy-linux-riscv64-gnu@7.3.3': + resolution: {integrity: sha512-OiHYdeuwj0TVBXADUmmQDQ4lL1TB+8EwmXnFgOutoDVXHaUl0CJFyXLa6tYUXe+gRY8hs1v7eb0vyE97LKY06Q==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/snappy-linux-s390x-gnu@7.3.3': + resolution: {integrity: sha512-66QdmuV9CTq/S/xifZXlMy3PsZTviAgkqqpZ+7vPCmLtuP+nqhaeupShOFf/sIDsS0gZePazPosPTeTBbhkLHg==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@napi-rs/snappy-linux-x64-gnu@7.3.3': + resolution: {integrity: sha512-g6KURjOxrgb8yXDEZMuIcHkUr/7TKlDwSiydEQtMtP3n4iI4sNjkcE/WNKlR3+t9bZh1pFGAq7NFRBtouQGHpQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/snappy-linux-x64-musl@7.3.3': + resolution: {integrity: sha512-6UvOyczHknpaKjrlKKSlX3rwpOrfJwiMG6qA0NRKJFgbcCAEUxmN9A8JvW4inP46DKdQ0bekdOxwRtAhFiTDfg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/snappy-openharmony-arm64@7.3.3': + resolution: {integrity: sha512-I5mak/5rTprobf7wMCk0vFhClmWOL/QiIJM4XontysnadmP/R9hAcmuFmoMV2GaxC9MblqLA7Z++gy8ou5hJVw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [openharmony] + + '@napi-rs/snappy-wasm32-wasi@7.3.3': + resolution: {integrity: sha512-+EroeygVYo9RksOchjF206frhMkfD2PaIun3yH4Zp5j/Y0oIEgs/+VhAYx/f+zHRylQYUIdLzDRclcoepvlR8Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@napi-rs/snappy-win32-arm64-msvc@7.3.3': + resolution: {integrity: sha512-rxqfntBsCfzgOha/OlG8ld2hs6YSMGhpMUbFjeQLyVDbooY041fRXv3S7yk52DfO6H4QQhLT5+p7cW0mYdhyiQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/snappy-win32-ia32-msvc@7.3.3': + resolution: {integrity: sha512-joRV16DsRtqjGt0CdSpxGCkO0UlHGeTZ/GqvdscoALpRKbikR2Top4C61dxEchmOd3lSYsXutuwWWGg3Nr++WA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/snappy-win32-x64-msvc@7.3.3': + resolution: {integrity: sha512-cEnQwcsdJyOU7HSZODWsHpKuQoSYM4jaqw/hn9pOXYbRN1+02WxYppD3fdMuKN6TOA6YG5KA5PHRNeVilNX86Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@noble/ciphers@1.3.0': resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} @@ -1449,6 +1704,36 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@resolver-engine/core@0.3.3': resolution: {integrity: sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ==} @@ -1685,6 +1970,10 @@ packages: resolution: {integrity: sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==} engines: {node: '>=16.0.0'} + '@smithy/abort-controller@4.2.12': + resolution: {integrity: sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==} + engines: {node: '>=18.0.0'} + '@smithy/abort-controller@4.2.7': resolution: {integrity: sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==} engines: {node: '>=18.0.0'} @@ -1699,6 +1988,10 @@ packages: resolution: {integrity: sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg==} engines: {node: '>=16.0.0'} + '@smithy/config-resolver@4.4.13': + resolution: {integrity: sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==} + engines: {node: '>=18.0.0'} + '@smithy/config-resolver@4.4.5': resolution: {integrity: sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==} engines: {node: '>=18.0.0'} @@ -1711,10 +2004,18 @@ packages: resolution: {integrity: sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==} engines: {node: '>=18.0.0'} + '@smithy/core@3.23.12': + resolution: {integrity: sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@3.2.8': resolution: {integrity: sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw==} engines: {node: '>=16.0.0'} + '@smithy/credential-provider-imds@4.2.12': + resolution: {integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.2.7': resolution: {integrity: sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==} engines: {node: '>=18.0.0'} @@ -1764,6 +2065,10 @@ packages: '@smithy/fetch-http-handler@4.1.3': resolution: {integrity: sha512-6SxNltSncI8s689nvnzZQc/dPXcpHQ34KUj6gR/HBroytKOd/isMG3gJF/zBE1TBmTT18TXyzhg3O3SOOqGEhA==} + '@smithy/fetch-http-handler@5.3.15': + resolution: {integrity: sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==} + engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.3.8': resolution: {integrity: sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==} engines: {node: '>=18.0.0'} @@ -1775,6 +2080,10 @@ packages: resolution: {integrity: sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA==} engines: {node: '>=16.0.0'} + '@smithy/hash-node@4.2.12': + resolution: {integrity: sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==} + engines: {node: '>=18.0.0'} + '@smithy/hash-node@4.2.7': resolution: {integrity: sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==} engines: {node: '>=18.0.0'} @@ -1786,6 +2095,10 @@ packages: '@smithy/invalid-dependency@3.0.11': resolution: {integrity: sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ==} + '@smithy/invalid-dependency@4.2.12': + resolution: {integrity: sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==} + engines: {node: '>=18.0.0'} + '@smithy/invalid-dependency@4.2.7': resolution: {integrity: sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==} engines: {node: '>=18.0.0'} @@ -1802,6 +2115,10 @@ packages: resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} engines: {node: '>=18.0.0'} + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + '@smithy/md5-js@3.0.11': resolution: {integrity: sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ==} @@ -1809,6 +2126,10 @@ packages: resolution: {integrity: sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw==} engines: {node: '>=16.0.0'} + '@smithy/middleware-content-length@4.2.12': + resolution: {integrity: sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-content-length@4.2.7': resolution: {integrity: sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==} engines: {node: '>=18.0.0'} @@ -1821,6 +2142,10 @@ packages: resolution: {integrity: sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==} engines: {node: '>=18.0.0'} + '@smithy/middleware-endpoint@4.4.27': + resolution: {integrity: sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@3.0.34': resolution: {integrity: sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==} engines: {node: '>=16.0.0'} @@ -1829,10 +2154,18 @@ packages: resolution: {integrity: sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==} engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@4.4.44': + resolution: {integrity: sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-serde@3.0.11': resolution: {integrity: sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw==} engines: {node: '>=16.0.0'} + '@smithy/middleware-serde@4.2.15': + resolution: {integrity: sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-serde@4.2.8': resolution: {integrity: sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==} engines: {node: '>=18.0.0'} @@ -1841,6 +2174,10 @@ packages: resolution: {integrity: sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA==} engines: {node: '>=16.0.0'} + '@smithy/middleware-stack@4.2.12': + resolution: {integrity: sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-stack@4.2.7': resolution: {integrity: sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==} engines: {node: '>=18.0.0'} @@ -1849,6 +2186,10 @@ packages: resolution: {integrity: sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ==} engines: {node: '>=16.0.0'} + '@smithy/node-config-provider@4.3.12': + resolution: {integrity: sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==} + engines: {node: '>=18.0.0'} + '@smithy/node-config-provider@4.3.7': resolution: {integrity: sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==} engines: {node: '>=18.0.0'} @@ -1861,10 +2202,18 @@ packages: resolution: {integrity: sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.5.0': + resolution: {integrity: sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@3.1.11': resolution: {integrity: sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A==} engines: {node: '>=16.0.0'} + '@smithy/property-provider@4.2.12': + resolution: {integrity: sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.7': resolution: {integrity: sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==} engines: {node: '>=18.0.0'} @@ -1873,6 +2222,10 @@ packages: resolution: {integrity: sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==} engines: {node: '>=16.0.0'} + '@smithy/protocol-http@5.3.12': + resolution: {integrity: sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==} + engines: {node: '>=18.0.0'} + '@smithy/protocol-http@5.3.7': resolution: {integrity: sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==} engines: {node: '>=18.0.0'} @@ -1881,6 +2234,10 @@ packages: resolution: {integrity: sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg==} engines: {node: '>=16.0.0'} + '@smithy/querystring-builder@4.2.12': + resolution: {integrity: sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-builder@4.2.7': resolution: {integrity: sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==} engines: {node: '>=18.0.0'} @@ -1889,6 +2246,10 @@ packages: resolution: {integrity: sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw==} engines: {node: '>=16.0.0'} + '@smithy/querystring-parser@4.2.12': + resolution: {integrity: sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-parser@4.2.7': resolution: {integrity: sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==} engines: {node: '>=18.0.0'} @@ -1897,6 +2258,10 @@ packages: resolution: {integrity: sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==} engines: {node: '>=16.0.0'} + '@smithy/service-error-classification@4.2.12': + resolution: {integrity: sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==} + engines: {node: '>=18.0.0'} + '@smithy/service-error-classification@4.2.7': resolution: {integrity: sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==} engines: {node: '>=18.0.0'} @@ -1909,10 +2274,18 @@ packages: resolution: {integrity: sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==} engines: {node: '>=18.0.0'} + '@smithy/shared-ini-file-loader@4.4.7': + resolution: {integrity: sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==} + engines: {node: '>=18.0.0'} + '@smithy/signature-v4@3.1.2': resolution: {integrity: sha512-3BcPylEsYtD0esM4Hoyml/+s7WP2LFhcM3J2AGdcL2vx9O60TtfpDOL72gjb4lU8NeRPeKAwR77YNyyGvMbuEA==} engines: {node: '>=16.0.0'} + '@smithy/signature-v4@5.3.12': + resolution: {integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==} + engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.3.7': resolution: {integrity: sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==} engines: {node: '>=18.0.0'} @@ -1925,6 +2298,10 @@ packages: resolution: {integrity: sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==} engines: {node: '>=18.0.0'} + '@smithy/smithy-client@4.12.7': + resolution: {integrity: sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==} + engines: {node: '>=18.0.0'} + '@smithy/types@3.7.2': resolution: {integrity: sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==} engines: {node: '>=16.0.0'} @@ -1933,9 +2310,17 @@ packages: resolution: {integrity: sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==} engines: {node: '>=18.0.0'} + '@smithy/types@4.13.1': + resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==} + engines: {node: '>=18.0.0'} + '@smithy/url-parser@3.0.11': resolution: {integrity: sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw==} + '@smithy/url-parser@4.2.12': + resolution: {integrity: sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==} + engines: {node: '>=18.0.0'} + '@smithy/url-parser@4.2.7': resolution: {integrity: sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==} engines: {node: '>=18.0.0'} @@ -1948,6 +2333,10 @@ packages: resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} engines: {node: '>=18.0.0'} + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-body-length-browser@3.0.0': resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} @@ -1955,6 +2344,10 @@ packages: resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} engines: {node: '>=18.0.0'} + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-body-length-node@3.0.0': resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} engines: {node: '>=16.0.0'} @@ -1963,6 +2356,10 @@ packages: resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} engines: {node: '>=18.0.0'} + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} @@ -1975,6 +2372,10 @@ packages: resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} engines: {node: '>=18.0.0'} + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + '@smithy/util-config-provider@3.0.0': resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} engines: {node: '>=16.0.0'} @@ -1983,6 +2384,10 @@ packages: resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-browser@3.0.34': resolution: {integrity: sha512-FumjjF631lR521cX+svMLBj3SwSDh9VdtyynTYDAiBDEf8YPP5xORNXKQ9j0105o5+ARAGnOOP/RqSl40uXddA==} engines: {node: '>= 10.0.0'} @@ -1991,6 +2396,10 @@ packages: resolution: {integrity: sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==} engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-browser@4.3.43': + resolution: {integrity: sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-node@3.0.34': resolution: {integrity: sha512-vN6aHfzW9dVVzkI0wcZoUXvfjkl4CSbM9nE//08lmUMyf00S75uuCpTrqF9uD4bD9eldIXlt53colrlwKAT8Gw==} engines: {node: '>= 10.0.0'} @@ -1999,6 +2408,10 @@ packages: resolution: {integrity: sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==} engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-node@4.2.47': + resolution: {integrity: sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-endpoints@2.1.7': resolution: {integrity: sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw==} engines: {node: '>=16.0.0'} @@ -2007,6 +2420,10 @@ packages: resolution: {integrity: sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==} engines: {node: '>=18.0.0'} + '@smithy/util-endpoints@3.3.3': + resolution: {integrity: sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==} + engines: {node: '>=18.0.0'} + '@smithy/util-hex-encoding@3.0.0': resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} engines: {node: '>=16.0.0'} @@ -2015,10 +2432,18 @@ packages: resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} engines: {node: '>=18.0.0'} + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + '@smithy/util-middleware@3.0.11': resolution: {integrity: sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==} engines: {node: '>=16.0.0'} + '@smithy/util-middleware@4.2.12': + resolution: {integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-middleware@4.2.7': resolution: {integrity: sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==} engines: {node: '>=18.0.0'} @@ -2027,6 +2452,10 @@ packages: resolution: {integrity: sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==} engines: {node: '>=16.0.0'} + '@smithy/util-retry@4.2.12': + resolution: {integrity: sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-retry@4.2.7': resolution: {integrity: sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==} engines: {node: '>=18.0.0'} @@ -2035,6 +2464,10 @@ packages: resolution: {integrity: sha512-SGhGBG/KupieJvJSZp/rfHHka8BFgj56eek9px4pp7lZbOF+fRiVr4U7A3y3zJD8uGhxq32C5D96HxsTC9BckQ==} engines: {node: '>=16.0.0'} + '@smithy/util-stream@4.5.20': + resolution: {integrity: sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==} + engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.5.8': resolution: {integrity: sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==} engines: {node: '>=18.0.0'} @@ -2047,6 +2480,10 @@ packages: resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} @@ -2059,6 +2496,10 @@ packages: resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} engines: {node: '>=18.0.0'} + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + '@smithy/util-waiter@3.2.0': resolution: {integrity: sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg==} engines: {node: '>=16.0.0'} @@ -2071,6 +2512,10 @@ packages: resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} engines: {node: '>=18.0.0'} + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} @@ -2105,6 +2550,21 @@ packages: resolution: {integrity: sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==} engines: {node: '>= 10.0.0'} + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@typechain/ethers-v5@10.2.1': resolution: {integrity: sha512-n3tQmCZjRE6IU4h6lqUGiQ1j866n5MTCBJreNEHHVWXa2u9GJTaeYyU1/k+1qLutkyw+sS6VAN+AbeiTqsxd/A==} peerDependencies: @@ -2181,6 +2641,9 @@ packages: '@types/pbkdf2@3.1.2': resolution: {integrity: sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==} + '@types/pg@8.20.0': + resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} + '@types/prettier@2.7.3': resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} @@ -2217,6 +2680,65 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript-eslint/eslint-plugin@8.57.2': + resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.57.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.57.2': + resolution: {integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.57.2': + resolution: {integrity: sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.57.2': + resolution: {integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.57.2': + resolution: {integrity: sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.57.2': + resolution: {integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.57.2': + resolution: {integrity: sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.57.2': + resolution: {integrity: sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.57.2': + resolution: {integrity: sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.57.2': + resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -2285,6 +2807,10 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + acorn@6.4.2: resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==} engines: {node: '>=0.4.0'} @@ -2322,9 +2848,6 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - amazon-cognito-identity-js@6.3.16: resolution: {integrity: sha512-HPGSBGD6Q36t99puWh0LnptxO/4icnk2kqIQ9cTJ2tFQo5NMUnWQIgtrTAk8nm+caqUbjDzXzG56GBjI2tS6jQ==} @@ -2401,6 +2924,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -2456,6 +2982,10 @@ packages: async-eventemitter@0.2.4: resolution: {integrity: sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==} + async-exit-hook@2.0.1: + resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} + engines: {node: '>=0.12.0'} + async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} @@ -2501,6 +3031,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base-x@3.0.11: resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} @@ -2553,9 +3087,6 @@ packages: bn.js@5.2.2: resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} - bn.js@5.2.3: - resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} - body-parser@1.20.4: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -2573,6 +3104,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2962,6 +3497,9 @@ packages: create-hmac@1.1.7: resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cross-fetch@4.1.0: resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} @@ -3019,6 +3557,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -3274,6 +3821,10 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@5.16.0: resolution: {integrity: sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==} engines: {node: ^6.14.0 || ^8.10.0 || >=9.10.0} @@ -3467,6 +4018,9 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-xml-builder@1.1.4: + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} + fast-xml-parser@4.2.5: resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} hasBin: true @@ -3475,6 +4029,10 @@ packages: resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} hasBin: true + fast-xml-parser@5.5.6: + resolution: {integrity: sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==} + hasBin: true + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -3700,20 +4258,22 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@11.1.0: resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@5.0.15: resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} @@ -3721,7 +4281,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} @@ -3930,6 +4490,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} @@ -4346,6 +4910,9 @@ packages: resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} engines: {node: '>= 12.0.0'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} @@ -4380,6 +4947,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + markdown-table@2.0.0: resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} @@ -4479,12 +5049,13 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@5.0.1: resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} engines: {node: '>=10'} @@ -4811,6 +5382,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.2.0: + resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} + engines: {node: '>=14.0.0'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -4858,6 +5433,40 @@ packages: performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + pg-cloudflare@1.3.0: + resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + + pg-connection-string@2.12.0: + resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.13.0: + resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.20.0: + resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -4897,6 +5506,22 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -4940,14 +5565,18 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - prompts@git+https://git@github.com:meshin-blox/prompts.git#a22bdac044f6b32ba67adb4eacc2e58322512a2d: - resolution: {commit: a22bdac044f6b32ba67adb4eacc2e58322512a2d, repo: git@github.com:meshin-blox/prompts.git, type: git} + prompts@git+https://github.com/meshin-blox/prompts.git#a22bdac044f6b32ba67adb4eacc2e58322512a2d: + resolution: {commit: a22bdac044f6b32ba67adb4eacc2e58322512a2d, repo: https://github.com/meshin-blox/prompts.git, type: git} version: 2.4.2 engines: {node: '>= 6'} proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4982,14 +5611,6 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} - qs@6.14.2: - resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} - engines: {node: '>=0.6'} - - qs@6.15.0: - resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} - engines: {node: '>=0.6'} - qs@6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'} @@ -5336,6 +5957,10 @@ packages: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} + snappy@7.3.3: + resolution: {integrity: sha512-UDJVCunvgblRpfTOjo/uT7pQzfrTsSICJ4yVS4aq7SsGBaUSpJwaVP15nF//jqinSLpN7boe/BqbUmtWMTQ5MQ==} + engines: {node: '>= 10'} + snappyjs@0.7.0: resolution: {integrity: sha512-u5iEEXkMe2EInQio6Wv9LWHOQYRDbD2O9hzS27GpT/lwfIQhTCnHCTqedqHIHe9ZcvQo+9au6vngQayipz1NYw==} @@ -5410,8 +6035,12 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.23: - resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -5426,8 +6055,8 @@ packages: engines: {node: '>=12'} hasBin: true - ssv-scanner@git+https://git@github.com:bloxapp/ssv-scanner.git#45068c93b3de84495f6cfad2767787c91d98aa8f: - resolution: {commit: 45068c93b3de84495f6cfad2767787c91d98aa8f, repo: git@github.com:bloxapp/ssv-scanner.git, type: git} + ssv-scanner@https://codeload.github.com/ssvlabs/ssv-scanner/tar.gz/45068c93b3de84495f6cfad2767787c91d98aa8f: + resolution: {tarball: https://codeload.github.com/ssvlabs/ssv-scanner/tar.gz/45068c93b3de84495f6cfad2767787c91d98aa8f} version: 1.0.4 engines: {node: '>=18'} hasBin: true @@ -5623,6 +6252,12 @@ packages: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-command-line-args@2.5.1: resolution: {integrity: sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==} hasBin: true @@ -5632,6 +6267,20 @@ packages: peerDependencies: typescript: '>=3.7.0' + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -5717,6 +6366,11 @@ packages: engines: {node: '>=4.2.0'} hasBin: true + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + typical@4.0.0: resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} engines: {node: '>=8'} @@ -5768,6 +6422,9 @@ packages: resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} engines: {node: '>=4'} + url-polyfill@1.1.14: + resolution: {integrity: sha512-p4f3TTAG6ADVF3mwbXw7hGw+QJyw5CnNGvYh5fCuQQZIiuKUswqcznyV3pGDP9j0TSmC4UvRKm8kl1QsX1diiQ==} + url-set-query@1.0.0: resolution: {integrity: sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==} @@ -5814,6 +6471,9 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -6043,6 +6703,9 @@ packages: engines: {node: '>= 0.10.0'} hasBin: true + winston-loki@6.1.4: + resolution: {integrity: sha512-/j/Zf7TGLjgSBck29BkPnpJlEnGQr5xqlx8A0N6LZnXYYYvyK7lFk6FPpWiD+VMO8xjaxOu1KNF9SzWaRDsigA==} + winston-transport@4.9.0: resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} engines: {node: '>= 12.0.0'} @@ -6212,6 +6875,10 @@ packages: yargs@4.8.1: resolution: {integrity: sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA==} + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -6234,13 +6901,13 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.957.0 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.957.0 tslib: 2.8.1 '@aws-crypto/sha1-browser@5.2.0': @@ -6257,7 +6924,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.973.6 '@aws-sdk/util-locate-window': 3.957.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -6271,7 +6938,7 @@ snapshots: '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.973.6 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -6286,52 +6953,50 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.957.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-kms@3.598.0': + '@aws-sdk/client-kms@3.1013.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.598.0(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/client-sts': 3.598.0 - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.598.0(@aws-sdk/client-sso-oidc@3.598.0)(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 + '@aws-sdk/core': 3.973.22 + '@aws-sdk/credential-provider-node': 3.972.23 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.8 + '@aws-sdk/middleware-user-agent': 3.972.23 + '@aws-sdk/region-config-resolver': 3.972.8 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.9 + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-retry': 4.4.44 + '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.0 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.43 + '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -6448,52 +7113,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.598.0(@aws-sdk/client-sts@3.598.0)': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.598.0 - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.598.0(@aws-sdk/client-sso-oidc@3.598.0)(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - transitivePeerDependencies: - - '@aws-sdk/client-sts' - - aws-crt - '@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0)': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -6626,51 +7245,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sts@3.598.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.598.0(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.598.0(@aws-sdk/client-sso-oidc@3.598.0)(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/client-sts@3.600.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -6742,6 +7316,22 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@aws-sdk/core@3.973.22': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/xml-builder': 3.972.14 + '@smithy/core': 3.23.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -6757,6 +7347,14 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.972.20': + dependencies: + '@aws-sdk/core': 3.973.22 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -6782,23 +7380,18 @@ snapshots: '@smithy/util-stream': 4.5.8 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.598.0(@aws-sdk/client-sso-oidc@3.598.0)(@aws-sdk/client-sts@3.598.0)': + '@aws-sdk/credential-provider-http@3.972.22': dependencies: - '@aws-sdk/client-sts': 3.598.0 - '@aws-sdk/credential-provider-env': 3.598.0 - '@aws-sdk/credential-provider-http': 3.598.0 - '@aws-sdk/credential-provider-process': 3.598.0 - '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.598.0) - '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/types': 3.598.0 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.973.22 + '@aws-sdk/types': 3.973.6 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.0 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.20 tslib: 2.8.1 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt '@aws-sdk/credential-provider-ini@3.598.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0)': dependencies: @@ -6837,6 +7430,25 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-ini@3.972.22': + dependencies: + '@aws-sdk/core': 3.973.22 + '@aws-sdk/credential-provider-env': 3.972.20 + '@aws-sdk/credential-provider-http': 3.972.22 + '@aws-sdk/credential-provider-login': 3.972.22 + '@aws-sdk/credential-provider-process': 3.972.20 + '@aws-sdk/credential-provider-sso': 3.972.22 + '@aws-sdk/credential-provider-web-identity': 3.972.22 + '@aws-sdk/nested-clients': 3.996.12 + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-login@3.958.0': dependencies: '@aws-sdk/core': 3.957.0 @@ -6850,23 +7462,17 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.598.0(@aws-sdk/client-sso-oidc@3.598.0)(@aws-sdk/client-sts@3.598.0)': + '@aws-sdk/credential-provider-login@3.972.22': dependencies: - '@aws-sdk/credential-provider-env': 3.598.0 - '@aws-sdk/credential-provider-http': 3.598.0 - '@aws-sdk/credential-provider-ini': 3.598.0(@aws-sdk/client-sso-oidc@3.598.0)(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/credential-provider-process': 3.598.0 - '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.598.0) - '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/types': 3.598.0 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.973.22 + '@aws-sdk/nested-clients': 3.996.12 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 tslib: 2.8.1 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/credential-provider-node@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0)': @@ -6905,6 +7511,23 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-node@3.972.23': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.20 + '@aws-sdk/credential-provider-http': 3.972.22 + '@aws-sdk/credential-provider-ini': 3.972.22 + '@aws-sdk/credential-provider-process': 3.972.20 + '@aws-sdk/credential-provider-sso': 3.972.22 + '@aws-sdk/credential-provider-web-identity': 3.972.22 + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-process@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -6922,18 +7545,14 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.598.0(@aws-sdk/client-sso-oidc@3.598.0)': + '@aws-sdk/credential-provider-process@3.972.20': dependencies: - '@aws-sdk/client-sso': 3.598.0 - '@aws-sdk/token-providers': 3.598.0(@aws-sdk/client-sso-oidc@3.598.0) - '@aws-sdk/types': 3.598.0 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.973.22 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 tslib: 2.8.1 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt '@aws-sdk/credential-provider-sso@3.598.0(@aws-sdk/client-sso-oidc@3.600.0)': dependencies: @@ -6961,13 +7580,18 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.598.0(@aws-sdk/client-sts@3.598.0)': + '@aws-sdk/credential-provider-sso@3.972.22': dependencies: - '@aws-sdk/client-sts': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.973.22 + '@aws-sdk/nested-clients': 3.996.12 + '@aws-sdk/token-providers': 3.1013.0 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt '@aws-sdk/credential-provider-web-identity@3.598.0(@aws-sdk/client-sts@3.600.0)': dependencies: @@ -6989,6 +7613,18 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-web-identity@3.972.22': + dependencies: + '@aws-sdk/core': 3.973.22 + '@aws-sdk/nested-clients': 3.996.12 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/middleware-bucket-endpoint@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -7031,6 +7667,13 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@aws-sdk/middleware-host-header@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/middleware-location-constraint@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -7049,6 +7692,12 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@aws-sdk/middleware-logger@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/middleware-recursion-detection@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -7064,6 +7713,14 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@aws-sdk/middleware-recursion-detection@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws/lambda-invoke-store': 0.2.2 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/middleware-sdk-s3@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -7110,6 +7767,17 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@aws-sdk/middleware-user-agent@3.972.23': + dependencies: + '@aws-sdk/core': 3.973.22 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@smithy/core': 3.23.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-retry': 4.2.12 + tslib: 2.8.1 + '@aws-sdk/nested-clients@3.958.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -7153,6 +7821,49 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/nested-clients@3.996.12': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.22 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.8 + '@aws-sdk/middleware-user-agent': 3.972.23 + '@aws-sdk/region-config-resolver': 3.972.8 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.9 + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-retry': 4.4.44 + '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.0 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.43 + '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/region-config-resolver@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -7170,6 +7881,14 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@aws-sdk/region-config-resolver@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/config-resolver': 4.4.13 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.598.0': dependencies: '@aws-sdk/middleware-sdk-s3': 3.598.0 @@ -7179,14 +7898,17 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 - '@aws-sdk/token-providers@3.598.0(@aws-sdk/client-sso-oidc@3.598.0)': + '@aws-sdk/token-providers@3.1013.0': dependencies: - '@aws-sdk/client-sso-oidc': 3.598.0(@aws-sdk/client-sts@3.598.0) - '@aws-sdk/types': 3.598.0 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.973.22 + '@aws-sdk/nested-clients': 3.996.12 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt '@aws-sdk/token-providers@3.598.0(@aws-sdk/client-sso-oidc@3.600.0)': dependencies: @@ -7219,6 +7941,11 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@aws-sdk/types@3.973.6': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/util-arn-parser@3.568.0': dependencies: tslib: 2.8.1 @@ -7238,6 +7965,14 @@ snapshots: '@smithy/util-endpoints': 3.2.7 tslib: 2.8.1 + '@aws-sdk/util-endpoints@3.996.5': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-endpoints': 3.3.3 + tslib: 2.8.1 + '@aws-sdk/util-locate-window@3.957.0': dependencies: tslib: 2.8.1 @@ -7256,6 +7991,13 @@ snapshots: bowser: 2.13.1 tslib: 2.8.1 + '@aws-sdk/util-user-agent-browser@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + bowser: 2.13.1 + tslib: 2.8.1 + '@aws-sdk/util-user-agent-node@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 @@ -7271,6 +8013,15 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@aws-sdk/util-user-agent-node@3.973.9': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.23 + '@aws-sdk/types': 3.973.6 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + '@aws-sdk/util-utf8-browser@3.259.0': dependencies: tslib: 2.8.1 @@ -7286,6 +8037,12 @@ snapshots: fast-xml-parser: 5.2.5 tslib: 2.8.1 + '@aws-sdk/xml-builder@3.972.14': + dependencies: + '@smithy/types': 4.13.1 + fast-xml-parser: 5.5.6 + tslib: 2.8.1 + '@aws/lambda-invoke-store@0.2.2': {} '@axelar-network/axelar-gmp-sdk-solidity@5.10.0': {} @@ -7474,12 +8231,32 @@ snapshots: '@colors/colors@1.6.0': {} + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@dabh/diagnostics@2.0.8': dependencies: '@so-ric/colorspace': 1.1.6 enabled: 2.0.0 kuler: 2.0.0 + '@emnapi/core@1.9.1': + dependencies: + '@emnapi/wasi-threads': 1.2.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.0': + dependencies: + tslib: 2.8.1 + optional: true + '@ensdomains/ens@0.4.5': dependencies: bluebird: 3.7.2 @@ -7495,6 +8272,11 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.2': {} '@eslint/eslintrc@2.1.4': @@ -7556,18 +8338,18 @@ snapshots: - '@ensdomains/resolver' - supports-color - '@ethereum-waffle/compiler@4.0.3(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(solc@0.8.15(debug@4.3.4))(typechain@8.3.2(typescript@4.9.5))(typescript@4.9.5)': + '@ethereum-waffle/compiler@4.0.3(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(solc@0.8.15(debug@4.3.4))(typechain@8.3.2(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@resolver-engine/imports': 0.3.3 '@resolver-engine/imports-fs': 0.3.3 - '@typechain/ethers-v5': 10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@4.9.5))(typescript@4.9.5) + '@typechain/ethers-v5': 10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.9.3))(typescript@5.9.3) '@types/mkdirp': 0.5.2 '@types/node-fetch': 2.6.13 ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) mkdirp: 0.5.6 node-fetch: 2.7.0 solc: 0.8.15(debug@4.3.4) - typechain: 8.3.2(typescript@4.9.5) + typechain: 8.3.2(typescript@5.9.3) transitivePeerDependencies: - '@ethersproject/abi' - '@ethersproject/providers' @@ -8276,8 +9058,27 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lastdotnet/purrikey@1.0.0(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + dependencies: + '@aws-sdk/client-kms': 3.1013.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/rlp': 5.8.0 + ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@layerzerolabs/devtools@0.4.10(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.1.17(zod@3.25.76))(@layerzerolabs/lz-definitions@3.0.151)(zod@3.25.76)': dependencies: '@ethersproject/bytes': 5.8.0 @@ -8367,7 +9168,7 @@ snapshots: '@lodestar/types': 1.41.0 '@lodestar/utils': 1.41.0 eventsource: 2.0.2 - qs: 6.15.0 + qs: 6.14.0 transitivePeerDependencies: - '@vekexasia/bigint-uint8array' - vitest @@ -8430,6 +9231,69 @@ snapshots: transitivePeerDependencies: - '@vekexasia/bigint-uint8array' + '@napi-rs/snappy-android-arm-eabi@7.3.3': + optional: true + + '@napi-rs/snappy-android-arm64@7.3.3': + optional: true + + '@napi-rs/snappy-darwin-arm64@7.3.3': + optional: true + + '@napi-rs/snappy-darwin-x64@7.3.3': + optional: true + + '@napi-rs/snappy-freebsd-x64@7.3.3': + optional: true + + '@napi-rs/snappy-linux-arm-gnueabihf@7.3.3': + optional: true + + '@napi-rs/snappy-linux-arm64-gnu@7.3.3': + optional: true + + '@napi-rs/snappy-linux-arm64-musl@7.3.3': + optional: true + + '@napi-rs/snappy-linux-ppc64-gnu@7.3.3': + optional: true + + '@napi-rs/snappy-linux-riscv64-gnu@7.3.3': + optional: true + + '@napi-rs/snappy-linux-s390x-gnu@7.3.3': + optional: true + + '@napi-rs/snappy-linux-x64-gnu@7.3.3': + optional: true + + '@napi-rs/snappy-linux-x64-musl@7.3.3': + optional: true + + '@napi-rs/snappy-openharmony-arm64@7.3.3': + optional: true + + '@napi-rs/snappy-wasm32-wasi@7.3.3': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@napi-rs/snappy-win32-arm64-msvc@7.3.3': + optional: true + + '@napi-rs/snappy-win32-ia32-msvc@7.3.3': + optional: true + + '@napi-rs/snappy-win32-x64-msvc@7.3.3': + optional: true + + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.9.1 + '@emnapi/runtime': 1.9.1 + '@tybys/wasm-util': 0.10.1 + optional: true + '@noble/ciphers@1.3.0': {} '@noble/curves@1.2.0': @@ -8496,18 +9360,18 @@ snapshots: '@nomicfoundation/edr-linux-x64-musl': 0.11.3 '@nomicfoundation/edr-win32-x64-msvc': 0.11.3 - '@nomicfoundation/hardhat-network-helpers@1.0.9(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))': + '@nomicfoundation/hardhat-network-helpers@1.0.9(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: ethereumjs-util: 7.1.5 - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) - '@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))': + '@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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.3.4(supports-color@8.1.1) - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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 @@ -8551,12 +9415,12 @@ 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.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) - '@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))': + '@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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 @@ -8564,7 +9428,7 @@ snapshots: chalk: 2.4.2 debug: 4.3.4(supports-color@8.1.1) fs-extra: 7.0.1 - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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 @@ -8572,20 +9436,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@nomiclabs/hardhat-solhint@2.0.1(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))': + '@nomiclabs/hardhat-solhint@2.0.1(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) solhint: 2.3.1 transitivePeerDependencies: - supports-color - '@nomiclabs/hardhat-waffle@2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@4.9.5))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))': + '@nomiclabs/hardhat-waffle@2.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@4.9.5) + 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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@5.9.3) ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) '@openzeppelin/contracts-upgradeable@4.7.3': {} @@ -8781,16 +9645,16 @@ snapshots: - web3-core - web3-utils - '@openzeppelin/hardhat-upgrades@1.27.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))': + '@openzeppelin/hardhat-upgrades@1.27.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) - '@nomiclabs/hardhat-etherscan': 3.1.8(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-etherscan': 3.1.8(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@openzeppelin/upgrades-core': 1.44.2 chalk: 4.1.2 debug: 4.3.4(supports-color@8.1.1) defender-base-client: 1.44.3(debug@4.3.4) ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) platform-deploy-client: 0.6.0(debug@4.3.4) proper-lockfile: 4.1.2 transitivePeerDependencies: @@ -8816,6 +9680,29 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@resolver-engine/core@0.3.3': dependencies: debug: 3.2.7 @@ -9052,6 +9939,11 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/abort-controller@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/abort-controller@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9074,6 +9966,15 @@ snapshots: '@smithy/util-middleware': 3.0.11 tslib: 2.8.1 + '@smithy/config-resolver@4.4.13': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + '@smithy/config-resolver@4.4.5': dependencies: '@smithy/node-config-provider': 4.3.7 @@ -9107,6 +10008,19 @@ snapshots: '@smithy/uuid': 1.1.0 tslib: 2.8.1 + '@smithy/core@3.23.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.20 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + '@smithy/credential-provider-imds@3.2.8': dependencies: '@smithy/node-config-provider': 3.1.12 @@ -9115,6 +10029,14 @@ snapshots: '@smithy/url-parser': 3.0.11 tslib: 2.8.1 + '@smithy/credential-provider-imds@4.2.12': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + tslib: 2.8.1 + '@smithy/credential-provider-imds@4.2.7': dependencies: '@smithy/node-config-provider': 4.3.7 @@ -9199,6 +10121,14 @@ snapshots: '@smithy/util-base64': 3.0.0 tslib: 2.8.1 + '@smithy/fetch-http-handler@5.3.15': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + '@smithy/fetch-http-handler@5.3.8': dependencies: '@smithy/protocol-http': 5.3.7 @@ -9221,6 +10151,13 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + '@smithy/hash-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/hash-node@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9239,6 +10176,11 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/invalid-dependency@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/invalid-dependency@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9256,6 +10198,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/md5-js@3.0.11': dependencies: '@smithy/types': 3.7.2 @@ -9268,6 +10214,12 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/middleware-content-length@4.2.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/middleware-content-length@4.2.7': dependencies: '@smithy/protocol-http': 5.3.7 @@ -9296,6 +10248,17 @@ snapshots: '@smithy/util-middleware': 4.2.7 tslib: 2.8.1 + '@smithy/middleware-endpoint@4.4.27': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/middleware-serde': 4.2.15 + '@smithy/node-config-provider': 4.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + '@smithy/middleware-retry@3.0.34': dependencies: '@smithy/node-config-provider': 3.1.12 @@ -9320,11 +10283,30 @@ snapshots: '@smithy/uuid': 1.1.0 tslib: 2.8.1 + '@smithy/middleware-retry@4.4.44': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/service-error-classification': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + '@smithy/middleware-serde@3.0.11': dependencies: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/middleware-serde@4.2.15': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/middleware-serde@4.2.8': dependencies: '@smithy/protocol-http': 5.3.7 @@ -9336,6 +10318,11 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/middleware-stack@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/middleware-stack@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9348,6 +10335,13 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/node-config-provider@4.3.12': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/node-config-provider@4.3.7': dependencies: '@smithy/property-provider': 4.2.7 @@ -9371,11 +10365,24 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@smithy/node-http-handler@4.5.0': + dependencies: + '@smithy/abort-controller': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/property-provider@3.1.11': dependencies: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/property-provider@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/property-provider@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9386,6 +10393,11 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/protocol-http@5.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/protocol-http@5.3.7': dependencies: '@smithy/types': 4.11.0 @@ -9397,6 +10409,12 @@ snapshots: '@smithy/util-uri-escape': 3.0.0 tslib: 2.8.1 + '@smithy/querystring-builder@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 + '@smithy/querystring-builder@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9408,6 +10426,11 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/querystring-parser@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/querystring-parser@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9417,6 +10440,10 @@ snapshots: dependencies: '@smithy/types': 3.7.2 + '@smithy/service-error-classification@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/service-error-classification@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9431,6 +10458,11 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@smithy/shared-ini-file-loader@4.4.7': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/signature-v4@3.1.2': dependencies: '@smithy/is-array-buffer': 3.0.0 @@ -9441,6 +10473,17 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + '@smithy/signature-v4@5.3.12': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/signature-v4@5.3.7': dependencies: '@smithy/is-array-buffer': 4.2.0 @@ -9472,6 +10515,16 @@ snapshots: '@smithy/util-stream': 4.5.8 tslib: 2.8.1 + '@smithy/smithy-client@4.12.7': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-stack': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.20 + tslib: 2.8.1 + '@smithy/types@3.7.2': dependencies: tslib: 2.8.1 @@ -9480,12 +10533,22 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/types@4.13.1': + dependencies: + tslib: 2.8.1 + '@smithy/url-parser@3.0.11': dependencies: '@smithy/querystring-parser': 3.0.11 '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/url-parser@4.2.12': + dependencies: + '@smithy/querystring-parser': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/url-parser@4.2.7': dependencies: '@smithy/querystring-parser': 4.2.7 @@ -9504,6 +10567,12 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/util-body-length-browser@3.0.0': dependencies: tslib: 2.8.1 @@ -9512,6 +10581,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-body-length-node@3.0.0': dependencies: tslib: 2.8.1 @@ -9520,6 +10593,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 + '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 @@ -9535,6 +10612,11 @@ snapshots: '@smithy/is-array-buffer': 4.2.0 tslib: 2.8.1 + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 + '@smithy/util-config-provider@3.0.0': dependencies: tslib: 2.8.1 @@ -9543,6 +10625,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-defaults-mode-browser@3.0.34': dependencies: '@smithy/property-provider': 3.1.11 @@ -9558,6 +10644,13 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@smithy/util-defaults-mode-browser@4.3.43': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-defaults-mode-node@3.0.34': dependencies: '@smithy/config-resolver': 3.0.13 @@ -9578,6 +10671,16 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@smithy/util-defaults-mode-node@4.2.47': + dependencies: + '@smithy/config-resolver': 4.4.13 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-endpoints@2.1.7': dependencies: '@smithy/node-config-provider': 3.1.12 @@ -9590,6 +10693,12 @@ snapshots: '@smithy/types': 4.11.0 tslib: 2.8.1 + '@smithy/util-endpoints@3.3.3': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-hex-encoding@3.0.0': dependencies: tslib: 2.8.1 @@ -9598,11 +10707,20 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-middleware@3.0.11': dependencies: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/util-middleware@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-middleware@4.2.7': dependencies: '@smithy/types': 4.11.0 @@ -9614,6 +10732,12 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/util-retry@4.2.12': + dependencies: + '@smithy/service-error-classification': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-retry@4.2.7': dependencies: '@smithy/service-error-classification': 4.2.7 @@ -9631,6 +10755,17 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + '@smithy/util-stream@4.5.20': + dependencies: + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.0 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/util-stream@4.5.8': dependencies: '@smithy/fetch-http-handler': 5.3.8 @@ -9650,6 +10785,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 @@ -9665,6 +10804,11 @@ snapshots: '@smithy/util-buffer-from': 4.2.0 tslib: 2.8.1 + '@smithy/util-utf8@4.2.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 + '@smithy/util-waiter@3.2.0': dependencies: '@smithy/abort-controller': 3.1.9 @@ -9681,6 +10825,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/uuid@1.1.2': + dependencies: + tslib: 2.8.1 + '@so-ric/colorspace@1.1.6': dependencies: color: 5.0.3 @@ -9717,21 +10865,34 @@ snapshots: node-gyp-build: 4.3.0 optional: true - '@typechain/ethers-v5@10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@4.9.5))(typescript@4.9.5)': + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@typechain/ethers-v5@10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/providers': 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) lodash: 4.17.21 - ts-essentials: 7.0.3(typescript@4.9.5) - typechain: 8.3.2(typescript@4.9.5) - typescript: 4.9.5 + ts-essentials: 7.0.3(typescript@5.9.3) + typechain: 8.3.2(typescript@5.9.3) + typescript: 5.9.3 '@types/abstract-leveldown@7.2.5': {} '@types/bn.js@4.11.6': dependencies: - '@types/node': 12.20.55 + '@types/node': 25.0.3 '@types/bn.js@5.2.0': dependencies: @@ -9741,7 +10902,7 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 12.20.55 + '@types/node': 25.0.3 '@types/responselike': 1.0.3 '@types/chai@5.2.3': @@ -9764,7 +10925,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 12.20.55 + '@types/node': 25.0.3 '@types/level-errors@3.0.2': {} @@ -9805,6 +10966,12 @@ snapshots: dependencies: '@types/node': 25.0.3 + '@types/pg@8.20.0': + dependencies: + '@types/node': 25.0.3 + pg-protocol: 1.13.0 + pg-types: 2.2.0 + '@types/prettier@2.7.3': {} '@types/qs@6.14.0': {} @@ -9813,7 +10980,7 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 12.20.55 + '@types/node': 25.0.3 '@types/secp256k1@4.0.7': dependencies: @@ -9840,6 +11007,97 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.57.2(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/type-utils': 8.57.2(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.2 + eslint: 8.57.1 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.2 + debug: 4.4.3 + eslint: 8.57.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.57.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) + '@typescript-eslint/types': 8.57.2 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.57.2': + dependencies: + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/visitor-keys': 8.57.2 + + '@typescript-eslint/tsconfig-utils@8.57.2(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.57.2(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.57.2': {} + + '@typescript-eslint/typescript-estree@8.57.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.57.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/visitor-keys': 8.57.2 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.57.2(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + eslint: 8.57.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.57.2': + dependencies: + '@typescript-eslint/types': 8.57.2 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} '@uniswap/lib@4.0.1-alpha': {} @@ -9862,9 +11120,9 @@ snapshots: abbrev@1.0.9: {} - abitype@1.2.3(typescript@4.9.5)(zod@3.25.76): + abitype@1.2.3(typescript@5.9.3)(zod@3.25.76): optionalDependencies: - typescript: 4.9.5 + typescript: 5.9.3 zod: 3.25.76 abortcontroller-polyfill@1.7.8: {} @@ -9898,6 +11156,10 @@ snapshots: dependencies: acorn: 8.15.0 + acorn-walk@8.3.5: + dependencies: + acorn: 8.15.0 + acorn@6.4.2: {} acorn@8.15.0: {} @@ -9935,13 +11197,6 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - amazon-cognito-identity-js@6.3.16: dependencies: '@aws-crypto/sha256-js': 1.2.2 @@ -10002,6 +11257,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + arg@4.1.3: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -10050,6 +11307,8 @@ snapshots: dependencies: async: 2.6.4 + async-exit-hook@2.0.1: {} + async-limiter@1.0.1: {} async-retry@1.3.3: @@ -10100,6 +11359,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base-x@3.0.11: dependencies: safe-buffer: 5.2.1 @@ -10143,8 +11404,6 @@ snapshots: bn.js@5.2.2: {} - bn.js@5.2.3: {} - body-parser@1.20.4: dependencies: bytes: 3.1.2 @@ -10155,7 +11414,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.2 + qs: 6.14.0 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -10184,6 +11443,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -10590,14 +11853,14 @@ snapshots: js-yaml: 3.14.2 parse-json: 4.0.0 - cosmiconfig@8.3.6(typescript@4.9.5): + cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 4.9.5 + typescript: 5.9.3 crc-32@1.2.2: {} @@ -10623,6 +11886,8 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.12 + create-require@1.1.1: {} + cross-fetch@4.1.0: dependencies: node-fetch: 2.7.0 @@ -10686,6 +11951,10 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decamelize@4.0.0: {} @@ -10934,6 +12203,8 @@ snapshots: eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@5.0.1: {} + eslint@5.16.0: dependencies: '@babel/code-frame': 7.27.1 @@ -11133,15 +12404,15 @@ snapshots: '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 - 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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@4.9.5): + 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.1.0)(utf-8-validate@5.0.10))(debug@4.3.4)(ethers@5.7.2(bufferutil@4.1.0)(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.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - '@ethereum-waffle/compiler': 4.0.3(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(solc@0.8.15(debug@4.3.4))(typechain@8.3.2(typescript@4.9.5))(typescript@4.9.5) + '@ethereum-waffle/compiler': 4.0.3(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(solc@0.8.15(debug@4.3.4))(typechain@8.3.2(typescript@5.9.3))(typescript@5.9.3) '@ethereum-waffle/mock-contract': 4.0.4(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@ethereum-waffle/provider': 4.0.5(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10)) ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) solc: 0.8.15(debug@4.3.4) - typechain: 8.3.2(typescript@4.9.5) + typechain: 8.3.2(typescript@5.9.3) transitivePeerDependencies: - '@ensdomains/ens' - '@ensdomains/resolver' @@ -11296,7 +12567,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.14.2 + qs: 6.14.0 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -11343,6 +12614,10 @@ snapshots: fast-uri@3.1.0: {} + fast-xml-builder@1.1.4: + dependencies: + path-expression-matcher: 1.2.0 + fast-xml-parser@4.2.5: dependencies: strnum: 1.1.2 @@ -11351,6 +12626,12 @@ snapshots: dependencies: strnum: 2.1.2 + fast-xml-parser@5.5.6: + dependencies: + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.2.0 + strnum: 2.1.2 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -11749,17 +13030,17 @@ snapshots: ajv: 6.12.6 har-schema: 2.0.0 - hardhat-contract-sizer@2.10.0(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)): + hardhat-contract-sizer@2.10.0(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) strip-ansi: 6.0.1 - hardhat-deploy-ethers@0.3.0-beta.13(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)): + hardhat-deploy-ethers@0.3.0-beta.13(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): dependencies: ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) hardhat-deploy@0.11.30(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: @@ -11792,7 +13073,7 @@ snapshots: - supports-color - utf-8-validate - hardhat-gas-reporter@2.3.0(bufferutil@4.1.0)(debug@4.3.4)(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))(typescript@4.9.5)(utf-8-validate@5.0.10)(zod@3.25.76): + hardhat-gas-reporter@2.3.0(bufferutil@4.1.0)(debug@4.3.4)(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76): dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/bytes': 5.8.0 @@ -11804,12 +13085,12 @@ snapshots: cli-table3: 0.6.5 ethereum-cryptography: 2.2.1 glob: 10.5.0 - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) jsonschema: 1.5.0 lodash: 4.17.21 markdown-table: 2.0.0 sha1: 1.1.1 - viem: 2.43.3(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - bufferutil - debug @@ -11817,20 +13098,20 @@ snapshots: - utf-8-validate - zod - hardhat-tracer@3.2.1(bufferutil@4.1.0)(chai@4.3.7)(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10): + hardhat-tracer@3.2.1(bufferutil@4.1.0)(chai@4.3.7)(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10): dependencies: chai: 4.3.7 chalk: 4.1.2 debug: 4.3.4(supports-color@8.1.1) ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) semver: 7.7.3 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10): + hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(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 @@ -11872,7 +13153,8 @@ snapshots: uuid: 8.3.2 ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 4.9.5 + ts-node: 10.9.2(@types/node@25.0.3)(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - bufferutil - supports-color @@ -11975,6 +13257,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + immediate@3.0.6: {} immediate@3.2.3: {} @@ -12370,6 +13654,8 @@ snapshots: safe-stable-stringify: 2.5.0 triple-beam: 1.4.1 + long@5.3.2: {} + loupe@2.3.7: dependencies: get-func-name: 2.0.2 @@ -12396,6 +13682,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + make-error@1.3.6: {} + markdown-table@2.0.0: dependencies: repeat-string: 1.6.1 @@ -12488,11 +13776,11 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 - minimatch@3.1.2: + minimatch@10.2.4: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 5.0.5 - minimatch@3.1.5: + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -12745,7 +14033,7 @@ snapshots: os-tmpdir@1.0.2: {} - ox@0.11.1(typescript@4.9.5)(zod@3.25.76): + ox@0.11.1(typescript@5.9.3)(zod@3.25.76): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -12753,10 +14041,10 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@4.9.5)(zod@3.25.76) + abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: - typescript: 4.9.5 + typescript: 5.9.3 transitivePeerDependencies: - zod @@ -12824,6 +14112,8 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.2.0: {} + path-is-absolute@1.0.1: {} path-is-inside@1.0.2: {} @@ -12867,6 +14157,41 @@ snapshots: performance-now@2.1.0: {} + pg-cloudflare@1.3.0: + optional: true + + pg-connection-string@2.12.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.13.0(pg@8.20.0): + dependencies: + pg: 8.20.0 + + pg-protocol@1.13.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.20.0: + dependencies: + pg-connection-string: 2.12.0 + pg-pool: 3.13.0(pg@8.20.0) + pg-protocol: 1.13.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.3.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -12898,6 +14223,16 @@ snapshots: possible-typed-array-names@1.1.0: {} + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + prelude-ls@1.1.2: {} prelude-ls@1.2.1: {} @@ -12930,7 +14265,7 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - prompts@git+https://git@github.com:meshin-blox/prompts.git#a22bdac044f6b32ba67adb4eacc2e58322512a2d: + prompts@git+https://github.com/meshin-blox/prompts.git#a22bdac044f6b32ba67adb4eacc2e58322512a2d: dependencies: kleur: 4.1.5 sisteransi: 1.0.5 @@ -12941,6 +14276,21 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.0.3 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -12978,14 +14328,6 @@ snapshots: dependencies: side-channel: 1.1.0 - qs@6.14.2: - dependencies: - side-channel: 1.1.0 - - qs@6.15.0: - dependencies: - side-channel: 1.1.0 - qs@6.5.3: {} query-string@5.1.1: @@ -13399,6 +14741,28 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + snappy@7.3.3: + optionalDependencies: + '@napi-rs/snappy-android-arm-eabi': 7.3.3 + '@napi-rs/snappy-android-arm64': 7.3.3 + '@napi-rs/snappy-darwin-arm64': 7.3.3 + '@napi-rs/snappy-darwin-x64': 7.3.3 + '@napi-rs/snappy-freebsd-x64': 7.3.3 + '@napi-rs/snappy-linux-arm-gnueabihf': 7.3.3 + '@napi-rs/snappy-linux-arm64-gnu': 7.3.3 + '@napi-rs/snappy-linux-arm64-musl': 7.3.3 + '@napi-rs/snappy-linux-ppc64-gnu': 7.3.3 + '@napi-rs/snappy-linux-riscv64-gnu': 7.3.3 + '@napi-rs/snappy-linux-s390x-gnu': 7.3.3 + '@napi-rs/snappy-linux-x64-gnu': 7.3.3 + '@napi-rs/snappy-linux-x64-musl': 7.3.3 + '@napi-rs/snappy-openharmony-arm64': 7.3.3 + '@napi-rs/snappy-wasm32-wasi': 7.3.3 + '@napi-rs/snappy-win32-arm64-msvc': 7.3.3 + '@napi-rs/snappy-win32-ia32-msvc': 7.3.3 + '@napi-rs/snappy-win32-x64-msvc': 7.3.3 + optional: true + snappyjs@0.7.0: {} solc@0.4.26: @@ -13466,7 +14830,7 @@ snapshots: transitivePeerDependencies: - supports-color - solhint@3.4.1(typescript@4.9.5): + solhint@3.4.1(typescript@5.9.3): dependencies: '@solidity-parser/parser': 0.16.2 ajv: 6.12.6 @@ -13474,7 +14838,7 @@ snapshots: ast-parents: 0.0.1 chalk: 4.1.2 commander: 10.0.1 - cosmiconfig: 8.3.6(typescript@4.9.5) + cosmiconfig: 8.3.6(typescript@5.9.3) fast-diff: 1.3.0 glob: 8.1.0 ignore: 5.3.2 @@ -13500,7 +14864,7 @@ snapshots: solidity-comments-extractor@0.0.7: {} - solidity-coverage@0.8.14(hardhat@2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)): + solidity-coverage@0.8.14(hardhat@2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): dependencies: '@ethersproject/abi': 5.8.0 '@solidity-parser/parser': 0.19.0 @@ -13511,7 +14875,7 @@ snapshots: ghost-testrpc: 0.0.2 global-modules: 2.0.0 globby: 10.0.2 - hardhat: 2.26.2(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.26.2(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) jsonschema: 1.5.0 lodash: 4.17.21 mocha: 10.2.0 @@ -13540,16 +14904,18 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.23 + spdx-license-ids: 3.0.22 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.23 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} - spdx-license-ids@3.0.23: {} + split2@4.2.0: {} sprintf-js@1.0.3: {} @@ -13590,7 +14956,7 @@ snapshots: minimist: 1.2.8 moment: 2.30.1 node-jsencrypt: 1.0.0 - prompts: git+https://git@github.com:meshin-blox/prompts.git#a22bdac044f6b32ba67adb4eacc2e58322512a2d + prompts: git+https://github.com/meshin-blox/prompts.git#a22bdac044f6b32ba67adb4eacc2e58322512a2d scrypt-js: 3.0.1 semver: 7.7.3 stream: 0.0.2 @@ -13602,7 +14968,7 @@ snapshots: - supports-color - utf-8-validate - ssv-scanner@git+https://git@github.com:bloxapp/ssv-scanner.git#45068c93b3de84495f6cfad2767787c91d98aa8f(bufferutil@4.1.0)(utf-8-validate@5.0.10): + ssv-scanner@https://codeload.github.com/ssvlabs/ssv-scanner/tar.gz/45068c93b3de84495f6cfad2767787c91d98aa8f(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: '@types/figlet': 1.7.0 argparse: 2.0.1 @@ -13760,7 +15126,7 @@ snapshots: table@6.8.2: dependencies: - ajv: 8.18.0 + ajv: 8.17.1 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 @@ -13828,6 +15194,10 @@ snapshots: triple-beam@1.4.1: {} + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-command-line-args@2.5.1: dependencies: chalk: 4.1.2 @@ -13835,9 +15205,27 @@ snapshots: command-line-usage: 6.1.3 string-format: 2.0.0 - ts-essentials@7.0.3(typescript@4.9.5): + ts-essentials@7.0.3(typescript@5.9.3): dependencies: - typescript: 4.9.5 + typescript: 5.9.3 + + ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 25.0.3 + acorn: 8.15.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 tslib@1.14.1: {} @@ -13856,7 +15244,7 @@ snapshots: diff: 4.0.2 glob: 7.2.3 js-yaml: 3.14.2 - minimatch: 3.1.5 + minimatch: 3.1.2 mkdirp: 0.5.6 resolve: 1.22.11 semver: 5.7.2 @@ -13900,7 +15288,7 @@ snapshots: type@2.7.3: {} - typechain@8.3.2(typescript@4.9.5): + typechain@8.3.2(typescript@5.9.3): dependencies: '@types/prettier': 2.7.3 debug: 4.3.4(supports-color@8.1.1) @@ -13911,8 +15299,8 @@ snapshots: mkdirp: 1.0.4 prettier: 2.8.8 ts-command-line-args: 2.5.1 - ts-essentials: 7.0.3(typescript@4.9.5) - typescript: 4.9.5 + ts-essentials: 7.0.3(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -13928,6 +15316,8 @@ snapshots: typescript@4.9.5: {} + typescript@5.9.3: {} + typical@4.0.0: {} typical@5.2.0: {} @@ -13963,6 +15353,8 @@ snapshots: dependencies: prepend-http: 2.0.0 + url-polyfill@1.1.14: {} + url-set-query@1.0.0: {} url@0.11.4: @@ -14001,6 +15393,8 @@ snapshots: uuid@9.0.1: {} + v8-compile-cache-lib@3.0.1: {} + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -14018,18 +15412,18 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - viem@2.43.3(bufferutil@4.1.0)(typescript@4.9.5)(utf-8-validate@5.0.10)(zod@3.25.76): + viem@2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@4.9.5)(zod@3.25.76) + abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - ox: 0.11.1(typescript@4.9.5)(zod@3.25.76) + ox: 0.11.1(typescript@5.9.3)(zod@3.25.76) ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 4.9.5 + typescript: 5.9.3 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -14243,7 +15637,7 @@ snapshots: web3-eth-iban@1.10.4: dependencies: - bn.js: 5.2.3 + bn.js: 5.2.2 web3-utils: 1.10.4 web3-eth-iban@1.7.3: @@ -14479,6 +15873,16 @@ snapshots: window-size@0.2.0: {} + winston-loki@6.1.4: + dependencies: + async-exit-hook: 2.0.1 + btoa: 1.2.1 + protobufjs: 7.5.4 + url-polyfill: 1.1.14 + winston-transport: 4.9.0 + optionalDependencies: + snappy: 7.3.3 + winston-transport@4.9.0: dependencies: logform: 2.7.0 @@ -14655,6 +16059,8 @@ snapshots: y18n: 3.2.2 yargs-parser: 2.4.1 + yn@3.1.1: {} + yocto-queue@0.1.0: {} zksync-web3@0.14.4(ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@5.0.10)): diff --git a/contracts/scripts/defender-actions/claimBribes.js b/contracts/scripts/defender-actions/claimBribes.js deleted file mode 100644 index 381b0d4e82..0000000000 --- a/contracts/scripts/defender-actions/claimBribes.js +++ /dev/null @@ -1,105 +0,0 @@ -const { ethers } = require("ethers"); -const { Defender } = require("@openzeppelin/defender-sdk"); - -const { logTxDetails } = require("../../utils/txLogger"); - -// ClaimBribesSafeModule1 for Coinbase AERO Locker Safe -const COINBASE_AERO_LOCKER_MODULE = - "0x60d3d6ec213d84dea193dbd79673340061178893"; - -// ClaimBribesSafeModule3 for Old Guardian Safe -const OLD_GUARDIAN_MODULE = "0x26179ada0f7cb714c11a8190e1f517988c28e759"; - -const moduleLabels = { - [COINBASE_AERO_LOCKER_MODULE]: "Coinbase AERO Locker Safe", - [OLD_GUARDIAN_MODULE]: "Old Guardian Safe", -}; - -const batchSizes = { - [COINBASE_AERO_LOCKER_MODULE]: 50, - [OLD_GUARDIAN_MODULE]: 50, -}; - -const MODULE_ABI = [ - "function getNFTIdsLength() external view returns (uint256)", - "function fetchNFTIds() external", - "function removeAllNFTIds() external", - "function claimBribes(uint256 nftIndexStart, uint256 nftIndexEnd, bool silent) external", -]; - -const handler = async (event) => { - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - const network = await provider.getNetwork(); - if (network.chainId != 8453) { - throw new Error("Only supported on Base"); - } - - const modules = [COINBASE_AERO_LOCKER_MODULE, OLD_GUARDIAN_MODULE]; - - for (const moduleAddr of modules) { - const module = new ethers.Contract(moduleAddr, MODULE_ABI, signer); - - console.log( - `Claiming bribes from ${moduleLabels[moduleAddr.toLowerCase()]}` - ); - await manageNFTsOnModule(module, signer); - await claimBribesFromModule(module, signer); - } -}; - -async function manageNFTsOnModule(module, signer) { - // Remove all NFTs from the module - console.log( - `Running removeAllNFTIds on module ${ - moduleLabels[module.address.toLowerCase()] - }` - ); - let tx = await module.connect(signer).removeAllNFTIds({ - gasLimit: 20000000, - }); - logTxDetails(tx, `removeAllNFTIds`); - await tx.wait(); - - // Fetch all NFTs from the veNFT contract - console.log( - `Running fetchNFTIds on module ${ - moduleLabels[module.address.toLowerCase()] - }` - ); - tx = await module.connect(signer).fetchNFTIds({ - gasLimit: 20000000, - }); - logTxDetails(tx, `fetchNFTIds`); - await tx.wait(); -} - -async function claimBribesFromModule(module, signer) { - const nftIdsLength = ( - await module.connect(signer).getNFTIdsLength() - ).toNumber(); - const batchSize = batchSizes[module.address.toLowerCase()] || 50; - const batchCount = Math.ceil(nftIdsLength / batchSize); - - console.log(`Found ${nftIdsLength} NFTs on the module`); - console.log(`Claiming bribes in ${batchCount} batches of ${batchSize}`); - - for (let i = 0; i < batchCount; i++) { - const start = i * batchSize; - const end = Math.min(start + batchSize, nftIdsLength); - - const tx = await module.connect(signer).claimBribes(start, end, true, { - gasLimit: 20000000, - }); - console.log(`claimBribes (batch ${i + 1} of ${batchCount})`); - await logTxDetails(tx, `claimBribes (batch ${i + 1} of ${batchCount})`); - } -} - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/crossChainRelay.js b/contracts/scripts/defender-actions/crossChainRelay.js deleted file mode 100644 index 121e48f48f..0000000000 --- a/contracts/scripts/defender-actions/crossChainRelay.js +++ /dev/null @@ -1,72 +0,0 @@ -const { ethers } = require("ethers"); -const { Defender } = require("@openzeppelin/defender-sdk"); -const { processCctpBridgeTransactions } = require("../../tasks/crossChain"); -const { getNetworkName } = require("../../utils/hardhat-helpers"); -const { configuration } = require("../../utils/cctp"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - // Chain ID of the target contract relayer signer - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const { chainId } = await provider.getNetwork(); - let sourceProvider; - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - // destinatino chain is mainnet, source chain is base - if (chainId === 1) { - if (!event.secrets.BASE_PROVIDER_URL) { - throw new Error("BASE_PROVIDER_URL env var required"); - } - sourceProvider = new ethers.providers.JsonRpcProvider( - event.secrets.BASE_PROVIDER_URL - ); - } - // destination chain is base, source chain is mainnet - else if (chainId === 8453) { - if (!event.secrets.PROVIDER_URL) { - throw new Error("PROVIDER_URL env var required"); - } - sourceProvider = new ethers.providers.JsonRpcProvider( - event.secrets.PROVIDER_URL - ); - } else { - throw new Error(`Unsupported chain id: ${chainId}`); - } - - const networkName = await getNetworkName(sourceProvider); - const isMainnet = networkName === "mainnet"; - const isBase = networkName === "base"; - - let config; - if (isMainnet) { - config = configuration.mainnetBaseMorpho.mainnet; - } else if (isBase) { - config = configuration.mainnetBaseMorpho.base; - } else { - throw new Error(`Unsupported network name: ${networkName}`); - } - - await processCctpBridgeTransactions({ - destinationChainSigner: signer, - sourceChainProvider: sourceProvider, - store: client.keyValueStore, - networkName, - blockLookback: config.blockLookback, - cctpDestinationDomainId: config.cctpDestinationDomainId, - cctpSourceDomainId: config.cctpSourceDomainId, - cctpIntegrationContractAddress: config.cctpIntegrationContractAddress, - cctpIntegrationContractAddressDestination: - config.cctpIntegrationContractAddressDestination, - }); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/crossChainRelayHyperEVM.js b/contracts/scripts/defender-actions/crossChainRelayHyperEVM.js deleted file mode 100644 index c50e4a14c2..0000000000 --- a/contracts/scripts/defender-actions/crossChainRelayHyperEVM.js +++ /dev/null @@ -1,72 +0,0 @@ -const { ethers } = require("ethers"); -const { Defender } = require("@openzeppelin/defender-sdk"); -const { processCctpBridgeTransactions } = require("../../tasks/crossChain"); -const { getNetworkName } = require("../../utils/hardhat-helpers"); -const { configuration } = require("../../utils/cctp"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - // Chain ID of the target contract relayer signer - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const { chainId } = await provider.getNetwork(); - let sourceProvider; - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - // destination chain is mainnet, source chain is hyperevm - if (chainId === 1) { - if (!event.secrets.HYPEREVM_PROVIDER_URL) { - throw new Error("HYPEREVM_PROVIDER_URL env var required"); - } - sourceProvider = new ethers.providers.JsonRpcProvider( - event.secrets.HYPEREVM_PROVIDER_URL - ); - } - // destination chain is hyperevm, source chain is mainnet - else if (chainId === 999) { - if (!event.secrets.PROVIDER_URL) { - throw new Error("PROVIDER_URL env var required"); - } - sourceProvider = new ethers.providers.JsonRpcProvider( - event.secrets.PROVIDER_URL - ); - } else { - throw new Error(`Unsupported chain id: ${chainId}`); - } - - const networkName = await getNetworkName(sourceProvider); - const isMainnet = networkName === "mainnet"; - const isHyperEVM = networkName === "hyperevm"; - - let config; - if (isMainnet) { - config = configuration.mainnetHyperEVMMorpho.mainnet; - } else if (isHyperEVM) { - config = configuration.mainnetHyperEVMMorpho.hyperevm; - } else { - throw new Error(`Unsupported network name: ${networkName}`); - } - - await processCctpBridgeTransactions({ - destinationChainSigner: signer, - sourceChainProvider: sourceProvider, - store: client.keyValueStore, - networkName, - blockLookback: config.blockLookback, - cctpDestinationDomainId: config.cctpDestinationDomainId, - cctpSourceDomainId: config.cctpSourceDomainId, - cctpIntegrationContractAddress: config.cctpIntegrationContractAddress, - cctpIntegrationContractAddressDestination: - config.cctpIntegrationContractAddressDestination, - }); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/doAccounting.js b/contracts/scripts/defender-actions/doAccounting.js deleted file mode 100644 index be605198bb..0000000000 --- a/contracts/scripts/defender-actions/doAccounting.js +++ /dev/null @@ -1,85 +0,0 @@ -const { ethers } = require("ethers"); -const { Defender } = require("@openzeppelin/defender-sdk"); -const addresses = require("../../utils/addresses"); -const { logTxDetails } = require("../../utils/txLogger"); - -const { - address: mainnetConsolidationControllerAddress, - abi: consolidationControllerAbi, -} = require("../../deployments/mainnet/ConsolidationController.json"); -const { - address: hoodiConsolidationControllerAddress, -} = require("../../deployments/hoodi/ConsolidationController.json"); - -const log = require("../../utils/logger")("action:doAccounting"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - const network = await provider.getNetwork(); - const networkName = - network.chainId === 1 - ? "mainnet" - : network.chainId === 560048 - ? "hoodi" - : undefined; - if (!networkName) { - throw new Error( - `Action only supports mainnet and hoodi, not chainId ${network.chainId}` - ); - } - log(`Network: ${networkName} with chain id (${network.chainId})`); - - const consolidationControllerAddress = - networkName === "mainnet" - ? mainnetConsolidationControllerAddress - : hoodiConsolidationControllerAddress; - log( - `Resolved ConsolidationController address to ${consolidationControllerAddress}` - ); - const consolidationController = new ethers.Contract( - consolidationControllerAddress, - consolidationControllerAbi, - signer - ); - - await doAccounting( - "NativeStakingSSVStrategy2Proxy", - networkName, - signer, - consolidationController - ); -}; - -const doAccounting = async ( - proxyName, - networkName, - signer, - consolidationController -) => { - const nativeStakingProxyAddress = addresses[networkName][proxyName]; - if (!nativeStakingProxyAddress) { - throw new Error( - `Failed to resolve ${proxyName} on the ${networkName} network` - ); - } - log(`Resolved ${proxyName} address to ${nativeStakingProxyAddress}`); - - const tx = await consolidationController - .connect(signer) - .doAccounting(nativeStakingProxyAddress); - await logTxDetails(tx, `doAccounting for ${proxyName} via controller`); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/harvest.js b/contracts/scripts/defender-actions/harvest.js deleted file mode 100644 index 4ea88fa979..0000000000 --- a/contracts/scripts/defender-actions/harvest.js +++ /dev/null @@ -1,77 +0,0 @@ -const { ethers } = require("ethers"); -const { Defender } = require("@openzeppelin/defender-sdk"); - -const addresses = require("../../utils/addresses"); -const { logTxDetails } = require("../../utils/txLogger"); -const { - shouldHarvestFromNativeStakingStrategy, - claimStrategyRewards, -} = require("../../utils/harvest"); -const { claimMerklRewards } = require("../../tasks/merkl"); - -const harvesterAbi = require("../../abi/harvester.json"); - -const log = require("../../utils/logger")("action:harvest"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - const { chainId } = await provider.getNetwork(); - if (chainId !== 1) { - throw new Error( - `Action should only be run on mainnet, not on network with chainId ${chainId}` - ); - } - - const harvesterAddress = addresses.mainnet.OETHHarvesterSimpleProxy; - log(`Resolved OETH Harvester Simple address to ${harvesterAddress}`); - const harvester = new ethers.Contract(harvesterAddress, harvesterAbi, signer); - - const nativeStakingStrategies = [ - // addresses[networkName].NativeStakingSSVStrategyProxy, - addresses.mainnet.NativeStakingSSVStrategy2Proxy, - // addresses.mainnet.NativeStakingSSVStrategy3Proxy, - ]; - - const strategiesToHarvest = []; - for (const strategy of nativeStakingStrategies) { - log(`Resolved Native Staking Strategy address to ${strategy}`); - const shouldHarvest = await shouldHarvestFromNativeStakingStrategy( - strategy, - signer - ); - - if (shouldHarvest) { - // Harvest if there are sufficient rewards to be harvested - log(`Will harvest from ${strategy}`); - strategiesToHarvest.push(strategy); - } - } - - if (strategiesToHarvest.length > 0) { - const tx = await harvester - .connect(signer) - ["harvestAndTransfer(address[])"](strategiesToHarvest); - await logTxDetails(tx, `harvestAndTransfer`); - } else { - log("No native staking strategies require harvesting at this time"); - } - - // Claim MORPHO rewards to the Morpho OUSD v2 Strategy - await claimMerklRewards(addresses.mainnet.MorphoOUSDv2StrategyProxy, signer); - // Collect the CRV and MORPHO rewards from the strategies using the Safe module - await claimStrategyRewards(signer); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/manageBribeOnSonic.js b/contracts/scripts/defender-actions/manageBribeOnSonic.js deleted file mode 100644 index 54c8dc6a8f..0000000000 --- a/contracts/scripts/defender-actions/manageBribeOnSonic.js +++ /dev/null @@ -1,42 +0,0 @@ -const { ethers } = require("ethers"); -const { Defender } = require("@openzeppelin/defender-sdk"); - -const poolBoosterSwapXAbi = require("../../abi/poolBoosterSwapX.json"); -const poolBoosterCentralRegistryAbi = require("../../abi/poolBoosterCentralRegistry.json"); -const { logTxDetails } = require("../../utils/txLogger"); -const log = require("../../utils/logger")("action:harvest"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - const poolBoosterCentralRegistryProxyAddress = - "0x4F3B656Aa5Fb5E708bF7B63D6ff71623eb4a218A"; - const poolBoosterCentralRegistryProxy = new ethers.Contract( - poolBoosterCentralRegistryProxyAddress, - poolBoosterCentralRegistryAbi, - signer - ); - - // Fetch all factories - const factories = await poolBoosterCentralRegistryProxy.getAllFactories(); - log(`Factories: ${factories}`); - - for (const f of factories) { - const factory = new ethers.Contract(f, poolBoosterSwapXAbi, signer); - const tx = await factory.connect(signer).bribeAll([]); - await logTxDetails(tx, `Bribed all pools in factory ${f}`); - } -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/manageBribes.js b/contracts/scripts/defender-actions/manageBribes.js deleted file mode 100644 index 6410895b81..0000000000 --- a/contracts/scripts/defender-actions/manageBribes.js +++ /dev/null @@ -1,38 +0,0 @@ -const { Defender } = require("@openzeppelin/defender-sdk"); - -const { manageBribes } = require("../../tasks/poolBooster"); - -const log = require("../../utils/logger")("action:manageBribes"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - // Parse options from event - const skipRewardPerVote = event.request?.body?.skipRewardPerVote ?? false; - const targetEfficiency = event.request?.body?.targetEfficiency ?? 1; - const chunkSize = event.request?.body?.chunkSize ?? 4; - - log( - `Managing max reward per vote with target efficiency ${targetEfficiency}, skip reward per vote ${skipRewardPerVote}, and chunk size ${chunkSize}` - ); - await manageBribes({ - provider, - signer, - targetEfficiency, - skipRewardPerVote, - chunkSize, - }); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/manageMerklBribes.js b/contracts/scripts/defender-actions/manageMerklBribes.js deleted file mode 100644 index 0049f57213..0000000000 --- a/contracts/scripts/defender-actions/manageMerklBribes.js +++ /dev/null @@ -1,32 +0,0 @@ -const { Defender } = require("@openzeppelin/defender-sdk"); - -const { manageMerklBribes } = require("../../tasks/merklPoolBooster"); - -const log = require("../../utils/logger")("action:manageMerklBribes"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - // Parse options from event - const exclusionList = event.request?.body?.exclusionList ?? []; - - log(`Calling bribeAll with exclusion list: [${exclusionList.join(", ")}]`); - await manageMerklBribes({ - provider, - signer, - exclusionList, - }); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/managePassThrough.js b/contracts/scripts/defender-actions/managePassThrough.js deleted file mode 100644 index 97e321c7f0..0000000000 --- a/contracts/scripts/defender-actions/managePassThrough.js +++ /dev/null @@ -1,21 +0,0 @@ -const { Defender } = require("@openzeppelin/defender-sdk"); -const { transferTokens } = require("../../utils/managePassThrough"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - await transferTokens({ signer }); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/rollup.config.cjs b/contracts/scripts/defender-actions/rollup.config.cjs deleted file mode 100644 index 180c74919d..0000000000 --- a/contracts/scripts/defender-actions/rollup.config.cjs +++ /dev/null @@ -1,59 +0,0 @@ -const path = require("path"); -const resolve = require("@rollup/plugin-node-resolve"); -const commonjs = require("@rollup/plugin-commonjs"); -const json = require("@rollup/plugin-json"); -const builtins = require("builtin-modules"); -const { visualizer } = require("rollup-plugin-visualizer"); - -const commonConfig = { - plugins: [ - resolve({ preferBuiltins: true, exportConditions: ["node"] }), - commonjs(), - json({ compact: true }), - visualizer(), - ], - // Do not bundle these packages. - // ethers is required to be bundled even though its an Autotask package. - external: [ - ...builtins, - "axios", - "chai", - "@openzeppelin/defender-relay-client/lib/ethers", - "@openzeppelin/defender-sdk", - "@openzeppelin/defender-autotask-client", - "@openzeppelin/defender-kvstore-client", - "@openzeppelin/defender-relay-client/lib/ethers", - "@nomicfoundation/solidity-analyzer-darwin-arm64", - "@nomicfoundation/solidity-analyzer-darwin-x64", - "fsevents", - "ethers", - "web3", - "mocha", - ], -}; - -const actions = [ - "doAccounting", - "harvest", - "manageBribeOnSonic", - "managePassThrough", - "sonicRequestWithdrawal", - "sonicClaimWithdrawals", - "claimBribes", - "crossChainRelay", - "updateVotemarketEpochs", - "crossChainRelayHyperEVM", - "manageBribes", - "manageMerklBribes", - "claimSSVRewards" -]; - -module.exports = actions.map((action) => ({ - input: path.resolve(__dirname, `${action}.js`), - output: { - file: path.resolve(__dirname, `dist/${action}/index.js`), - inlineDynamicImports: true, - format: "cjs", - }, - ...commonConfig, -})); diff --git a/contracts/scripts/defender-actions/sonicClaimWithdrawals.js b/contracts/scripts/defender-actions/sonicClaimWithdrawals.js deleted file mode 100644 index 8f3c9efee7..0000000000 --- a/contracts/scripts/defender-actions/sonicClaimWithdrawals.js +++ /dev/null @@ -1,22 +0,0 @@ -const { Defender } = require("@openzeppelin/defender-sdk"); - -const { withdrawFromSFC } = require("../../utils/sonicActions"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - await withdrawFromSFC({ signer }); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/sonicRequestWithdrawal.js b/contracts/scripts/defender-actions/sonicRequestWithdrawal.js deleted file mode 100644 index ac71f60c43..0000000000 --- a/contracts/scripts/defender-actions/sonicRequestWithdrawal.js +++ /dev/null @@ -1,25 +0,0 @@ -const { Defender } = require("@openzeppelin/defender-sdk"); - -const { undelegateValidator } = require("../../utils/sonicActions"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer - const client = new Defender(event); - const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); - const signer = await client.relaySigner.getSigner(provider, { - speed: "fastest", - ethersVersion: "v5", - }); - - // The vault buffer in basis points, so 100 = 1% - const bufferPct = 50; - - await undelegateValidator({ signer, bufferPct }); -}; - -module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/updateVotemarketEpochs.js b/contracts/scripts/defender-actions/updateVotemarketEpochs.js deleted file mode 100644 index 3b4a49a704..0000000000 --- a/contracts/scripts/defender-actions/updateVotemarketEpochs.js +++ /dev/null @@ -1,52 +0,0 @@ -const { ethers } = require("ethers"); -const { Defender } = require("@openzeppelin/defender-sdk"); - -const { updateVotemarketEpochs } = require("../../tasks/votemarket"); - -const log = require("../../utils/logger")("action:updateVotemarketEpochs"); - -// Entrypoint for the Defender Action -const handler = async (event) => { - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // Initialize defender relayer provider and signer (Arbitrum relayer) - const client = new Defender(event); - const arbitrumProvider = client.relaySigner.getProvider({ - ethersVersion: "v5", - }); - const { chainId } = await arbitrumProvider.getNetwork(); - - if (chainId !== 42161) { - throw new Error( - `Defender relayer must be on Arbitrum (42161), got chainId ${chainId}` - ); - } - - const arbitrumSigner = await client.relaySigner.getSigner(arbitrumProvider, { - speed: "fastest", - ethersVersion: "v5", - }); - - // Create read-only Mainnet provider - if (!event.secrets.PROVIDER_URL) { - throw new Error("PROVIDER_URL secret required for Mainnet connection"); - } - const mainnetProvider = new ethers.providers.JsonRpcProvider( - event.secrets.PROVIDER_URL - ); - - const dryRun = event.request?.body?.dryRun ?? false; - - log(`Starting updateVotemarketEpochs, dryRun=${dryRun}`); - - await updateVotemarketEpochs({ - mainnetProvider, - arbitrumProvider, - arbitrumSigner, - dryRun, - }); -}; - -module.exports = { handler }; diff --git a/contracts/tasks/actions/SKILL.md b/contracts/tasks/actions/SKILL.md new file mode 100644 index 0000000000..b0669c8ef5 --- /dev/null +++ b/contracts/tasks/actions/SKILL.md @@ -0,0 +1,98 @@ +--- +name: action-migration-hardhat +description: Use this skill when migrating cron actions in contracts/tasks/actions from viem/raw signer.sendTransaction calls to readable ethers contract method calls resolved from Hardhat deployments. +--- + +# Action Migration Skill + +## Use This For + +- Migrating existing action tasks in `contracts/tasks/actions/*.ts` +- Replacing raw calldata + `signer.sendTransaction` patterns +- Standardizing actions on Hardhat deployments + ethers `Contract` calls + +## Naming Conventions + +- Action task `name` values should be `camelCase` and match the action filename (without `.ts`), e.g. `otokenOusdOethRebase`. +- Variables, functions, and local identifiers should use `camelCase`. +- Contract/deployment names passed to `ethers.getContract("")` should use the existing deployment name casing (usually `PascalCase` with suffixes like `Proxy`). + +## Target State + +- Keep the `action({ ... })` wrapper and chain restrictions. +- Prefer fetching deployed contracts by name via Hardhat (`ethers.getContract("")`). +- Call named contract methods (`contract.rebase()`) instead of encoding selectors manually. +- Keep logs clear and action-specific. + +## Migration Rules + +1. Remove `viem` usage (`parseAbi`, `encodeFunctionData`) from actions. +2. Avoid `signer.sendTransaction({ to, data })` for contract calls. +3. Prefer `ethers.getContract("")` and call methods on that contract. +4. Connect to action signer before write calls: `contract.connect(signer).myMethod()`. +5. Use `new ethers.Contract(address, abi, signer)` only as fallback when no deployment name is available. +6. For proxy contracts, check existing patterns in `contracts/deploy//*.js` (mainnet/base/sonic/etc.) and `contracts/test/**/*.js` to choose the correct binding style (`getContract`, `getContractAt("I...")`, or implementation ABI at proxy address). +7. Keep explicit `chains: [...]` guardrails for each action. +8. Prefer `logTxDetails` from `../../utils/txLogger` for transaction logging/confirmation instead of manual `log.info(tx.hash)` + `await tx.wait()`. +9. When replacing a hard-coded contract address with `ethers.getContract("")`, verify the old address equals the deployment address for that contract on the target chain; if it does not match, stop and flag it. +10. If only a 4-byte selector/call-data hash is available, resolve it using 4byte API, then confirm the resolved text signature exists in the target contract ABI before coding the method call. +11. Cron nonce safety: assume action tasks share a single signer/EOA unless explicitly configured otherwise. When adding or modifying cron jobs in `contracts/cron/cron-jobs.ts`, avoid scheduling two transaction-writing actions in the same 5-minute window to reduce nonce contention/race conditions. + +## 4byte Selector Resolution (Proposal) + +Use this when migrating a raw `sendTransaction({ to, data })` call and the method name is unclear. + +1. Extract selector: + - Selector is the first 4 bytes of calldata (first 10 hex chars including `0x`), e.g. `0x80bef06d`. +2. Query 4byte: + - `GET https://www.4byte.directory/api/v1/signatures/?hex_signature=` + - Parse `results[].text_signature`. +3. Handle ambiguity: + - If multiple candidates are returned, treat them as hypotheses only. + - Narrow using expected argument count/types from calldata length and usage context. +4. Verify against local ABI (required): + - Check `contracts/deployments//.json` ABI for exact method presence. + - Recompute selector locally from candidate text signature and ensure exact match. +5. Only then migrate: + - Replace raw calldata call with `contract.connect(signer).methodName(...)`. +6. If unresolved: + - Keep the raw call and leave a TODO with selector + 4byte candidates, or stop and flag. + +## Preferred Pattern + +```ts +import { ethers } from "hardhat"; +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +action({ + name: "example-action", + description: "Example migration target", + chains: [1], + run: async ({ signer, log }) => { + const contract = await ethers.getContract("MyContractProxy"); + const tx = await contract.connect(signer).myMethod(); + await logTxDetails(tx, "myMethod"); + }, +}); +``` + +Fallback only (if contract cannot be fetched by deployment name): + +```ts +// import { ethers } from "ethers"; +// import { abi, address } from "../../deployments/mainnet/MyContract.json"; +// const contract = new ethers.Contract(address, abi, signer); +``` + +## Review Checklist + +- Action still runs on intended chain(s) only. +- No manual calldata encoding for known contract methods. +- Contract source (deployment/address) is readable and obvious. +- For proxy targets, contract binding style matches existing deploy/test patterns for that proxy and network. +- If a hard-coded address was migrated, its value was checked against the deployment address for the chosen contract name and chain. +- Method name reflects protocol intent (better than raw selector calls). +- Transaction logging uses `logTxDetails` (or an equivalent shared helper), not ad-hoc hash logging and manual waits. +- For selector-based migrations, 4byte lookup was used and final method choice was validated against the local deployment ABI. +- For cron wiring changes, transaction-writing actions are not co-scheduled in the same 5-minute window; check `contracts/cron/cron-jobs.ts` for collisions before finalizing. diff --git a/contracts/tasks/actions/autoValidatorDeposits.ts b/contracts/tasks/actions/autoValidatorDeposits.ts new file mode 100644 index 0000000000..e2551db149 --- /dev/null +++ b/contracts/tasks/actions/autoValidatorDeposits.ts @@ -0,0 +1,24 @@ +/// + +import { types } from "hardhat/config"; +import { action } from "../lib/action"; + +const { autoValidatorDeposits } = require("../validatorCompound"); + +action({ + name: "autoValidatorDeposits", + chains: [1], + description: + "Automatically withdraw ETH/WETH from the strategy if needed for withdrawals, then deposit WETH to validators with a balance under 2030 ETH from the largest balance to the smallest", + params: (t) => { + t.addOptionalParam( + "dryrun", + "Do not send any txs to the staking strategy contract", + false, + types.boolean + ); + }, + run: async ({ signer, args }) => { + await autoValidatorDeposits({ ...args, signer }); + }, +}); diff --git a/contracts/tasks/actions/autoValidatorWithdrawals.ts b/contracts/tasks/actions/autoValidatorWithdrawals.ts new file mode 100644 index 0000000000..f6eb3e3d57 --- /dev/null +++ b/contracts/tasks/actions/autoValidatorWithdrawals.ts @@ -0,0 +1,30 @@ +/// + +import { types } from "hardhat/config"; +import { action } from "../lib/action"; + +const { autoValidatorWithdrawals } = require("../validatorCompound"); + +action({ + name: "autoValidatorWithdrawals", + chains: [1], + description: + "Automatically withdraw ETH from a validators if the Vault needs WETH for user withdrawals. Start with the validator with the smallest balance over 42.25 ETH.", + params: (t) => { + t.addOptionalParam( + "buffer", + "Withdrawal buffer in basis points. 100 = 1%", + 100, + types.int + ); + t.addOptionalParam( + "dryrun", + "Do not send any txs to the staking strategy contract", + false, + types.boolean + ); + }, + run: async ({ signer, args }) => { + await autoValidatorWithdrawals({ ...args, signer }); + }, +}); diff --git a/contracts/tasks/actions/claimBribes.ts b/contracts/tasks/actions/claimBribes.ts new file mode 100644 index 0000000000..8fc170ace6 --- /dev/null +++ b/contracts/tasks/actions/claimBribes.ts @@ -0,0 +1,86 @@ +import { ethers } from "ethers"; +import type { Logger } from "winston"; +import { logTxDetails } from "../../utils/txLogger"; +import { action } from "../lib/action"; + +const COINBASE_AERO_LOCKER_MODULE = + "0x60d3d6ec213d84dea193dbd79673340061178893"; +const OLD_GUARDIAN_MODULE = "0x26179ada0f7cb714c11a8190e1f517988c28e759"; + +const moduleLabels: Record = { + [COINBASE_AERO_LOCKER_MODULE]: "Coinbase AERO Locker Safe", + [OLD_GUARDIAN_MODULE]: "Old Guardian Safe", +}; + +const batchSizes: Record = { + [COINBASE_AERO_LOCKER_MODULE]: 50, + [OLD_GUARDIAN_MODULE]: 50, +}; + +const MODULE_ABI = [ + "function getNFTIdsLength() external view returns (uint256)", + "function fetchNFTIds() external", + "function removeAllNFTIds() external", + "function claimBribes(uint256 nftIndexStart, uint256 nftIndexEnd, bool silent) external", +]; + +async function manageNFTsOnModule( + module: ethers.Contract, + signer: ethers.Signer, + log: Logger +) { + const label = moduleLabels[module.address.toLowerCase()]; + + log.info(`Running removeAllNFTIds on module ${label}`); + let tx = await module.connect(signer).removeAllNFTIds({ gasLimit: 20000000 }); + logTxDetails(tx, "removeAllNFTIds"); + await tx.wait(); + + log.info(`Running fetchNFTIds on module ${label}`); + tx = await module.connect(signer).fetchNFTIds({ gasLimit: 20000000 }); + logTxDetails(tx, "fetchNFTIds"); + await tx.wait(); +} + +async function claimBribesFromModule( + module: ethers.Contract, + signer: ethers.Signer, + log: Logger +) { + const nftIdsLength = ( + await module.connect(signer).getNFTIdsLength() + ).toNumber(); + const batchSize = batchSizes[module.address.toLowerCase()] || 50; + const batchCount = Math.ceil(nftIdsLength / batchSize); + + log.info(`Found ${nftIdsLength} NFTs on the module`); + log.info(`Claiming bribes in ${batchCount} batches of ${batchSize}`); + + for (let i = 0; i < batchCount; i++) { + const start = i * batchSize; + const end = Math.min(start + batchSize, nftIdsLength); + + const tx = await module.connect(signer).claimBribes(start, end, true, { + gasLimit: 20000000, + }); + await logTxDetails(tx, `claimBribes (batch ${i + 1} of ${batchCount})`); + } +} + +action({ + name: "claimBribes", + description: "Claim bribes from Aerodrome veNFT lockers on Base", + chains: [8453], + run: async ({ signer, log }) => { + const modules = [COINBASE_AERO_LOCKER_MODULE, OLD_GUARDIAN_MODULE]; + + for (const moduleAddr of modules) { + const module = new ethers.Contract(moduleAddr, MODULE_ABI, signer); + log.info( + `Claiming bribes from ${moduleLabels[moduleAddr.toLowerCase()]}` + ); + await manageNFTsOnModule(module, signer, log); + await claimBribesFromModule(module, signer, log); + } + }, +}); diff --git a/contracts/tasks/actions/claimSSVRewards.ts b/contracts/tasks/actions/claimSSVRewards.ts new file mode 100644 index 0000000000..8eefdbbf23 --- /dev/null +++ b/contracts/tasks/actions/claimSSVRewards.ts @@ -0,0 +1,15 @@ +/// + +import { action } from "../lib/action"; + +const { claimSSVRewards } = require("../ssvRewards"); + +action({ + name: "claimSSVRewards", + description: "Claim SSV rewards and forward claimed SSV", + chains: [1], + run: async ({ signer, log }) => { + log.info("Claiming SSV rewards from CumulativeMerkleDrop"); + await claimSSVRewards(signer); + }, +}); diff --git a/contracts/tasks/actions/crossChainBalanceUpdateBase.ts b/contracts/tasks/actions/crossChainBalanceUpdateBase.ts new file mode 100644 index 0000000000..29b5790c61 --- /dev/null +++ b/contracts/tasks/actions/crossChainBalanceUpdateBase.ts @@ -0,0 +1,42 @@ +/// + +import addresses from "../../utils/addresses"; +import { logTxDetails } from "../../utils/txLogger"; +import { action } from "../lib/action"; + +const EXPECTED_CROSS_CHAIN_CONTROLLER = + "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866"; + +action({ + name: "crossChainBalanceUpdateBase", + description: "Send cross-chain balance update from Base", + chains: [8453], + run: async ({ signer, log, networkName }) => { + const strategyAddress = (addresses as any)[networkName] + .CrossChainRemoteStrategy; + if (!strategyAddress) { + throw new Error( + `CrossChainRemoteStrategy address missing for network ${networkName}` + ); + } + if ( + strategyAddress.toLowerCase() !== + EXPECTED_CROSS_CHAIN_CONTROLLER.toLowerCase() + ) { + throw new Error( + `CrossChainRemoteStrategy address mismatch: expected ${EXPECTED_CROSS_CHAIN_CONTROLLER}, got ${strategyAddress}` + ); + } + + const strategy = await hre.ethers.getContractAt( + "CrossChainRemoteStrategy", + strategyAddress + ); + log.info(`Calling sendBalanceUpdate on ${strategy.address}`); + + const tx = await strategy.connect(signer).sendBalanceUpdate({ + gasLimit: 1000000, + }); + await logTxDetails(tx, "sendBalanceUpdate on CrossChainRemoteStrategy"); + }, +}); diff --git a/contracts/tasks/actions/crossChainBalanceUpdateHyperevm.ts b/contracts/tasks/actions/crossChainBalanceUpdateHyperevm.ts new file mode 100644 index 0000000000..994c5c8f2e --- /dev/null +++ b/contracts/tasks/actions/crossChainBalanceUpdateHyperevm.ts @@ -0,0 +1,42 @@ +/// + +import addresses from "../../utils/addresses"; +import { logTxDetails } from "../../utils/txLogger"; +import { action } from "../lib/action"; + +const EXPECTED_CROSS_CHAIN_CONTROLLER = + "0xE0228DB13F8C4Eb00fD1e08e076b09eF5cD0EA1e"; + +action({ + name: "crossChainBalanceUpdateHyperevm", + description: "Send cross-chain balance update from HyperEVM", + chains: [999], + run: async ({ signer, log, networkName }) => { + const strategyAddress = (addresses as any)[networkName] + .CrossChainRemoteStrategy; + if (!strategyAddress) { + throw new Error( + `CrossChainRemoteStrategy address missing for network ${networkName}` + ); + } + if ( + strategyAddress.toLowerCase() !== + EXPECTED_CROSS_CHAIN_CONTROLLER.toLowerCase() + ) { + throw new Error( + `CrossChainRemoteStrategy address mismatch: expected ${EXPECTED_CROSS_CHAIN_CONTROLLER}, got ${strategyAddress}` + ); + } + + const strategy = await hre.ethers.getContractAt( + "CrossChainRemoteStrategy", + strategyAddress + ); + log.info(`Calling sendBalanceUpdate on ${strategy.address}`); + + const tx = await strategy.connect(signer).sendBalanceUpdate({ + gasLimit: 1000000, + }); + await logTxDetails(tx, "sendBalanceUpdate on CrossChainRemoteStrategy"); + }, +}); diff --git a/contracts/tasks/actions/crossChainRelay.ts b/contracts/tasks/actions/crossChainRelay.ts new file mode 100644 index 0000000000..b7f7855e77 --- /dev/null +++ b/contracts/tasks/actions/crossChainRelay.ts @@ -0,0 +1,60 @@ +import { ethers } from "ethers"; +import { configuration } from "../../utils/cctp"; +import { keyValueStoreLocalClient } from "../../utils/defender"; +import { getNetworkName } from "../../utils/hardhat-helpers"; +import { processCctpBridgeTransactions } from "../crossChain"; +import { action } from "../lib/action"; + +action({ + name: "crossChainRelay", + description: "Relay CCTP bridge transactions between mainnet and Base", + chains: [1, 8453], + run: async ({ signer, chainId, log }) => { + let sourceProvider: ethers.providers.JsonRpcProvider; + + if (chainId === 1) { + if (!process.env.BASE_PROVIDER_URL) { + throw new Error("BASE_PROVIDER_URL env var required"); + } + sourceProvider = new ethers.providers.JsonRpcProvider( + process.env.BASE_PROVIDER_URL + ); + } else { + if (!process.env.PROVIDER_URL) { + throw new Error("PROVIDER_URL env var required"); + } + sourceProvider = new ethers.providers.JsonRpcProvider( + process.env.PROVIDER_URL + ); + } + + const networkName = await getNetworkName(sourceProvider); + + let config: any; + if (networkName === "mainnet") { + config = configuration.mainnetBaseMorpho.mainnet; + } else if (networkName === "base") { + config = configuration.mainnetBaseMorpho.base; + } else { + throw new Error(`Unsupported source network: ${networkName}`); + } + + log.info(`Relaying CCTP from ${networkName}`); + const store = keyValueStoreLocalClient({ + _storePath: ".store/crossChainRelay.json", + }); + + await processCctpBridgeTransactions({ + destinationChainSigner: signer, + sourceChainProvider: sourceProvider, + store, + networkName, + blockLookback: config.blockLookback, + cctpDestinationDomainId: config.cctpDestinationDomainId, + cctpSourceDomainId: config.cctpSourceDomainId, + cctpIntegrationContractAddress: config.cctpIntegrationContractAddress, + cctpIntegrationContractAddressDestination: + config.cctpIntegrationContractAddressDestination, + }); + }, +}); diff --git a/contracts/tasks/actions/crossChainRelayHyperEVM.ts b/contracts/tasks/actions/crossChainRelayHyperEVM.ts new file mode 100644 index 0000000000..0729a48614 --- /dev/null +++ b/contracts/tasks/actions/crossChainRelayHyperEVM.ts @@ -0,0 +1,60 @@ +import { ethers } from "ethers"; +import { configuration } from "../../utils/cctp"; +import { keyValueStoreLocalClient } from "../../utils/defender"; +import { getNetworkName } from "../../utils/hardhat-helpers"; +import { processCctpBridgeTransactions } from "../crossChain"; +import { action } from "../lib/action"; + +action({ + name: "crossChainRelayHyperEVM", + description: "Relay CCTP bridge transactions between mainnet and HyperEVM", + chains: [1, 999], + run: async ({ signer, chainId, log }) => { + let sourceProvider: ethers.providers.JsonRpcProvider; + + if (chainId === 1) { + if (!process.env.HYPEREVM_PROVIDER_URL) { + throw new Error("HYPEREVM_PROVIDER_URL env var required"); + } + sourceProvider = new ethers.providers.JsonRpcProvider( + process.env.HYPEREVM_PROVIDER_URL + ); + } else { + if (!process.env.PROVIDER_URL) { + throw new Error("PROVIDER_URL env var required"); + } + sourceProvider = new ethers.providers.JsonRpcProvider( + process.env.PROVIDER_URL + ); + } + + const networkName = await getNetworkName(sourceProvider); + + let config: any; + if (networkName === "mainnet") { + config = configuration.mainnetHyperEVMMorpho.mainnet; + } else if (networkName === "hyperevm") { + config = configuration.mainnetHyperEVMMorpho.hyperevm; + } else { + throw new Error(`Unsupported source network: ${networkName}`); + } + + log.info(`Relaying CCTP from ${networkName}`); + const store = keyValueStoreLocalClient({ + _storePath: ".store/crossChainRelayHyperEVM.json", + }); + + await processCctpBridgeTransactions({ + destinationChainSigner: signer, + sourceChainProvider: sourceProvider, + store, + networkName, + blockLookback: config.blockLookback, + cctpDestinationDomainId: config.cctpDestinationDomainId, + cctpSourceDomainId: config.cctpSourceDomainId, + cctpIntegrationContractAddress: config.cctpIntegrationContractAddress, + cctpIntegrationContractAddressDestination: + config.cctpIntegrationContractAddressDestination, + }); + }, +}); diff --git a/contracts/tasks/actions/doAccounting.ts b/contracts/tasks/actions/doAccounting.ts new file mode 100644 index 0000000000..e9cf303646 --- /dev/null +++ b/contracts/tasks/actions/doAccounting.ts @@ -0,0 +1,79 @@ +import { ethers } from "ethers"; +import { types } from "hardhat/config"; +import type { Logger } from "winston"; +import { address as hoodiConsolidationControllerAddress } from "../../deployments/hoodi/ConsolidationController.json"; +import { + abi as consolidationControllerAbi, + address as mainnetConsolidationControllerAddress, +} from "../../deployments/mainnet/ConsolidationController.json"; +import addresses from "../../utils/addresses"; +import { logTxDetails } from "../../utils/txLogger"; +import { action } from "../lib/action"; + +async function doAccountingForProxy( + proxyName: string, + networkName: string, + signer: ethers.Signer, + consolidationController: ethers.Contract, + log: Logger +) { + const nativeStakingProxyAddress = (addresses as any)[networkName][proxyName]; + if (!nativeStakingProxyAddress) { + throw new Error(`Failed to resolve ${proxyName} on ${networkName}`); + } + log.info(`Resolved ${proxyName} address to ${nativeStakingProxyAddress}`); + + const tx = await consolidationController + .connect(signer) + .doAccounting(nativeStakingProxyAddress); + await logTxDetails(tx, `doAccounting for ${proxyName} via controller`); +} + +action({ + name: "doAccounting", + description: + "Account for consensus rewards and validator exits in the Native Staking Strategy", + chains: [1, 560048], + params: (t) => { + t.addOptionalParam( + "index", + "The number of the Native Staking Contract deployed.", + undefined, + types.int + ); + t.addOptionalParam( + "consol", + "Call the consolidation controller instead of the strategy", + false, + types.boolean + ); + }, + run: async ({ signer, networkName, log }) => { + const controllerAddress = + networkName === "mainnet" + ? mainnetConsolidationControllerAddress + : hoodiConsolidationControllerAddress; + log.info(`ConsolidationController: ${controllerAddress}`); + + const consolidationController = new ethers.Contract( + controllerAddress, + consolidationControllerAbi, + signer + ); + + await doAccountingForProxy( + "NativeStakingSSVStrategy2Proxy", + networkName, + signer, + consolidationController, + log + ); + await doAccountingForProxy( + "NativeStakingSSVStrategy3Proxy", + networkName, + signer, + consolidationController, + log + ); + }, +}); diff --git a/contracts/tasks/actions/executeGovernorSixProposal.ts b/contracts/tasks/actions/executeGovernorSixProposal.ts new file mode 100644 index 0000000000..0d4391f70b --- /dev/null +++ b/contracts/tasks/actions/executeGovernorSixProposal.ts @@ -0,0 +1,43 @@ +/// + +import { types } from "hardhat/config"; +import { action } from "../lib/action"; +import addresses from "../../utils/addresses"; +import { logTxDetails } from "../../utils/txLogger"; + +const governorSixAbi = [ + "function queue(uint256 proposalId) external", + "function execute(uint256 proposalId) external payable", +]; + +action({ + name: "executeGovernorSixProposal", + description: "Execute a GovernorSix proposal on mainnet", + chains: [1], + params: (t) => { + t.addParam( + "propid", + "GovernorSix proposal id to execute", + undefined, + types.string + ); + }, + run: async ({ signer, log, args }) => { + const governorSixAddress = (addresses as any).mainnet.GovernorSix; + const governorSix = await hre.ethers.getContractAt( + governorSixAbi, + governorSixAddress + ); + + const proposalId = args.propid; + if (!/^[0-9]+$/.test(proposalId)) { + throw new Error(`Invalid proposalId: ${proposalId}`); + } + + log.info( + `Executing proposal ${proposalId} on GovernorSix ${governorSixAddress}` + ); + const tx = await governorSix.connect(signer).execute(proposalId); + await logTxDetails(tx, `execute(${proposalId})`); + }, +}); diff --git a/contracts/tasks/actions/harvest.ts b/contracts/tasks/actions/harvest.ts new file mode 100644 index 0000000000..8a0f2cdb6c --- /dev/null +++ b/contracts/tasks/actions/harvest.ts @@ -0,0 +1,61 @@ +import { ethers } from "ethers"; +import addresses from "../../utils/addresses"; +import { + claimStrategyRewards, + shouldHarvestFromNativeStakingStrategy, +} from "../../utils/harvest"; +import { logTxDetails } from "../../utils/txLogger"; +import { action } from "../lib/action"; +import { claimMerklRewards } from "../merkl"; + +const harvesterAbi = require("../../abi/harvester.json"); + +action({ + name: "harvest", + description: "Harvest and swap rewards from native staking strategies", + chains: [1], + run: async ({ signer, log }) => { + const harvesterAddress = addresses.mainnet.OETHHarvesterSimpleProxy; + log.info(`Resolved OETH Harvester Simple address to ${harvesterAddress}`); + const harvester = new ethers.Contract( + harvesterAddress, + harvesterAbi, + signer + ); + + const nativeStakingStrategies = [ + addresses.mainnet.NativeStakingSSVStrategy2Proxy, + // TODO: NativeStakingSSVStrategy3Proxy will soon be obsolete + addresses.mainnet.NativeStakingSSVStrategy3Proxy, + ]; + + const strategiesToHarvest: string[] = []; + for (const strategy of nativeStakingStrategies) { + log.info(`Checking strategy ${strategy}`); + const shouldHarvest = await shouldHarvestFromNativeStakingStrategy( + strategy, + signer + ); + if (shouldHarvest) { + log.info(`Will harvest from ${strategy}`); + strategiesToHarvest.push(strategy); + } + } + + if (strategiesToHarvest.length > 0) { + const connection = harvester.connect(signer); + const tx = await connection["harvestAndTransfer(address[])"]( + strategiesToHarvest + ); + await logTxDetails(tx, "harvestAndTransfer"); + } else { + log.info("No native staking strategies require harvesting at this time"); + } + + await claimMerklRewards( + addresses.mainnet.MorphoOUSDv2StrategyProxy, + signer + ); + await claimStrategyRewards(signer); + }, +}); diff --git a/contracts/tasks/actions/healthcheck.ts b/contracts/tasks/actions/healthcheck.ts new file mode 100644 index 0000000000..0bf36ef16c --- /dev/null +++ b/contracts/tasks/actions/healthcheck.ts @@ -0,0 +1,24 @@ +import { action } from "../lib/action"; + +action({ + name: "healthcheck", + description: + "Verify the action execution pipeline (signer, network, logging)", + run: async ({ log, signer, chainId, networkName }) => { + log.info(`Node version: ${process.version}`); + log.info(`Network: ${networkName} (${chainId})`); + + const address = await signer.getAddress(); + log.info(`Signer address: ${address}`); + + const balance = await signer.provider!.getBalance(address); + log.info(`Signer balance: ${balance.toString()} wei`); + + log.info( + `AWS KMS available: ${!!( + process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY + )}` + ); + log.info("Healthcheck passed"); + }, +}); diff --git a/contracts/tasks/actions/manageBribeOnSonic.ts b/contracts/tasks/actions/manageBribeOnSonic.ts new file mode 100644 index 0000000000..13f2581ab1 --- /dev/null +++ b/contracts/tasks/actions/manageBribeOnSonic.ts @@ -0,0 +1,30 @@ +import { ethers } from "ethers"; +import { logTxDetails } from "../../utils/txLogger"; +import { action } from "../lib/action"; + +const poolBoosterSwapXAbi = require("../../abi/poolBoosterSwapX.json"); +const poolBoosterCentralRegistryAbi = require("../../abi/poolBoosterCentralRegistry.json"); + +action({ + name: "manageBribeOnSonic", + description: "Manage bribes on all Sonic pool booster factories", + chains: [146], + run: async ({ signer, log }) => { + const poolBoosterCentralRegistryProxyAddress = + "0x4F3B656Aa5Fb5E708bF7B63D6ff71623eb4a218A"; + const poolBoosterCentralRegistryProxy = new ethers.Contract( + poolBoosterCentralRegistryProxyAddress, + poolBoosterCentralRegistryAbi, + signer + ); + + const factories = await poolBoosterCentralRegistryProxy.getAllFactories(); + log.info(`Factories: ${factories}`); + + for (const f of factories) { + const factory = new ethers.Contract(f, poolBoosterSwapXAbi, signer); + const tx = await factory.connect(signer).bribeAll([]); + await logTxDetails(tx, `Bribed all pools in factory ${f}`); + } + }, +}); diff --git a/contracts/tasks/actions/manageBribes.ts b/contracts/tasks/actions/manageBribes.ts new file mode 100644 index 0000000000..2fe85d97eb --- /dev/null +++ b/contracts/tasks/actions/manageBribes.ts @@ -0,0 +1,42 @@ +import { types } from "hardhat/config"; +import { action } from "../lib/action"; +import { manageBribes } from "../poolBooster"; + +action({ + name: "manageBribes", + description: + "Calls manageBribes on the CurvePoolBoosterBribesModule and calculates the rewards per vote based on the target efficiency", + chains: [1], + params: (t) => { + t.addOptionalParam( + "efficiency", + "Target efficiency (0-10, e.g. 1 for 100%, 0.5 for 50%)", + "1", + types.string + ); + t.addOptionalParam( + "skipRewardPerVote", + "Skip setting RewardPerVote (pass array of zeros)", + false, + types.boolean + ); + t.addOptionalParam( + "chunkSize", + "Number of pool boosters to manage per transaction", + 4, + types.int + ); + }, + run: async ({ signer, log, args }) => { + log.info( + `Managing max reward per vote with target efficiency ${args.efficiency}, skip reward per vote ${args.skipRewardPerVote}, chunk size ${args.chunkSize}` + ); + await manageBribes({ + signer, + provider: signer.provider!, + targetEfficiency: args.efficiency, + skipRewardPerVote: args.skipRewardPerVote, + chunkSize: args.chunkSize, + }); + }, +}); diff --git a/contracts/tasks/actions/manageMerklBribes.ts b/contracts/tasks/actions/manageMerklBribes.ts new file mode 100644 index 0000000000..ff961de86d --- /dev/null +++ b/contracts/tasks/actions/manageMerklBribes.ts @@ -0,0 +1,32 @@ +import { types } from "hardhat/config"; +import { action } from "../lib/action"; +import { manageMerklBribes } from "../merklPoolBooster"; + +action({ + name: "manageMerklBribes", + description: + "Calls bribeAll on the MerklPoolBoosterBribesModule through the Gnosis Safe", + chains: [1, 8453], + params: (t) => { + t.addOptionalParam( + "exclusionList", + "Comma-separated list of pool booster addresses to exclude", + "", + types.string + ); + }, + run: async ({ signer, log, args }) => { + const exclusionList = args.exclusionList + ? args.exclusionList.split(",").map((s: string) => s.trim()) + : []; + + log.info( + `Calling bribeAll with exclusion list: [${exclusionList.join(", ")}]` + ); + await manageMerklBribes({ + provider: signer.provider!, + signer, + exclusionList, + }); + }, +}); diff --git a/contracts/tasks/actions/managePassThrough.ts b/contracts/tasks/actions/managePassThrough.ts new file mode 100644 index 0000000000..9b4a97078d --- /dev/null +++ b/contracts/tasks/actions/managePassThrough.ts @@ -0,0 +1,11 @@ +import { transferTokens } from "../../utils/managePassThrough"; +import { action } from "../lib/action"; + +action({ + name: "managePassThrough", + description: "Transfer tokens via pass-through mechanism", + chains: [1], + run: async ({ signer }) => { + await transferTokens({ signer }); + }, +}); diff --git a/contracts/tasks/actions/ognClaimAndForwardRewards.ts b/contracts/tasks/actions/ognClaimAndForwardRewards.ts new file mode 100644 index 0000000000..67fc891b5d --- /dev/null +++ b/contracts/tasks/actions/ognClaimAndForwardRewards.ts @@ -0,0 +1,33 @@ +/// + +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const MODULE_DEPLOYMENTS = [ + "CollectXOGNRewardsModule1", + "CollectXOGNRewardsModule2", + "CollectXOGNRewardsModule3", + "CollectXOGNRewardsModule4", + "CollectXOGNRewardsModule5", + "CollectXOGNRewardsModule6", +] as const; + +action({ + name: "ognClaimAndForwardRewards", + description: "Claim and forward OGN rewards from all modules", + chains: [1], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + + for (const deploymentName of MODULE_DEPLOYMENTS) { + const module = await ethers.getContract(deploymentName); + log.info( + `Calling collectRewards on ${deploymentName} at ${module.address}` + ); + const tx = await module.connect(signer).collectRewards({ + gasLimit: 500000, + }); + await logTxDetails(tx, `collectRewards on ${deploymentName}`); + } + }, +}); diff --git a/contracts/tasks/actions/otokenOethbHarvest.ts b/contracts/tasks/actions/otokenOethbHarvest.ts new file mode 100644 index 0000000000..a0090a5756 --- /dev/null +++ b/contracts/tasks/actions/otokenOethbHarvest.ts @@ -0,0 +1,40 @@ +/// + +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const HARVESTER_PROXY_DEPLOYMENT = "OETHBaseHarvesterProxy"; +const STRATEGY_PROXY_DEPLOYMENTS = [ + "OETHBaseCurveAMOProxy", + "AerodromeAMOStrategyProxy", +] as const; + +action({ + name: "otokenOethbHarvest", + description: "Harvest strategies on Base OETHb", + chains: [8453], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + const harvesterProxy = await ethers.getContract(HARVESTER_PROXY_DEPLOYMENT); + const harvester = await ethers.getContractAt( + "SuperOETHHarvester", + harvesterProxy.address + ); + const strategies = await Promise.all( + STRATEGY_PROXY_DEPLOYMENTS.map(async (deploymentName) => { + const strategy = await ethers.getContract(deploymentName); + return strategy.address; + }) + ); + + log.info( + `Calling harvestAndTransfer on ${HARVESTER_PROXY_DEPLOYMENT} at ${harvester.address} for ${strategies.length} strategy(ies)` + ); + const connectedHarvester = harvester.connect(signer); + const tx = await connectedHarvester["harvestAndTransfer(address[])"]( + strategies, + { gasLimit: 800000 } + ); + await logTxDetails(tx, "harvestAndTransfer"); + }, +}); diff --git a/contracts/tasks/actions/otokenOethbRebase.ts b/contracts/tasks/actions/otokenOethbRebase.ts new file mode 100644 index 0000000000..b183f051e3 --- /dev/null +++ b/contracts/tasks/actions/otokenOethbRebase.ts @@ -0,0 +1,21 @@ +/// + +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const VAULT_PROXY_DEPLOYMENT = "OETHBaseVaultProxy"; + +action({ + name: "otokenOethbRebase", + description: "Rebase OETHb vault on Base", + chains: [8453], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + const vaultProxy = await ethers.getContract(VAULT_PROXY_DEPLOYMENT); + const vault = await ethers.getContractAt("IVault", vaultProxy.address); + + log.info(`Calling rebase on ${VAULT_PROXY_DEPLOYMENT} at ${vault.address}`); + const tx = await vault.connect(signer).rebase(); + await logTxDetails(tx, "rebase"); + }, +}); diff --git a/contracts/tasks/actions/otokenOethbUpdateWoethPrice.ts b/contracts/tasks/actions/otokenOethbUpdateWoethPrice.ts new file mode 100644 index 0000000000..aa488ac85f --- /dev/null +++ b/contracts/tasks/actions/otokenOethbUpdateWoethPrice.ts @@ -0,0 +1,28 @@ +/// + +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const STRATEGY_PROXY_DEPLOYMENT = "BridgedWOETHStrategyProxy"; + +action({ + name: "otokenOethbUpdateWoethPrice", + description: "Update WOETH price on Base", + chains: [8453], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + const strategyProxy = await ethers.getContract(STRATEGY_PROXY_DEPLOYMENT); + const strategy = await ethers.getContractAt( + "BridgedWOETHStrategy", + strategyProxy.address + ); + + log.info( + `Calling updateWOETHOraclePrice on ${STRATEGY_PROXY_DEPLOYMENT} at ${strategy.address}` + ); + const tx = await strategy.connect(signer).updateWOETHOraclePrice({ + gasLimit: 200000, + }); + await logTxDetails(tx, "updateWOETHOraclePrice"); + }, +}); diff --git a/contracts/tasks/actions/otokenOethpAddWithdrawalQueueLiquidity.ts b/contracts/tasks/actions/otokenOethpAddWithdrawalQueueLiquidity.ts new file mode 100644 index 0000000000..ffe7187575 --- /dev/null +++ b/contracts/tasks/actions/otokenOethpAddWithdrawalQueueLiquidity.ts @@ -0,0 +1,25 @@ +/// + +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const OETHP_VAULT_PROXY_DEPLOYMENT = "OETHPlumeVaultProxy"; + +action({ + name: "otokenOethpAddWithdrawalQueueLiquidity", + description: "Add liquidity to Plume OETH withdrawal queue", + chains: [98866], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + const vaultProxy = await ethers.getContract(OETHP_VAULT_PROXY_DEPLOYMENT); + const vault = await ethers.getContractAt("IVault", vaultProxy.address); + + log.info( + `Calling addWithdrawalQueueLiquidity on ${OETHP_VAULT_PROXY_DEPLOYMENT} at ${vault.address}` + ); + const tx = await vault + .connect(signer) + .addWithdrawalQueueLiquidity({ gasLimit: 400000 }); + await logTxDetails(tx, "addWithdrawalQueueLiquidity"); + }, +}); diff --git a/contracts/tasks/actions/otokenOsCollectAndRelease.ts b/contracts/tasks/actions/otokenOsCollectAndRelease.ts new file mode 100644 index 0000000000..a1c1df37cd --- /dev/null +++ b/contracts/tasks/actions/otokenOsCollectAndRelease.ts @@ -0,0 +1,44 @@ +/// + +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const OS_VAULT_PROXY_DEPLOYMENT = "OSonicVaultProxy"; +const OS_HARVESTER_PROXY_DEPLOYMENT = "OSonicHarvesterProxy"; +const STRATEGY_PROXY_DEPLOYMENT = "SonicSwapXAMOStrategyProxy"; + +action({ + name: "otokenOsCollectAndRelease", + description: "Rebase OS vault and harvest on Sonic", + chains: [146], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + const vaultProxy = await ethers.getContract(OS_VAULT_PROXY_DEPLOYMENT); + const vault = await ethers.getContractAt("IVault", vaultProxy.address); + + const harvesterProxy = await ethers.getContract( + OS_HARVESTER_PROXY_DEPLOYMENT + ); + const harvester = await ethers.getContractAt( + "OSonicHarvester", + harvesterProxy.address + ); + const strategyProxy = await ethers.getContract(STRATEGY_PROXY_DEPLOYMENT); + + log.info( + `Calling rebase on ${OS_VAULT_PROXY_DEPLOYMENT} at ${vault.address}` + ); + const rebaseTx = await vault.connect(signer).rebase({ gasLimit: 400000 }); + await logTxDetails(rebaseTx, "rebase"); + + log.info( + `Calling harvestAndTransfer on ${OS_HARVESTER_PROXY_DEPLOYMENT} at ${harvester.address} for strategy ${strategyProxy.address}` + ); + const connectedHarvester = harvester.connect(signer); + const harvestTx = await connectedHarvester["harvestAndTransfer(address)"]( + strategyProxy.address, + { gasLimit: 400000 } + ); + await logTxDetails(harvestTx, "harvestAndTransfer"); + }, +}); diff --git a/contracts/tasks/actions/otokenOsSonicRestakeRewards.ts b/contracts/tasks/actions/otokenOsSonicRestakeRewards.ts new file mode 100644 index 0000000000..0934a395d2 --- /dev/null +++ b/contracts/tasks/actions/otokenOsSonicRestakeRewards.ts @@ -0,0 +1,29 @@ +/// + +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const SONIC_STAKING_STRATEGY_PROXY_DEPLOYMENT = "SonicStakingStrategyProxy"; +const VALIDATOR_IDS = [15n, 16n, 17n, 18n, 45n]; + +action({ + name: "otokenOsSonicRestakeRewards", + description: "Restake rewards for Sonic validators", + chains: [146], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + const strategyProxy = await ethers.getContract( + SONIC_STAKING_STRATEGY_PROXY_DEPLOYMENT + ); + const strategy = await ethers.getContractAt( + "SonicStakingStrategy", + strategyProxy.address + ); + + log.info(`Restaking rewards for validators: ${VALIDATOR_IDS.join(", ")}`); + const tx = await strategy + .connect(signer) + .restakeRewards(VALIDATOR_IDS, { gasLimit: 300000 }); + await logTxDetails(tx, "restakeRewards"); + }, +}); diff --git a/contracts/tasks/actions/otokenOusdAutoWithdrawal.ts b/contracts/tasks/actions/otokenOusdAutoWithdrawal.ts new file mode 100644 index 0000000000..0f55a38737 --- /dev/null +++ b/contracts/tasks/actions/otokenOusdAutoWithdrawal.ts @@ -0,0 +1,23 @@ +/// + +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const MODULE_DEPLOYMENT = "AutoWithdrawalModule"; + +action({ + name: "otokenOusdAutoWithdrawal", + description: "Auto-process OUSD withdrawals", + chains: [1], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + const autoWithdrawalModule = await ethers.getContract(MODULE_DEPLOYMENT); + log.info( + `Calling fundWithdrawals on ${MODULE_DEPLOYMENT} at ${autoWithdrawalModule.address}` + ); + const tx = await autoWithdrawalModule.connect(signer).fundWithdrawals({ + gasLimit: 4000000, + }); + await logTxDetails(tx, `fundWithdrawals on ${MODULE_DEPLOYMENT}`); + }, +}); diff --git a/contracts/tasks/actions/otokenOusdOethRebase.ts b/contracts/tasks/actions/otokenOusdOethRebase.ts new file mode 100644 index 0000000000..255dff0cf3 --- /dev/null +++ b/contracts/tasks/actions/otokenOusdOethRebase.ts @@ -0,0 +1,45 @@ +import { action } from "../lib/action"; +import { logTxDetails } from "../../utils/txLogger"; + +const GAS_MULTIPLIER = 1.1; + +action({ + name: "otokenOusdOethRebase", + description: + "Rebase both OETH (collectAndRebase) and OUSD (rebase) on mainnet", + chains: [1], + run: async ({ signer, log }) => { + const ethers = hre.ethers; + const oethDripperProxy = await ethers.getContract( + "OETHFixedRateDripperProxy" + ); + const vaultProxy = await ethers.getContract("VaultProxy"); + const oethDripper = await ethers.getContractAt( + "IDripper", + oethDripperProxy.address + ); + const ousdVault = await ethers.getContractAt("IVault", vaultProxy.address); + + const oethDripperWithSigner = oethDripper.connect(signer); + const ousdVaultWithSigner = ousdVault.connect(signer); + + // OETH collectAndRebase with gas estimation + 10% buffer + log.info("Estimating gas for OETH collectAndRebase"); + const oethGas = await oethDripperWithSigner.estimateGas.collectAndRebase(); + const oethGasLimit = oethGas.mul(Math.floor(GAS_MULTIPLIER * 100)).div(100); + const oethTx = await oethDripperWithSigner.collectAndRebase({ + gasLimit: oethGasLimit, + }); + await logTxDetails( + oethTx, + `collectAndRebase (gasLimit: ${oethGasLimit.toString()})` + ); + + // OUSD rebase with gas estimation + 10% buffer + log.info("Estimating gas for OUSD rebase"); + const ousdGas = await ousdVaultWithSigner.estimateGas.rebase(); + const ousdGasLimit = ousdGas.mul(Math.floor(GAS_MULTIPLIER * 100)).div(100); + const ousdTx = await ousdVaultWithSigner.rebase({ gasLimit: ousdGasLimit }); + await logTxDetails(ousdTx, `rebase (gasLimit: ${ousdGasLimit.toString()})`); + }, +}); diff --git a/contracts/tasks/actions/queueGovernorSixProposal.ts b/contracts/tasks/actions/queueGovernorSixProposal.ts new file mode 100644 index 0000000000..e3dc3e4176 --- /dev/null +++ b/contracts/tasks/actions/queueGovernorSixProposal.ts @@ -0,0 +1,43 @@ +/// + +import { types } from "hardhat/config"; +import { action } from "../lib/action"; +import addresses from "../../utils/addresses"; +import { logTxDetails } from "../../utils/txLogger"; + +const governorSixAbi = [ + "function queue(uint256 proposalId) external", + "function execute(uint256 proposalId) external payable", +]; + +action({ + name: "queueGovernorSixProposal", + description: "Queue a GovernorSix proposal on mainnet", + chains: [1], + params: (t) => { + t.addParam( + "propid", + "GovernorSix proposal id to queue", + undefined, + types.string + ); + }, + run: async ({ signer, log, args }) => { + const governorSixAddress = (addresses as any).mainnet.GovernorSix; + const governorSix = await hre.ethers.getContractAt( + governorSixAbi, + governorSixAddress + ); + + const proposalId = args.propid; + if (!/^[0-9]+$/.test(proposalId)) { + throw new Error(`Invalid proposalId: ${proposalId}`); + } + + log.info( + `Queueing proposal ${proposalId} on GovernorSix ${governorSixAddress}` + ); + const tx = await governorSix.connect(signer).queue(proposalId); + await logTxDetails(tx, `queue(${proposalId})`); + }, +}); diff --git a/contracts/tasks/actions/registerValidators.ts b/contracts/tasks/actions/registerValidators.ts new file mode 100644 index 0000000000..9eb3694ad9 --- /dev/null +++ b/contracts/tasks/actions/registerValidators.ts @@ -0,0 +1,109 @@ +import { ethers } from "ethers"; +import { types } from "hardhat/config"; +import addresses from "../../utils/addresses"; +import { keyValueStoreLocalClient } from "../../utils/defender"; +import { registerValidators } from "../../utils/validator"; +import { action } from "../lib/action"; + +const nativeStakingStrategyAbi = require("../../abi/native_staking_SSV_strategy.json"); +const IWETH9Abi = require("../../abi/IWETH9.json"); + +action({ + name: "registerValidators", + description: + "Creates the required amount of new SSV validators and stakes ETH", + chains: [1, 560048], + params: (t) => { + t.addOptionalParam( + "days", + "SSV Cluster operational time in days", + 2, + types.int + ); + t.addOptionalParam( + "validators", + "The number of validators to register. defaults to the max that can be registered", + undefined, + types.int + ); + t.addOptionalParam("clear", "Clear storage", false, types.boolean); + t.addOptionalParam( + "eth", + "Override the days option and set the amount of ETH to deposit to the cluster.", + undefined, + types.float + ); + t.addOptionalParam( + "uuid", + "uuid of P2P's request SSV validator API call.", + undefined, + types.string + ); + t.addOptionalParam( + "index", + "The number of the Native Staking Contract deployed.", + undefined, + types.int + ); + }, + run: async ({ signer, chainId, networkName, log, args }) => { + const store = keyValueStoreLocalClient({ + _storePath: ".store/registerValidators.json", + }); + + const nativeStakingProxyAddress = (addresses as any)[networkName] + .NativeStakingSSVStrategy3Proxy; + log.info(`NativeStakingStrategy: ${nativeStakingProxyAddress}`); + const nativeStakingStrategy = new ethers.Contract( + nativeStakingProxyAddress, + nativeStakingStrategyAbi, + signer + ); + + const wethAddress = (addresses as any)[networkName].WETH; + log.info(`WETH: ${wethAddress}`); + const WETH = new ethers.Contract(wethAddress, IWETH9Abi, signer); + + const feeAccumulatorAddress = + await nativeStakingStrategy.FEE_ACCUMULATOR_ADDRESS(); + + const p2p_api_key = + chainId === 1 + ? process.env.P2P_MAINNET_API_KEY + : process.env.P2P_HOLESKY_API_KEY; + if (!p2p_api_key) { + throw new Error( + "Secret with P2P API key not set. Add P2P_MAINNET_API_KEY or P2P_HOLESKY_API_KEY" + ); + } + const p2p_base_url = + chainId === 1 ? "api.p2p.org" : "api-test-holesky.p2p.org"; + + const awsS3AccessKeyId = process.env.AWS_ACCESS_S3_KEY_ID; + const awsS3SecretAccessKeyId = process.env.AWS_SECRET_S3_ACCESS_KEY; + const s3BucketName = process.env.VALIDATOR_KEYS_S3_BUCKET_NAME; + + if (!awsS3AccessKeyId) throw new Error("AWS_ACCESS_S3_KEY_ID not set"); + if (!awsS3SecretAccessKeyId) + throw new Error("AWS_SECRET_S3_ACCESS_KEY not set"); + if (!s3BucketName) throw new Error("VALIDATOR_KEYS_S3_BUCKET_NAME not set"); + + await registerValidators({ + signer, + store, + nativeStakingStrategy, + WETH, + feeAccumulatorAddress, + p2p_api_key, + p2p_base_url, + validatorSpawnOperationalPeriodInDays: args.days, + clear: args.clear, + uuid: args.uuid, + maxValidatorsToRegister: args.validators, + ethAmount: args.eth, + awsS3AccessKeyId, + awsS3SecretAccessKeyId, + s3BucketName, + }); + }, +}); diff --git a/contracts/tasks/actions/relayCCTPMessage.ts b/contracts/tasks/actions/relayCCTPMessage.ts new file mode 100644 index 0000000000..d6e88327e7 --- /dev/null +++ b/contracts/tasks/actions/relayCCTPMessage.ts @@ -0,0 +1,60 @@ +/// + +import path from "path"; +import { types } from "hardhat/config"; +import { configuration } from "../../utils/cctp"; +import { keyValueStoreLocalClient } from "../../utils/defender"; +import { processCctpBridgeTransactions } from "../crossChain"; +import { action } from "../lib/action"; + +action({ + name: "relayCCTPMessage", + description: + "Fetches CCTP attested Messages via Circle Gateway API and relays it to the integrator contract", + chains: [1, 8453], + params: (t) => { + t.addOptionalParam( + "block", + "Override the block number at which the message emission transaction happened", + undefined, + types.int + ); + t.addOptionalParam( + "dryrun", + "Do not call verifyBalances on the strategy contract. Just log the params including the proofs", + false, + types.boolean + ); + }, + run: async ({ signer, networkName, args }) => { + const storeFilePath = path.join( + __dirname, + "..", + `.localKeyValueStorage.${networkName}` + ); + const store = keyValueStoreLocalClient({ _storePath: storeFilePath }); + + let config; + if (networkName === "mainnet") { + config = configuration.mainnetBaseMorpho.mainnet; + } else if (networkName === "base") { + config = configuration.mainnetBaseMorpho.base; + } else { + throw new Error(`Unsupported network name: ${networkName}`); + } + + await processCctpBridgeTransactions({ + ...args, + destinationChainSigner: signer, + sourceChainProvider: hre.ethers.provider, + store, + networkName, + blockLookback: config.blockLookback, + cctpDestinationDomainId: config.cctpDestinationDomainId, + cctpSourceDomainId: config.cctpSourceDomainId, + cctpIntegrationContractAddress: config.cctpIntegrationContractAddress, + cctpIntegrationContractAddressDestination: + config.cctpIntegrationContractAddressDestination, + }); + }, +}); diff --git a/contracts/tasks/actions/snapBalances.ts b/contracts/tasks/actions/snapBalances.ts new file mode 100644 index 0000000000..4ee7a0bdd9 --- /dev/null +++ b/contracts/tasks/actions/snapBalances.ts @@ -0,0 +1,23 @@ +/// + +import { types } from "hardhat/config"; +import { action } from "../lib/action"; + +const { snapBalances } = require("../validatorCompound"); + +action({ + name: "snapBalances", + chains: [1], + description: "Takes a snapshot of the staking strategy's balance", + params: (t) => { + t.addOptionalParam( + "consol", + "Call the consolidation controller instead of the strategy", + false, + types.boolean + ); + }, + run: async ({ args }) => { + await snapBalances(args); + }, +}); diff --git a/contracts/tasks/actions/sonicClaimWithdrawals.ts b/contracts/tasks/actions/sonicClaimWithdrawals.ts new file mode 100644 index 0000000000..da0de122d4 --- /dev/null +++ b/contracts/tasks/actions/sonicClaimWithdrawals.ts @@ -0,0 +1,11 @@ +import { withdrawFromSFC } from "../../utils/sonicActions"; +import { action } from "../lib/action"; + +action({ + name: "sonicClaimWithdrawals", + description: "Withdraw native S from a previously undelegated validator", + chains: [146], + run: async ({ signer }) => { + await withdrawFromSFC({ signer }); + }, +}); diff --git a/contracts/tasks/actions/stakeValidators.ts b/contracts/tasks/actions/stakeValidators.ts new file mode 100644 index 0000000000..21dfd927d0 --- /dev/null +++ b/contracts/tasks/actions/stakeValidators.ts @@ -0,0 +1,82 @@ +import { ethers } from "ethers"; +import { types } from "hardhat/config"; +import addresses from "../../utils/addresses"; +import { keyValueStoreLocalClient } from "../../utils/defender"; +import { stakeValidators } from "../../utils/validator"; +import { action } from "../lib/action"; + +const nativeStakingStrategyAbi = require("../../abi/native_staking_SSV_strategy.json"); +const IWETH9Abi = require("../../abi/IWETH9.json"); + +action({ + name: "stakeValidators", + description: + "Creates the required amount of new SSV validators and stakes ETH", + chains: [1, 17000], + params: (t) => { + t.addOptionalParam( + "uuid", + "uuid of P2P's request SSV validator API call", + undefined, + types.string + ); + t.addOptionalParam( + "index", + "The number of the Native Staking Contract deployed.", + undefined, + types.int + ); + }, + run: async ({ signer, chainId, networkName, log, args }) => { + const store = keyValueStoreLocalClient({ + _storePath: ".store/stakeValidators.json", + }); + + const nativeStakingProxyAddress = (addresses as any)[networkName] + .NativeStakingSSVStrategy3Proxy; + log.info(`NativeStakingStrategy: ${nativeStakingProxyAddress}`); + const nativeStakingStrategy = new ethers.Contract( + nativeStakingProxyAddress, + nativeStakingStrategyAbi, + signer + ); + + const wethAddress = (addresses as any)[networkName].WETH; + log.info(`WETH: ${wethAddress}`); + const WETH = new ethers.Contract(wethAddress, IWETH9Abi, signer); + + const p2p_api_key = + chainId === 1 + ? process.env.P2P_MAINNET_API_KEY + : process.env.P2P_HOLESKY_API_KEY; + if (!p2p_api_key) { + throw new Error( + "Secret with P2P API key not set. Add P2P_MAINNET_API_KEY or P2P_HOLESKY_API_KEY" + ); + } + const p2p_base_url = + chainId === 1 ? "api.p2p.org" : "api-test-holesky.p2p.org"; + + const awsS3AccessKeyId = process.env.AWS_ACCESS_S3_KEY_ID; + const awsS3SexcretAccessKeyId = process.env.AWS_SECRET_S3_ACCESS_KEY; + const s3BucketName = process.env.VALIDATOR_KEYS_S3_BUCKET_NAME; + + if (!awsS3AccessKeyId) throw new Error("AWS_ACCESS_S3_KEY_ID not set"); + if (!awsS3SexcretAccessKeyId) + throw new Error("AWS_SECRET_S3_ACCESS_KEY not set"); + if (!s3BucketName) throw new Error("VALIDATOR_KEYS_S3_BUCKET_NAME not set"); + + await stakeValidators({ + signer, + store, + nativeStakingStrategy, + WETH, + p2p_api_key, + p2p_base_url, + uuid: args.uuid, + awsS3AccessKeyId, + awsS3SexcretAccessKeyId, + s3BucketName, + }); + }, +}); diff --git a/contracts/tasks/actions/undelegateValidator.ts b/contracts/tasks/actions/undelegateValidator.ts new file mode 100644 index 0000000000..7a5055e1d2 --- /dev/null +++ b/contracts/tasks/actions/undelegateValidator.ts @@ -0,0 +1,36 @@ +import { types } from "hardhat/config"; +import { undelegateValidator } from "../../utils/sonicActions"; +import { action } from "../lib/action"; + +action({ + name: "sonicUndelegate", + description: "Remove liquidity from a Sonic validator", + chains: [146], + params: (t) => { + t.addOptionalParam( + "id", + "Validator identifier. 15, 16, 17 or 18", + undefined, + types.int + ); + t.addOptionalParam( + "amount", + "Amount of liquidity to remove", + undefined, + types.float + ); + t.addOptionalParam( + "buffer", + "Percentage of total assets to keep as buffer in basis points. 100 = 1%", + 50, + types.float + ); + }, + run: async ({ signer, args }) => { + await undelegateValidator({ + ...args, + bufferPct: args.buffer, + signer, + }); + }, +}); diff --git a/contracts/tasks/actions/updateVotemarketEpochs.ts b/contracts/tasks/actions/updateVotemarketEpochs.ts new file mode 100644 index 0000000000..79f7a26922 --- /dev/null +++ b/contracts/tasks/actions/updateVotemarketEpochs.ts @@ -0,0 +1,24 @@ +/// + +import { types } from "hardhat/config"; +import { action } from "../lib/action"; + +const { updateVotemarketEpochsTask } = require("../votemarket"); + +action({ + name: "updateVotemarketEpochs", + chains: [42161], // Arbitrum + description: + "Update Votemarket epochs for all Curve Pool Booster campaigns on Arbitrum", + params: (t) => { + t.addOptionalParam( + "dryRun", + "If true, log actions but do not send transactions", + false, + types.boolean + ); + }, + run: async ({ args }) => { + await updateVotemarketEpochsTask({ ...args }); + }, +}); diff --git a/contracts/tasks/actions/verifyBalances.ts b/contracts/tasks/actions/verifyBalances.ts new file mode 100644 index 0000000000..a72f3120d7 --- /dev/null +++ b/contracts/tasks/actions/verifyBalances.ts @@ -0,0 +1,65 @@ +/// + +import { types } from "hardhat/config"; +import { action } from "../lib/action"; + +const { verifyBalances } = require("../beacon"); + +action({ + name: "verifyBalances", + chains: [1], + description: "Verify validator balances on the Beacon chain", + params: (t) => { + t.addOptionalParam( + "slot", + "The slot snapBalances was executed. Default: last balances snapshot", + undefined, + types.int + ); + t.addOptionalParam( + "indexes", + "Comma separated list of validator indexes. Default: strategy's active validators", + undefined, + types.string + ); + t.addOptionalParam( + "deposits", + "Comma separated list of indexes to beacon chain pending deposits used for generating unit test data", + undefined, + types.string + ); + t.addOptionalParam( + "dryrun", + "Do not call verifyBalances on the strategy contract. Just log the params including the proofs", + false, + types.boolean + ); + t.addOptionalParam( + "test", + "Used for generating unit test data.", + false, + types.boolean + ); + t.addOptionalParam( + "overIds", + "A comma separated list of validator IDs to override balances.", + "", + types.string + ); + t.addOptionalParam( + "overBals", + "A comma separated list of validator balances to override in Gwei.", + "", + types.string + ); + t.addOptionalParam( + "consol", + "Call the consolidation controller instead of the strategy", + false, + types.boolean + ); + }, + run: async ({ signer, args }) => { + await verifyBalances({ ...args, signer }); + }, +}); diff --git a/contracts/tasks/actions/verifyDeposits.ts b/contracts/tasks/actions/verifyDeposits.ts new file mode 100644 index 0000000000..4866d04c99 --- /dev/null +++ b/contracts/tasks/actions/verifyDeposits.ts @@ -0,0 +1,29 @@ +/// + +import { types } from "hardhat/config"; +import { action } from "../lib/action"; + +const { verifyDeposits } = require("../beacon"); + +action({ + name: "verifyDeposits", + chains: [1], + description: "Verify any processed deposit on the Beacon chain", + params: (t) => { + t.addOptionalParam( + "dryrun", + "Do not call verifyDeposit on the strategy contract. Just log the params including the proofs", + false, + types.boolean + ); + t.addOptionalParam( + "consol", + "Call the consolidation controller instead of the strategy", + false, + types.boolean + ); + }, + run: async ({ signer, args }) => { + await verifyDeposits({ ...args, signer }); + }, +}); diff --git a/contracts/tasks/crossChain.js b/contracts/tasks/crossChain.js index 076056291a..22ab211dad 100644 --- a/contracts/tasks/crossChain.js +++ b/contracts/tasks/crossChain.js @@ -2,6 +2,7 @@ const ethers = require("ethers"); const { logTxDetails } = require("../utils/txLogger"); const { api: cctpApi } = require("../utils/cctp"); +const log = require("../utils/logger")("task:crossChain"); const cctpOperationsConfig = async ({ destinationChainSigner, @@ -35,7 +36,7 @@ const cctpOperationsConfig = async ({ }; const fetchAttestation = async ({ transactionHash, cctpChainId }) => { - console.log( + log( `Fetching attestation for transaction hash: ${transactionHash} on cctp chain id: ${cctpChainId}` ); const response = await fetch( @@ -50,6 +51,8 @@ const fetchAttestation = async ({ transactionHash, cctpChainId }) => { } const resultJson = await response.json(); + log("resultJson", resultJson); + if (resultJson.messages.length !== 1) { throw new Error( `Expected 1 attestation, got ${resultJson.messages.length}` @@ -94,7 +97,7 @@ const fetchTxHashesFromCctpTransactions = async ({ const messageTransmittedTopic = cctpIntegrationContractSource.interface.getEventTopic("MessageTransmitted"); - console.log( + log( `Fetching event logs from block ${resolvedFromBlock} to block ${resolvedToBlock}` ); const [eventLogsTokenBridged, eventLogsMessageTransmitted] = @@ -120,7 +123,7 @@ const fetchTxHashesFromCctpTransactions = async ({ ].map((log) => log.transactionHash); const allTxHashes = Array.from(new Set([...possiblyDuplicatedTxHashes])); - console.log(`Found ${allTxHashes.length} transactions that emitted messages`); + log(`Found ${allTxHashes.length} transactions that emitted messages`); return { allTxHashes }; }; @@ -144,7 +147,7 @@ const processCctpBridgeTransactions = async ({ cctpIntegrationContractAddress, cctpIntegrationContractAddressDestination, }); - console.log( + log( `Fetching cctp messages posted on ${config.networkName} network.${ block ? ` Only for block: ${block}` : " Looking at most recent blocks" }` @@ -161,7 +164,7 @@ const processCctpBridgeTransactions = async ({ const storedValue = await store.get(storeKey); if (storedValue === "processed") { - console.log( + log( `Transaction with hash: ${txHash} has already been processed. Skipping...` ); continue; @@ -172,17 +175,17 @@ const processCctpBridgeTransactions = async ({ cctpChainId: cctpSourceDomainId, }); if (status !== "ok") { - console.log( + log( `Attestation from tx hash: ${txHash} on cctp chain id: ${config.cctpSourceDomainId} is not attested yet, status: ${status}. Skipping...` ); } - console.log( + log( `Attempting to relay attestation with tx hash: ${txHash} and message: ${message} to cctp chain id: ${cctpDestinationDomainId}` ); if (dryrun) { - console.log( + log( `Dryrun: Would have relayed attestation with tx hash: ${txHash} to cctp chain id: ${cctpDestinationDomainId}` ); continue; @@ -193,17 +196,17 @@ const processCctpBridgeTransactions = async ({ attestation, { gasLimit: 4000000 } ); - console.log( + log( `Relay transaction with hash ${relayTx.hash} sent to cctp chain id: ${cctpDestinationDomainId}` ); const receipt = await logTxDetails(relayTx, "CCTP relay"); // Final verification if (receipt.status === 1) { - console.log("SUCCESS: Transaction executed successfully!"); + log("SUCCESS: Transaction executed successfully!"); await store.put(storeKey, "processed"); } else { - console.log("FAILURE: Transaction reverted!"); + log("FAILURE: Transaction reverted!"); throw new Error(`Transaction reverted - status: ${receipt.status}`); } } diff --git a/contracts/tasks/lib/action.snapshot.jsonl b/contracts/tasks/lib/action.snapshot.jsonl new file mode 100644 index 0000000000..18ab0221c1 --- /dev/null +++ b/contracts/tasks/lib/action.snapshot.jsonl @@ -0,0 +1,8 @@ +{"action":"test_harvest","run_id":"","source":"task","event":"action.start","chain_id":1,"network":"mainnet","level":"info","message":"Running on mainnet (1)"} +{"action":"test_harvest","run_id":"","source":"task","message":"Harvesting rewards","level":"info"} +{"action":"test_harvest","run_id":"","source":"task","message":"Swapped 100 CRV -> 0.5 ETH","level":"info"} +{"action":"test_harvest","run_id":"","source":"task","event":"action.success","chain_id":1,"network":"mainnet","duration_ms":"","level":"info","message":"Completed in 0.0s"} +{"action":"test_harvest","run_id":"","source":"task","event":"action.start","chain_id":1,"network":"mainnet","level":"info","message":"Running on mainnet (1)"} +{"action":"test_harvest","run_id":"","source":"task","event":"action.error","chain_id":1,"network":"mainnet","duration_ms":"","error_name":"Error","error_message":"insufficient funds for gas","error_stack":"Error: insufficient funds for gas","level":"error","message":"Error: insufficient funds for gas"} +{"action":"sonic_only_action","run_id":"","source":"task","event":"action.start","chain_id":1,"network":"mainnet","level":"info","message":"Running on mainnet (1)"} +{"action":"sonic_only_action","run_id":"","source":"task","event":"action.error","chain_id":1,"network":"mainnet","duration_ms":"","error_name":"Error","error_message":"sonic_only_action only supports sonic (146), not mainnet (1)","error_stack":"Error: sonic_only_action only supports sonic (146), not mainnet (1)","level":"error","message":"Error: sonic_only_action only supports sonic (146), not mainnet (1)"} diff --git a/contracts/tasks/lib/action.test.ts b/contracts/tasks/lib/action.test.ts new file mode 100644 index 0000000000..30101324de --- /dev/null +++ b/contracts/tasks/lib/action.test.ts @@ -0,0 +1,156 @@ +// Snapshot test for the action framework's structured logging. +// +// Calls createActionHandler() directly with a mock signer, capturing winston +// records via a capture transport. Tests the real code path in action.ts. +// +// Run with: npx ts-node tasks/lib/action.test.ts +// Update snapshot: npx ts-node tasks/lib/action.test.ts --update + +import fs from "node:fs"; +import path from "node:path"; +import { format, transports } from "winston"; +import { createActionHandler } from "./action"; +import type { ActionContext } from "./action"; +import logger from "./logger"; + +process.env.WINSTON_LOG_MODE_ENABLED = "true"; + +const SNAPSHOT_PATH = path.join(__dirname, "action.snapshot.jsonl"); +const updateSnapshot = process.argv.includes("--update"); + +// --- Winston capture --------------------------------------------------------- + +const captured: Record[] = []; +const captureFormat = format((info) => { + captured.push({ ...info }); + return false; +}); +logger.add(new transports.Console({ format: captureFormat() })); + +function stabilize(record: Record): Record { + const out: Record = {}; + for (const [k, v] of Object.entries(record)) { + if (k === "timestamp") continue; + if (k === "duration_ms") { + out[k] = ""; + continue; + } + if (k === "error_stack" && typeof v === "string") { + out[k] = v.split("\n")[0]; + continue; + } + if (k === "run_id") { + out[k] = ""; + continue; + } + out[k] = v; + } + return out; +} + +// --- Mock signer ------------------------------------------------------------- + +function mockSigner(chainId: number) { + return async () => + ({ + provider: { + getNetwork: async () => ({ chainId: BigInt(chainId) }), + }, + }) as any; +} + +// --- Scenarios --------------------------------------------------------------- + +async function runSuccessScenario() { + const handler = createActionHandler( + { + name: "test_harvest", + description: "test", + run: async (ctx: ActionContext) => { + ctx.log.info("Harvesting rewards"); + ctx.log.info("Swapped 100 CRV -> 0.5 ETH"); + }, + }, + { getSigner: mockSigner(1) }, + ); + await handler({}); +} + +async function runErrorScenario() { + const handler = createActionHandler( + { + name: "test_harvest", + description: "test", + run: async () => { + throw new Error("insufficient funds for gas"); + }, + }, + { getSigner: mockSigner(1) }, + ); + try { + await handler({}); + } catch { + // expected + } +} + +async function runChainValidationScenario() { + const handler = createActionHandler( + { + name: "sonic_only_action", + description: "test", + chains: [146], + run: async () => {}, + }, + { getSigner: mockSigner(1) }, + ); + try { + await handler({}); + } catch { + // expected + } +} + +// --- Main -------------------------------------------------------------------- + +async function main() { + await runSuccessScenario(); + await runErrorScenario(); + await runChainValidationScenario(); + + const stable = captured.map(stabilize); + const actualJsonl = stable.map((r) => JSON.stringify(r)).join("\n") + "\n"; + + if (updateSnapshot) { + fs.writeFileSync(SNAPSHOT_PATH, actualJsonl, "utf8"); + console.log(`Snapshot updated: ${SNAPSHOT_PATH}`); + console.log(actualJsonl); + return; + } + + if (!fs.existsSync(SNAPSHOT_PATH)) { + console.error( + "No snapshot found. Run with --update to create it:\n" + + " npx ts-node tasks/lib/action.test.ts --update", + ); + process.exit(1); + } + + const expected = fs.readFileSync(SNAPSHOT_PATH, "utf8"); + if (actualJsonl === expected) { + console.log("OK — action log output matches snapshot"); + console.log(` ${stable.length} records across 3 scenarios`); + } else { + console.error("FAIL — action log output does not match snapshot\n"); + console.error("Expected:"); + console.error(expected); + console.error("Actual:"); + console.error(actualJsonl); + process.exit(1); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/contracts/tasks/lib/action.ts b/contracts/tasks/lib/action.ts new file mode 100644 index 0000000000..bc9436f059 --- /dev/null +++ b/contracts/tasks/lib/action.ts @@ -0,0 +1,164 @@ +import { randomUUID } from "node:crypto"; +import type { ethers } from "ethers"; +import { subtask, task } from "hardhat/config"; +import type { ConfigurableTaskDefinition } from "hardhat/types"; +import type { Logger } from "winston"; + +import { getSigner as defaultGetSigner } from "../../utils/signers"; +import logger, { + flushLogger, + isWinstonLogModeEnabled, + withLogContext, +} from "./logger"; +import { wrapWithNonceQueue } from "./nonceQueue"; + +export interface ActionContext { + signer: ethers.Signer; + chainId: number; + networkName: string; + log: Logger; + args: Record; +} + +export interface ActionConfig { + name: string; + description: string; + chains?: number[]; + params?: (t: ConfigurableTaskDefinition) => void; + run: (ctx: ActionContext) => Promise; +} + +export interface ActionDeps { + getSigner?: () => Promise; +} + +const CHAIN_NAMES: Record = { + 1: "mainnet", + 8453: "base", + 146: "sonic", + 560048: "hoodi", + 999: "hyperevm", + 17000: "holesky", + 42161: "arbitrum", +}; + +export function createActionHandler( + config: ActionConfig, + deps: ActionDeps = {} +) { + const { name, chains, run } = config; + const getSigner = deps.getSigner ?? defaultGetSigner; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const legacyLoggerFactory = require("../../utils/logger"); + + return async (taskArgs: Record) => { + const winstonMode = isWinstonLogModeEnabled(); + const propagatedRunId = process.env.ACTION_RUN_ID?.trim(); + const propagatedActionName = process.env.ACTION_NAME?.trim(); + const actionLabel = + propagatedActionName && propagatedActionName.length > 0 + ? propagatedActionName + : name; + const runId = + propagatedRunId && propagatedRunId.length > 0 + ? propagatedRunId + : randomUUID(); + const log: Logger = winstonMode + ? logger.child({ action: actionLabel, run_id: runId, source: "task" }) + : legacyLoggerFactory(`action:${name}`); + const startTime = Date.now(); + let chainId: number | undefined; + let networkName: string | undefined; + + const execute = async () => { + try { + const rawSigner = await getSigner(); + const network = await rawSigner.provider!.getNetwork(); + chainId = Number(network.chainId); + const signer = wrapWithNonceQueue(rawSigner, chainId); + networkName = CHAIN_NAMES[chainId] ?? `unknown-${chainId}`; + + if (winstonMode) { + log.info(`Running on ${networkName} (${chainId})`, { + event: "action.start", + source: "task", + chain_id: chainId, + network: networkName, + }); + } else { + log.info(`Running on ${networkName} (${chainId})`); + } + + if (chains && !chains.includes(chainId)) { + const valid = chains + .map((id) => `${CHAIN_NAMES[id] ?? id} (${id})`) + .join(", "); + throw new Error( + `${name} only supports ${valid}, not ${networkName} (${chainId})` + ); + } + + await run({ signer, chainId, networkName, log, args: taskArgs }); + if (winstonMode) { + log.info( + `Completed in ${((Date.now() - startTime) / 1000).toFixed(1)}s`, + { + event: "action.success", + source: "task", + chain_id: chainId, + network: networkName, + duration_ms: Date.now() - startTime, + } + ); + } else { + log.info( + `Completed in ${((Date.now() - startTime) / 1000).toFixed(1)}s` + ); + } + } catch (err: any) { + if (winstonMode) { + log.error(`${err?.name ?? "Error"}: ${err?.message ?? String(err)}`, { + event: "action.error", + source: "task", + chain_id: chainId, + network: networkName, + duration_ms: Date.now() - startTime, + error_name: err?.name ?? "Error", + error_message: err?.message ?? String(err), + error_stack: err?.stack, + }); + } else { + log.error(`${err?.name ?? "Error"}: ${err?.message ?? String(err)}`); + } + throw err; + } finally { + if (winstonMode) { + await flushLogger(); + } + } + }; + + if (!winstonMode) { + return execute(); + } + + return withLogContext( + { action: actionLabel, run_id: runId, source: "task" }, + execute + ); + }; +} + +export function action(config: ActionConfig) { + const handler = createActionHandler(config); + + const definition = subtask(config.name, config.description); + if (config.params) { + config.params(definition); + } + definition.setAction(handler); + + task(config.name).setAction(async (_, __, runSuper) => { + return runSuper(); + }); +} diff --git a/contracts/tasks/lib/addresses.ts b/contracts/tasks/lib/addresses.ts new file mode 100644 index 0000000000..fb816858e5 --- /dev/null +++ b/contracts/tasks/lib/addresses.ts @@ -0,0 +1,18 @@ +import type { Address } from "viem"; +import addresses from "../../utils/addresses"; + +/** + * Look up a contract address from the contracts/utils/addresses.js registry. + * Throws if the network or contract name is not found. + */ +export function getAddress(network: string, name: string): Address { + const networkAddresses = (addresses as Record)[network]; + if (!networkAddresses || typeof networkAddresses !== "object") { + throw new Error(`Unknown network "${network}" in address registry`); + } + const addr = (networkAddresses as Record)[name]; + if (!addr) { + throw new Error(`Address "${name}" not found on network "${network}"`); + } + return addr as Address; +} diff --git a/contracts/tasks/lib/client.ts b/contracts/tasks/lib/client.ts new file mode 100644 index 0000000000..87e46aec6d --- /dev/null +++ b/contracts/tasks/lib/client.ts @@ -0,0 +1,77 @@ +import { + type Chain, + createPublicClient, + createWalletClient, + http, + type LocalAccount, +} from "viem"; +import { base, holesky, hoodi, hyperEvm, mainnet, sonic } from "viem/chains"; +import { requireEnv } from "./env"; + +const CHAIN_CONFIG: Record = { + [mainnet.id]: { chain: mainnet, envVar: "PROVIDER_URL" }, + [base.id]: { chain: base, envVar: "BASE_PROVIDER_URL" }, + [sonic.id]: { chain: sonic, envVar: "SONIC_PROVIDER_URL" }, + [hoodi.id]: { chain: hoodi, envVar: "HOODI_PROVIDER_URL" }, + [holesky.id]: { chain: holesky, envVar: "HOLESKY_PROVIDER_URL" }, + [hyperEvm.id]: { chain: hyperEvm, envVar: "HYPEREVM_PROVIDER_URL" }, +}; + +const CHAIN_BY_NAME: Record = Object.fromEntries( + Object.values(CHAIN_CONFIG).map((c) => [c.chain.name.toLowerCase(), c.chain]) +); +const CHAIN_BY_ID: Record = Object.fromEntries( + Object.values(CHAIN_CONFIG).map((c) => [c.chain.id, c.chain]) +); + +/** + * Resolve a chain from a name (e.g. "mainnet") or chain ID (e.g. "1" or 1). + */ +export function resolveChain(nameOrId: string | number): Chain { + if (typeof nameOrId === "number") { + const chain = CHAIN_BY_ID[nameOrId]; + if (!chain) throw new Error(`Unknown chain id ${nameOrId}`); + return chain; + } + // Try as number first, then as name + const asNum = Number(nameOrId); + if (!Number.isNaN(asNum)) { + const chain = CHAIN_BY_ID[asNum]; + if (chain) return chain; + } + const chain = CHAIN_BY_NAME[nameOrId.toLowerCase()]; + if (!chain) { + const supported = Object.values(CHAIN_CONFIG) + .map((c) => `${c.chain.name.toLowerCase()} (${c.chain.id})`) + .join(", "); + throw new Error(`Unknown chain "${nameOrId}". Supported: ${supported}`); + } + return chain; +} + +function getConfig(chain: Chain) { + const config = CHAIN_CONFIG[chain.id]; + if (!config) { + throw new Error(`No config for chain id ${chain.id}`); + } + return config; +} + +export function getRpcUrl(chain: Chain): string { + return requireEnv(getConfig(chain).envVar); +} + +export function getPublicClient(chain: Chain) { + return createPublicClient({ + chain, + transport: http(getRpcUrl(chain)), + }); +} + +export function getWalletClient(chain: Chain, account: LocalAccount) { + return createWalletClient({ + chain, + account, + transport: http(getRpcUrl(chain)), + }); +} diff --git a/contracts/tasks/lib/env.ts b/contracts/tasks/lib/env.ts new file mode 100644 index 0000000000..70eff0a0a4 --- /dev/null +++ b/contracts/tasks/lib/env.ts @@ -0,0 +1,18 @@ +/** + * Typed environment variable access with validation. + * Bun loads .env automatically — no dotenv needed. + */ +export function requireEnv(name: string): string { + const value = process.env[name]; + if (!value) { + throw new Error(`Required environment variable ${name} is not set`); + } + return value; +} + +export function optionalEnv( + name: string, + fallback?: string +): string | undefined { + return process.env[name] ?? fallback; +} diff --git a/contracts/tasks/lib/logger.ts b/contracts/tasks/lib/logger.ts new file mode 100644 index 0000000000..68c68e4847 --- /dev/null +++ b/contracts/tasks/lib/logger.ts @@ -0,0 +1,113 @@ +import { AsyncLocalStorage } from "node:async_hooks"; +import util from "node:util"; +import { createLogger, format, transports } from "winston"; +import LokiTransport from "winston-loki"; + +const lokiUrl = process.env.LOKI_URL; +const lokiUser = process.env.LOKI_USER; +const lokiApiKey = process.env.LOKI_API_KEY; +const WINSTON_LOG_MODE_ENABLED_ENV = "WINSTON_LOG_MODE_ENABLED"; + +const LOG_MODE_ENABLED_VALUES = new Set(["1", "true", "yes", "on"]); + +export interface LogContext { + action?: string; + run_id?: string; + [key: string]: unknown; +} + +const logContextStorage = new AsyncLocalStorage(); + +export function isWinstonLogModeEnabled(): boolean { + const rawValue = process.env[WINSTON_LOG_MODE_ENABLED_ENV]; + if (!rawValue) return false; + return LOG_MODE_ENABLED_VALUES.has(rawValue.toLowerCase()); +} + +export function withLogContext( + context: LogContext, + fn: () => Promise | T +): Promise | T { + return logContextStorage.run(context, fn); +} + +export function getLogContext(): LogContext | undefined { + return logContextStorage.getStore(); +} + +function toMessageString(message: unknown): string { + if (typeof message === "string") return message; + return util.format("%o", message); +} + +export function logWithContext( + level: string, + message: unknown, + meta: Record = {} +): void { + const context = getLogContext() ?? {}; + logger.log(level, toMessageString(message), { + ...context, + ...meta, + }); +} + +const consoleFormat = format.combine( + format.timestamp(), + format.errors({ stack: true }), + format.printf(({ timestamp, level, message, action, ...rest }) => { + const prefix = action ? `[${action}] ` : ""; + const extra = Object.keys(rest).length ? ` ${JSON.stringify(rest)}` : ""; + return `${timestamp} ${level}: ${prefix}${message}${extra}`; + }) +); + +const logTransports: InstanceType< + typeof transports.Console | typeof LokiTransport +>[] = [new transports.Console({ format: consoleFormat })]; + +let lokiTransport: LokiTransport | undefined; +if (lokiUrl) { + lokiTransport = new LokiTransport({ + host: lokiUrl, + basicAuth: lokiUrl && lokiApiKey ? `${lokiUser}:${lokiApiKey}` : undefined, + labels: { app: "origin-dollar", service_name: "origin-dollar" }, + json: true, + format: format.combine( + // Promote select low-cardinality fields from metadata to Loki labels. + // Keep high-cardinality fields (run_id, error_*, duration_ms, chain_id) + // as JSON fields — they're still queryable via `| json`. + format((info) => { + const LABEL_FIELDS = ["action", "event", "source"] as const; + const labels: Record = {}; + for (const k of LABEL_FIELDS) { + if (info[k]) labels[k] = String(info[k]); + } + if (Object.keys(labels).length) { + info.labels = { ...(info.labels || {}), ...labels }; + } + return info; + })(), + format.json() + ), + replaceTimestamp: true, + batching: true, + interval: 5, + onConnectionError: (err: unknown) => { + console.error("Loki connection error:", err); + }, + }); + logTransports.push(lokiTransport); +} + +const logger = createLogger({ + level: process.env.LOG_LEVEL ?? "info", + format: format.combine(format.timestamp(), format.errors({ stack: true })), + transports: logTransports, +}); + +export async function flushLogger(): Promise { + await lokiTransport?.flush(); +} + +export default logger; diff --git a/contracts/tasks/lib/nonceQueue.test.ts b/contracts/tasks/lib/nonceQueue.test.ts new file mode 100644 index 0000000000..e25cd55418 --- /dev/null +++ b/contracts/tasks/lib/nonceQueue.test.ts @@ -0,0 +1,324 @@ +/** + * Integration test for the Postgres nonce queue. + * + * Prerequisites: + * docker run -d --name nonce-test-pg -p 5433:5432 \ + * -e POSTGRES_DB=nonce_test -e POSTGRES_USER=test -e POSTGRES_PASSWORD=test \ + * postgres:16-alpine + * + * Run: + * DATABASE_URL=postgresql://test:test@localhost:5433/nonce_test \ + * npx ts-node tasks/lib/nonceQueue.test.ts + */ + +import { Pool } from "pg"; + +if (!process.env.DATABASE_URL) { + console.error( + "Set DATABASE_URL to run this test, e.g.:\n" + + " DATABASE_URL=postgresql://test:test@localhost:5433/nonce_test npx ts-node tasks/lib/nonceQueue.test.ts" + ); + process.exit(1); +} + +import { + wrapWithNonceQueue, + getNoncePool, + _resetForTesting, +} from "./nonceQueue"; +import { _resetNonceQueueTxHistoryForTesting } from "./nonceQueueTxHistory"; + +// Minimal mock signer for testing +function createMockSigner( + address: string, + onSend: (nonce: number) => Promise +) { + const signer: any = { + getAddress: async () => address, + getTransactionCount: async () => 0, + provider: { + getNetwork: async () => ({ chainId: 1 }), + }, + sendTransaction: async (tx: any) => { + await onSend(tx.nonce); + return { + hash: "0x" + Math.random().toString(16).slice(2), + from: address, + nonce: tx.nonce, + wait: async () => ({ status: 1 }), + }; + }, + signMessage: async () => "0x", + signTransaction: async () => "0x", + connect: () => signer, + _isSigner: true, + }; + return signer; +} + +async function waitFor( + predicate: () => Promise, + timeoutMs = 5_000, + pollMs = 50 +) { + const started = Date.now(); + while (Date.now() - started < timeoutMs) { + if (await predicate()) return; + await new Promise((resolve) => setTimeout(resolve, pollMs)); + } + throw new Error(`Condition not met within ${timeoutMs}ms`); +} + +/** Drop and recreate table, reset module state */ +async function resetAll() { + // Close any existing pool in the module + const existing = getNoncePool(); + if (existing) await existing.end(); + _resetForTesting(); + _resetNonceQueueTxHistoryForTesting(); + + // Drop table directly + const tmpPool = new Pool({ connectionString: process.env.DATABASE_URL }); + await tmpPool.query("DROP TABLE IF EXISTS nonce_queue"); + await tmpPool.query("DROP TABLE IF EXISTS nonce_queue_transactions"); + await tmpPool.end(); +} + +async function test() { + const results: { + id: number; + nonce: number; + startMs: number; + endMs: number; + }[] = []; + + // --- Test 1 --- + console.log("--- Test 1: Sequential nonce assignment ---"); + await resetAll(); + + const signer1 = createMockSigner("0xaaaa", async (nonce) => { + results.push({ id: 1, nonce, startMs: Date.now(), endMs: 0 }); + await new Promise((r) => setTimeout(r, 100)); + results[results.length - 1].endMs = Date.now(); + }); + + const wrapped1 = wrapWithNonceQueue(signer1, 1); + + for (let i = 0; i < 3; i++) { + await wrapped1.sendTransaction({ to: "0xbbbb", data: "0x" }); + } + + console.log("Results:", results.map((r) => `nonce=${r.nonce}`).join(", ")); + const nonces = results.map((r) => r.nonce); + console.assert( + nonces[0] === 0 && nonces[1] === 1 && nonces[2] === 2, + `Expected nonces [0,1,2] but got [${nonces}]` + ); + console.log("PASS: Sequential nonces assigned correctly\n"); + + // --- Test 2 --- + console.log("--- Test 2: Concurrent processes block on lock ---"); + results.length = 0; + await resetAll(); + + const signer2a = createMockSigner("0xaaaa", async (nonce) => { + results.push({ id: 1, nonce, startMs: Date.now(), endMs: 0 }); + await new Promise((r) => setTimeout(r, 500)); // Hold lock for 500ms + results[results.length - 1].endMs = Date.now(); + }); + const signer2b = createMockSigner("0xaaaa", async (nonce) => { + results.push({ id: 2, nonce, startMs: Date.now(), endMs: 0 }); + await new Promise((r) => setTimeout(r, 100)); + results[results.length - 1].endMs = Date.now(); + }); + + // Wrap both, then start them with a short stagger to ensure both hit the + // DB lock. The first acquires it immediately; the second blocks on FOR UPDATE. + const wrapped2a = wrapWithNonceQueue(signer2a, 1); + const wrapped2b = wrapWithNonceQueue(signer2b, 1); + + const p1 = wrapped2a.sendTransaction({ to: "0xbbbb", data: "0x" }); + // Small delay so both connections are open and racing for the lock + await new Promise((r) => setTimeout(r, 50)); + const p2 = wrapped2b.sendTransaction({ to: "0xbbbb", data: "0x" }); + + await Promise.all([p1, p2]); + + console.log( + "Results:", + results.map((r) => `id=${r.id} nonce=${r.nonce}`).join(", ") + ); + + const sortedNonces = results.map((r) => r.nonce).sort(); + console.assert( + sortedNonces[0] === 0 && sortedNonces[1] === 1, + `Expected nonces [0,1] but got [${sortedNonces}]` + ); + + // The key assertion: second tx's onSend must start AFTER first tx's onSend + // finishes, proving the DB lock serialized them. + const sorted = [...results].sort((a, b) => a.startMs - b.startMs); + const gap = sorted[1].startMs - sorted[0].endMs; + console.log( + ` First tx: ${sorted[0].startMs}-${sorted[0].endMs} (${ + sorted[0].endMs - sorted[0].startMs + }ms)` + ); + console.log( + ` Second tx: ${sorted[1].startMs}-${sorted[1].endMs} (${ + sorted[1].endMs - sorted[1].startMs + }ms)` + ); + console.log(` Gap between first end and second start: ${gap}ms`); + console.assert( + gap >= -50, // small tolerance for timing jitter + `Second tx started ${-gap}ms BEFORE first finished — lock didn't serialize!` + ); + console.log("PASS: Concurrent transactions serialized correctly\n"); + + // --- Test 3 --- + console.log("--- Test 3: Different chains have independent nonces ---"); + results.length = 0; + await resetAll(); + + const signer3a = createMockSigner("0xaaaa", async (nonce) => { + results.push({ id: 1, nonce, startMs: Date.now(), endMs: 0 }); + }); + const signer3b = createMockSigner("0xaaaa", async (nonce) => { + results.push({ id: 2, nonce, startMs: Date.now(), endMs: 0 }); + }); + + const wrappedMainnet = wrapWithNonceQueue(signer3a, 1); + const wrappedSonic = wrapWithNonceQueue(signer3b, 146); + + await wrappedMainnet.sendTransaction({ to: "0xbbbb", data: "0x" }); + await wrappedSonic.sendTransaction({ to: "0xbbbb", data: "0x" }); + + console.log( + "Results:", + results.map((r) => `chain=${r.id} nonce=${r.nonce}`).join(", ") + ); + console.assert( + results[0].nonce === 0 && results[1].nonce === 0, + `Expected both nonces to be 0 but got [${results.map((r) => r.nonce)}]` + ); + console.log("PASS: Independent nonces per chain\n"); + + // --- Test 4 --- + console.log("--- Test 4: Rollback on failure keeps nonce ---"); + results.length = 0; + await resetAll(); + + let callCount = 0; + const signerFail = createMockSigner("0xaaaa", async (nonce) => { + callCount++; + results.push({ id: callCount, nonce, startMs: Date.now(), endMs: 0 }); + if (callCount === 1) { + throw new Error("Transaction reverted"); + } + }); + + const wrappedFail = wrapWithNonceQueue(signerFail, 1); + + try { + await wrappedFail.sendTransaction({ to: "0xbbbb", data: "0x" }); + } catch (e: any) { + console.log(`First tx failed as expected: ${e.message}`); + } + + await wrappedFail.sendTransaction({ to: "0xbbbb", data: "0x" }); + + console.log( + "Results:", + results.map((r) => `id=${r.id} nonce=${r.nonce}`).join(", ") + ); + console.assert( + results[0].nonce === 0 && results[1].nonce === 0, + `Expected both nonces to be 0 but got [${results.map((r) => r.nonce)}]` + ); + console.log("PASS: Nonce preserved after rollback\n"); + + // --- Test 5 --- + console.log( + "--- Test 5: Persisted tx history keeps nonce order under concurrency ---" + ); + results.length = 0; + await resetAll(); + + const signer5a = createMockSigner("0xaaaa", async (nonce) => { + results.push({ id: 1, nonce, startMs: Date.now(), endMs: 0 }); + await new Promise((r) => setTimeout(r, 200)); + }); + const signer5b = createMockSigner("0xaaaa", async (nonce) => { + results.push({ id: 2, nonce, startMs: Date.now(), endMs: 0 }); + await new Promise((r) => setTimeout(r, 150)); + }); + const signer5c = createMockSigner("0xaaaa", async (nonce) => { + results.push({ id: 3, nonce, startMs: Date.now(), endMs: 0 }); + await new Promise((r) => setTimeout(r, 100)); + }); + + const wrapped5a = wrapWithNonceQueue(signer5a, 1); + const wrapped5b = wrapWithNonceQueue(signer5b, 1); + const wrapped5c = wrapWithNonceQueue(signer5c, 1); + + await Promise.all([ + wrapped5a.sendTransaction({ to: "0xbbbb", data: "0x" }), + wrapped5b.sendTransaction({ to: "0xbbbb", data: "0x" }), + wrapped5c.sendTransaction({ to: "0xbbbb", data: "0x" }), + ]); + + const verifyPool = new Pool({ connectionString: process.env.DATABASE_URL }); + await waitFor(async () => { + const { rows } = await verifyPool.query( + `SELECT COUNT(*)::int AS count + FROM nonce_queue_transactions + WHERE signer_address = $1 AND chain_id = $2`, + ["0xaaaa", 1] + ); + return Number(rows[0].count) >= 3; + }, 10_000); + const persisted = await verifyPool.query( + `SELECT nonce, status, tx_hash + FROM nonce_queue_transactions + WHERE signer_address = $1 AND chain_id = $2 + ORDER BY nonce ASC, submitted_at ASC`, + ["0xaaaa", 1] + ); + await verifyPool.end(); + + const persistedNonces = persisted.rows.map((row: any) => Number(row.nonce)); + const uniqueHashes = new Set(persisted.rows.map((row: any) => row.tx_hash)); + + console.log( + "Results:", + persisted.rows + .map((row: any) => `nonce=${row.nonce} status=${row.status}`) + .join(", ") + ); + console.assert( + persisted.rows.length === 3, + `Expected 3 persisted tx rows, got ${persisted.rows.length}` + ); + console.assert( + persistedNonces.join(",") === "0,1,2", + `Expected persisted nonces [0,1,2], got [${persistedNonces}]` + ); + console.assert( + uniqueHashes.size === 3, + `Expected 3 unique tx hashes, got ${uniqueHashes.size}` + ); + console.log( + "PASS: Concurrent sends persisted with strictly increasing nonces\n" + ); + + // --- Cleanup --- + const pool = getNoncePool(); + if (pool) await pool.end(); + console.log("All tests passed!"); +} + +test().catch((err) => { + console.error("TEST FAILED:", err); + process.exit(1); +}); diff --git a/contracts/tasks/lib/nonceQueue.ts b/contracts/tasks/lib/nonceQueue.ts new file mode 100644 index 0000000000..2e7ba72f6b --- /dev/null +++ b/contracts/tasks/lib/nonceQueue.ts @@ -0,0 +1,246 @@ +import type { Pool, PoolClient } from "pg"; +import type { ethers } from "ethers"; +import { + isNonceMismatchError, + recoverNonceFromChain, + submitNonceQueuedTransaction, +} from "./nonceQueueTxLifecycle"; +import { recordNonceQueueTxLifecycleEvent } from "./nonceQueueTxHistory"; + +const log = require("../../utils/logger")("utils:nonceQueue"); + +let pool: Pool | null = null; +let tableEnsurePromise: Promise | null = null; + +function getNonceQueueLockTimeoutSeconds(): number { + const value = process.env.NONCE_QUEUE_LOCK_TIMEOUT_S; + if (!value) return 0; + + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < 0) { + log( + `Invalid NONCE_QUEUE_LOCK_TIMEOUT_S="${value}" (expected integer >= 0). Falling back to 0 (wait forever).` + ); + return 0; + } + + return parsed; +} + +export function getNoncePool(): Pool | null { + if (!process.env.DATABASE_URL) return null; + if (!pool) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { Pool: PgPool } = require("pg"); + pool = new PgPool({ + connectionString: process.env.DATABASE_URL, + max: 5, + connectionTimeoutMillis: 120_000, + }); + } + return pool; +} + +function ensureNonceTable(p: Pool): Promise { + if (!tableEnsurePromise) { + tableEnsurePromise = p + .query( + ` + CREATE TABLE IF NOT EXISTS nonce_queue ( + signer_address TEXT NOT NULL, + chain_id INTEGER NOT NULL, + nonce INTEGER NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (signer_address, chain_id) + ) + ` + ) + .then(() => {}); + } + return tableEnsurePromise; +} + +async function ensureNonceRow( + client: PoolClient, + signerAddress: string, + chainId: number, + getOnChainNonce: () => Promise +): Promise { + const { rows } = await client.query( + "SELECT 1 FROM nonce_queue WHERE signer_address = $1 AND chain_id = $2", + [signerAddress, chainId] + ); + // fetching the on-chain nonce only if there is no signer & chain id combination in the database + if (rows.length === 0) { + const onChainNonce = await getOnChainNonce(); + log( + `Initializing nonce row: address=${signerAddress} chain=${chainId} nonce=${onChainNonce}` + ); + await client.query( + `INSERT INTO nonce_queue (signer_address, chain_id, nonce) + VALUES ($1, $2, $3) + ON CONFLICT DO NOTHING`, + [signerAddress, chainId, onChainNonce] + ); + } +} + +function isLockTimeoutError(err: any): boolean { + const msg = (err?.message ?? "").toLowerCase(); + return ( + err?.code === "55P03" || + msg.includes("lock timeout") || + msg.includes("canceling statement due to lock timeout") + ); +} + +async function withNonceLock( + p: Pool, + signerAddress: string, + chainId: number, + getOnChainNonce: () => Promise, + fn: (nonce: number) => Promise, + maxRetries = 3 +): Promise { + await ensureNonceTable(p); + + for (let attempt = 0; attempt < maxRetries; attempt++) { + const client = await p.connect(); + const lockTimeoutSeconds = getNonceQueueLockTimeoutSeconds(); + try { + await client.query("BEGIN"); + if (lockTimeoutSeconds > 0) { + await client.query("SELECT set_config('lock_timeout', $1, true)", [ + `${lockTimeoutSeconds}s`, + ]); + } + await ensureNonceRow(client, signerAddress, chainId, getOnChainNonce); + + const { rows } = await client.query( + "SELECT nonce FROM nonce_queue WHERE signer_address = $1 AND chain_id = $2 FOR UPDATE", + [signerAddress, chainId] + ); + const nonce: number = rows[0].nonce; + + log( + `Acquired nonce lock: address=${signerAddress} chain=${chainId} nonce=${nonce}` + ); + + const result = await fn(nonce); + + await client.query( + "UPDATE nonce_queue SET nonce = nonce + 1, updated_at = NOW() WHERE signer_address = $1 AND chain_id = $2", + [signerAddress, chainId] + ); + await client.query("COMMIT"); + + log( + `Released nonce lock: address=${signerAddress} chain=${chainId} nonce=${nonce} → ${ + nonce + 1 + }` + ); + + return result; + } catch (err: any) { + await client.query("ROLLBACK").catch(() => {}); + + if (isLockTimeoutError(err)) { + const configuredTimeout = + lockTimeoutSeconds > 0 + ? `${lockTimeoutSeconds}s` + : "Postgres default"; + log( + `Nonce lock timeout: unable to acquire lock for address=${signerAddress} chain=${chainId} within ${configuredTimeout}.` + ); + } + + if (isNonceMismatchError(err)) { + log( + `Nonce mismatch (attempt ${ + attempt + 1 + }/${maxRetries}), recovering from chain…` + ); + await recoverNonceFromChain({ + pool: p, + signerAddress, + chainId, + getOnChainNonce, + }); + if (attempt < maxRetries - 1) continue; + } + + throw err; + } finally { + client.release(); + } + } + + throw new Error("withNonceLock: max retries exhausted"); +} + +/** Reset module state. Only for testing. */ +export function _resetForTesting() { + tableEnsurePromise = null; + pool = null; +} + +/** + * Wraps an ethers v5 Signer so that every `sendTransaction` call is + * serialized through a Postgres row lock. The nonce is managed by the + * database — not by the provider. The lock is held until the transaction + * is confirmed on-chain, so concurrent processes block rather than collide. + * + * If DATABASE_URL is not set, the signer is returned unmodified. + */ +export function wrapWithNonceQueue( + signer: ethers.Signer, + chainId: number +): ethers.Signer { + const p = getNoncePool(); + if (!p) return signer; + + const originalSendTransaction = signer.sendTransaction.bind(signer); + const addressPromise = signer.getAddress().then((a) => a.toLowerCase()); + const getOnChainNonce = () => signer.getTransactionCount("pending"); + + signer.sendTransaction = async function ( + transaction: Parameters[0] + ) { + const signerAddress = await addressPromise; + return withNonceLock( + p, + signerAddress, + chainId, + getOnChainNonce, + async (nonce) => { + return submitNonceQueuedTransaction({ + sendTransaction: originalSendTransaction, + provider: signer.provider ?? undefined, + transaction, + nonce, + signerAddress, + chainId, + onLifecycleEvent: (event) => { + // Keep lifecycle persistence out-of-band so lock holders never + // block on acquiring another DB connection from the same pool. + void recordNonceQueueTxLifecycleEvent({ + pool: p, + event, + }).catch((err: any) => { + // History persistence must not block transaction sending flow. + log( + `Failed to persist nonce-queue transaction history: type=${ + event.type + } address=${signerAddress} chain=${chainId} nonce=${nonce} error="${ + err?.message ?? String(err) + }"` + ); + }); + }, + }); + } + ); + }; + + return signer; +} diff --git a/contracts/tasks/lib/nonceQueueTxHistory.test.ts b/contracts/tasks/lib/nonceQueueTxHistory.test.ts new file mode 100644 index 0000000000..27cdc854eb --- /dev/null +++ b/contracts/tasks/lib/nonceQueueTxHistory.test.ts @@ -0,0 +1,523 @@ +import { Pool } from "pg"; + +import { submitNonceQueuedTransaction } from "./nonceQueueTxLifecycle"; +import { + _resetNonceQueueTxHistoryForTesting, + listNonceQueueTransactions, + recordNonceQueueTxLifecycleEvent, +} from "./nonceQueueTxHistory"; + +type EnvOverrides = Record; + +if (!process.env.DATABASE_URL) { + console.error( + "Set DATABASE_URL to run this test, e.g.:\n" + + " DATABASE_URL=postgresql://test:test@localhost:5433/nonce_test npx ts-node tasks/lib/nonceQueueTxHistory.test.ts" + ); + process.exit(1); +} + +function makeResponse(hash: string, raw?: string): any { + return { + hash, + raw, + rawTransaction: raw, + wait: async () => ({ + status: 1, + transactionHash: hash, + blockNumber: 12345, + }), + }; +} + +async function withEnv(overrides: EnvOverrides, fn: () => Promise) { + const previousValues: Record = {}; + for (const key of Object.keys(overrides)) { + previousValues[key] = process.env[key]; + } + + for (const [key, value] of Object.entries(overrides)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + + try { + return await fn(); + } finally { + for (const [key, value] of Object.entries(previousValues)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + +async function waitFor( + predicate: () => Promise, + timeoutMs = 5_000, + pollMs = 50 +) { + const started = Date.now(); + while (Date.now() - started < timeoutMs) { + if (await predicate()) return; + await new Promise((resolve) => setTimeout(resolve, pollMs)); + } + throw new Error(`Condition not met within ${timeoutMs}ms`); +} + +async function resetHistoryTable(pool: Pool) { + await pool.query("DROP TABLE IF EXISTS nonce_queue_transactions"); + _resetNonceQueueTxHistoryForTesting(); +} + +async function testInitialSendCreatesPendingRow(pool: Pool) { + console.log("--- Tx History Test 1: Initial send creates pending row ---"); + await resetHistoryTable(pool); + + let shouldMine = false; + const signerSendTransaction = async () => + makeResponse("0xhist-pending", "0xraw"); + const provider: any = { + async getTransactionReceipt() { + if (!shouldMine) return null; + return { status: 1, transactionHash: "0xhist-pending", blockNumber: 100 }; + }, + async getFeeData() { + return {}; + }, + async sendTransaction(raw: string) { + return makeResponse("0xhist-pending", raw); + }, + }; + + const txPromise = withEnv( + { + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "20", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + } as any, + nonce: 100, + signerAddress: "0xAAAA", + chainId: 1, + onLifecycleEvent: (event) => + recordNonceQueueTxLifecycleEvent({ pool, event }), + }) + ); + + await waitFor(async () => { + try { + const { rows } = await pool.query( + "SELECT status, lifecycle_state FROM nonce_queue_transactions WHERE tx_hash = $1", + ["0xhist-pending"] + ); + return rows.length === 1; + } catch (err: any) { + if (err?.code === "42P01") { + return false; + } + throw err; + } + }); + + const pendingRow = await pool.query( + "SELECT status, lifecycle_state FROM nonce_queue_transactions WHERE tx_hash = $1", + ["0xhist-pending"] + ); + + console.assert( + pendingRow.rows[0].status === "pending", + `Expected pending status, got ${pendingRow.rows[0].status}` + ); + console.assert( + pendingRow.rows[0].lifecycle_state === "submitted_initial", + `Expected lifecycle_state=submitted_initial, got ${pendingRow.rows[0].lifecycle_state}` + ); + + shouldMine = true; + await txPromise; + console.log("PASS: pending row created on initial send\n"); +} + +async function testMinedSuccessPersistsCompleted(pool: Pool) { + console.log("--- Tx History Test 2: Mined success marks completed ---"); + await resetHistoryTable(pool); + + const signerSendTransaction = async () => + makeResponse("0xhist-success", "0xraw"); + const provider: any = { + async getTransactionReceipt() { + return { status: 1, transactionHash: "0xhist-success", blockNumber: 200 }; + }, + async getFeeData() { + return {}; + }, + async sendTransaction(raw: string) { + return makeResponse("0xhist-success", raw); + }, + }; + + await withEnv( + { + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + } as any, + nonce: 101, + signerAddress: "0xAAAA", + chainId: 1, + onLifecycleEvent: (event) => + recordNonceQueueTxLifecycleEvent({ pool, event }), + }) + ); + + const { rows } = await pool.query( + "SELECT status, lifecycle_state, block_number FROM nonce_queue_transactions WHERE tx_hash = $1", + ["0xhist-success"] + ); + + console.assert(rows[0].status === "completed", "Expected completed status"); + console.assert( + rows[0].lifecycle_state === "confirmed", + `Expected lifecycle_state=confirmed, got ${rows[0].lifecycle_state}` + ); + console.assert( + Number(rows[0].block_number) === 200, + `Expected block_number=200, got ${rows[0].block_number}` + ); + console.log("PASS: completed row persisted after mined success\n"); +} + +async function testMinedRevertPersistsFailed(pool: Pool) { + console.log("--- Tx History Test 3: Mined revert marks failed/reverted ---"); + await resetHistoryTable(pool); + + const signerSendTransaction = async () => + makeResponse("0xhist-revert", "0xraw"); + const provider: any = { + async getTransactionReceipt() { + return { status: 0, transactionHash: "0xhist-revert", blockNumber: 300 }; + }, + async getFeeData() { + return {}; + }, + async sendTransaction(raw: string) { + return makeResponse("0xhist-revert", raw); + }, + }; + + let revertError: Error | undefined; + await withEnv( + { + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + async () => { + try { + await submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + } as any, + nonce: 102, + signerAddress: "0xAAAA", + chainId: 1, + onLifecycleEvent: (event) => + recordNonceQueueTxLifecycleEvent({ pool, event }), + }); + } catch (err: any) { + revertError = err; + } + } + ); + + if (!revertError) { + throw new Error("Expected revert error"); + } + + const { rows } = await pool.query( + "SELECT status, lifecycle_state FROM nonce_queue_transactions WHERE tx_hash = $1", + ["0xhist-revert"] + ); + console.assert(rows[0].status === "failed", "Expected failed status"); + console.assert( + rows[0].lifecycle_state === "reverted", + `Expected lifecycle_state=reverted, got ${rows[0].lifecycle_state}` + ); + console.log("PASS: reverted row persisted as failed/reverted\n"); +} + +async function testTimeoutMarksPendingRows(pool: Pool) { + console.log("--- Tx History Test 4: Timeout marks failed/timed_out ---"); + await resetHistoryTable(pool); + + const signerSendTransaction = async () => + makeResponse("0xhist-timeout", "0xraw"); + const provider: any = { + async getTransactionReceipt() { + return null; + }, + async getFeeData() { + return {}; + }, + async sendTransaction(raw: string) { + return makeResponse("0xhist-timeout", raw); + }, + }; + + let timeoutError: Error | undefined; + await withEnv( + { + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "2", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + async () => { + try { + await submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + } as any, + nonce: 103, + signerAddress: "0xAAAA", + chainId: 1, + onLifecycleEvent: (event) => + recordNonceQueueTxLifecycleEvent({ pool, event }), + }); + } catch (err: any) { + timeoutError = err; + } + } + ); + + if (!timeoutError) { + throw new Error("Expected timeout error"); + } + + const { rows } = await pool.query( + "SELECT status, lifecycle_state, error_message FROM nonce_queue_transactions WHERE tx_hash = $1", + ["0xhist-timeout"] + ); + console.assert(rows[0].status === "failed", "Expected failed status"); + console.assert( + rows[0].lifecycle_state === "timed_out", + `Expected lifecycle_state=timed_out, got ${rows[0].lifecycle_state}` + ); + console.assert( + String(rows[0].error_message).includes( + "Timed out waiting for nonce-queued tx confirmation" + ), + `Expected timeout error message, got ${rows[0].error_message}` + ); + console.log("PASS: timeout persisted as failed/timed_out\n"); +} + +async function testReplacementPathPersistsWinnerAndLoser(pool: Pool) { + console.log("--- Tx History Test 5: Replacement winner/loser states ---"); + await resetHistoryTable(pool); + + let sendCount = 0; + const signerSendTransaction = async () => { + if (sendCount === 0) { + sendCount++; + return makeResponse("0xhist-initial", "0xraw-initial"); + } + sendCount++; + return makeResponse("0xhist-replacement", "0xraw-replacement"); + }; + + let receiptChecks = 0; + const provider: any = { + async getTransactionReceipt(hash: string) { + receiptChecks++; + if (hash === "0xhist-replacement" && receiptChecks >= 4) { + return { + status: 1, + transactionHash: "0xhist-replacement", + blockNumber: 400, + }; + } + return null; + }, + async getFeeData() { + return { + maxFeePerGas: 400, + maxPriorityFeePerGas: 5, + }; + }, + async sendTransaction() { + throw new Error("rebroadcast disabled"); + }, + }; + + await withEnv( + { + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "20", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "1", + NONCE_QUEUE_MAX_REPLACEMENTS: "2", + NONCE_QUEUE_FEE_BUMP_PCT: "20", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + maxFeePerGas: 100, + maxPriorityFeePerGas: 2, + } as any, + nonce: 104, + signerAddress: "0xAAAA", + chainId: 1, + onLifecycleEvent: (event) => + recordNonceQueueTxLifecycleEvent({ pool, event }), + }) + ); + + const records = await listNonceQueueTransactions({ + pool, + params: { limit: 10, address: "0xaaaa", chainId: 1 }, + }); + const initial = records.find((row) => row.txHash === "0xhist-initial"); + const replacement = records.find( + (row) => row.txHash === "0xhist-replacement" + ); + + if (!initial || !replacement) { + throw new Error("Expected both initial and replacement rows"); + } + + console.assert( + initial.status === "failed" && initial.lifecycleState === "replaced", + `Expected initial row failed/replaced, got ${initial.status}/${initial.lifecycleState}` + ); + console.assert( + replacement.status === "completed" && + replacement.lifecycleState === "confirmed", + `Expected replacement row completed/confirmed, got ${replacement.status}/${replacement.lifecycleState}` + ); + console.log("PASS: replacement winner/loser rows persisted correctly\n"); +} + +async function testRebroadcastIncrementsSendCount(pool: Pool) { + console.log("--- Tx History Test 6: Rebroadcast increments send_count ---"); + await resetHistoryTable(pool); + + const signerSendTransaction = async () => + makeResponse("0xhist-rebroadcast", "0xraw"); + + let receiptChecks = 0; + let providerSends = 0; + const provider: any = { + async getTransactionReceipt(hash: string) { + receiptChecks++; + if (hash === "0xhist-rebroadcast" && receiptChecks >= 3) { + return { + status: 1, + transactionHash: "0xhist-rebroadcast", + blockNumber: 500, + }; + } + return null; + }, + async getFeeData() { + return {}; + }, + async sendTransaction(raw: string) { + providerSends++; + return makeResponse("0xhist-rebroadcast", raw); + }, + }; + + await withEnv( + { + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "10", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "1", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + } as any, + nonce: 105, + signerAddress: "0xAAAA", + chainId: 1, + onLifecycleEvent: (event) => + recordNonceQueueTxLifecycleEvent({ pool, event }), + }) + ); + + console.assert( + providerSends === 1, + `Expected 1 rebroadcast, got ${providerSends}` + ); + + const { rows } = await pool.query( + "SELECT send_count, status FROM nonce_queue_transactions WHERE tx_hash = $1", + ["0xhist-rebroadcast"] + ); + + console.assert(rows.length === 1, `Expected 1 row, got ${rows.length}`); + console.assert( + Number(rows[0].send_count) === 2, + `Expected send_count=2, got ${rows[0].send_count}` + ); + console.assert(rows[0].status === "completed", "Expected completed status"); + console.log( + "PASS: rebroadcast upsert increments send_count without duplicates\n" + ); +} + +async function test() { + const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + try { + await testInitialSendCreatesPendingRow(pool); + await testMinedSuccessPersistsCompleted(pool); + await testMinedRevertPersistsFailed(pool); + await testTimeoutMarksPendingRows(pool); + await testReplacementPathPersistsWinnerAndLoser(pool); + await testRebroadcastIncrementsSendCount(pool); + console.log("All nonceQueueTxHistory tests passed!"); + } finally { + await pool.end(); + } +} + +test().catch((err) => { + console.error("TEST FAILED:", err); + process.exit(1); +}); diff --git a/contracts/tasks/lib/nonceQueueTxHistory.ts b/contracts/tasks/lib/nonceQueueTxHistory.ts new file mode 100644 index 0000000000..e74df01c7c --- /dev/null +++ b/contracts/tasks/lib/nonceQueueTxHistory.ts @@ -0,0 +1,394 @@ +import type { Pool } from "pg"; + +const EXPLORER_TX_BASE_BY_CHAIN: Record = { + 1: "https://etherscan.io/tx/", + 8453: "https://basescan.org/tx/", + 42161: "https://arbiscan.io/tx/", + 17000: "https://holesky.etherscan.io/tx/", +}; + +let tableEnsurePromise: Promise | null = null; + +export type NonceQueueTxStage = "initial" | "replacement" | "rebroadcast"; + +export type NonceQueueTxStatus = "pending" | "completed" | "failed"; + +export type NonceQueueTxLifecycleState = + | "submitted_initial" + | "submitted_replacement" + | "submitted_rebroadcast" + | "confirmed" + | "reverted" + | "replaced" + | "timed_out" + | "send_error"; + +export type NonceQueueTxLifecycleEvent = + | { + type: "send_accepted"; + txHash: string; + stage: NonceQueueTxStage; + signerAddress: string; + chainId: number; + nonce: number; + } + | { + type: "mined_success"; + txHash: string; + signerAddress: string; + chainId: number; + nonce: number; + blockNumber: number | null; + } + | { + type: "mined_revert"; + txHash: string; + signerAddress: string; + chainId: number; + nonce: number; + blockNumber: number | null; + errorMessage?: string; + } + | { + type: "timeout"; + signerAddress: string; + chainId: number; + nonce: number; + errorMessage: string; + } + | { + type: "terminal_send_error"; + signerAddress: string; + chainId: number; + nonce: number; + errorMessage: string; + txHash?: string; + }; + +export interface NonceQueueTransactionRecord { + txHash: string; + signerAddress: string; + chainId: number; + nonce: number; + stage: NonceQueueTxStage; + status: NonceQueueTxStatus; + lifecycleState: string; + explorerUrl: string | null; + sendCount: number; + blockNumber: number | null; + errorMessage: string | null; + submittedAt: string; + finalizedAt: string | null; + updatedAt: string; +} + +export interface ListNonceQueueTransactionsParams { + limit: number; + address?: string; + chainId?: number; +} + +function lifecycleStateForSubmit( + stage: NonceQueueTxStage +): NonceQueueTxLifecycleState { + if (stage === "initial") return "submitted_initial"; + if (stage === "replacement") return "submitted_replacement"; + return "submitted_rebroadcast"; +} + +function explorerUrlForHash(chainId: number, txHash: string): string | null { + const base = EXPLORER_TX_BASE_BY_CHAIN[chainId]; + return base ? `${base}${txHash}` : null; +} + +async function ensureNonceQueueTransactionsTable(pool: Pool): Promise { + if (!tableEnsurePromise) { + tableEnsurePromise = pool + .query( + ` + CREATE TABLE IF NOT EXISTS nonce_queue_transactions ( + tx_hash TEXT PRIMARY KEY, + signer_address TEXT NOT NULL, + chain_id INTEGER NOT NULL, + nonce INTEGER NOT NULL, + stage TEXT NOT NULL CHECK (stage IN ('initial', 'replacement', 'rebroadcast')), + status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'failed')), + lifecycle_state TEXT NOT NULL, + explorer_url TEXT, + send_count INTEGER NOT NULL DEFAULT 1, + block_number INTEGER, + error_message TEXT, + submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + finalized_at TIMESTAMPTZ, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ); + + CREATE INDEX IF NOT EXISTS nonce_queue_transactions_submitted_at_idx + ON nonce_queue_transactions (submitted_at DESC); + + CREATE INDEX IF NOT EXISTS nonce_queue_transactions_address_chain_submitted_at_idx + ON nonce_queue_transactions (signer_address, chain_id, submitted_at DESC); + ` + ) + .then(() => {}); + } + + await tableEnsurePromise; +} + +export async function recordNonceQueueTxLifecycleEvent({ + pool, + event, +}: { + pool: Pool; + event: NonceQueueTxLifecycleEvent; +}): Promise { + await ensureNonceQueueTransactionsTable(pool); + + const signerAddress = event.signerAddress.toLowerCase(); + + if (event.type === "send_accepted") { + const lifecycleState = lifecycleStateForSubmit(event.stage); + await pool.query( + ` + INSERT INTO nonce_queue_transactions ( + tx_hash, + signer_address, + chain_id, + nonce, + stage, + status, + lifecycle_state, + explorer_url, + send_count, + submitted_at, + updated_at + ) + VALUES ($1, $2, $3, $4, $5, 'pending', $6, $7, 1, NOW(), NOW()) + ON CONFLICT (tx_hash) DO UPDATE SET + send_count = nonce_queue_transactions.send_count + 1, + stage = EXCLUDED.stage, + lifecycle_state = EXCLUDED.lifecycle_state, + explorer_url = COALESCE(nonce_queue_transactions.explorer_url, EXCLUDED.explorer_url), + updated_at = NOW(), + error_message = NULL + `, + [ + event.txHash, + signerAddress, + event.chainId, + event.nonce, + event.stage, + lifecycleState, + explorerUrlForHash(event.chainId, event.txHash), + ] + ); + return; + } + + if (event.type === "timeout") { + await pool.query( + ` + UPDATE nonce_queue_transactions + SET + status = 'failed', + lifecycle_state = 'timed_out', + finalized_at = COALESCE(finalized_at, NOW()), + updated_at = NOW(), + error_message = $4 + WHERE signer_address = $1 + AND chain_id = $2 + AND nonce = $3 + AND status = 'pending' + `, + [signerAddress, event.chainId, event.nonce, event.errorMessage] + ); + return; + } + + if (event.type === "terminal_send_error") { + if (event.txHash) { + await pool.query( + ` + UPDATE nonce_queue_transactions + SET + status = 'failed', + lifecycle_state = 'send_error', + finalized_at = COALESCE(finalized_at, NOW()), + updated_at = NOW(), + error_message = $2 + WHERE tx_hash = $1 + AND status = 'pending' + `, + [event.txHash, event.errorMessage] + ); + return; + } + + await pool.query( + ` + UPDATE nonce_queue_transactions + SET + status = 'failed', + lifecycle_state = 'send_error', + finalized_at = COALESCE(finalized_at, NOW()), + updated_at = NOW(), + error_message = $4 + WHERE signer_address = $1 + AND chain_id = $2 + AND nonce = $3 + AND status = 'pending' + `, + [signerAddress, event.chainId, event.nonce, event.errorMessage] + ); + return; + } + + const client = await pool.connect(); + const isSuccess = event.type === "mined_success"; + const minedStatus: NonceQueueTxStatus = isSuccess ? "completed" : "failed"; + const minedLifecycleState: NonceQueueTxLifecycleState = isSuccess + ? "confirmed" + : "reverted"; + + try { + await client.query("BEGIN"); + + await client.query( + ` + UPDATE nonce_queue_transactions + SET + status = $2, + lifecycle_state = $3, + block_number = $4, + finalized_at = COALESCE(finalized_at, NOW()), + updated_at = NOW(), + error_message = $5 + WHERE tx_hash = $1 + `, + [ + event.txHash, + minedStatus, + minedLifecycleState, + event.blockNumber, + isSuccess + ? null + : event.errorMessage ?? "Nonce-queued transaction reverted", + ] + ); + + await client.query( + ` + UPDATE nonce_queue_transactions + SET + status = 'failed', + lifecycle_state = 'replaced', + finalized_at = COALESCE(finalized_at, NOW()), + updated_at = NOW(), + error_message = $5 + WHERE signer_address = $1 + AND chain_id = $2 + AND nonce = $3 + AND tx_hash <> $4 + AND status = 'pending' + `, + [ + signerAddress, + event.chainId, + event.nonce, + event.txHash, + `Replaced by mined transaction ${event.txHash}`, + ] + ); + + await client.query("COMMIT"); + } catch (err) { + await client.query("ROLLBACK").catch(() => {}); + throw err; + } finally { + client.release(); + } +} + +export async function listNonceQueueTransactions({ + pool, + params, +}: { + pool: Pool; + params: ListNonceQueueTransactionsParams; +}): Promise { + await ensureNonceQueueTransactionsTable(pool); + + const clauses: string[] = []; + const values: Array = []; + + if (params.address) { + values.push(params.address.toLowerCase()); + clauses.push(`signer_address = $${values.length}`); + } + + if (params.chainId !== undefined) { + values.push(params.chainId); + clauses.push(`chain_id = $${values.length}`); + } + + values.push(params.limit); + const limitParam = `$${values.length}`; + const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : ""; + + const { rows } = await pool.query( + ` + SELECT + tx_hash, + signer_address, + chain_id, + nonce, + stage, + status, + lifecycle_state, + explorer_url, + send_count, + block_number, + error_message, + submitted_at, + finalized_at, + updated_at + FROM nonce_queue_transactions + ${where} + ORDER BY submitted_at DESC + LIMIT ${limitParam} + `, + values + ); + + return rows.map((row: any) => ({ + txHash: row.tx_hash, + signerAddress: row.signer_address, + chainId: Number(row.chain_id), + nonce: Number(row.nonce), + stage: row.stage, + status: row.status, + lifecycleState: row.lifecycle_state, + explorerUrl: row.explorer_url, + sendCount: Number(row.send_count), + blockNumber: + row.block_number === null || row.block_number === undefined + ? null + : Number(row.block_number), + errorMessage: row.error_message, + submittedAt: new Date(row.submitted_at).toISOString(), + finalizedAt: row.finalized_at + ? new Date(row.finalized_at).toISOString() + : null, + updatedAt: new Date(row.updated_at).toISOString(), + })); +} + +/** Reset module state. Only for testing. */ +export function _resetNonceQueueTxHistoryForTesting() { + tableEnsurePromise = null; +} + +export function _getExplorerTxBaseByChainForTesting() { + return EXPLORER_TX_BASE_BY_CHAIN; +} diff --git a/contracts/tasks/lib/nonceQueueTxLifecycle.test.ts b/contracts/tasks/lib/nonceQueueTxLifecycle.test.ts new file mode 100644 index 0000000000..443741db99 --- /dev/null +++ b/contracts/tasks/lib/nonceQueueTxLifecycle.test.ts @@ -0,0 +1,694 @@ +import { BigNumber, Wallet } from "ethers"; + +import { submitNonceQueuedTransaction } from "./nonceQueueTxLifecycle"; + +type EnvOverrides = Record; +const MAINNET_GAS_CAP_ENV = "NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_1"; +const BASE_GAS_CAP_ENV = "NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_8453"; + +function makeResponse(hash: string, raw?: string): any { + return { + hash, + raw, + rawTransaction: raw, + wait: async () => ({ status: 1, transactionHash: hash }), + }; +} + +async function withEnv(overrides: EnvOverrides, fn: () => Promise) { + const previousValues: Record = {}; + for (const key of Object.keys(overrides)) { + previousValues[key] = process.env[key]; + } + + for (const [key, value] of Object.entries(overrides)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + + try { + return await fn(); + } finally { + for (const [key, value] of Object.entries(previousValues)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + +async function testReplacementPath() { + console.log("--- Lifecycle Test 1: Replacement path fee bump ---"); + + const sentTxs: any[] = []; + let sendCount = 0; + const signerSendTransaction = async (tx: any) => { + sentTxs.push(tx); + if (sendCount === 0) { + sendCount++; + return makeResponse("0xinitial", "0xraw-initial"); + } + sendCount++; + return makeResponse("0xreplacement", "0xraw-replacement"); + }; + + let receiptChecks = 0; + const provider: any = { + async getTransactionReceipt(hash: string) { + receiptChecks++; + if (hash === "0xreplacement" && receiptChecks >= 4) { + return { status: 1, transactionHash: "0xreplacement" }; + } + return null; + }, + async getFeeData() { + return { + maxFeePerGas: BigNumber.from(400), + maxPriorityFeePerGas: BigNumber.from(5), + }; + }, + async sendTransaction() { + throw new Error("rebroadcast disabled in this test"); + }, + }; + + const result = await withEnv( + { + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "20", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "1", + NONCE_QUEUE_MAX_REPLACEMENTS: "2", + NONCE_QUEUE_FEE_BUMP_PCT: "20", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + maxFeePerGas: BigNumber.from(100), + maxPriorityFeePerGas: BigNumber.from(2), + } as any, + nonce: 7, + signerAddress: "0xaaaa", + chainId: 1, + }) + ); + + if (result.hash !== "0xreplacement") { + throw new Error(`Expected replacement hash, got ${result.hash}`); + } + if (sentTxs.length < 2) { + throw new Error(`Expected at least 2 submissions, got ${sentTxs.length}`); + } + if (sentTxs[1].nonce !== 7) { + throw new Error(`Expected replacement nonce=7, got ${sentTxs[1].nonce}`); + } + if (!BigNumber.from(sentTxs[1].maxFeePerGas).gt(sentTxs[0].maxFeePerGas)) { + throw new Error("Expected replacement maxFeePerGas to increase"); + } + if ( + !BigNumber.from(sentTxs[1].maxPriorityFeePerGas).gte( + sentTxs[0].maxPriorityFeePerGas + ) + ) { + throw new Error("Expected replacement maxPriorityFeePerGas to increase"); + } + + console.log("PASS: replacement submitted with same nonce and higher fees\n"); +} + +async function testTimeoutPath() { + console.log("--- Lifecycle Test 2: Confirmation timeout path ---"); + + const signerSendTransaction = async () => makeResponse("0xtimeout"); + const provider: any = { + async getTransactionReceipt() { + return null; + }, + async getFeeData() { + return {}; + }, + async sendTransaction() { + return makeResponse("0xnever"); + }, + }; + + let timeoutError: Error | undefined; + await withEnv( + { + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "2", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + async () => { + try { + await submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + } as any, + nonce: 11, + signerAddress: "0xbbbb", + chainId: 1, + }); + } catch (err: any) { + timeoutError = err; + } + } + ); + + if (!timeoutError) { + throw new Error("Expected timeout error but transaction did not fail"); + } + if (!timeoutError.message.includes("after 2s")) { + throw new Error( + `Unexpected timeout error message: ${timeoutError.message}` + ); + } + + console.log("PASS: confirmation timeout errors as expected\n"); +} + +async function testRebroadcastPath() { + console.log("--- Lifecycle Test 3: Rebroadcast duplicate handling ---"); + + const signerSendTransaction = async () => + makeResponse("0xrebroadcast", "0xraw-rebroadcast"); + + let rebroadcastAttempts = 0; + let receiptChecks = 0; + const provider: any = { + async getTransactionReceipt(hash: string) { + receiptChecks++; + if (hash === "0xrebroadcast" && receiptChecks >= 3) { + return { status: 1, transactionHash: "0xrebroadcast" }; + } + return null; + }, + async getFeeData() { + return {}; + }, + async sendTransaction() { + rebroadcastAttempts++; + throw new Error("already known"); + }, + }; + + const result = await withEnv( + { + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "10", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "1", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + } as any, + nonce: 12, + signerAddress: "0xcccc", + chainId: 1, + }) + ); + + if (result.hash !== "0xrebroadcast") { + throw new Error(`Expected original hash, got ${result.hash}`); + } + if (rebroadcastAttempts < 1) { + throw new Error("Expected at least one rebroadcast attempt"); + } + + console.log("PASS: rebroadcast duplicate errors are handled\n"); +} + +async function testInitialSubmissionGasCap() { + console.log("--- Lifecycle Test 4: Initial submission per-chain gas cap ---"); + + let sendCalls = 0; + const signerSendTransaction = async () => { + sendCalls++; + return makeResponse("0xshould-not-send"); + }; + const provider: any = { + async getFeeData() { + return {}; + }, + }; + + let capError: Error | undefined; + await withEnv( + { + [MAINNET_GAS_CAP_ENV]: "20", + [BASE_GAS_CAP_ENV]: undefined, + NONCE_QUEUE_MAX_GAS_PRICE_GWEI: undefined, + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "10", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + async () => { + try { + await submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + gasPrice: BigNumber.from(25).mul(1_000_000_000), + } as any, + nonce: 21, + signerAddress: "0xdddd", + chainId: 1, + }); + } catch (err: any) { + capError = err; + } + } + ); + + if (!capError) { + throw new Error("Expected initial submission gas cap error"); + } + if (!capError.message.includes("initial submission")) { + throw new Error(`Unexpected gas cap error message: ${capError.message}`); + } + if (sendCalls !== 0) { + throw new Error( + `Expected sendTransaction not to be called, got ${sendCalls}` + ); + } + + console.log("PASS: initial submission over cap fails before sending\n"); +} + +async function testMissingChainSpecificCapDisablesEnforcement() { + console.log( + "--- Lifecycle Test 5: Missing chain-specific cap disables enforcement ---" + ); + + let sendCalls = 0; + const signerSendTransaction = async () => { + sendCalls++; + return makeResponse("0xno-cap-chain"); + }; + + const result = await withEnv( + { + [MAINNET_GAS_CAP_ENV]: "20", + [BASE_GAS_CAP_ENV]: undefined, + NONCE_QUEUE_MAX_GAS_PRICE_GWEI: undefined, + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + gasPrice: BigNumber.from(25).mul(1_000_000_000), + } as any, + nonce: 23, + signerAddress: "0xffff", + chainId: 8453, + }) + ); + + if (result.hash !== "0xno-cap-chain") { + throw new Error(`Expected successful tx hash, got ${result.hash}`); + } + if (sendCalls !== 1) { + throw new Error(`Expected one send call, got ${sendCalls}`); + } + + console.log("PASS: cap is disabled when chain-specific env is missing\n"); +} + +async function testReplacementGasCap() { + console.log("--- Lifecycle Test 6: Replacement gas cap ---"); + + const sentTxs: any[] = []; + const signerSendTransaction = async (tx: any) => { + sentTxs.push(tx); + return makeResponse("0xreplacement-seed", "0xraw-replacement-seed"); + }; + + const provider: any = { + async getTransactionReceipt() { + return null; + }, + async getFeeData() { + return { + maxFeePerGas: BigNumber.from(10).mul(1_000_000_000), + maxPriorityFeePerGas: BigNumber.from(2).mul(1_000_000_000), + }; + }, + async sendTransaction() { + throw new Error("rebroadcast disabled in replacement cap test"); + }, + }; + + let capError: Error | undefined; + await withEnv( + { + [MAINNET_GAS_CAP_ENV]: "12", + [BASE_GAS_CAP_ENV]: undefined, + NONCE_QUEUE_MAX_GAS_PRICE_GWEI: undefined, + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "10", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "1", + NONCE_QUEUE_MAX_REPLACEMENTS: "2", + NONCE_QUEUE_FEE_BUMP_PCT: "30", + }, + async () => { + try { + await submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + maxFeePerGas: BigNumber.from(10).mul(1_000_000_000), + maxPriorityFeePerGas: BigNumber.from(2).mul(1_000_000_000), + type: 2, + } as any, + nonce: 24, + signerAddress: "0x1111", + chainId: 1, + }); + } catch (err: any) { + capError = err; + } + } + ); + + if (!capError) { + throw new Error("Expected replacement gas cap error"); + } + if (!capError.message.includes("replacement")) { + throw new Error(`Unexpected replacement cap error: ${capError.message}`); + } + if (sentTxs.length !== 1) { + throw new Error( + `Expected only initial send to happen before replacement cap failure, got ${sentTxs.length}` + ); + } + + console.log("PASS: replacement over cap fails before sending replacement\n"); +} + +async function testRebroadcastGasCap() { + console.log("--- Lifecycle Test 7: Rebroadcast gas cap ---"); + + const wallet = new Wallet( + "0x59c6995e998f97a5a0044966f0945388cf0f6e44f9c76c9d83f816f94f8f93f4" + ); + const highGasRaw = await wallet.signTransaction({ + to: "0x0000000000000000000000000000000000000001", + nonce: 0, + gasLimit: 21_000, + gasPrice: BigNumber.from(30).mul(1_000_000_000), + value: 0, + chainId: 1, + data: "0x", + }); + + let providerSendCalls = 0; + const signerSendTransaction = async () => makeResponse("0xseed", highGasRaw); + const provider: any = { + async getTransactionReceipt() { + return null; + }, + async getFeeData() { + return {}; + }, + async sendTransaction() { + providerSendCalls++; + return makeResponse("0xrebroadcast-high"); + }, + }; + + let capError: Error | undefined; + await withEnv( + { + [MAINNET_GAS_CAP_ENV]: "20", + [BASE_GAS_CAP_ENV]: undefined, + NONCE_QUEUE_MAX_GAS_PRICE_GWEI: undefined, + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "10", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "1", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + async () => { + try { + await submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + gasPrice: BigNumber.from(10).mul(1_000_000_000), + } as any, + nonce: 22, + signerAddress: "0xeeee", + chainId: 1, + }); + } catch (err: any) { + capError = err; + } + } + ); + + if (!capError) { + throw new Error("Expected rebroadcast gas cap error"); + } + if (!capError.message.includes("rebroadcast")) { + throw new Error(`Unexpected rebroadcast cap error: ${capError.message}`); + } + if (providerSendCalls !== 0) { + throw new Error( + `Expected provider rebroadcast sendTransaction not to be called, got ${providerSendCalls}` + ); + } + + console.log("PASS: rebroadcast over cap fails with clear error\n"); +} + +async function testDeprecatedGlobalGasCapIgnored() { + console.log("--- Lifecycle Test 8: Deprecated global gas cap is ignored ---"); + + let sendCalls = 0; + const signerSendTransaction = async () => { + sendCalls++; + return makeResponse("0xglobal-ignored"); + }; + + const result = await withEnv( + { + [MAINNET_GAS_CAP_ENV]: undefined, + [BASE_GAS_CAP_ENV]: undefined, + NONCE_QUEUE_MAX_GAS_PRICE_GWEI: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + gasPrice: BigNumber.from(50).mul(1_000_000_000), + } as any, + nonce: 25, + signerAddress: "0x2222", + chainId: 1, + }) + ); + + if (result.hash !== "0xglobal-ignored") { + throw new Error(`Expected tx success when only global cap is set`); + } + if (sendCalls !== 1) { + throw new Error(`Expected one send call, got ${sendCalls}`); + } + + console.log("PASS: deprecated global cap does not enforce lifecycle limit\n"); +} + +async function testInitialSubmissionGasLimitBuffer() { + console.log("--- Lifecycle Test 9: Initial submission gas-limit buffer ---"); + + const sentTxs: any[] = []; + const signerSendTransaction = async (tx: any) => { + sentTxs.push(tx); + return makeResponse("0xgas-buffer-initial"); + }; + + const provider: any = { + async getTransactionReceipt(hash: string) { + if (hash === "0xgas-buffer-initial") { + return { status: 1, transactionHash: "0xgas-buffer-initial" }; + } + return null; + }, + async getFeeData() { + return {}; + }, + async estimateGas() { + return BigNumber.from(100_000); + }, + }; + + await withEnv( + { + NONCE_QUEUE_GAS_LIMIT_BUFFER_PCT: "25", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "0", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + } as any, + nonce: 26, + signerAddress: "0x3333", + chainId: 1, + }) + ); + + if (sentTxs.length !== 1) { + throw new Error(`Expected a single submission, got ${sentTxs.length}`); + } + if (!BigNumber.from(sentTxs[0].gasLimit).eq(125_000)) { + throw new Error( + `Expected buffered gasLimit=125000, got ${sentTxs[0].gasLimit}` + ); + } + + console.log("PASS: initial submission gas limit buffered from estimate\n"); +} + +async function testReplacementGasLimitBuffer() { + console.log("--- Lifecycle Test 10: Replacement gas-limit buffer ---"); + + const sentTxs: any[] = []; + let sendCount = 0; + const signerSendTransaction = async (tx: any) => { + sentTxs.push(tx); + if (sendCount === 0) { + sendCount++; + return makeResponse("0xgas-buffer-seed", "0xraw-seed"); + } + sendCount++; + return makeResponse("0xgas-buffer-replacement", "0xraw-replacement"); + }; + + let receiptChecks = 0; + let estimateCalls = 0; + const provider: any = { + async getTransactionReceipt(hash: string) { + receiptChecks++; + if (hash === "0xgas-buffer-replacement" && receiptChecks >= 4) { + return { status: 1, transactionHash: "0xgas-buffer-replacement" }; + } + return null; + }, + async getFeeData() { + return { + maxFeePerGas: BigNumber.from(400), + maxPriorityFeePerGas: BigNumber.from(5), + }; + }, + async estimateGas() { + estimateCalls++; + return estimateCalls === 1 + ? BigNumber.from(100_000) + : BigNumber.from(150_000); + }, + async sendTransaction() { + throw new Error("rebroadcast disabled in this test"); + }, + }; + + await withEnv( + { + NONCE_QUEUE_GAS_LIMIT_BUFFER_PCT: "25", + NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S: "20", + NONCE_QUEUE_RECEIPT_POLL_S: "1", + NONCE_QUEUE_REBROADCAST_INTERVAL_S: "0", + NONCE_QUEUE_REPLACE_INTERVAL_S: "1", + NONCE_QUEUE_MAX_REPLACEMENTS: "2", + NONCE_QUEUE_FEE_BUMP_PCT: "20", + }, + () => + submitNonceQueuedTransaction({ + sendTransaction: signerSendTransaction as any, + provider, + transaction: { + to: "0x0000000000000000000000000000000000000001", + data: "0x", + maxFeePerGas: BigNumber.from(100), + maxPriorityFeePerGas: BigNumber.from(2), + } as any, + nonce: 27, + signerAddress: "0x4444", + chainId: 1, + }) + ); + + if (sentTxs.length < 2) { + throw new Error(`Expected replacement submission, got ${sentTxs.length}`); + } + if (!BigNumber.from(sentTxs[0].gasLimit).eq(125_000)) { + throw new Error( + `Expected initial buffered gasLimit=125000, got ${sentTxs[0].gasLimit}` + ); + } + if (!BigNumber.from(sentTxs[1].gasLimit).eq(187_500)) { + throw new Error( + `Expected replacement buffered gasLimit=187500, got ${sentTxs[1].gasLimit}` + ); + } + + console.log( + "PASS: replacement submission gas limit buffered from estimate\n" + ); +} + +async function test() { + await testReplacementPath(); + await testTimeoutPath(); + await testRebroadcastPath(); + await testInitialSubmissionGasCap(); + await testMissingChainSpecificCapDisablesEnforcement(); + await testReplacementGasCap(); + await testRebroadcastGasCap(); + await testDeprecatedGlobalGasCapIgnored(); + await testInitialSubmissionGasLimitBuffer(); + await testReplacementGasLimitBuffer(); + console.log("All nonceQueueTxLifecycle tests passed!"); +} + +test().catch((err) => { + console.error("TEST FAILED:", err); + process.exit(1); +}); diff --git a/contracts/tasks/lib/nonceQueueTxLifecycle.ts b/contracts/tasks/lib/nonceQueueTxLifecycle.ts new file mode 100644 index 0000000000..80e3bb615e --- /dev/null +++ b/contracts/tasks/lib/nonceQueueTxLifecycle.ts @@ -0,0 +1,756 @@ +import type { Pool, PoolClient } from "pg"; +import { BigNumber, utils } from "ethers"; +import type { ethers } from "ethers"; +import type { NonceQueueTxLifecycleEvent } from "./nonceQueueTxHistory"; + +const log = require("../../utils/logger")("utils:nonceQueueTxLifecycle"); + +const GWEI = BigNumber.from(1_000_000_000); +const DEFAULT_PRIORITY_FEE = GWEI.mul(2); +const DEFAULT_GAS_PRICE = GWEI.mul(20); + +export interface SubmitNonceQueuedTxParams { + sendTransaction: ( + transaction: Parameters[0] + ) => Promise; + provider?: ethers.providers.Provider; + transaction: Parameters[0]; + nonce: number; + signerAddress: string; + chainId: number; + onLifecycleEvent?: ( + event: NonceQueueTxLifecycleEvent + ) => void | Promise; +} + +interface TxLifecycleConfig { + txConfirmTimeoutS: number; + receiptPollS: number; + rebroadcastIntervalS: number; + replaceIntervalS: number; + maxReplacements: number; + feeBumpPct: number; + gasLimitBufferPct: number; +} + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function parseIntEnv( + name: string, + fallback: number, + minimum = 0, + maximum?: number +): number { + const value = process.env[name]; + if (!value) return fallback; + + const parsed = Number(value); + const withinMax = maximum === undefined || parsed <= maximum; + if (!Number.isInteger(parsed) || parsed < minimum || !withinMax) { + const maxMsg = maximum === undefined ? "" : ` and <= ${maximum}`; + log( + `Invalid ${name}="${value}" (expected integer >= ${minimum}${maxMsg}). Falling back to ${fallback}.` + ); + return fallback; + } + return parsed; +} + +function getTxLifecycleConfig(): TxLifecycleConfig { + return { + txConfirmTimeoutS: parseIntEnv("NONCE_QUEUE_TX_CONFIRM_TIMEOUT_S", 600), + receiptPollS: parseIntEnv("NONCE_QUEUE_RECEIPT_POLL_S", 5, 1), + rebroadcastIntervalS: parseIntEnv("NONCE_QUEUE_REBROADCAST_INTERVAL_S", 30), + replaceIntervalS: parseIntEnv("NONCE_QUEUE_REPLACE_INTERVAL_S", 90), + maxReplacements: parseIntEnv("NONCE_QUEUE_MAX_REPLACEMENTS", 3), + feeBumpPct: parseIntEnv("NONCE_QUEUE_FEE_BUMP_PCT", 15, 0, 500), + gasLimitBufferPct: parseIntEnv( + "NONCE_QUEUE_GAS_LIMIT_BUFFER_PCT", + 0, + 0, + 500 + ), + }; +} + +function secondsToMs(seconds: number): number { + return seconds * 1_000; +} + +function asBigNumber(value: any): BigNumber | undefined { + if (value === undefined || value === null) return undefined; + try { + return BigNumber.from(value); + } catch { + return undefined; + } +} + +function bumpByPercent(value: BigNumber, percent: number): BigNumber { + if (percent === 0) return value; + return value + .mul(100 + percent) + .add(99) + .div(100); +} + +function maxBigNumber(a: BigNumber, b: BigNumber): BigNumber { + return a.gte(b) ? a : b; +} + +function getTxCapComparableGasPrice( + transaction: Parameters[0] +): BigNumber | undefined { + // For EIP-1559, maxFeePerGas is the effective ceiling; for legacy tx use gasPrice. + return ( + asBigNumber(transaction.maxFeePerGas) ?? asBigNumber(transaction.gasPrice) + ); +} + +async function applyGasLimitBuffer({ + transaction, + provider, + gasLimitBufferPct, + signerAddress, + chainId, + nonce, + stage, +}: { + transaction: Parameters[0]; + provider?: ethers.providers.Provider; + gasLimitBufferPct: number; + signerAddress: string; + chainId: number; + nonce: number; + stage: "initial submission" | "replacement"; +}): Promise[0]> { + if (gasLimitBufferPct <= 0) return transaction; + if (!provider || typeof provider.estimateGas !== "function") + return transaction; + + const estimateRequest: Parameters< + ethers.providers.Provider["estimateGas"] + >[0] = { + ...transaction, + from: signerAddress, + }; + if (estimateRequest.nonce === undefined) { + estimateRequest.nonce = nonce; + } + + const estimatedGas = await provider + .estimateGas(estimateRequest) + .catch((err) => { + log( + `Failed to estimate gas for ${stage}; skipping NONCE_QUEUE_GAS_LIMIT_BUFFER_PCT: address=${signerAddress} chain=${chainId} nonce=${nonce} error="${ + err?.message ?? String(err) + }"` + ); + return null; + }); + if (!estimatedGas) return transaction; + + const bufferedGasLimit = bumpByPercent(estimatedGas, gasLimitBufferPct); + const configuredGasLimit = asBigNumber(transaction.gasLimit); + const finalGasLimit = configuredGasLimit + ? maxBigNumber(configuredGasLimit, bufferedGasLimit) + : bufferedGasLimit; + + log( + `Applied gas-limit buffer: stage=${stage} address=${signerAddress} chain=${chainId} nonce=${nonce} estimatedGas=${estimatedGas.toString()} bufferPct=${gasLimitBufferPct} bufferedGas=${bufferedGasLimit.toString()} finalGasLimit=${finalGasLimit.toString()}${ + configuredGasLimit + ? ` configuredGasLimit=${configuredGasLimit.toString()}` + : "" + }` + ); + + if (configuredGasLimit && finalGasLimit.eq(configuredGasLimit)) { + return transaction; + } + + return { + ...transaction, + gasLimit: finalGasLimit, + }; +} + +function getPerChainMaxGasPriceEnvKey(chainId: number): string { + return `NONCE_QUEUE_MAX_GAS_PRICE_GWEI_CHAIN_${chainId}`; +} + +function resolveMaxGasPriceWeiForChain(chainId: number): BigNumber | null { + const envKey = getPerChainMaxGasPriceEnvKey(chainId); + const value = process.env[envKey]; + if (!value) return null; + + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < 0) { + log( + `Invalid ${envKey}="${value}" (expected integer >= 0). Gas cap disabled for chain ${chainId}.` + ); + return null; + } + if (parsed === 0) return null; + return GWEI.mul(parsed); +} + +function assertWithinGasPriceCap({ + priceWei, + maxGasPriceWei, + maxGasPriceEnvKey, + stage, + signerAddress, + chainId, + nonce, +}: { + priceWei: BigNumber; + maxGasPriceWei: BigNumber; + maxGasPriceEnvKey: string; + stage: "initial submission" | "rebroadcast" | "replacement"; + signerAddress: string; + chainId: number; + nonce: number; +}) { + if (priceWei.lte(maxGasPriceWei)) return; + throw new Error( + `Nonce queue gas price cap exceeded during ${stage}: address=${signerAddress} chain=${chainId} nonce=${nonce} gasPrice=${priceWei.toString()} wei cap=${maxGasPriceWei.toString()} wei (set by ${maxGasPriceEnvKey})` + ); +} + +async function enforceSubmissionGasPriceCap({ + transaction, + provider, + maxGasPriceWei, + maxGasPriceEnvKey, + stage, + signerAddress, + chainId, + nonce, +}: { + transaction: Parameters[0]; + provider?: ethers.providers.Provider; + maxGasPriceWei: BigNumber | null; + maxGasPriceEnvKey: string; + stage: "initial submission" | "replacement"; + signerAddress: string; + chainId: number; + nonce: number; +}) { + if (!maxGasPriceWei) return; + + // Prefer explicit tx fee fields. If missing, query network fee data so the cap + // still applies to transactions where callers omitted fee overrides. + let comparableGasPrice = getTxCapComparableGasPrice(transaction); + if (!comparableGasPrice && provider) { + const feeData = await provider.getFeeData().catch(() => null); + comparableGasPrice = + asBigNumber(feeData?.maxFeePerGas) ?? asBigNumber(feeData?.gasPrice); + } + + if (!comparableGasPrice) { + throw new Error( + `Unable to enforce ${maxGasPriceEnvKey} for ${stage}: address=${signerAddress} chain=${chainId} nonce=${nonce} transaction has no gasPrice/maxFeePerGas and provider fee data is unavailable` + ); + } + + assertWithinGasPriceCap({ + priceWei: comparableGasPrice, + maxGasPriceWei, + maxGasPriceEnvKey, + stage, + signerAddress, + chainId, + nonce, + }); +} + +function enforceRebroadcastGasPriceCap({ + rawTransaction, + maxGasPriceWei, + maxGasPriceEnvKey, + signerAddress, + chainId, + nonce, +}: { + rawTransaction: string; + maxGasPriceWei: BigNumber | null; + maxGasPriceEnvKey: string; + signerAddress: string; + chainId: number; + nonce: number; +}) { + if (!maxGasPriceWei) return; + + // Rebroadcast path operates on raw signed tx bytes. Parse them to recover + // gas settings and enforce the same cap policy before re-sending. + const parsedTransaction = utils.parseTransaction(rawTransaction); + const comparableGasPrice = + asBigNumber(parsedTransaction.maxFeePerGas) ?? + asBigNumber(parsedTransaction.gasPrice); + + if (!comparableGasPrice) { + throw new Error( + `Unable to enforce ${maxGasPriceEnvKey} for rebroadcast: address=${signerAddress} chain=${chainId} nonce=${nonce} raw transaction has no gasPrice/maxFeePerGas` + ); + } + + assertWithinGasPriceCap({ + priceWei: comparableGasPrice, + maxGasPriceWei, + maxGasPriceEnvKey, + stage: "rebroadcast", + signerAddress, + chainId, + nonce, + }); +} + +function extractRawTransaction( + response: ethers.providers.TransactionResponse +): string | undefined { + const candidate = (response as any).raw ?? (response as any).rawTransaction; + return typeof candidate === "string" && candidate.length > 0 + ? candidate + : undefined; +} + +function isDuplicateBroadcastError(err: any): boolean { + const msg = (err?.message ?? "").toLowerCase(); + return ( + msg.includes("already known") || + msg.includes("known transaction") || + msg.includes("already imported") + ); +} + +async function findMinedReceipt( + provider: ethers.providers.Provider, + hashes: string[] +) { + for (const hash of hashes) { + const receipt = await provider.getTransactionReceipt(hash); + if (receipt) return receipt; + } + return null; +} + +async function buildReplacementTransaction( + transaction: Parameters[0], + provider: ethers.providers.Provider, + feeBumpPct: number +): Promise[0]> { + const feeData = await provider.getFeeData().catch(() => null); + const nextTx: Parameters[0] = { + ...transaction, + }; + + const hasEip1559Fees = + nextTx.maxFeePerGas !== undefined || + nextTx.maxPriorityFeePerGas !== undefined || + nextTx.type === 2; + + if (hasEip1559Fees) { + // Keep replacement monotonic: bump previous fee settings and never go below + // current network recommendations. + const basePriority = + asBigNumber(nextTx.maxPriorityFeePerGas) ?? + asBigNumber(feeData?.maxPriorityFeePerGas) ?? + DEFAULT_PRIORITY_FEE; + const baseMaxFee = + asBigNumber(nextTx.maxFeePerGas) ?? + asBigNumber(feeData?.maxFeePerGas) ?? + basePriority.mul(2); + + const bumpedPriority = bumpByPercent(basePriority, feeBumpPct); + const bumpedMaxFee = bumpByPercent(baseMaxFee, feeBumpPct); + const networkPriority = asBigNumber(feeData?.maxPriorityFeePerGas); + const networkMaxFee = asBigNumber(feeData?.maxFeePerGas); + + let finalPriority = bumpedPriority; + let finalMaxFee = bumpedMaxFee; + if (networkPriority) { + finalPriority = maxBigNumber(finalPriority, networkPriority); + } + if (networkMaxFee) { + finalMaxFee = maxBigNumber(finalMaxFee, networkMaxFee); + } + if (finalMaxFee.lt(finalPriority)) { + finalMaxFee = finalPriority; + } + + delete nextTx.gasPrice; + nextTx.maxPriorityFeePerGas = finalPriority; + nextTx.maxFeePerGas = finalMaxFee; + nextTx.type = 2; + return nextTx; + } + + const baseGasPrice = + asBigNumber(nextTx.gasPrice) ?? + asBigNumber(feeData?.gasPrice) ?? + DEFAULT_GAS_PRICE; + // Legacy path: bump prior gasPrice and floor at current network gasPrice. + const networkGasPrice = asBigNumber(feeData?.gasPrice); + let finalGasPrice = bumpByPercent(baseGasPrice, feeBumpPct); + if (networkGasPrice) { + finalGasPrice = maxBigNumber(finalGasPrice, networkGasPrice); + } + nextTx.gasPrice = finalGasPrice; + return nextTx; +} + +/** + * Send a nonce-pinned transaction and wait for on-chain confirmation. + * Future resend / replacement strategy should stay in this file. + */ +export async function submitNonceQueuedTransaction({ + sendTransaction, + provider, + transaction, + nonce, + signerAddress, + chainId, + onLifecycleEvent, +}: SubmitNonceQueuedTxParams): Promise { + const emitLifecycleEvent = async (event: NonceQueueTxLifecycleEvent) => { + if (!onLifecycleEvent) return; + try { + await onLifecycleEvent(event); + } catch (err: any) { + log( + `Failed to record nonce queue transaction lifecycle event: type=${ + event.type + } address=${event.signerAddress} chain=${event.chainId} nonce=${ + event.nonce + } error="${err?.message ?? String(err)}"` + ); + } + }; + + const config = getTxLifecycleConfig(); + const maxGasPriceEnvKey = getPerChainMaxGasPriceEnvKey(chainId); + // The lifecycle intentionally ignores the legacy global cap and only reads + // per-chain caps, so operators can tune limits independently per network. + const maxGasPriceWei = resolveMaxGasPriceWeiForChain(chainId); + let activeResponse: ethers.providers.TransactionResponse | undefined; + let terminalStateAlreadyRecorded = false; + + try { + let initialTx: Parameters[0] = { + ...transaction, + nonce, + }; + + // Apply optional global gas-limit headroom over provider estimate to reduce + // under-estimation failures during volatile state changes. + initialTx = await applyGasLimitBuffer({ + transaction: initialTx, + provider, + gasLimitBufferPct: config.gasLimitBufferPct, + signerAddress, + chainId, + nonce, + stage: "initial submission", + }); + + // Enforce fee cap before any on-chain submission. + await enforceSubmissionGasPriceCap({ + transaction: initialTx, + provider, + maxGasPriceWei, + maxGasPriceEnvKey, + stage: "initial submission", + signerAddress, + chainId, + nonce, + }); + + const firstResponse = await sendTransaction(initialTx); + const responsesByHash = new Map< + string, + ethers.providers.TransactionResponse + >([[firstResponse.hash, firstResponse]]); + const knownHashes: string[] = [firstResponse.hash]; + let activeTx = initialTx; + activeResponse = firstResponse; + let activeRawTx = extractRawTransaction(firstResponse); + let replacementCount = 0; + let rebroadcastRawUnavailableLogged = false; + + await emitLifecycleEvent({ + type: "send_accepted", + stage: "initial", + txHash: firstResponse.hash, + signerAddress, + chainId, + nonce, + }); + + log( + `Submitted tx: address=${signerAddress} chain=${chainId} nonce=${nonce} hash=${firstResponse.hash}` + ); + + if (!provider || typeof provider.getTransactionReceipt !== "function") { + const receipt = await firstResponse.wait(); + if (receipt?.status === 0) { + terminalStateAlreadyRecorded = true; + await emitLifecycleEvent({ + type: "mined_revert", + txHash: firstResponse.hash, + signerAddress, + chainId, + nonce, + blockNumber: receipt?.blockNumber ?? null, + errorMessage: `Nonce-queued transaction reverted on-chain: hash=${firstResponse.hash} nonce=${nonce}`, + }); + throw new Error( + `Nonce-queued transaction reverted on-chain: hash=${firstResponse.hash} nonce=${nonce}` + ); + } + + await emitLifecycleEvent({ + type: "mined_success", + txHash: firstResponse.hash, + signerAddress, + chainId, + nonce, + blockNumber: receipt?.blockNumber ?? null, + }); + return firstResponse; + } + + const txProvider = provider; + + const startedAt = Date.now(); + let nextRebroadcastAt = + config.rebroadcastIntervalS > 0 + ? startedAt + secondsToMs(config.rebroadcastIntervalS) + : Number.POSITIVE_INFINITY; + let nextReplaceAt = + config.replaceIntervalS > 0 + ? startedAt + secondsToMs(config.replaceIntervalS) + : Number.POSITIVE_INFINITY; + + // Single in-flight lifecycle loop: + // 1) check mined receipts across known hashes + // 2) optional rebroadcast raw tx + // 3) optional same-nonce replacement with bumped fee + while (true) { + const receipt = await findMinedReceipt(txProvider, knownHashes); + if (receipt) { + if (receipt.status === 0) { + terminalStateAlreadyRecorded = true; + await emitLifecycleEvent({ + type: "mined_revert", + txHash: receipt.transactionHash, + signerAddress, + chainId, + nonce, + blockNumber: receipt.blockNumber ?? null, + errorMessage: `Nonce-queued transaction reverted on-chain: hash=${receipt.transactionHash} nonce=${nonce}`, + }); + throw new Error( + `Nonce-queued transaction reverted on-chain: hash=${receipt.transactionHash} nonce=${nonce}` + ); + } + + await emitLifecycleEvent({ + type: "mined_success", + txHash: receipt.transactionHash, + signerAddress, + chainId, + nonce, + blockNumber: receipt.blockNumber ?? null, + }); + return responsesByHash.get(receipt.transactionHash) ?? activeResponse; + } + + const now = Date.now(); + if ( + config.txConfirmTimeoutS > 0 && + now - startedAt >= secondsToMs(config.txConfirmTimeoutS) + ) { + const timeoutMessage = `Timed out waiting for nonce-queued tx confirmation after ${config.txConfirmTimeoutS}s: address=${signerAddress} chain=${chainId} nonce=${nonce} lastHash=${activeResponse.hash}`; + terminalStateAlreadyRecorded = true; + await emitLifecycleEvent({ + type: "timeout", + signerAddress, + chainId, + nonce, + errorMessage: timeoutMessage, + }); + throw new Error(timeoutMessage); + } + + if (now >= nextRebroadcastAt) { + nextRebroadcastAt = now + secondsToMs(config.rebroadcastIntervalS); + + if (activeRawTx && typeof txProvider.sendTransaction === "function") { + try { + // Re-check cap at rebroadcast time; tx may have been replaced with + // a higher fee since the initial submission. + enforceRebroadcastGasPriceCap({ + rawTransaction: activeRawTx, + maxGasPriceWei, + maxGasPriceEnvKey, + signerAddress, + chainId, + nonce, + }); + const rebroadcastResponse = await txProvider.sendTransaction( + activeRawTx + ); + if (!responsesByHash.has(rebroadcastResponse.hash)) { + responsesByHash.set( + rebroadcastResponse.hash, + rebroadcastResponse + ); + knownHashes.push(rebroadcastResponse.hash); + } + await emitLifecycleEvent({ + type: "send_accepted", + stage: "rebroadcast", + txHash: rebroadcastResponse.hash, + signerAddress, + chainId, + nonce, + }); + log( + `Rebroadcasted raw tx: address=${signerAddress} chain=${chainId} nonce=${nonce} hash=${rebroadcastResponse.hash}` + ); + } catch (err: any) { + if (!isDuplicateBroadcastError(err)) throw err; + log( + `Rebroadcast ignored duplicate: address=${signerAddress} chain=${chainId} nonce=${nonce} hash=${activeResponse.hash}` + ); + } + } else if (!rebroadcastRawUnavailableLogged) { + rebroadcastRawUnavailableLogged = true; + log( + `Rebroadcast skipped: raw transaction payload unavailable for hash=${activeResponse.hash} address=${signerAddress} chain=${chainId}` + ); + } + } + + if ( + now >= nextReplaceAt && + replacementCount < config.maxReplacements && + config.replaceIntervalS > 0 + ) { + nextReplaceAt = now + secondsToMs(config.replaceIntervalS); + activeTx = await buildReplacementTransaction( + activeTx, + txProvider, + config.feeBumpPct + ); + activeTx = await applyGasLimitBuffer({ + transaction: activeTx, + provider: txProvider, + gasLimitBufferPct: config.gasLimitBufferPct, + signerAddress, + chainId, + nonce, + stage: "replacement", + }); + // Replacement txs are still fresh submissions. Enforce the same chain cap + // after fee bumping so retries never exceed operator limits. + await enforceSubmissionGasPriceCap({ + transaction: activeTx, + provider: txProvider, + maxGasPriceWei, + maxGasPriceEnvKey, + stage: "replacement", + signerAddress, + chainId, + nonce, + }); + + try { + const replacementResponse = await sendTransaction(activeTx); + replacementCount++; + activeResponse = replacementResponse; + activeRawTx = extractRawTransaction(replacementResponse); + if (!responsesByHash.has(replacementResponse.hash)) { + responsesByHash.set(replacementResponse.hash, replacementResponse); + knownHashes.push(replacementResponse.hash); + } + await emitLifecycleEvent({ + type: "send_accepted", + stage: "replacement", + txHash: replacementResponse.hash, + signerAddress, + chainId, + nonce, + }); + log( + `Submitted replacement tx: address=${signerAddress} chain=${chainId} nonce=${nonce} hash=${replacementResponse.hash} replacements=${replacementCount}/${config.maxReplacements}` + ); + } catch (err: any) { + if (!isNonceMismatchError(err) && !isDuplicateBroadcastError(err)) { + throw err; + } + log( + `Replacement attempt not accepted yet: address=${signerAddress} chain=${chainId} nonce=${nonce} reason="${ + err?.message ?? String(err) + }"` + ); + } + } + + await sleep(secondsToMs(config.receiptPollS)); + } + } catch (err: any) { + if (!terminalStateAlreadyRecorded && activeResponse?.hash) { + await emitLifecycleEvent({ + type: "terminal_send_error", + signerAddress, + chainId, + nonce, + txHash: activeResponse.hash, + errorMessage: err?.message ?? String(err), + }); + } + throw err; + } +} + +export function isNonceMismatchError(err: any): boolean { + const msg = (err?.message ?? "").toLowerCase(); + return ( + msg.includes("nonce too low") || + msg.includes("nonce has already been used") || + msg.includes("replacement transaction underpriced") + ); +} + +export async function recoverNonceFromChain({ + pool, + signerAddress, + chainId, + getOnChainNonce, + client, +}: { + pool: Pool; + signerAddress: string; + chainId: number; + getOnChainNonce: () => Promise; + client?: PoolClient; +}) { + const onChainNonce = await getOnChainNonce(); + const recoveryClient = client ?? (await pool.connect()); + const usingExternalClient = !!client; + + try { + await recoveryClient.query( + "UPDATE nonce_queue SET nonce = $1, updated_at = NOW() WHERE signer_address = $2 AND chain_id = $3", + [onChainNonce, signerAddress, chainId] + ); + log( + `Recovered nonce from chain: address=${signerAddress} chain=${chainId} nonce=${onChainNonce}` + ); + } finally { + if (!usingExternalClient) recoveryClient.release(); + } +} diff --git a/contracts/tasks/lib/signer.ts b/contracts/tasks/lib/signer.ts new file mode 100644 index 0000000000..489c431902 --- /dev/null +++ b/contracts/tasks/lib/signer.ts @@ -0,0 +1,237 @@ +import { + GetPublicKeyCommand, + KMSClient, + SignCommand, +} from "@aws-sdk/client-kms"; +import { DirectKmsTransactionSigner } from "@lastdotnet/purrikey"; +import { ethers } from "ethers"; +import { + type Hex, + hashMessage, + hashTypedData, + hexToBytes, + keccak256, + type LocalAccount, + recoverAddress, + serializeTransaction, + signatureToHex, + type TransactionSerializable, + type TypedDataDefinition, +} from "viem"; +import { toAccount } from "viem/accounts"; +import { optionalEnv } from "./env"; + +const DEFAULT_KMS_RELAYER_ID = "mrk-248128595151466bb7f7b9a56501a98f"; +const AWS_KMS_REGION = "us-east-1"; + +// secp256k1 curve order +const SECP256K1_N = + 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n; + +function getKmsKeyId(): string { + return optionalEnv("KMS_RELAYER_ID") ?? DEFAULT_KMS_RELAYER_ID; +} + +/** + * Decode a DER-encoded ECDSA signature into (r, s) as bigints. + */ +function decodeDerSignature(der: Uint8Array): { r: bigint; s: bigint } { + let pos = 0; + + if (der[pos++] !== 0x30) + throw new Error("Invalid DER: expected SEQUENCE (0x30)"); + pos++; // skip sequence length + + if (der[pos++] !== 0x02) + throw new Error("Invalid DER: expected INTEGER (0x02) for r"); + const rLen = der[pos++]; + let rBytes = der.slice(pos, pos + rLen); + pos += rLen; + + if (der[pos++] !== 0x02) + throw new Error("Invalid DER: expected INTEGER (0x02) for s"); + const sLen = der[pos++]; + let sBytes = der.slice(pos, pos + sLen); + + // Strip leading zero padding (DER uses it to avoid negative interpretation) + while (rBytes.length > 1 && rBytes[0] === 0x00) rBytes = rBytes.slice(1); + while (sBytes.length > 1 && sBytes[0] === 0x00) sBytes = sBytes.slice(1); + + const r = BigInt(`0x${Buffer.from(rBytes).toString("hex")}`); + const s = BigInt(`0x${Buffer.from(sBytes).toString("hex")}`); + + return { r, s }; +} + +/** + * Extract the uncompressed secp256k1 public key from a DER-encoded + * SubjectPublicKeyInfo blob (as returned by KMS GetPublicKey). + * Returns the 65-byte uncompressed key (04 ‖ x ‖ y). + */ +function extractPublicKeyFromDer(der: Uint8Array): Uint8Array { + // AWS KMS returns a SubjectPublicKeyInfo structure. The last 65 bytes + // are the uncompressed EC point (0x04 prefix + 32-byte x + 32-byte y). + if (der.length < 65) { + throw new Error(`DER public key too short: ${der.length} bytes`); + } + const pubKey = der.slice(-65); + if (pubKey[0] !== 0x04) { + throw new Error( + `Expected uncompressed public key prefix 0x04, got 0x${pubKey[0].toString( + 16 + )}` + ); + } + return pubKey; +} + +/** + * Derive an Ethereum address from an uncompressed secp256k1 public key. + * address = last 20 bytes of keccak256(x ‖ y) + */ +function publicKeyToAddress(uncompressedKey: Uint8Array): `0x${string}` { + // Strip the 0x04 prefix — keccak256 the raw 64-byte (x, y) + const xy = uncompressedKey.slice(1); + const xyHex = `0x${Buffer.from(xy).toString("hex")}` as Hex; + const hash = keccak256(xyHex); + // Last 20 bytes of the hash + return `0x${hash.slice(-40)}` as `0x${string}`; +} + +/** + * Resolve the Ethereum address for a KMS key via GetPublicKey. + */ +async function resolveKmsAddress( + kmsClient: KMSClient, + keyId: string +): Promise<`0x${string}`> { + const response = await kmsClient.send( + new GetPublicKeyCommand({ KeyId: keyId }) + ); + if (!response.PublicKey) { + throw new Error("No public key returned from KMS"); + } + const pubKey = extractPublicKeyFromDer(new Uint8Array(response.PublicKey)); + return publicKeyToAddress(pubKey); +} + +function bigintToHex32(value: bigint): Hex { + return `0x${value.toString(16).padStart(64, "0")}` as Hex; +} + +/** + * Sign a 32-byte digest with AWS KMS and return {r, s, yParity}. + * Handles low-s canonicalization (EIP-2) and recovery parity detection. + */ +async function kmsSign( + kmsClient: KMSClient, + keyId: string, + digest: Hex, + expectedAddress: `0x${string}` +): Promise<{ r: Hex; s: Hex; yParity: 0 | 1 }> { + const response = await kmsClient.send( + new SignCommand({ + KeyId: keyId, + Message: Buffer.from(hexToBytes(digest)), + MessageType: "DIGEST", + SigningAlgorithm: "ECDSA_SHA_256", + }) + ); + + if (!response.Signature) { + throw new Error("No signature returned from KMS"); + } + + const { r, s: rawS } = decodeDerSignature(new Uint8Array(response.Signature)); + let s = rawS; + + // EIP-2: canonicalize s to lower half of curve order + if (s > SECP256K1_N / 2n) { + s = SECP256K1_N - s; + } + + const rHex = bigintToHex32(r); + const sHex = bigintToHex32(s); + + // Try all recovery parities to find the one matching our address + for (const yParity of [0, 1] as const) { + const recovered = await recoverAddress({ + hash: digest, + signature: { r: rHex, s: sHex, v: BigInt(yParity + 27) }, + }); + + if (recovered.toLowerCase() === expectedAddress.toLowerCase()) { + return { r: rHex, s: sHex, yParity }; + } + } + + throw new Error( + `KMS signature recovery failed: could not recover ${expectedAddress}` + ); +} + +/** + * Create a viem LocalAccount backed by AWS KMS. + * Chain-agnostic — chain context comes from the walletClient, not the account. + * Address is resolved once via GetPublicKey and cached in the closure. + */ +export async function getKmsAccount(): Promise { + const keyId = getKmsKeyId(); + const kmsClient = new KMSClient({ region: AWS_KMS_REGION }); + const address = await resolveKmsAddress(kmsClient, keyId); + + return toAccount({ + address, + + async signMessage({ message }) { + const digest = hashMessage(message); + const sig = await kmsSign(kmsClient, keyId, digest, address); + return signatureToHex({ + r: sig.r, + s: sig.s, + yParity: sig.yParity, + }); + }, + + async signTransaction(tx) { + const serialized = serializeTransaction(tx as TransactionSerializable); + const digest = keccak256(serialized); + const sig = await kmsSign(kmsClient, keyId, digest, address); + return serializeTransaction(tx as TransactionSerializable, { + r: sig.r, + s: sig.s, + yParity: sig.yParity, + }); + }, + + async signTypedData(typedData) { + const digest = hashTypedData(typedData as TypedDataDefinition); + const sig = await kmsSign(kmsClient, keyId, digest, address); + return signatureToHex({ + r: sig.r, + s: sig.s, + yParity: sig.yParity, + }); + }, + }) as LocalAccount; +} + +/** + * Create an ethers v5 Signer backed by AWS KMS. + * Used for contracts/ utility functions that expect ethers.Signer. + */ +export function getEthersSigner( + provider: ethers.providers.JsonRpcProvider +): DirectKmsTransactionSigner { + const keyId = getKmsKeyId(); + return new DirectKmsTransactionSigner(keyId, provider, AWS_KMS_REGION); +} + +/** + * Create an ethers v5 JsonRpcProvider from an RPC URL. + */ +export function getEthersProvider( + rpcUrl: string +): ethers.providers.JsonRpcProvider { + return new ethers.providers.JsonRpcProvider(rpcUrl); +} diff --git a/contracts/tasks/lib/store.ts b/contracts/tasks/lib/store.ts new file mode 100644 index 0000000000..02ece0cb5d --- /dev/null +++ b/contracts/tasks/lib/store.ts @@ -0,0 +1,55 @@ +import fs from "node:fs"; +import path from "node:path"; + +export interface KeyValueStore { + get(key: string): Promise; + put(key: string, value: string): Promise; + del(key: string): Promise; +} + +// WARNING +// TODO: Replace with persistent KV storage. + +/** + * File-based key-value store, compatible with the Defender KeyValueStoreClient + * interface. Reuses the pattern from contracts/utils/defender.js. + * + * Persists to actions/.store/{name}.json + */ +export function createStore(name: string): KeyValueStore { + const storeDir = path.resolve(__dirname, "../../.store"); + const storePath = path.join(storeDir, `${name}.json`); + + function getStore(): Record { + try { + if (!fs.existsSync(storePath)) return {}; + const contents = fs.readFileSync(storePath, "utf8"); + return contents ? JSON.parse(contents) : {}; + } catch { + return {}; + } + } + + function updateStore(updater: (store: Record) => void) { + const store = getStore(); + updater(store); + fs.mkdirSync(path.dirname(storePath), { recursive: true }); + fs.writeFileSync(storePath, JSON.stringify(store, null, 2)); + } + + return { + async get(key: string) { + return getStore()[key]; + }, + async put(key: string, value: string) { + updateStore((store) => { + store[key] = value; + }); + }, + async del(key: string) { + updateStore((store) => { + delete store[key]; + }); + }, + }; +} diff --git a/contracts/tasks/lib/transaction.ts b/contracts/tasks/lib/transaction.ts new file mode 100644 index 0000000000..4bff888495 --- /dev/null +++ b/contracts/tasks/lib/transaction.ts @@ -0,0 +1,40 @@ +import { + formatEther, + type Hash, + type PublicClient, + type TransactionReceipt, +} from "viem"; +import type { Logger } from "winston"; + +/** + * Wait for a transaction to be mined, log its details, and throw if it failed. + */ +export async function validateTransaction( + client: PublicClient, + hash: Hash, + method: string, + log: Logger +): Promise { + const tx = await client.getTransaction({ hash }); + log.info( + `Sent ${method} tx ${hash} from ${tx.from} (${ + tx.gasPrice ? Number(tx.gasPrice) / 1e9 : "unknown" + } Gwei)` + ); + + const receipt = await client.waitForTransactionReceipt({ hash }); + + if (receipt.status !== "success") { + throw new Error(`Transaction ${method} failed`); + } + + const txCost = + receipt.gasUsed * (receipt.effectiveGasPrice ?? tx.gasPrice ?? 0n); + log.info( + `Processed ${method} in block ${receipt.blockNumber}, ${ + receipt.gasUsed + } gas, ${formatEther(txCost)} ETH` + ); + + return receipt; +} diff --git a/contracts/tasks/tasks.js b/contracts/tasks/tasks.js index d67aaeed97..1325d65b18 100644 --- a/contracts/tasks/tasks.js +++ b/contracts/tasks/tasks.js @@ -1,4 +1,8 @@ -const { subtask, task, types } = require("hardhat/config"); +const { + subtask: baseSubtask, + task: baseTask, + types, +} = require("hardhat/config"); const { env } = require("./env"); const { setActionVars, updateAction } = require("./defender"); const { execute, executeOnFork, proposal, governors } = require("./governance"); @@ -16,7 +20,7 @@ const { encryptMasterPrivateKey, decryptMasterPrivateKey, } = require("./amazon"); -const { getSigner, getDefenderSigner } = require("../utils/signers"); +const { getSigner } = require("../utils/signers"); const { snapMorpho } = require("../utils/morpho"); const { snapAero } = require("./aero"); const { @@ -62,8 +66,7 @@ const { curveSwapTask, curvePoolTask, } = require("./curve"); -const { calculateMaxPricePerVoteTask, manageBribes } = require("./poolBooster"); -const { updateVotemarketEpochsTask } = require("./votemarket"); +const { calculateMaxPricePerVoteTask } = require("./poolBooster"); const { manageMerklBribesTask } = require("./merklPoolBooster"); const { depositSSV, @@ -91,9 +94,7 @@ const { transferToken, } = require("./strategy"); const { - validatorOperationsConfig, exitValidator, - doAccounting, manuallyFixAccounting, resetStakeETHTally, setStakeETHThreshold, @@ -106,24 +107,15 @@ const { } = require("./validator"); const { snapStakingStrategy, - snapBalances, registerValidatorCreateRequest, registerValidator, stakeValidator, - autoValidatorDeposits, withdrawValidator, removeValidator, - autoValidatorWithdrawals, setRegistrator, } = require("./validatorCompound"); const { tenderlySync, tenderlyUpload } = require("./tenderly"); const { setDefaultValidator, snapSonicStaking } = require("../utils/sonic"); -const { - undelegateValidator, - withdrawFromSFC, -} = require("../utils/sonicActions"); -const { registerValidators, stakeValidators } = require("../utils/validator"); -const { harvestAndSwap } = require("./harvest"); const { deployForceEtherSender, forceSend } = require("./simulation"); const { sleep } = require("../utils/time"); const { lzBridgeToken, lzSetConfig } = require("./layerzero"); @@ -134,8 +126,6 @@ const { getValidators, verifyValidator, verifyDeposit, - verifyDeposits, - verifyBalances, } = require("./beacon"); const { calcDepositRoot, @@ -151,11 +141,65 @@ const { getConsolidationFee, } = require("./consolidation"); -const { processCctpBridgeTransactions } = require("./crossChain"); -const { keyValueStoreLocalClient } = require("../utils/defender"); -const { configuration } = require("../utils/cctp"); +const { + withTaskSignerContext, + DEFAULT_KMS_RELAYER_ID, +} = require("../utils/signersNoHardhat"); const log = require("../utils/logger")("tasks"); +const RELAYER_ID_PARAM = "relayerId"; + +const withTaskContext = (taskName, action) => { + return async (taskArgs, hre, runSuper) => { + return withTaskSignerContext( + { + relayerId: taskArgs?.[RELAYER_ID_PARAM], + taskName, + }, + async () => action(taskArgs, hre, runSuper) + ); + }; +}; + +const decorateTaskDefinition = (definition, taskName) => { + const originalSetAction = definition.setAction.bind(definition); + definition.setAction = (action) => { + return originalSetAction(withTaskContext(taskName, action)); + }; + + if (definition.paramDefinitions?.[RELAYER_ID_PARAM] === undefined) { + definition.addOptionalParam( + RELAYER_ID_PARAM, + "KMS relayer id. Defaults to task map override or origin-relayer-production-evm", + DEFAULT_KMS_RELAYER_ID, + types.string + ); + } + return definition; +}; + +const buildTask = (factory) => (name, descriptionOrAction, maybeAction) => { + let description = descriptionOrAction; + let action = maybeAction; + + if (typeof descriptionOrAction === "function" && action === undefined) { + action = descriptionOrAction; + description = undefined; + } + + const definition = + description === undefined ? factory(name) : factory(name, description); + const decorated = decorateTaskDefinition(definition, name); + + if (action) { + decorated.setAction(action); + } + + return decorated; +}; + +const task = buildTask(baseTask); +const subtask = buildTask(baseSubtask); // Environment tasks. task("env", "Check env vars are properly set for a Mainnet deployment", env); @@ -682,58 +726,6 @@ task("calculateMaxPricePerVote").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask( - "manageCurvePoolBoosterBribes", - "Calls manageBribes on the CurvePoolBoosterBribesModule and calculates the rewards per vote based on the target efficiency" -) - .addOptionalParam( - "efficiency", - "Target efficiency (0-10, e.g. 1 for 100%, 0.5 for 50%)", - "1", - types.string - ) - .addOptionalParam( - "skipRewardPerVote", - "Skip setting RewardPerVote (pass array of zeros)", - false, - types.boolean - ) - .addOptionalParam( - "chunkSize", - "Number of pool boosters to manage per transaction", - 4, - types.int - ) - .setAction(async (taskArgs) => { - // This action only works with the Defender Relayer signer - const signer = await getDefenderSigner(); - await manageBribes({ - signer, - provider: signer.provider, - targetEfficiency: taskArgs.efficiency, - skipRewardPerVote: taskArgs.skipRewardPerVote, - chunkSize: taskArgs.chunkSize, - }); - }); -task("manageCurvePoolBoosterBribes").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - -subtask( - "updateVotemarketEpochs", - "Update Votemarket epochs for all Curve Pool Booster campaigns on Arbitrum" -) - .addOptionalParam( - "dryRun", - "If true, log actions but do not send transactions", - true, - types.boolean - ) - .setAction(updateVotemarketEpochsTask); -task("updateVotemarketEpochs").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask( "manageMerklPoolBoosterBribes", "Calls bribeAll on the MerklPoolBoosterBribesModule through the Gnosis Safe" @@ -1085,21 +1077,6 @@ task("setRewardTokenAddresses", "Sets the reward token of a strategy") // Harvester -task("harvest", "Harvest and swap rewards for a strategy") - .addParam( - "strategy", - "Name of the strategy proxy contract or address. eg NativeStakingSSVStrategyProxy", - undefined, - types.string - ) - .addOptionalParam( - "harvester", - "Name of the harvester proxy contract or address", - "OETHHarvesterProxy", - types.string - ) - .setAction(harvestAndSwap); - // SSV subtask("getClusterInfo", "Print out information regarding SSV cluster") @@ -1250,144 +1227,6 @@ task("deployStakingProxy").setAction(async (_, __, runSuper) => { // Validator Operations -subtask( - "registerValidators", - "Creates the required amount of new SSV validators and stakes ETH" -) - .addOptionalParam( - "days", - "SSV Cluster operational time in days", - 2, - types.int - ) - .addOptionalParam( - "validators", - "The number of validators to register. defaults to the max that can be registered", - undefined, - types.int - ) - .addOptionalParam("clear", "Clear storage", false, types.boolean) - .addOptionalParam( - "eth", - "Override the days option and set the amount of ETH to deposit to the cluster.", - undefined, - types.float - ) - .addOptionalParam( - "uuid", - "uuid of P2P's request SSV validator API call. Used to reprocess a registration that failed to get the SSV request status.", - undefined, - types.string - ) - .addOptionalParam( - "index", - "The number of the Native Staking Contract deployed.", - undefined, - types.int - ) - .setAction(async (taskArgs) => { - const config = await validatorOperationsConfig(taskArgs); - const signer = await getSigner(); - await registerValidators({ ...config, signer }); - }); -task("registerValidators").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - -subtask( - "stakeValidators", - "Creates the required amount of new SSV validators and stakes ETH" -) - .addOptionalParam( - "uuid", - "uuid of P2P's request SSV validator API call", - undefined, - types.string - ) - .addOptionalParam( - "index", - "The number of the Native Staking Contract deployed.", - undefined, - types.int - ) - .setAction(async (taskArgs) => { - const config = await validatorOperationsConfig(taskArgs); - const signer = await getSigner(); - await stakeValidators({ ...config, signer }); - }); -task("stakeValidators").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - -/** - * This function relays the messages between mainnet and base networks. - * - * IMPORTANT!!! - * If possible please use the defender action and not local execution. The defender action stores into the cloud - * key-value store the transaction hashes that have already been relayed. Relaying the transaction via this task - * will make the defender relayer continuously fail relaying the transaction that has already been processed. - * If the action is ran every ~12 hours and looks back for ~1 day worth of blocks it might fail to run 2-3 times and - * then skip some pending transactions that would need relaying. - */ -task( - "relayCCTPMessage", - "Fetches CCTP attested Messages via Circle Gateway API and relays it to the integrator contract" -) - .addOptionalParam( - "block", - "Override the block number at which the message emission transaction happened", - undefined, - types.int - ) - .addOptionalParam( - "dryrun", - "Do not call verifyBalances on the strategy contract. Just log the params including the proofs", - false, - types.boolean - ) - .setAction(async (taskArgs) => { - const networkName = await getNetworkName(); - const storeFilePath = require("path").join( - __dirname, - "..", - `.localKeyValueStorage.${networkName}` - ); - - // This action only works with the Defender Relayer signer - const signer = await getDefenderSigner(); - const store = keyValueStoreLocalClient({ _storePath: storeFilePath }); - - const isMainnet = networkName === "mainnet"; - const isBase = networkName === "base"; - - let config; - if (isMainnet) { - config = configuration.mainnetBaseMorpho.mainnet; - } else if (isBase) { - config = configuration.mainnetBaseMorpho.base; - } else { - throw new Error(`Unsupported network name: ${networkName}`); - } - - await processCctpBridgeTransactions({ - ...taskArgs, - destinationChainSigner: signer, - sourceChainProvider: ethers.provider, - store, - networkName, - blockLookback: config.blockLookback, - cctpDestinationDomainId: config.cctpDestinationDomainId, - cctpSourceDomainId: config.cctpSourceDomainId, - cctpIntegrationContractAddress: config.cctpIntegrationContractAddress, - cctpIntegrationContractAddressDestination: - config.cctpIntegrationContractAddressDestination, - }); - }); - -task("relayCCTPMessage").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask("exitValidator", "Starts the exit process from a validator") .addParam( "pubkey", @@ -1510,39 +1349,6 @@ task("removeValidators").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask( - "doAccounting", - "Account for consensus rewards and validator exits in the Native Staking Strategy" -) - .addOptionalParam( - "index", - "The number of the Native Staking Contract deployed.", - undefined, - types.int - ) - .addOptionalParam( - "consol", - "Call the consolidation controller instead of the strategy", - false, - types.boolean - ) - .setAction(async ({ index, consol }) => { - const signer = await getSigner(); - - const nativeStakingStrategy = await resolveNativeStakingStrategyProxy( - index - ); - - await doAccounting({ - consol, - signer, - nativeStakingStrategy, - }); - }); -task("doAccounting").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask( "manuallyFixAccounting", "Fix an accounting failure in a Native Staking Strategy" @@ -2011,50 +1817,6 @@ task("sonicDefaultValidator").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask("sonicUndelegate", "Remove liquidity from a Sonic validator") - .addOptionalParam( - "id", - "Validator identifier. 15, 16, 17 or 18", - undefined, - types.int - ) - .addOptionalParam( - "amount", - "Amount of liquidity to remove", - undefined, - types.float - ) - .addOptionalParam( - "buffer", - "Percentage of total assets to keep as buffer in basis points. 100 = 1%", - 50, - types.float - ) - .setAction(async (taskArgs) => { - const signer = await getSigner(); - - await undelegateValidator({ - ...taskArgs, - bufferPct: taskArgs.buffer, - signer, - }); - }); -task("sonicUndelegate").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - -subtask( - "sonicWithdraw", - "Withdraw native S from a previously undelegated validator" -).setAction(async () => { - const signer = await getSigner(); - - await withdrawFromSFC({ signer }); -}); -task("sonicWithdraw").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask("sonicStaking", "Snap of the Sonic Staking Strategy") .addOptionalParam( "block", @@ -2297,84 +2059,6 @@ task("verifyDeposit").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask("verifyDeposits", "Verify any processed deposit on the Beacon chain") - .addOptionalParam( - "dryrun", - "Do not call verifyDeposit on the strategy contract. Just log the params including the proofs", - false, - types.boolean - ) - .addOptionalParam( - "consol", - "Call the consolidation controller instead of the strategy", - false, - types.boolean - ) - .setAction(async (taskArgs) => { - const signer = await getSigner(); - await verifyDeposits({ ...taskArgs, signer }); - }); -task("verifyDeposits").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - -subtask("verifyBalances", "Verify validator balances on the Beacon chain") - .addOptionalParam( - "slot", - "The slot snapBalances was executed. Default: last balances snapshot", - undefined, - types.int - ) - .addOptionalParam( - "indexes", - "Comma separated list of validator indexes. Default: strategy's active validators", - undefined, - types.string - ) - .addOptionalParam( - "deposits", - "Comma separated list of indexes to beacon chain pending deposits used for generating unit test data", - undefined, - types.string - ) - .addOptionalParam( - "dryrun", - "Do not call verifyBalances on the strategy contract. Just log the params including the proofs", - false, - types.boolean - ) - .addOptionalParam( - "test", - "Used for generating unit test data.", - false, - types.boolean - ) - .addOptionalParam( - "overIds", - "A comma separated list of validator IDs to override balances.", - "", - types.string - ) - .addOptionalParam( - "overBals", - "A comma separated list of validator balances to override in Gwei.", - "", - types.string - ) - .addOptionalParam( - "consol", - "Call the consolidation controller instead of the strategy", - false, - types.boolean - ) - .setAction(async (taskArgs) => { - const signer = await getSigner(); - await verifyBalances({ ...taskArgs, signer }); - }); -task("verifyBalances").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask( "requestNewValidator", "Calls P2P's Create SSV Request to prepare a new SSV compounding (0x02) validator" @@ -2434,24 +2118,6 @@ task("registerValidator").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask( - "autoValidatorDeposits", - "Automatically withdraw ETH/WETH from the strategy if needed for withdrawals, then deposit WETH to validators with a balance under 2030 ETH from the largest balance to the smallest" -) - .addParam( - "dryrun", - "Do not send any txs to the staking strategy contract", - false, - types.boolean - ) - .setAction(async (taskArgs) => { - const signer = await getSigner(); - await autoValidatorDeposits({ ...taskArgs, signer }); - }); -task("autoValidatorDeposits").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask( "withdrawValidator", "Requests a partial or full withdrawal from a compounding validator" @@ -2510,30 +2176,6 @@ task("removeValidator").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask( - "autoValidatorWithdrawals", - "Automatically withdraw ETH from a validators if the Vault needs WETH for user withdrawals. Start with the validator with the smallest balance over 42.25 ETH." -) - .addOptionalParam( - "buffer", - "Withdrawal buffer in basis points. 100 = 1%", - 100, - types.int - ) - .addParam( - "dryrun", - "Do not send any txs to the staking strategy contract", - false, - types.boolean - ) - .setAction(async (taskArgs) => { - const signer = await getSigner(); - await autoValidatorWithdrawals({ ...taskArgs, signer }); - }); -task("autoValidatorWithdrawals").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask( "stakeValidatorUuid", "Converts WETH to ETH and deposits to a validator from the Compounding Staking Strategy" @@ -2606,18 +2248,6 @@ task("stakeValidator").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask("snapBalances", "Takes a snapshot of the staking strategy's balance") - .addOptionalParam( - "consol", - "Call the consolidation controller instead of the strategy", - false, - types.boolean - ) - .setAction(snapBalances); -task("snapBalances").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask("snapStakingStrat", "Dumps the staking strategy's data") .addOptionalParam( "block", diff --git a/contracts/tasks/votemarket.js b/contracts/tasks/votemarket.js index af5258a409..8f8f1a5972 100644 --- a/contracts/tasks/votemarket.js +++ b/contracts/tasks/votemarket.js @@ -1,8 +1,7 @@ const { Contract } = require("ethers"); -const { Wallet } = require("ethers"); - const addresses = require("../utils/addresses"); +const { getSigner } = require("../utils/signers"); const log = require("../utils/logger")("task:votemarket"); // Contract addresses @@ -160,15 +159,11 @@ async function updateVotemarketEpochs({ async function updateVotemarketEpochsTask(taskArguments) { const dryRun = taskArguments.dryRun !== false; - // Detect mainnet vs fork from hardhat's ethers provider - const mainnetProvider = ethers.provider; - // Create Arbitrum provider from env var - const arbitrumRpcUrl = - process.env.ARBITRUM_PROVIDER_URL || process.env.PROVIDER_URL; + const arbitrumRpcUrl = process.env.ARBITRUM_PROVIDER_URL; if (!arbitrumRpcUrl) { throw new Error( - "ARBITRUM_PROVIDER_URL or PROVIDER_URL env var required for Arbitrum connection" + "ARBITRUM_PROVIDER_URL env var required for Arbitrum connection" ); } @@ -176,16 +171,22 @@ async function updateVotemarketEpochsTask(taskArguments) { const arbitrumProvider = new ethersLib.providers.JsonRpcProvider( arbitrumRpcUrl ); + const mainnetProvider = new ethersLib.providers.JsonRpcProvider( + process.env.PROVIDER_URL + ); let arbitrumSigner = null; if (!dryRun) { - const pk = process.env.DEPLOYER_PK || process.env.GOVERNOR_PK; - if (!pk) { + const signer = await getSigner(); + console.log("signer", await signer.getAddress()); + try { + arbitrumSigner = signer; + log(`Using signer ${await signer.getAddress()} for Arbitrum`); + } catch (err) { throw new Error( - "DEPLOYER_PK or GOVERNOR_PK env var required for non-dry-run mode" + `Failed to bind signer to Arbitrum provider: ${err.message}` ); } - arbitrumSigner = new Wallet(pk, arbitrumProvider); } await updateVotemarketEpochs({ diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json new file mode 100644 index 0000000000..b6fde3e313 --- /dev/null +++ b/contracts/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "allowJs": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "outDir": "dist", + "types": ["node"] + }, + "include": ["tasks/actions/**/*.ts", "tasks/lib/**/*.ts", "scripts/**/*.ts", "cron/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/contracts/utils/logger.js b/contracts/utils/logger.js index b4362787ae..37350dce90 100644 --- a/contracts/utils/logger.js +++ b/contracts/utils/logger.js @@ -1,10 +1,145 @@ const debug = require("debug"); +const util = require("node:util"); // https://www.npmjs.com/package/debug#output-streams // set all output to go via console.log instead of stderr // This is needed for Defender Actions to capture the logs debug.log = console.log.bind(console); +const LOG_MODE_ENABLED_VALUES = new Set(["1", "true", "yes", "on"]); + +const isWinstonLogModeEnabled = () => { + const rawValue = process.env.WINSTON_LOG_MODE_ENABLED; + if (!rawValue) return false; + return LOG_MODE_ENABLED_VALUES.has(rawValue.toLowerCase()); +}; + +const isPlainObject = (value) => + value !== null && + typeof value === "object" && + Object.getPrototypeOf(value) === Object.prototype; + +const formatArgs = (args) => { + if (args.length === 0) return ""; + if (typeof args[0] === "string") { + return util.format(...args); + } + return args + .map((arg) => + typeof arg === "string" ? arg : util.inspect(arg, { depth: null }) + ) + .join(" "); +}; + +const parseLogArguments = (args) => { + if ( + args.length === 2 && + typeof args[0] === "string" && + isPlainObject(args[1]) + ) { + return { + message: args[0], + meta: args[1], + }; + } + + return { + message: formatArgs(args), + meta: {}, + }; +}; + +const createDebugLogger = (module) => { + const debugLogger = debug(`origin:${module}`); + + const write = (...args) => { + const { message, meta } = parseLogArguments(args); + const extra = + Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : ""; + debugLogger(`${message}${extra}`); + }; + + const logger = (...args) => write(...args); + logger.debug = (...args) => write(...args); + logger.info = (...args) => write(...args); + logger.warn = (...args) => write(...args); + logger.error = (...args) => write(...args); + logger.child = () => logger; + return logger; +}; + +const createWinstonLogger = (module) => { + let bridge; + try { + // ts-node is registered by hardhat and cron entrypoints. + bridge = require("../tasks/lib/logger"); + } catch (err) { + const fallback = createDebugLogger(module); + fallback( + `Failed to load winston bridge; falling back to debug logger: ${ + err?.message ?? String(err) + }` + ); + return fallback; + } + + const write = (level, ...args) => { + const { message, meta } = parseLogArguments(args); + bridge.logWithContext(level, message, { module, ...meta }); + }; + + const logger = (...args) => write("info", ...args); + logger.debug = (...args) => write("debug", ...args); + logger.info = (...args) => write("info", ...args); + logger.warn = (...args) => write("warn", ...args); + logger.error = (...args) => write("error", ...args); + logger.child = (meta = {}) => { + const childLogger = (...args) => { + const { message, meta: inlineMeta } = parseLogArguments(args); + bridge.logWithContext("info", message, { + module, + ...meta, + ...inlineMeta, + }); + }; + childLogger.debug = (...args) => { + const { message, meta: inlineMeta } = parseLogArguments(args); + bridge.logWithContext("debug", message, { + module, + ...meta, + ...inlineMeta, + }); + }; + childLogger.info = (...args) => { + const { message, meta: inlineMeta } = parseLogArguments(args); + bridge.logWithContext("info", message, { + module, + ...meta, + ...inlineMeta, + }); + }; + childLogger.warn = (...args) => { + const { message, meta: inlineMeta } = parseLogArguments(args); + bridge.logWithContext("warn", message, { + module, + ...meta, + ...inlineMeta, + }); + }; + childLogger.error = (...args) => { + const { message, meta: inlineMeta } = parseLogArguments(args); + bridge.logWithContext("error", message, { + module, + ...meta, + ...inlineMeta, + }); + }; + childLogger.child = () => childLogger; + return childLogger; + }; + return logger; +}; + /** * Creates a logger for a module. * @example @@ -12,6 +147,9 @@ debug.log = console.log.bind(console); * log('something interesting happened'); * @param {string} module name of the module to log for. eg "test:fork:vault", "task:token" or "utils:deploy" */ -const logger = (module) => debug(`origin:${module}`); +const logger = (module) => + isWinstonLogModeEnabled() + ? createWinstonLogger(module) + : createDebugLogger(module); module.exports = logger; diff --git a/contracts/utils/managePassThrough.js b/contracts/utils/managePassThrough.js index 056dded5e1..9c23de3817 100644 --- a/contracts/utils/managePassThrough.js +++ b/contracts/utils/managePassThrough.js @@ -2,6 +2,7 @@ const addresses = require("../utils/addresses"); const { ethers } = require("ethers"); const passThroughAbi = require("../abi/passThrough.json"); const { logTxDetails } = require("../utils/txLogger"); +const log = require("../utils/logger")("utils:managePassThrough"); async function transferTokens({ signer }) { const OUSD = addresses.mainnet.OUSDProxy; @@ -19,7 +20,7 @@ async function transferTokens({ signer }) { ], }; - console.log("DEBUG: Token PassThroughs mapping", tokenPassThroughs); + log("DEBUG: Token PassThroughs mapping", tokenPassThroughs); // Process all tokens and their passThrough contracts for (const [token, passThroughAddresses] of Object.entries( diff --git a/contracts/utils/morpho.js b/contracts/utils/morpho.js index 44b9a2db97..389d0755cb 100644 --- a/contracts/utils/morpho.js +++ b/contracts/utils/morpho.js @@ -90,13 +90,13 @@ async function snapMorpho({ block }) { { blockTag } ); - console.log( + log( `Strategy balance : ${formatUnits( strategyUSDCBalance, 6 )} USDC` ); - console.log( + log( `Max withdrawable from underlying: ${formatUnits(maxWithdrawal, 6)} USDC` ); @@ -108,7 +108,7 @@ async function snapMorpho({ block }) { .mul(BigNumber.from(10000)) .div(strategyUSDCBalance); - console.log( + log( `Withdraw shortfall : ${formatUnits( shortfall, 6 diff --git a/contracts/utils/signers.js b/contracts/utils/signers.js index 412ee879fe..e77eb33ae6 100644 --- a/contracts/utils/signers.js +++ b/contracts/utils/signers.js @@ -1,7 +1,12 @@ const { Wallet } = require("ethers"); const { parseEther } = require("ethers/lib/utils"); const hhHelpers = require("@nomicfoundation/hardhat-network-helpers"); -const { getDefenderSigner } = require("./signersNoHardhat"); +const { + getDefenderSigner, + getKmsAddress, + getKmsSigner, + hasAwsKmsCredentials, +} = require("./signersNoHardhat"); const { ethereumAddress, privateKey } = require("./regex"); const log = require("./logger")("utils:signers"); @@ -9,6 +14,7 @@ const log = require("./logger")("utils:signers"); /** * Signer factory that gets a signer for a hardhat test or task * If address is passed, use that address as signer. + * If AWS IAM KMS credentials are set, use a KMS-backed signer. * If DEPLOYER_PK or GOVERNOR_PK is set, use that private key as signer. * If a fork and IMPERSONATE is set, impersonate that account. * else get the first signer from the hardhat node. @@ -22,6 +28,13 @@ async function getSigner(address = undefined) { } return await hre.ethers.provider.getSigner(address); } + + if (hasAwsKmsCredentials()) { + const address = await getKmsAddress({ provider: hre.ethers.provider }); + log(`Using KMS signer ${address}`); + return await getKmsSigner(hre); + } + const pk = process.env.DEPLOYER_PK || process.env.GOVERNOR_PK; if (pk) { if (!pk.match(privateKey)) { @@ -90,4 +103,5 @@ module.exports = { impersonateAccount, impersonateAndFund, getDefenderSigner, + getKmsSigner, }; diff --git a/contracts/utils/signersNoHardhat.js b/contracts/utils/signersNoHardhat.js index f55752502f..8269f372d1 100644 --- a/contracts/utils/signersNoHardhat.js +++ b/contracts/utils/signersNoHardhat.js @@ -1,7 +1,85 @@ const ethers = require("ethers"); +const { DirectKmsTransactionSigner } = require("@lastdotnet/purrikey"); const { Defender } = require("@openzeppelin/defender-sdk"); const log = require("./logger")("utils:signers"); +// origin-relayer-production-evm +const DEFAULT_KMS_RELAYER_ID = "mrk-248128595151466bb7f7b9a56501a98f"; +const AWS_KMS_REGION = "us-east-1"; + +// Task specific relayer overrides. +const TASK_KMS_RELAYER_ID_OVERRIDES = {}; + +let signerContext = { + relayerId: undefined, + taskName: undefined, +}; + +const hasAwsKmsCredentials = () => { + return !!process.env.AWS_ACCESS_KEY_ID && !!process.env.AWS_SECRET_ACCESS_KEY; +}; + +const resolveKmsRelayerId = (context = signerContext) => { + if (context.relayerId) { + return context.relayerId; + } + if ( + context.taskName && + TASK_KMS_RELAYER_ID_OVERRIDES[context.taskName] !== undefined + ) { + return TASK_KMS_RELAYER_ID_OVERRIDES[context.taskName]; + } + return DEFAULT_KMS_RELAYER_ID; +}; + +const withTaskSignerContext = async (context, fn) => { + const previousContext = signerContext; + signerContext = { + ...previousContext, + ...context, + }; + try { + return await fn(); + } finally { + signerContext = previousContext; + } +}; + +const getKmsSigner = async (hre) => { + const relayerId = resolveKmsRelayerId(); + return new DirectKmsTransactionSigner( + relayerId, + hre.ethers.provider, + AWS_KMS_REGION + ); +}; + +/** + * Resolve the Ethereum address for a KMS key. + * If relayerId is not provided, task context / defaults are used. + * @param {object} params + * @param {string} params.relayerId optional explicit relayer id / kms key id + * @param {string} params.taskName optional task name for task-map resolution + * @param {object} params.provider optional ethers provider for signer construction + * @returns {Promise} ethereum address + */ +const getKmsAddress = async ({ relayerId, taskName, provider } = {}) => { + const keyId = resolveKmsRelayerId({ + ...signerContext, + relayerId, + taskName, + }); + const signer = new DirectKmsTransactionSigner( + keyId, + provider || ethers.getDefaultProvider(), + AWS_KMS_REGION + ); + const address = await signer.getAddress(); + + log(`Resolved KMS Ethereum address ${address} from relayer-id "${keyId}"`); + return address; +}; + const getDefenderSigner = async () => { const speed = process.env.SPEED || "fastest"; if (!["safeLow", "average", "fast", "fastest"].includes(speed)) { @@ -44,4 +122,9 @@ const getDefenderSigner = async () => { module.exports = { getDefenderSigner, + getKmsSigner, + getKmsAddress, + hasAwsKmsCredentials, + withTaskSignerContext, + DEFAULT_KMS_RELAYER_ID, }; diff --git a/contracts/utils/sonic.js b/contracts/utils/sonic.js index b12f64f93f..39b75e6205 100644 --- a/contracts/utils/sonic.js +++ b/contracts/utils/sonic.js @@ -46,7 +46,7 @@ async function snapSonicStaking(taskArguments) { let totalStaked = ethers.BigNumber.from(0); let totalPendingRewards = ethers.BigNumber.from(0); for (const validatorId of [15, 16, 17, 18]) { - console.log(`${validatorId}:`); + log(`${validatorId}:`); const stakedAmount = await sfc.getStake( sonicStakingStrategy.address, validatorId, @@ -62,37 +62,33 @@ async function snapSonicStaking(taskArguments) { ); totalPendingRewards = totalPendingRewards.add(pendingRewards); - console.log(` Staked amount : ${formatUnits(stakedAmount, 18)}`); - console.log( - ` Pending rewards : ${formatUnits(pendingRewards, 18)}` - ); + log(` Staked amount : ${formatUnits(stakedAmount, 18)}`); + log(` Pending rewards : ${formatUnits(pendingRewards, 18)}`); } const stakedPercent = totalStaked.mul(10000).div(strategyBalance); const pendingRewardsPercentage = totalPendingRewards .mul(10000) .div(strategyBalance); - console.log( + log( `\nTotal Staked : ${formatUnits( totalStaked, 18 )} ${formatUnits(stakedPercent, 2)}%` ); - console.log( + log( `Total pending withdrawals : ${formatUnits( pendingWithdrawals, 18 )} ${formatUnits(pendingWithdrawalsPercentage, 2)}%` ); - console.log( + log( `Total pending rewards : ${formatUnits( totalPendingRewards, 18 )} ${formatUnits(pendingRewardsPercentage, 2)}%` ); - console.log( - `Strategy balance : ${formatUnits(strategyBalance, 18)}` - ); + log(`Strategy balance : ${formatUnits(strategyBalance, 18)}`); } module.exports = { diff --git a/contracts/utils/txLogger.js b/contracts/utils/txLogger.js index 4e722c23fe..9f714eceb4 100644 --- a/contracts/utils/txLogger.js +++ b/contracts/utils/txLogger.js @@ -1,3 +1,4 @@ +const { BigNumber } = require("ethers"); const { formatUnits } = require("ethers/lib/utils"); const log = require("./logger")("utils:txLogger"); @@ -9,10 +10,14 @@ const log = require("./logger")("utils:txLogger"); * @returns {ContractReceipt} transaction receipt */ async function logTxDetails(tx, method) { + const submittedGasPrice = + tx.gasPrice ?? tx.maxFeePerGas ?? tx.maxPriorityFeePerGas; + const submittedGasPriceGwei = submittedGasPrice + ? formatUnits(submittedGasPrice, "gwei") + : "n/a"; + log( - `Sent ${method} transaction with hash ${tx.hash} from ${ - tx.from - } with gas price ${tx.gasPrice?.toNumber() / 1e9} Gwei` + `Sent ${method} transaction with hash ${tx.hash} from ${tx.from} with gas price ${submittedGasPriceGwei} Gwei` ); const receipt = await tx.wait(); @@ -20,8 +25,14 @@ async function logTxDetails(tx, method) { throw new Error(`Transaction ${method} failed`); } + const effectiveGasPrice = + receipt.effectiveGasPrice ?? + tx.gasPrice ?? + tx.maxFeePerGas ?? + BigNumber.from(0); + // Calculate tx cost in Wei - const txCost = receipt.gasUsed.mul(tx.gasPrice ?? 0); + const txCost = receipt.gasUsed.mul(effectiveGasPrice); log( `Processed ${method} tx in block ${receipt.blockNumber}, using ${ receipt.gasUsed diff --git a/contracts/utils/validator.js b/contracts/utils/validator.js index 733d24dc1c..aab18dabf5 100644 --- a/contracts/utils/validator.js +++ b/contracts/utils/validator.js @@ -57,7 +57,7 @@ const registerValidators = async ({ maxValidatorsToRegister, ethAmount, awsS3AccessKeyId, - awsS3SexcretAccessKeyId, + awsS3SecretAccessKeyId, s3BucketName, }) => { if (uuid && clear) { @@ -135,7 +135,7 @@ const registerValidators = async ({ p2p_api_key, p2p_base_url, awsS3AccessKeyId, - awsS3SexcretAccessKeyId, + awsS3SecretAccessKeyId, s3BucketName ); currentState = await getState(store);