From fc56eb80c96d4ebb18a104184d15d79618339f06 Mon Sep 17 00:00:00 2001 From: Iko Date: Sun, 26 Apr 2026 22:31:10 +0700 Subject: [PATCH 01/38] ops: enforce promotion freshness and commit lineage checks --- .github/workflows/contracts-ci.yml | 177 ++++++++++ .github/workflows/contracts-env-guard.yml | 61 ++++ .../workflows/contracts-evidence-manifest.yml | 190 +++++++++++ .../workflows/contracts-mainnet-readiness.yml | 80 +++++ .../contracts-production-lock-verify.yml | 112 +++++++ .../contracts-promotion-checklist.yml | 78 +++++ .github/workflows/contracts-slither.yml | 51 +++ .../workflows/contracts-staging-rehearsal.yml | 105 ++++++ .../workflows/release-evidence-validator.yml | 127 ++++++++ .github/workflows/release-pr-checklist.yml | 66 ++++ .github/workflows/secrets-drift-guard.yml | 53 +++ contracts/README.md | 306 +++++++++++++++++- contracts/RUNBOOK.md | 247 ++++++++++++++ .../ops/generate-promotion-checklist.sh | 270 ++++++++++++++++ 14 files changed, 1917 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/contracts-ci.yml create mode 100644 .github/workflows/contracts-env-guard.yml create mode 100644 .github/workflows/contracts-evidence-manifest.yml create mode 100644 .github/workflows/contracts-mainnet-readiness.yml create mode 100644 .github/workflows/contracts-production-lock-verify.yml create mode 100644 .github/workflows/contracts-promotion-checklist.yml create mode 100644 .github/workflows/contracts-slither.yml create mode 100644 .github/workflows/contracts-staging-rehearsal.yml create mode 100644 .github/workflows/release-evidence-validator.yml create mode 100644 .github/workflows/release-pr-checklist.yml create mode 100644 .github/workflows/secrets-drift-guard.yml create mode 100644 contracts/RUNBOOK.md create mode 100755 contracts/script/ops/generate-promotion-checklist.sh diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml new file mode 100644 index 0000000..e0b27a5 --- /dev/null +++ b/.github/workflows/contracts-ci.yml @@ -0,0 +1,177 @@ +name: Contracts CI + +on: + pull_request: + paths: + - "contracts/**" + - ".github/workflows/contracts-ci.yml" + push: + branches: + - main + - dev + paths: + - "contracts/**" + - ".github/workflows/contracts-ci.yml" + workflow_dispatch: + inputs: + run_integration: + description: "Run integration tests against local supersim endpoints (127.0.0.1:9545/9546)" + required: false + default: false + type: boolean + +jobs: + contracts-unit-invariant: + name: Contracts Unit + Invariant + runs-on: ubuntu-latest + defaults: + run: + working-directory: contracts + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run deterministic test suite + run: forge test -vv + + - name: Run production lock invariants + run: make test-production-lock + + contracts-release-check: + name: Contracts Release Check (Dry-Run + Execute Smoke) + runs-on: ubuntu-latest + needs: contracts-unit-invariant + defaults: + run: + working-directory: contracts + env: + PRIVATE_KEY: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + RPC_URL: http://127.0.0.1:8545 + MARK_RELEASE_EXECUTE: "false" + MARK_RELEASE_RUN_POSTDEPLOY: "false" + MARK_RELEASE_WRITE_ARTIFACT: "false" + MARK_GIT_COMMIT: ${{ github.sha }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Start anvil + run: anvil --host 127.0.0.1 --port 8545 > /tmp/anvil.log 2>&1 & + + - name: Run release orchestrator dry-run + run: forge script script/ops/ReleaseMARK.s.sol --rpc-url $RPC_URL -vv + + - name: Run release orchestrator execute smoke (local Anvil) + env: + MARK_RELEASE_EXECUTE: "true" + MARK_RELEASE_WRITE_ARTIFACT: "true" + MARK_RELEASE_ARTIFACT_PATH: broadcast/mark-release-ci.json + MARK_RELEASE_STRICT_VERIFY: "true" + VERIFY_MARK_SETTLEMENT_PROOF_ENABLED: "false" + VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE: "false" + VERIFY_MARK_SETTLEMENT_VERIFIER: "0x0000000000000000000000000000000000000000" + run: forge script script/ops/ReleaseMARK.s.sol --rpc-url $RPC_URL --broadcast -vv + + - name: Assert release artifact schema + run: | + test -f broadcast/mark-release-ci.json + jq -e ' + .chainId != null and + .deployer != "0x0000000000000000000000000000000000000000" and + .token != "0x0000000000000000000000000000000000000000" and + .adapter != "0x0000000000000000000000000000000000000000" and + .module != "0x0000000000000000000000000000000000000000" and + .timestamp != null and + .gitCommit != null + ' broadcast/mark-release-ci.json + + - name: Print anvil logs on failure + if: failure() + run: tail -n 200 /tmp/anvil.log || true + + contracts-production-mode-smoke: + name: Contracts Production Mode Smoke + runs-on: ubuntu-latest + needs: contracts-unit-invariant + defaults: + run: + working-directory: contracts + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run production mode smoke target + run: make smoke-production-mode + + - name: Print anvil logs on failure + if: failure() + run: tail -n 200 /tmp/mark-anvil.log || true + + contracts-integration: + name: Contracts Integration (RPC) + runs-on: ubuntu-latest + needs: [contracts-unit-invariant, contracts-release-check, contracts-production-mode-smoke] + if: ${{ github.event_name == 'workflow_dispatch' && inputs.run_integration }} + defaults: + run: + working-directory: contracts + env: + CHAIN_A_RPC_URL: http://127.0.0.1:9545 + CHAIN_B_RPC_URL: http://127.0.0.1:9546 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "pnpm" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Install JS dependencies + run: pnpm install --frozen-lockfile + working-directory: . + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Start supersim + run: pnpm dev:supersim > /tmp/supersim.log 2>&1 & + working-directory: . + + - name: Wait for supersim readiness + run: pnpm wait-port http://127.0.0.1:8420/ready + working-directory: . + + - name: Run integration suite + run: FOUNDRY_PROFILE=integration forge test --match-path 'test/integration/*.t.sol' -vv + + - name: Print supersim logs on failure + if: failure() + run: tail -n 200 /tmp/supersim.log || true diff --git a/.github/workflows/contracts-env-guard.yml b/.github/workflows/contracts-env-guard.yml new file mode 100644 index 0000000..055b970 --- /dev/null +++ b/.github/workflows/contracts-env-guard.yml @@ -0,0 +1,61 @@ +name: Contracts Env Guard + +on: + pull_request: + paths: + - "contracts/**" + - ".github/workflows/contracts-env-guard.yml" + push: + branches: + - main + - dev + paths: + - "contracts/**" + - ".github/workflows/contracts-env-guard.yml" + workflow_dispatch: + +jobs: + env-guard: + name: Contracts Env Guard + runs-on: ubuntu-latest + defaults: + run: + working-directory: contracts + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Bash syntax checks for ops scripts + run: | + bash -n script/ops/validate-prod-env.sh + bash -n script/ops/rehearse-production-lock.sh + bash -n script/ops/dispatch-production-lock-verify.sh + bash -n script/ops/verify-production-lock.sh + + - name: Validate rehearsal env profile + run: | + set -a + source config/profiles/staging.env + set +a + MARK_RELEASE_EXECUTE=true \ + MARK_SETTLEMENT_PROOF_ENABLED=true \ + MARK_SETTLEMENT_PRODUCTION_MODE=true \ + MARK_DEPLOY_ATTESTED_VERIFIER=true \ + VALIDATE_MODE=rehearsal ./script/ops/validate-prod-env.sh + + - name: Validate dispatch env profile + run: | + set -a + source config/profiles/mainnet.env + set +a + VALIDATE_MODE=dispatch ./script/ops/validate-prod-env.sh + + - name: Validate verify-lock env profile + run: | + set -a + source config/profiles/mainnet.env + set +a + VALIDATE_MODE=verify-lock ./script/ops/validate-prod-env.sh diff --git a/.github/workflows/contracts-evidence-manifest.yml b/.github/workflows/contracts-evidence-manifest.yml new file mode 100644 index 0000000..31612d0 --- /dev/null +++ b/.github/workflows/contracts-evidence-manifest.yml @@ -0,0 +1,190 @@ +name: Contracts Evidence Manifest + +on: + pull_request: + paths: + - "contracts/**" + - ".github/workflows/contracts-evidence-manifest.yml" + push: + branches: + - main + - dev + paths: + - "contracts/**" + - ".github/workflows/contracts-evidence-manifest.yml" + workflow_dispatch: + inputs: + release_artifact_path: + description: "Path to release artifact under contracts/" + required: false + default: "broadcast/mark-release-prodmode-ci.json" + type: string + production_lock_artifact_path: + description: "Path to production-lock verify artifact under contracts/" + required: false + default: "broadcast/mark-production-lock-verify.json" + type: string + staging_rehearsal_artifact_path: + description: "Path to staging rehearsal artifact under contracts/" + required: false + default: "broadcast/mark-staging-rehearsal.json" + type: string + promotion_checklist_path: + description: "Path to promotion checklist artifact under contracts/" + required: false + default: "broadcast/mark-promotion-checklist.json" + type: string + manifest_path: + description: "Output manifest path under contracts/" + required: false + default: "broadcast/mark-evidence-manifest.json" + type: string + signature_path: + description: "Output detached signature path under contracts/" + required: false + default: "broadcast/mark-evidence-manifest.sig" + type: string + signature_meta_path: + description: "Output signature metadata path under contracts/" + required: false + default: "broadcast/mark-evidence-signature.json" + type: string + +jobs: + manifest-self-test: + name: Manifest Tooling Self-Test + runs-on: ubuntu-latest + defaults: + run: + working-directory: contracts + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Bash syntax checks + run: | + bash -n script/ops/generate-evidence-manifest.sh + bash -n script/ops/verify-evidence-manifest.sh + bash -n script/ops/sign-evidence-manifest.sh + bash -n script/ops/verify-evidence-signature.sh + + - name: Generate sample artifacts and verify manifest + run: | + mkdir -p broadcast/ci-manifest + jq -n '{protocol:"MARK", tokenSymbol:"RYLA", kind:"release"}' > broadcast/ci-manifest/release.json + jq -n '{kind:"production-lock", status:"passed"}' > broadcast/ci-manifest/lock.json + jq -n '{kind:"staging-rehearsal", status:"passed"}' > broadcast/ci-manifest/staging.json + jq -n '{kind:"promotion-checklist", goNoGo:{decision:"pending"}}' > broadcast/ci-manifest/checklist.json + + RELEASE_ARTIFACT_PATH=broadcast/ci-manifest/release.json \ + PRODUCTION_LOCK_ARTIFACT_PATH=broadcast/ci-manifest/lock.json \ + STAGING_REHEARSAL_ARTIFACT_PATH=broadcast/ci-manifest/staging.json \ + PROMOTION_CHECKLIST_PATH=broadcast/ci-manifest/checklist.json \ + MANIFEST_PATH=broadcast/ci-manifest/manifest.json \ + make generate-evidence-manifest + + MANIFEST_PATH=broadcast/ci-manifest/manifest.json make verify-evidence-manifest + + - name: Generate ephemeral signing keypair + run: | + openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out /tmp/manifest_ci_private.pem + openssl rsa -in /tmp/manifest_ci_private.pem -pubout -out /tmp/manifest_ci_public.pem + + - name: Sign and verify manifest signature (self-test) + run: | + MANIFEST_PATH=broadcast/ci-manifest/manifest.json \ + SIGNATURE_PATH=broadcast/ci-manifest/manifest.sig \ + SIGNATURE_META_PATH=broadcast/ci-manifest/signature.json \ + SIGNING_KEY_FILE=/tmp/manifest_ci_private.pem \ + SIGNING_KEY_ID=ci-ephemeral \ + make sign-evidence-manifest + + MANIFEST_PATH=broadcast/ci-manifest/manifest.json \ + SIGNATURE_PATH=broadcast/ci-manifest/manifest.sig \ + SIGNATURE_META_PATH=broadcast/ci-manifest/signature.json \ + VERIFY_PUBLIC_KEY_FILE=/tmp/manifest_ci_public.pem \ + make verify-evidence-signature + + - name: Upload self-test manifest artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: mark-evidence-manifest-self-test + path: | + contracts/broadcast/ci-manifest/manifest.json + contracts/broadcast/ci-manifest/manifest.sig + contracts/broadcast/ci-manifest/signature.json + if-no-files-found: ignore + + manifest-dispatch-verify: + name: Manifest Verify (Manual Inputs) + if: ${{ github.event_name == 'workflow_dispatch' }} + runs-on: ubuntu-latest + environment: production + defaults: + run: + working-directory: contracts + env: + RELEASE_ARTIFACT_PATH: ${{ inputs.release_artifact_path }} + PRODUCTION_LOCK_ARTIFACT_PATH: ${{ inputs.production_lock_artifact_path }} + STAGING_REHEARSAL_ARTIFACT_PATH: ${{ inputs.staging_rehearsal_artifact_path }} + PROMOTION_CHECKLIST_PATH: ${{ inputs.promotion_checklist_path }} + MANIFEST_PATH: ${{ inputs.manifest_path }} + SIGNATURE_PATH: ${{ inputs.signature_path }} + SIGNATURE_META_PATH: ${{ inputs.signature_meta_path }} + MANIFEST_GENERATED_BY: github-actions + MANIFEST_NOTE: workflow_dispatch + MARK_GIT_COMMIT: ${{ github.sha }} + SIGNING_KEY_PEM: ${{ secrets.MARK_EVIDENCE_SIGNING_PRIVATE_KEY_PEM }} + VERIFY_PUBLIC_KEY_PEM: ${{ secrets.MARK_EVIDENCE_VERIFY_PUBLIC_KEY_PEM }} + + steps: + - name: Enforce main branch for production manifest run + run: | + if [ "${GITHUB_REF_NAME}" != "main" ]; then + echo "Manual production manifest verification must run from main branch. Current: ${GITHUB_REF_NAME}" + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Generate manifest from provided artifact paths + run: make generate-evidence-manifest + + - name: Verify generated manifest hashes + run: make verify-evidence-manifest + + - name: Ensure signing secrets are present + run: | + test -n "${SIGNING_KEY_PEM}" || { + echo "MARK_EVIDENCE_SIGNING_PRIVATE_KEY_PEM secret is required"; + exit 1; + } + test -n "${VERIFY_PUBLIC_KEY_PEM}" || { + echo "MARK_EVIDENCE_VERIFY_PUBLIC_KEY_PEM secret is required"; + exit 1; + } + + - name: Sign evidence manifest + run: | + SIGNING_KEY_ID=github-actions-production \ + make sign-evidence-manifest + + - name: Verify evidence signature + run: make verify-evidence-signature + + - name: Upload manifest artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: mark-evidence-manifest + path: | + contracts/${{ inputs.manifest_path }} + contracts/${{ inputs.signature_path }} + contracts/${{ inputs.signature_meta_path }} + if-no-files-found: error diff --git a/.github/workflows/contracts-mainnet-readiness.yml b/.github/workflows/contracts-mainnet-readiness.yml new file mode 100644 index 0000000..d1895ac --- /dev/null +++ b/.github/workflows/contracts-mainnet-readiness.yml @@ -0,0 +1,80 @@ +name: Contracts Mainnet Readiness + +on: + workflow_dispatch: + inputs: + mode: + description: "Gate mode: predeploy | postdeploy | full" + required: false + default: "predeploy" + type: choice + options: + - predeploy + - postdeploy + - full + rpc_url: + description: "Target RPC URL for readiness checks" + required: true + type: string + artifact_path: + description: "Artifact output path (relative to contracts/)" + required: false + default: "broadcast/mark-mainnet-gate-ci.json" + type: string + +jobs: + mainnet-readiness: + name: Mainnet Readiness Gate + runs-on: ubuntu-latest + environment: production + defaults: + run: + working-directory: contracts + env: + MARK_MAINNET_GATE_MODE: ${{ inputs.mode }} + RPC_URL: ${{ inputs.rpc_url }} + PRIVATE_KEY: ${{ secrets.MARK_DEPLOYER_PRIVATE_KEY }} + MARK_MAINNET_GATE_ARTIFACT_PATH: ${{ inputs.artifact_path }} + MARK_GIT_COMMIT: ${{ github.sha }} + + steps: + - name: Enforce main branch for production readiness + run: | + if [ "${GITHUB_REF_NAME}" != "main" ]; then + echo "Production readiness workflow must run from main branch. Current: ${GITHUB_REF_NAME}" + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Ensure required secret is present + run: | + test -n "${PRIVATE_KEY}" || { + echo "MARK_DEPLOYER_PRIVATE_KEY secret is required"; + exit 1; + } + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Slither + run: pip install slither-analyzer + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run mainnet readiness gate + run: ./script/ops/mainnet-readiness.sh + + - name: Upload readiness artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: mark-mainnet-readiness-artifact + path: contracts/${{ inputs.artifact_path }} + if-no-files-found: ignore diff --git a/.github/workflows/contracts-production-lock-verify.yml b/.github/workflows/contracts-production-lock-verify.yml new file mode 100644 index 0000000..2041d7a --- /dev/null +++ b/.github/workflows/contracts-production-lock-verify.yml @@ -0,0 +1,112 @@ +name: Contracts Production Lock Verify + +on: + workflow_dispatch: + inputs: + rpc_url: + description: "Target RPC URL for read-only verification" + required: true + type: string + token_address: + description: "Deployed RYLA token address" + required: true + type: string + module_address: + description: "Deployed MARK settlement module address" + required: true + type: string + verifier_address: + description: "Deployed settlement verifier address" + required: true + type: string + owner_address: + description: "Expected default admin owner address" + required: true + type: string + settlement_operator: + description: "Expected settlement operator address" + required: true + type: string + attester_address: + description: "Optional expected attester address (0x0 to skip)" + required: false + default: "0x0000000000000000000000000000000000000000" + type: string + +jobs: + production-lock-verify: + name: Production Lock Verify + runs-on: ubuntu-latest + environment: production + defaults: + run: + working-directory: contracts + env: + RPC_URL: ${{ inputs.rpc_url }} + MARK_RYLA_TOKEN: ${{ inputs.token_address }} + MARK_SETTLEMENT_MODULE: ${{ inputs.module_address }} + MARK_SETTLEMENT_VERIFIER: ${{ inputs.verifier_address }} + MARK_RYLA_OWNER: ${{ inputs.owner_address }} + MARK_SETTLEMENT_OPERATOR: ${{ inputs.settlement_operator }} + MARK_SETTLEMENT_ATTESTER: ${{ inputs.attester_address }} + + steps: + - name: Enforce main branch for production verification + run: | + if [ "${GITHUB_REF_NAME}" != "main" ]; then + echo "Production lock verification workflow must run from main branch. Current: ${GITHUB_REF_NAME}" + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run production lock verification + run: make verify-production-lock + + - name: Write verification summary + if: always() + run: | + mkdir -p broadcast + jq -n \ + --arg status "${{ job.status }}" \ + --arg rpcUrl "$RPC_URL" \ + --arg token "$MARK_RYLA_TOKEN" \ + --arg module "$MARK_SETTLEMENT_MODULE" \ + --arg verifier "$MARK_SETTLEMENT_VERIFIER" \ + --arg owner "$MARK_RYLA_OWNER" \ + --arg operator "$MARK_SETTLEMENT_OPERATOR" \ + --arg attester "$MARK_SETTLEMENT_ATTESTER" \ + --arg commit "${{ github.sha }}" \ + --arg runId "${{ github.run_id }}" \ + --arg ref "${{ github.ref_name }}" \ + --arg workflow "${{ github.workflow }}" \ + --arg runNumber "${{ github.run_number }}" \ + '{ + workflow: $workflow, + status: $status, + runId: $runId, + runNumber: $runNumber, + ref: $ref, + commit: $commit, + rpcUrl: $rpcUrl, + token: $token, + module: $module, + verifier: $verifier, + owner: $owner, + settlementOperator: $operator, + attester: $attester + }' > broadcast/mark-production-lock-verify.json + + - name: Upload verification artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: mark-production-lock-verify + path: contracts/broadcast/mark-production-lock-verify.json + if-no-files-found: error diff --git a/.github/workflows/contracts-promotion-checklist.yml b/.github/workflows/contracts-promotion-checklist.yml new file mode 100644 index 0000000..43de2f8 --- /dev/null +++ b/.github/workflows/contracts-promotion-checklist.yml @@ -0,0 +1,78 @@ +name: Contracts Promotion Checklist + +on: + workflow_dispatch: + inputs: + staging_run_id: + description: "Optional override for staging rehearsal run id" + required: false + default: "" + type: string + mainnet_run_id: + description: "Optional override for mainnet readiness run id" + required: false + default: "" + type: string + checklist_path: + description: "Checklist JSON path under contracts/" + required: false + default: "broadcast/mark-promotion-checklist.json" + type: string + checklist_markdown_path: + description: "Checklist markdown path under contracts/" + required: false + default: "broadcast/mark-promotion-checklist.md" + type: string + freshness_hours: + description: "Max allowed age (hours) for staging/mainnet evidence runs" + required: false + default: "72" + type: string + +permissions: + contents: read + actions: read + +jobs: + promotion-checklist: + name: Generate Promotion Checklist + runs-on: ubuntu-latest + environment: production + defaults: + run: + working-directory: contracts + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + STAGING_RUN_ID: ${{ inputs.staging_run_id }} + MAINNET_RUN_ID: ${{ inputs.mainnet_run_id }} + PROMOTION_CHECKLIST_PATH: ${{ inputs.checklist_path }} + PROMOTION_CHECKLIST_MARKDOWN_PATH: ${{ inputs.checklist_markdown_path }} + FRESHNESS_HOURS: ${{ inputs.freshness_hours }} + STRICT_PROMOTION_CHECKS: "true" + GENERATED_BY: "github-actions" + + steps: + - name: Enforce main branch for promotion checklist + run: | + if [ "${GITHUB_REF_NAME}" != "main" ]; then + echo "Promotion checklist workflow must run from main branch. Current: ${GITHUB_REF_NAME}" + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Generate checklist artifact + run: make generate-promotion-checklist + + - name: Upload checklist artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: mark-promotion-checklist + path: | + contracts/${{ inputs.checklist_path }} + contracts/${{ inputs.checklist_markdown_path }} + if-no-files-found: error diff --git a/.github/workflows/contracts-slither.yml b/.github/workflows/contracts-slither.yml new file mode 100644 index 0000000..082fd84 --- /dev/null +++ b/.github/workflows/contracts-slither.yml @@ -0,0 +1,51 @@ +name: Contracts Slither + +on: + pull_request: + paths: + - "contracts/src/**" + - "contracts/foundry.toml" + - ".github/workflows/contracts-slither.yml" + push: + branches: + - main + - dev + paths: + - "contracts/src/**" + - "contracts/foundry.toml" + - ".github/workflows/contracts-slither.yml" + +jobs: + slither-core: + name: Slither Core Contracts + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Slither + run: pip install slither-analyzer + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run Slither on MARK core contracts + working-directory: contracts + run: | + slither \ + src/token/RYLA.sol \ + src/protocol/MARKBridgeAdapter.sol \ + src/protocol/MARKSettlementModule.sol \ + src/verifier/AttestedSettlementVerifier.sol \ + --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ + --exclude-dependencies \ + --filter-paths "lib|test|script|out|cache" \ + --fail-medium diff --git a/.github/workflows/contracts-staging-rehearsal.yml b/.github/workflows/contracts-staging-rehearsal.yml new file mode 100644 index 0000000..548e48f --- /dev/null +++ b/.github/workflows/contracts-staging-rehearsal.yml @@ -0,0 +1,105 @@ +name: Contracts Staging Rehearsal + +on: + workflow_dispatch: + inputs: + rpc_url: + description: "Staging RPC URL (e.g. OP Sepolia)" + required: true + type: string + owner_address: + description: "Owner/default admin address (defaults to deployer if blank)" + required: false + default: "" + type: string + settlement_operator: + description: "Settlement operator address" + required: true + type: string + bridge_operator: + description: "Bridge operator address (defaults to settlement operator if blank)" + required: false + default: "" + type: string + destination_chain_id: + description: "Bridge destination chain id" + required: false + default: "10" + type: string + attester_address: + description: "Optional attester address (0x0 to skip attester check)" + required: false + default: "0x0000000000000000000000000000000000000000" + type: string + release_artifact_path: + description: "Release artifact path under contracts/" + required: false + default: "broadcast/mark-staging-release.json" + type: string + rehearsal_artifact_path: + description: "Rehearsal artifact path under contracts/" + required: false + default: "broadcast/mark-staging-rehearsal.json" + type: string + +jobs: + staging-rehearsal: + name: Staging Rehearsal + runs-on: ubuntu-latest + environment: staging + defaults: + run: + working-directory: contracts + env: + RPC_URL: ${{ inputs.rpc_url }} + PRIVATE_KEY: ${{ secrets.MARK_STAGING_DEPLOYER_PRIVATE_KEY }} + MARK_RYLA_OWNER: ${{ inputs.owner_address }} + MARK_SETTLEMENT_OPERATOR: ${{ inputs.settlement_operator }} + MARK_BRIDGE_OPERATOR: ${{ inputs.bridge_operator }} + MARK_BRIDGE_DESTINATION_CHAIN_ID: ${{ inputs.destination_chain_id }} + MARK_SETTLEMENT_ATTESTER: ${{ inputs.attester_address }} + MARK_RELEASE_ARTIFACT_PATH: ${{ inputs.release_artifact_path }} + MARK_REHEARSAL_ARTIFACT_PATH: ${{ inputs.rehearsal_artifact_path }} + MARK_GIT_COMMIT: ${{ github.sha }} + + steps: + - name: Enforce dev or main branch for rehearsal + run: | + if [ "${GITHUB_REF_NAME}" != "dev" ] && [ "${GITHUB_REF_NAME}" != "main" ]; then + echo "Staging rehearsal must run from dev or main branch. Current: ${GITHUB_REF_NAME}" + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Ensure required staging secret is present + run: | + test -n "${PRIVATE_KEY}" || { + echo "MARK_STAGING_DEPLOYER_PRIVATE_KEY secret is required"; + exit 1; + } + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run staging rehearsal (release + production lock verify) + run: make rehearse-production-lock + + - name: Upload release artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: mark-staging-release + path: contracts/${{ inputs.release_artifact_path }} + if-no-files-found: ignore + + - name: Upload rehearsal artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: mark-staging-rehearsal + path: contracts/${{ inputs.rehearsal_artifact_path }} + if-no-files-found: ignore diff --git a/.github/workflows/release-evidence-validator.yml b/.github/workflows/release-evidence-validator.yml new file mode 100644 index 0000000..8db2e2f --- /dev/null +++ b/.github/workflows/release-evidence-validator.yml @@ -0,0 +1,127 @@ +name: Release Evidence Validator + +on: + pull_request_target: + branches: + - main + types: + - opened + - edited + - synchronize + - reopened + +permissions: + contents: read + pull-requests: read + +jobs: + validate-release-evidence: + name: Validate Release Evidence + runs-on: ubuntu-latest + + steps: + - name: Validate release PR fields and evidence + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + const body = (pr.body || "").replace(/\r/g, ""); + const errors = []; + + const requiredFields = [ + "Release tag/version candidate", + "Commit range / PR range", + "Contracts affected", + "Mainnet readiness run URL", + "Readiness artifact SHA256", + "RPC target", + "Artifact path", + "`MARK_GIT_COMMIT` value", + "Environment used: `production`", + ]; + + const getFieldValue = (label) => { + const esc = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const re = new RegExp(`^-\\s*${esc}:\\s*(.*)$`, "m"); + const m = body.match(re); + return m ? m[1].trim() : null; + }; + + const isPlaceholder = (v) => { + if (!v) return true; + const normalized = v.toLowerCase(); + const bad = ["tbd", "todo", "n/a", "-", "", "", ""]; + return bad.includes(normalized); + }; + + for (const label of requiredFields) { + const value = getFieldValue(label); + if (value === null) { + errors.push(`Missing field line: "${label}:"`); + continue; + } + if (isPlaceholder(value)) { + errors.push(`Field is empty/placeholder: "${label}"`); + } + } + + // Validate environment exactness + const envValue = getFieldValue("Environment used: `production`"); + if (envValue !== "`production`" && envValue !== "production") { + errors.push('Environment must be "production"'); + } + + // Validate MARK_GIT_COMMIT format (short or full hex) + const commitValue = getFieldValue("`MARK_GIT_COMMIT` value"); + if (commitValue && !/^[0-9a-f]{7,40}$/i.test(commitValue)) { + errors.push("`MARK_GIT_COMMIT` value must be a 7-40 char hex commit id"); + } + + // Validate readiness URL points to GitHub Actions + const runUrl = getFieldValue("Mainnet readiness run URL"); + if (runUrl && !/^https:\/\/github\.com\/.+\/actions\/runs\/\d+/i.test(runUrl)) { + errors.push("Mainnet readiness run URL must be a GitHub Actions run URL"); + } + + // Validate SHA256 format + const sha256 = getFieldValue("Readiness artifact SHA256"); + if (sha256 && !/^[a-f0-9]{64}$/i.test(sha256)) { + errors.push("Readiness artifact SHA256 must be 64 hex characters"); + } + + // If artifact path exists in PR head, validate core JSON fields. + const artifactPath = getFieldValue("Artifact path"); + if (artifactPath && !isPlaceholder(artifactPath)) { + const repo = context.repo; + const ref = pr.head.sha; + const normalizedPath = artifactPath.replace(/^\/+/, ""); + try { + const file = await github.rest.repos.getContent({ + owner: repo.owner, + repo: repo.repo, + path: normalizedPath, + ref, + }); + if (!Array.isArray(file.data) && file.data.type === "file" && file.data.content) { + const raw = Buffer.from(file.data.content, "base64").toString("utf8"); + try { + const json = JSON.parse(raw); + if (json.protocol !== "MARK") errors.push('Artifact JSON "protocol" must be "MARK"'); + if (json.tokenSymbol !== "RYLA") errors.push('Artifact JSON "tokenSymbol" must be "RYLA"'); + if (!json.chainId) errors.push('Artifact JSON must include non-zero "chainId"'); + if (!json.gitCommit) errors.push('Artifact JSON must include "gitCommit"'); + } catch (e) { + errors.push(`Artifact path exists but is not valid JSON: ${normalizedPath}`); + } + } + } catch (e) { + // Artifact file might not be committed in repo; this is acceptable if URL/hash are present. + } + } + + if (errors.length > 0) { + core.setFailed("Release evidence validation failed:\n- " + errors.join("\n- ")); + } else { + core.info("Release evidence validation passed."); + } + diff --git a/.github/workflows/release-pr-checklist.yml b/.github/workflows/release-pr-checklist.yml new file mode 100644 index 0000000..7d500ce --- /dev/null +++ b/.github/workflows/release-pr-checklist.yml @@ -0,0 +1,66 @@ +name: Release PR Checklist + +on: + pull_request_target: + branches: + - main + types: + - opened + - edited + - synchronize + - reopened + +jobs: + validate-release-pr: + name: Validate Release PR Checklist + runs-on: ubuntu-latest + + steps: + - name: Validate required release checkboxes + env: + PR_BODY: ${{ github.event.pull_request.body }} + run: | + set -euo pipefail + + body="${PR_BODY//$'\r'/}" + missing=() + + require_checked() { + local label="$1" + if ! grep -Fq -- "- [x] ${label}" <<< "$body"; then + missing+=("${label}") + fi + } + + # Required evidence + require_checked '`Contracts Unit + Invariant` CI passed' + require_checked '`Contracts Release Check (Dry-Run + Execute Smoke)` CI passed' + require_checked '`Slither Core Contracts` CI passed' + require_checked '`Contracts Mainnet Readiness` run from `main` branch' + require_checked 'Readiness artifact uploaded and reviewed' + require_checked 'Verify output reviewed (role/config expectations)' + + # Required sign-off confirmations + require_checked 'Protocol owner/admin signer approval' + require_checked 'Security reviewer approval' + require_checked 'Deployment operator approval' + + # Require explicit go/no-go decision + go_checked=0 + nogo_checked=0 + grep -Fq -- '- [x] Go' <<< "$body" && go_checked=1 + grep -Fq -- '- [x] No-Go (reason)' <<< "$body" && nogo_checked=1 + if [[ "$go_checked" -eq 0 && "$nogo_checked" -eq 0 ]]; then + missing+=('Go / No-Go decision') + fi + + if [[ "${#missing[@]}" -gt 0 ]]; then + echo "Missing required checked items in release PR checklist:" + for item in "${missing[@]}"; do + echo " - ${item}" + done + exit 1 + fi + + echo "Release PR checklist validation passed." + diff --git a/.github/workflows/secrets-drift-guard.yml b/.github/workflows/secrets-drift-guard.yml new file mode 100644 index 0000000..b20c2d5 --- /dev/null +++ b/.github/workflows/secrets-drift-guard.yml @@ -0,0 +1,53 @@ +name: Secrets Drift Guard + +on: + pull_request: + branches: + - dev + - main + +permissions: + contents: read + +jobs: + detect-secrets-drift: + name: Detect Secrets Drift + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Scan added lines for secret-like patterns + env: + BASE_REF: ${{ github.base_ref }} + run: | + set -euo pipefail + + git fetch origin "${BASE_REF}" --depth=1 + git diff --unified=0 --no-color "origin/${BASE_REF}"...HEAD > /tmp/pr.diff + + # Added lines only (exclude file headers). + grep -E '^\+[^+]' /tmp/pr.diff > /tmp/pr.added || true + + # Remove obvious placeholders and known non-secret examples. + grep -Ev 'YOUR_PRIVATE_KEY|0x0000000000000000000000000000000000000000|gho_\*+|github\.com/cli/cli/releases' /tmp/pr.added > /tmp/pr.added.filtered || true + + # High-signal patterns. + if grep -Ein \ + -e 'ghp_[A-Za-z0-9]{20,}' \ + -e 'github_pat_[A-Za-z0-9_]{20,}' \ + -e 'AKIA[0-9A-Z]{16}' \ + -e '-----BEGIN (RSA|EC|OPENSSH|DSA|PRIVATE) KEY-----' \ + -e '(^|\s)(MNEMONIC|SEED_PHRASE|PRIVATE_KEY)\s*[:=]\s*[^[:space:]]{12,}' \ + -e '0x[a-fA-F0-9]{64}' \ + /tmp/pr.added.filtered > /tmp/secret.hits; then + echo "Potential secret material detected in added lines:" + cat /tmp/secret.hits + exit 1 + fi + + echo "No secret-like drift detected in added lines." + diff --git a/contracts/README.md b/contracts/README.md index 4ed0e58..7bf2433 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -1,17 +1,50 @@ # Contracts -Smart contracts demonstrating cross-chain messaging on the Superchain using [interoperability](https://specs.optimism.io/interop/overview.html). +Smart contracts for Superchain interoperability and `RYLA` standard credit primitives. + +Operational procedures (deployment, incident, rollback) are documented in [RUNBOOK.md](./RUNBOOK.md). ## Contracts -### [CrossChainCounter.sol](./src/CrossChainCounter.sol) +### [RYLA.sol](./src/token/RYLA.sol) + +- Superchain-compatible ERC-7802 token (mint/burn on bridge is enforced by `SuperchainTokenBridge`) +- Market symbol: `RYLA` +- `MINTER_ROLE` / `BURNER_ROLE` for protocol issuance modules +- Uses `AccessControlDefaultAdminRules` for delayed default-admin transfer hardening + +### [MARKBridgeAdapter.sol](./src/protocol/MARKBridgeAdapter.sol) + +- Operator-gated bridge-out adapter that routes through `SuperchainTokenBridge` +- Destination allowlist and optional `maxPerTx` / `dailyCap` risk limits +- Keeps token core bridge authority on the canonical predeploy +- Uses `AccessControlDefaultAdminRules` (`OPERATOR_ROLE`) + +### [MARKSettlementModule.sol](./src/protocol/MARKSettlementModule.sol) + +- Phase-2 integration boundary for UTXO / zk accounting +- Operator-gated settlement with replay protection (`intentId`) +- Optional external proof verifier hook (`IUTXOSettlementVerifier`) +- Holds token minter/burner roles for controlled mint and burn settlement +- Uses `AccessControlDefaultAdminRules` (`OPERATOR_ROLE`) + +### [AttestedSettlementVerifier.sol](./src/verifier/AttestedSettlementVerifier.sol) + +- Signature-based settlement verifier (`ATTESTER_ROLE` allowlist) +- Intended as a concrete verifier baseline before integrating zk circuits +- Uses OZ `ECDSA` + `MessageHashUtils` +- Binds signed proofs to settlement module address (prevents cross-module replay) +- Proof format: + - `abi.encode(uint256 deadline, bytes32 contextHash, uint8 v, bytes32 r, bytes32 s)` + +### [CrossChainCounter.sol](./src/examples/CrossChainCounter.sol) - Counter that can only be incremented through cross-chain messages - Uses `L2ToL2CrossDomainMessenger` for message verification - Tracks last incrementer's chain ID and address - Events emitted for all increments with source chain details -### [CrossChainCounterIncrementer.sol](./src/CrossChainCounterIncrementer.sol) +### [CrossChainCounterIncrementer.sol](./src/examples/CrossChainCounterIncrementer.sol) - Sends cross-chain increment messages to `CrossChainCounter` instances - Uses `L2ToL2CrossDomainMessenger` for message passing @@ -36,6 +69,14 @@ forge build forge test ``` +Run integration (fork/RPC-dependent) tests only: + +```bash +CHAIN_A_RPC_URL=http://127.0.0.1:9545 \ +CHAIN_B_RPC_URL=http://127.0.0.1:9546 \ +FOUNDRY_PROFILE=integration forge test --match-path 'test/integration/*.t.sol' +``` + ### Deploy Deploy to multiple chains using either: @@ -49,7 +90,239 @@ cd ../ && pnpm sup 2. Direct Forge script: ```bash -forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast +forge script script/examples/Deploy.s.sol --rpc-url $RPC_URL --broadcast +``` + +Deploy `RYLA` stack: + +```bash +set -a && source .env && set +a +forge script script/deploy/DeployMARKStack.s.sol --rpc-url $RPC_URL --broadcast +``` + +Deploy settlement module: + +```bash +set -a && source .env && set +a +forge script script/deploy/DeployMARKSettlementModule.s.sol --rpc-url $RPC_URL --broadcast +``` + +Deploy settlement module with in-script attested verifier: + +```bash +set -a && source .env && set +a +forge script script/deploy/DeployMARKSettlementModule.s.sol --rpc-url $RPC_URL --broadcast +``` + +### Post-Deploy Setup + +Apply deterministic role/config setup on already deployed contracts: + +```bash +set -a && source .env && set +a +forge script script/ops/PostDeployMARKSetup.s.sol --rpc-url $RPC_URL --broadcast +``` + +### Preflight (Recommended Before Broadcast) + +Run read-only checks to validate env wiring and admin permissions before deployment/setup: + +```bash +set -a && source .env && set +a +forge script script/ops/PreflightMARKDeployment.s.sol --rpc-url $RPC_URL +``` + +`MARK_PREFLIGHT_MODE` values: +- `1` => checks for `DeployMARKStack.s.sol` +- `2` => checks for `DeployMARKSettlementModule.s.sol` +- `3` => checks for `PostDeployMARKSetup.s.sol` + +### Release Orchestrator + +Run full release pipeline (preflight -> deploy -> optional setup -> verify -> artifact): + +```bash +set -a && source .env && set +a +forge script script/ops/ReleaseMARK.s.sol --rpc-url $RPC_URL +``` + +Local production-mode smoke (starts Anvil, deploys verifier, runs strict release verify): + +```bash +make smoke-production-mode +``` + +Control behavior with: +- `MARK_RELEASE_EXECUTE=false` for dry-run (no broadcast) +- `MARK_RELEASE_EXECUTE=true` for execution +- `MARK_RELEASE_RUN_POSTDEPLOY=true` to run `PostDeployMARKSetup` after deployment +- `MARK_RELEASE_WRITE_ARTIFACT=true` to write JSON artifact (requires Foundry FS write permission) +- `MARK_RELEASE_ARTIFACT_PATH` for JSON output path +- `MARK_GIT_COMMIT` to tag artifact with commit id +- `MARK_RELEASE_STRICT_VERIFY=true` to require explicit `VERIFY_MARK_SETTLEMENT_*` expectations during execute-mode verify +- `MARK_SETTLEMENT_PRODUCTION_MODE=true` to lock settlement verifier/proof validation configuration in production + +### Mainnet Readiness Gate + +Run the full pre-mainnet gate in one command: + +```bash +RPC_URL= \ +PRIVATE_KEY= \ +./script/ops/mainnet-readiness.sh +``` + +Gate mode is controlled by `MARK_MAINNET_GATE_MODE`: +- `predeploy` (default): tests + slither + preflight + dry-run artifact +- `postdeploy`: verify-only checks against deployed contracts +- `full`: predeploy checks + postdeploy verify + dry-run artifact + +This gate enforces (by mode): +- contract tests pass +- slither scan pass +- preflight pass (all modes) +- deployment verify pass +- release artifact generation + schema validation + +Manual CI entrypoint: +- `.github/workflows/contracts-mainnet-readiness.yml` (`workflow_dispatch`) + +### Post-Deploy Verify + +Run read-only checks against deployed contracts and role wiring: + +```bash +set -a && source .env && set +a +forge script script/ops/VerifyMARKDeployment.s.sol --rpc-url $RPC_URL +``` + +Optional strict checks supported by verify script: +- `VERIFY_MARK_BRIDGE_MAX_PER_TX` / `VERIFY_MARK_BRIDGE_DAILY_CAP` +- Verifier admin check via `VERIFY_MARK_RYLA_OWNER` when `VERIFY_MARK_SETTLEMENT_VERIFIER` is set +- Verifier attester check via `VERIFY_MARK_SETTLEMENT_ATTESTER` +- Settlement production lock expectation via `VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE` + +Run production lock post-deploy assurance (read-only checks): + +```bash +set -a && source .env && set +a +make verify-production-lock +``` + +Manual CI entrypoint for post-deploy production checks: +- `.github/workflows/contracts-production-lock-verify.yml` (`workflow_dispatch`) + +Dispatch helper (auto-fills workflow inputs from env/artifact): + +```bash +set -a && source .env && set +a +make dispatch-production-lock-verify +``` + +By default this is dry-run only. To actually dispatch: + +```bash +set -a && source .env && set +a +DISPATCH_EXECUTE=true make dispatch-production-lock-verify +``` + +Staging rehearsal (release + production-lock verify): + +```bash +set -a && source .env && set +a +make rehearse-production-lock +``` + +Manual CI entrypoint for staging rehearsal: +- `.github/workflows/contracts-staging-rehearsal.yml` (`workflow_dispatch`) + +Promotion checklist generator (links latest successful staging rehearsal + mainnet readiness): + +```bash +make generate-promotion-checklist +``` + +Policy defaults: +- freshness window: `72` hours (`FRESHNESS_HOURS`) +- lineage: mainnet commit must be `identical` or `ahead` vs staging commit +- strict mode: fails command/workflow when any check fails (`STRICT_PROMOTION_CHECKS=true`) + +Manual CI entrypoint: +- `.github/workflows/contracts-promotion-checklist.yml` (`workflow_dispatch`) + +Environment safety precheck utility: + +```bash +VALIDATE_MODE=rehearsal make validate-prod-env +``` + +Supported modes: +- `rehearsal` +- `dispatch` +- `verify-lock` + +Canonical env profiles: +- `config/profiles/staging.env` +- `config/profiles/mainnet.env` + +CI env schema guard entrypoint: +- `.github/workflows/contracts-env-guard.yml` + +Evidence manifest tooling: + +```bash +make generate-evidence-manifest +make verify-evidence-manifest +make sign-evidence-manifest +make verify-evidence-signature +``` + +CI/manual entrypoint: +- `.github/workflows/contracts-evidence-manifest.yml` + +### Deployment Config + +- Copy `.env.example` to `.env` and fill all required deploy/verify values. +- Use MARK-prefixed keys (`MARK_*`, `VERIFY_MARK_*`) for all deploy and verify scripts. +- Network-specific templates are in: + - `config/networks/optimism-mainnet.env.example` + - `config/networks/optimism-sepolia.env.example` + +## Admin Runbook + +All RYLA contracts use `AccessControlDefaultAdminRules` with a 1-day admin delay. + +### Grant operational roles + +```bash +# Examples via cast: +# cast send "setMinter(address,bool)" true --private-key $PK +# cast send "setBurner(address,bool)" true --private-key $PK +# cast send "setOperator(address,bool)" true --private-key $PK +# cast send "setOperator(address,bool)" true --private-key $PK +# cast send "setAttester(address,bool)" true --private-key $PK +``` + +### Rotate default admin (delayed) + +Step 1: current admin starts transfer + +```bash +# cast send "beginDefaultAdminTransfer(address)" --private-key $OLD_ADMIN_PK +``` + +Step 2: wait at least `defaultAdminDelay()` (1 day) + +Step 3: new admin accepts transfer + +```bash +# cast send "acceptDefaultAdminTransfer()" --private-key $NEW_ADMIN_PK +``` + +Optional: cancel pending transfer before acceptance + +```bash +# cast send "cancelDefaultAdminTransfer()" --private-key $OLD_ADMIN_PK ``` ## Architecture @@ -71,13 +344,34 @@ forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast Tests are in `test/` directory: -- Unit tests for both contracts -- Uses Foundry's cheatcodes for chain simulation +- Unit tests for token/protocol/verifier and deploy scripts +- Invariant tests for settlement supply and authorization safety +- Integration tests (in `test/integration/`) are isolated from default runs ```bash forge test ``` +## Security Analysis + +Run Slither locally on MARK core contracts: + +```bash +cd /Users/iap/mark/contracts +slither \ + src/token/RYLA.sol \ + src/protocol/MARKBridgeAdapter.sol \ + src/protocol/MARKSettlementModule.sol \ + src/verifier/AttestedSettlementVerifier.sol \ + --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ + --exclude-dependencies \ + --filter-paths "lib|test|script|out|cache" \ + --fail-medium +``` + +CI workflow: +- `.github/workflows/contracts-slither.yml` + ## License MIT diff --git a/contracts/RUNBOOK.md b/contracts/RUNBOOK.md new file mode 100644 index 0000000..e648fea --- /dev/null +++ b/contracts/RUNBOOK.md @@ -0,0 +1,247 @@ +# MARK Mainnet Runbook + +This runbook is the operational source of truth for MARK (`RYLA`) deployments. + +## 0) Branch Policy + +- Production deployment and mainnet readiness checks are executed from `main` branch only. +- Development and config iteration happen on `dev` branch and feature branches. +- Merge flow is documented in `/BRANCHING.md`. + +## 1) Pre-Deployment Checklist + +Run the gate script from `contracts/`: + +```bash +RPC_URL= \ +PRIVATE_KEY= \ +./script/ops/mainnet-readiness.sh +``` + +Gate mode: +- `MARK_MAINNET_GATE_MODE=predeploy` (default) +- `MARK_MAINNET_GATE_MODE=postdeploy` +- `MARK_MAINNET_GATE_MODE=full` + +The gate enforces: +- `forge test` passes +- Slither core scan passes (`--fail-medium`) +- Preflight checks pass (modes 1, 2, 3) +- Deployment verify script passes +- Release artifact is generated and schema-validated +- Env schema guard passes (`contracts-env-guard.yml`) + +Do not broadcast to production if this gate fails. + +### Run Readiness Gate via GitHub Actions (UI) + +Workflow: +- `.github/workflows/contracts-mainnet-readiness.yml` + +Steps: +1. Open Actions -> `Contracts Mainnet Readiness`. +2. Click `Run workflow`. +3. Set inputs: + - `mode`: `predeploy` (default), `postdeploy`, or `full` + - `rpc_url`: target network RPC endpoint + - `artifact_path` (optional): default `broadcast/mark-mainnet-gate-ci.json` +4. Run and wait for completion. + +Required repository secret: +- `MARK_DEPLOYER_PRIVATE_KEY` + +Expected outputs: +- Job status is green. +- Readiness artifact is uploaded (`mark-mainnet-readiness-artifact`). + +Do not run production broadcasts from CI unless your organization explicitly approves that process. + +### Run Post-Deploy Production Lock Verify via GitHub Actions (UI) + +Workflow: +- `.github/workflows/contracts-production-lock-verify.yml` + +Inputs: +- `rpc_url` +- `token_address` +- `module_address` +- `verifier_address` +- `owner_address` +- `settlement_operator` +- `attester_address` (optional, default zero address) + +Output artifact: +- `mark-production-lock-verify` (contains `mark-production-lock-verify.json`) + +Optional CLI dispatch helper (from `contracts/`): +```bash +set -a && source .env && set +a +make dispatch-production-lock-verify +``` +To execute dispatch (not dry-run): +```bash +set -a && source .env && set +a +DISPATCH_EXECUTE=true make dispatch-production-lock-verify +``` + +### Run Staging Rehearsal via GitHub Actions (UI) + +Workflow: +- `.github/workflows/contracts-staging-rehearsal.yml` + +Required repository secret: +- `MARK_STAGING_DEPLOYER_PRIVATE_KEY` + +Result artifacts: +- `mark-staging-release` +- `mark-staging-rehearsal` + +### Generate Promotion Checklist (Go/No-Go Evidence) + +Workflow: +- `.github/workflows/contracts-promotion-checklist.yml` + +This generates: +- `mark-promotion-checklist.json` +- `mark-promotion-checklist.md` + +The checklist links latest successful: +- staging rehearsal run evidence +- mainnet readiness run evidence + +Promotion policy (enforced): +- freshness: both evidence runs must be within `freshness_hours` (default `72`) +- lineage: mainnet run commit must be `identical` or `ahead` of staging run commit +- strict checks: workflow exits non-zero when any policy check fails + +### Evidence Manifest (Hash Integrity Baseline) + +Workflow: +- `.github/workflows/contracts-evidence-manifest.yml` + +Local commands: +```bash +make generate-evidence-manifest +make verify-evidence-manifest +make sign-evidence-manifest +make verify-evidence-signature +``` + +No-Go triggers for this baseline: +- any required artifact missing +- manifest hash mismatch +- signature missing/invalid + +### Operator Sign-Off Checklist + +Before approving production deployment, capture: +1. Commit/tag being deployed (`MARK_GIT_COMMIT`). +2. Successful readiness gate run link (CLI logs or GitHub Actions run URL). +3. Release artifact file and hash. +4. Verify script output proving expected role/config wiring. +5. Security scan result (Slither pass). + +Required approvals (minimum): +- Protocol owner/admin signer. +- Security reviewer. +- Deployment operator. + +Go/No-Go decision rule: +- Go only if all checklist items are present and all approvals are recorded. +- No-Go if any verify/preflight/security check is missing or failed. +- For production settlement deployments, require `VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE=true` in verify inputs and confirm verify passes. + +## 2) Standard Deployment Sequence + +1. Run preflight gate: +```bash +RPC_URL= PRIVATE_KEY= ./script/ops/mainnet-readiness.sh +``` +2. Run release orchestrator: +```bash +set -a && source .env && set +a +MARK_RELEASE_EXECUTE=true MARK_RELEASE_WRITE_ARTIFACT=true \ +forge script script/ops/ReleaseMARK.s.sol --rpc-url $RPC_URL --broadcast +``` +3. Run post-deploy verify: +```bash +set -a && source .env && set +a +forge script script/ops/VerifyMARKDeployment.s.sol --rpc-url $RPC_URL +``` +4. Run production lock assurance: +```bash +set -a && source .env && set +a +make verify-production-lock +``` + +## 3) Verify Failure Playbook + +Condition: `VerifyMARKDeployment` fails after broadcast. + +Actions: +1. Stop all further broadcasts. +2. Record failing assertion and tx hashes. +3. If issue is role/config only: + - fix deterministically with `PostDeployMARKSetup.s.sol` + - re-run verify +4. If issue is ownership/admin mismatch: + - do not continue + - execute admin correction via delayed transfer flow +5. If issue cannot be safely corrected: + - treat deployment as failed + - redeploy clean stack + +## 4) Wrong Config / Wrong Address Playbook + +Condition: wrong env values, wrong target address, wrong chain, or wrong operator. + +Actions: +1. Stop deployment immediately. +2. Revoke accidental operational roles from incorrect addresses. +3. Re-run preflight with corrected env. +4. Re-run release only after preflight + verify are green. + +## 5) Operator Key Compromise Playbook + +Condition: suspected compromise of bridge/settlement operator key. + +Actions: +1. Revoke compromised operator role(s) immediately: + - `MARKBridgeAdapter.setOperator(compromised, false)` + - `MARKSettlementModule.setOperator(compromised, false)` +2. Rotate to new operator key(s). +3. Re-run verify to confirm clean role state. +4. If admin key may also be compromised: + - start delayed default admin transfer to secure key + - accept after delay + - re-verify all contracts + +## 6) Default Admin Rotation (Delayed) + +1. Current admin schedules transfer: +```bash +cast send "beginDefaultAdminTransfer(address)" --private-key $OLD_ADMIN_PK +``` +2. Wait at least `defaultAdminDelay()` (expected: 1 day). +3. New admin accepts: +```bash +cast send "acceptDefaultAdminTransfer()" --private-key $NEW_ADMIN_PK +``` +4. Re-run verify script. + +Optional cancel before acceptance: +```bash +cast send "cancelDefaultAdminTransfer()" --private-key $OLD_ADMIN_PK +``` + +## 7) Rollback Decision Rule + +Rollback is mandatory if either is true: +- Verify cannot be made green with deterministic post-deploy setup. +- Privileged roles/admin are not provably under expected control. + +When rollback is triggered: +1. Freeze further changes. +2. Revoke unsafe operators/attesters. +3. Redeploy from clean, verified env snapshot. +4. Re-run full readiness gate. diff --git a/contracts/script/ops/generate-promotion-checklist.sh b/contracts/script/ops/generate-promotion-checklist.sh new file mode 100755 index 0000000..a9f1507 --- /dev/null +++ b/contracts/script/ops/generate-promotion-checklist.sh @@ -0,0 +1,270 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd gh +require_cmd jq +require_cmd git + +infer_repo_from_remote() { + local remote + remote="$(git remote get-url origin)" + if [[ "$remote" =~ ^git@github.com:([^/]+/[^.]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + if [[ "$remote" =~ ^https://github.com/([^/]+/[^.]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + echo "Could not infer GH_REPO from origin: $remote" >&2 + exit 1 +} + +require_nonempty() { + local label="$1" + local value="$2" + if [[ -z "$value" || "$value" == "null" ]]; then + echo "Missing required value: $label" >&2 + exit 1 + fi +} + +fetch_latest_success_run_id() { + local repo="$1" + local workflow="$2" + local branch="${3:-}" + local args=(run list --repo "$repo" --workflow "$workflow" --status success --limit 1 --json databaseId) + if [[ -n "$branch" ]]; then + args+=(--branch "$branch") + fi + gh "${args[@]}" | jq -r '.[0].databaseId // empty' +} + +fetch_run_json() { + local repo="$1" + local run_id="$2" + gh api "/repos/$repo/actions/runs/$run_id" +} + +fetch_artifacts_json() { + local repo="$1" + local run_id="$2" + gh api "/repos/$repo/actions/runs/$run_id/artifacts?per_page=100" +} + +fetch_compare_json() { + local repo="$1" + local base_sha="$2" + local head_sha="$3" + gh api "/repos/$repo/compare/$base_sha...$head_sha" +} + +GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" +STAGING_WORKFLOW="${STAGING_WORKFLOW:-contracts-staging-rehearsal.yml}" +MAINNET_WORKFLOW="${MAINNET_WORKFLOW:-contracts-mainnet-readiness.yml}" +STAGING_RUN_ID="${STAGING_RUN_ID:-}" +MAINNET_RUN_ID="${MAINNET_RUN_ID:-}" +MAINNET_BRANCH="${MAINNET_BRANCH:-main}" +CHECKLIST_PATH="${PROMOTION_CHECKLIST_PATH:-broadcast/mark-promotion-checklist.json}" +CHECKLIST_MARKDOWN_PATH="${PROMOTION_CHECKLIST_MARKDOWN_PATH:-broadcast/mark-promotion-checklist.md}" +GENERATED_BY="${GENERATED_BY:-manual}" +FRESHNESS_HOURS="${FRESHNESS_HOURS:-72}" +STRICT_PROMOTION_CHECKS="${STRICT_PROMOTION_CHECKS:-true}" + +if [[ -z "$STAGING_RUN_ID" ]]; then + STAGING_RUN_ID="$(fetch_latest_success_run_id "$GH_REPO" "$STAGING_WORKFLOW" "")" +fi +if [[ -z "$MAINNET_RUN_ID" ]]; then + MAINNET_RUN_ID="$(fetch_latest_success_run_id "$GH_REPO" "$MAINNET_WORKFLOW" "$MAINNET_BRANCH")" +fi + +require_nonempty "STAGING_RUN_ID" "$STAGING_RUN_ID" +require_nonempty "MAINNET_RUN_ID" "$MAINNET_RUN_ID" + +STAGING_RUN_JSON="$(fetch_run_json "$GH_REPO" "$STAGING_RUN_ID")" +MAINNET_RUN_JSON="$(fetch_run_json "$GH_REPO" "$MAINNET_RUN_ID")" +STAGING_ARTIFACTS_JSON="$(fetch_artifacts_json "$GH_REPO" "$STAGING_RUN_ID")" +MAINNET_ARTIFACTS_JSON="$(fetch_artifacts_json "$GH_REPO" "$MAINNET_RUN_ID")" + +STAGING_SHA="$(jq -r '.head_sha // empty' <<<"$STAGING_RUN_JSON")" +MAINNET_SHA="$(jq -r '.head_sha // empty' <<<"$MAINNET_RUN_JSON")" +require_nonempty "staging head sha" "$STAGING_SHA" +require_nonempty "mainnet head sha" "$MAINNET_SHA" + +COMPARE_JSON="$(fetch_compare_json "$GH_REPO" "$STAGING_SHA" "$MAINNET_SHA")" +COMPARE_STATUS="$(jq -r '.status // empty' <<<"$COMPARE_JSON")" + +mkdir -p "$(dirname "$CHECKLIST_PATH")" + +jq -n \ + --arg generatedAt "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ + --arg generatedBy "$GENERATED_BY" \ + --arg repo "$GH_REPO" \ + --arg stagingWorkflow "$STAGING_WORKFLOW" \ + --arg mainnetWorkflow "$MAINNET_WORKFLOW" \ + --arg freshnessHours "$FRESHNESS_HOURS" \ + --arg compareStatus "$COMPARE_STATUS" \ + --argjson stagingRun "$STAGING_RUN_JSON" \ + --argjson mainnetRun "$MAINNET_RUN_JSON" \ + --argjson stagingArtifacts "$STAGING_ARTIFACTS_JSON" \ + --argjson mainnetArtifacts "$MAINNET_ARTIFACTS_JSON" \ + --argjson compare "$COMPARE_JSON" \ + '{ + generatedAt: $generatedAt, + generatedBy: $generatedBy, + repository: $repo, + policy: { + freshnessHours: ($freshnessHours | tonumber), + lineageRule: "mainnet-head must be identical-or-ahead of staging-head" + }, + goNoGo: { + decision: "pending", + rationale: "", + requiredApprovals: [ + "protocol-owner-admin", + "security-reviewer", + "deployment-operator" + ] + }, + stagingRehearsal: { + workflow: $stagingWorkflow, + runId: $stagingRun.id, + runNumber: $stagingRun.run_number, + conclusion: $stagingRun.conclusion, + status: $stagingRun.status, + branch: $stagingRun.head_branch, + commit: $stagingRun.head_sha, + htmlUrl: $stagingRun.html_url, + createdAt: $stagingRun.created_at, + updatedAt: $stagingRun.updated_at, + artifacts: ($stagingArtifacts.artifacts // [] | map({ + id, + name, + size_in_bytes, + expired, + created_at, + expires_at + })) + }, + mainnetReadiness: { + workflow: $mainnetWorkflow, + runId: $mainnetRun.id, + runNumber: $mainnetRun.run_number, + conclusion: $mainnetRun.conclusion, + status: $mainnetRun.status, + branch: $mainnetRun.head_branch, + commit: $mainnetRun.head_sha, + htmlUrl: $mainnetRun.html_url, + createdAt: $mainnetRun.created_at, + updatedAt: $mainnetRun.updated_at, + artifacts: ($mainnetArtifacts.artifacts // [] | map({ + id, + name, + size_in_bytes, + expired, + created_at, + expires_at + })) + }, + lineage: { + base: $stagingRun.head_sha, + head: $mainnetRun.head_sha, + status: $compareStatus, + aheadBy: ($compare.ahead_by // 0), + behindBy: ($compare.behind_by // 0), + totalCommits: ($compare.total_commits // 0), + htmlUrl: ($compare.html_url // "") + }, + checks: [ + { + id: "staging-rehearsal-success", + passed: ($stagingRun.conclusion == "success") + }, + { + id: "mainnet-readiness-success", + passed: ($mainnetRun.conclusion == "success") + }, + { + id: "staging-artifact-present", + passed: (($stagingArtifacts.artifacts // [] | map(.name) | index("mark-staging-rehearsal")) != null) + }, + { + id: "mainnet-artifact-present", + passed: (($mainnetArtifacts.artifacts // [] | map(.name) | index("mark-mainnet-readiness-artifact")) != null) + }, + { + id: "staging-fresh-enough", + passed: ( + ((now - ($stagingRun.created_at | fromdateiso8601)) / 3600) + <= ($freshnessHours | tonumber) + ) + }, + { + id: "mainnet-fresh-enough", + passed: ( + ((now - ($mainnetRun.created_at | fromdateiso8601)) / 3600) + <= ($freshnessHours | tonumber) + ) + }, + { + id: "lineage-identical-or-ahead", + passed: (($compareStatus == "identical") or ($compareStatus == "ahead")) + } + ] + }' >"$CHECKLIST_PATH" + +jq -r ' + "# MARK Promotion Checklist", + "", + "- Generated At: \(.generatedAt)", + "- Repository: \(.repository)", + "- Freshness Window (hours): \(.policy.freshnessHours)", + "- Go/No-Go Decision: \(.goNoGo.decision)", + "", + "## Staging Rehearsal", + "- Workflow: \(.stagingRehearsal.workflow)", + "- Run: \(.stagingRehearsal.runId) (\(.stagingRehearsal.htmlUrl))", + "- Conclusion: \(.stagingRehearsal.conclusion)", + "- Commit: \(.stagingRehearsal.commit)", + "- Artifacts: " + ((.stagingRehearsal.artifacts | map(.name) | join(", ")) // "none"), + "", + "## Mainnet Readiness", + "- Workflow: \(.mainnetReadiness.workflow)", + "- Run: \(.mainnetReadiness.runId) (\(.mainnetReadiness.htmlUrl))", + "- Conclusion: \(.mainnetReadiness.conclusion)", + "- Commit: \(.mainnetReadiness.commit)", + "- Artifacts: " + ((.mainnetReadiness.artifacts | map(.name) | join(", ")) // "none"), + "", + "## Lineage", + "- Compare: \(.lineage.base)...\(.lineage.head)", + "- Status: \(.lineage.status)", + "- Ahead By: \(.lineage.aheadBy)", + "- Behind By: \(.lineage.behindBy)", + "- URL: \(.lineage.htmlUrl)", + "", + "## Gate Checks", + (.checks[] | "- \(.id): " + (if .passed then "PASS" else "FAIL" end)) +' "$CHECKLIST_PATH" >"$CHECKLIST_MARKDOWN_PATH" + +checks_passed="$(jq -r '[.checks[].passed] | all' "$CHECKLIST_PATH")" + +echo "Promotion checklist generated:" +echo " JSON: $CHECKLIST_PATH" +echo " Markdown: $CHECKLIST_MARKDOWN_PATH" + +if [[ "$checks_passed" != "true" ]]; then + echo "Promotion checklist policy checks FAILED." >&2 + if [[ "$STRICT_PROMOTION_CHECKS" == "true" || "$STRICT_PROMOTION_CHECKS" == "1" ]]; then + exit 1 + fi +fi From d0044b3fca80cd925182c69f2b1d720c0f1c5f45 Mon Sep 17 00:00:00 2001 From: Iko Date: Mon, 27 Apr 2026 01:12:00 +0700 Subject: [PATCH 02/38] ops: add full release evidence dispatch sequence --- contracts/Makefile | 47 +++++ contracts/README.md | 15 ++ contracts/RUNBOOK.md | 23 ++ .../ops/dispatch-release-evidence-sequence.sh | 197 ++++++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 contracts/Makefile create mode 100755 contracts/script/ops/dispatch-release-evidence-sequence.sh diff --git a/contracts/Makefile b/contracts/Makefile new file mode 100644 index 0000000..5c9f8c3 --- /dev/null +++ b/contracts/Makefile @@ -0,0 +1,47 @@ +.PHONY: smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature + +smoke-production-mode: + @./script/ops/smoke-production-mode.sh + +test-production-lock: + @forge test --match-contract MARKSettlementModuleTest --match-test ProductionMode -vv + @forge test --match-contract PreflightMARKDeploymentTest --match-test ProductionMode -vv + @forge test --match-contract PostDeployMARKSetupTest --match-test ProductionMode -vv + +verify-production-lock: + @./script/ops/verify-production-lock.sh + +dispatch-production-lock-verify: + @./script/ops/dispatch-production-lock-verify.sh + +dispatch-release-evidence-sequence: + @./script/ops/dispatch-release-evidence-sequence.sh + +rehearse-production-lock: + @./script/ops/rehearse-production-lock.sh + +generate-promotion-checklist: + @./script/ops/generate-promotion-checklist.sh + +validate-prod-env: + @./script/ops/validate-prod-env.sh + +validate-prod-env-all: + @set -a; source config/profiles/staging.env; set +a; \ + MARK_RELEASE_EXECUTE=true MARK_SETTLEMENT_PROOF_ENABLED=true MARK_SETTLEMENT_PRODUCTION_MODE=true MARK_DEPLOY_ATTESTED_VERIFIER=true VALIDATE_MODE=rehearsal ./script/ops/validate-prod-env.sh + @set -a; source config/profiles/mainnet.env; set +a; \ + VALIDATE_MODE=dispatch ./script/ops/validate-prod-env.sh + @set -a; source config/profiles/mainnet.env; set +a; \ + VALIDATE_MODE=verify-lock ./script/ops/validate-prod-env.sh + +generate-evidence-manifest: + @./script/ops/generate-evidence-manifest.sh + +verify-evidence-manifest: + @./script/ops/verify-evidence-manifest.sh + +sign-evidence-manifest: + @./script/ops/sign-evidence-manifest.sh + +verify-evidence-signature: + @./script/ops/verify-evidence-signature.sh diff --git a/contracts/README.md b/contracts/README.md index 7bf2433..7e3e9db 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -226,6 +226,21 @@ set -a && source .env && set +a DISPATCH_EXECUTE=true make dispatch-production-lock-verify ``` +Dispatch full evidence sequence (staging rehearsal -> mainnet readiness -> promotion checklist): + +```bash +STAGING_RPC_URL= \ +STAGING_SETTLEMENT_OPERATOR=<0x_operator> \ +MAINNET_RPC_URL= \ +DISPATCH_EXECUTE=true \ +WAIT_FOR_COMPLETION=true \ +make dispatch-release-evidence-sequence +``` + +Required GitHub secrets for this sequence: +- `MARK_STAGING_DEPLOYER_PRIVATE_KEY` +- `MARK_DEPLOYER_PRIVATE_KEY` + Staging rehearsal (release + production-lock verify): ```bash diff --git a/contracts/RUNBOOK.md b/contracts/RUNBOOK.md index e648fea..114011b 100644 --- a/contracts/RUNBOOK.md +++ b/contracts/RUNBOOK.md @@ -114,6 +114,29 @@ Promotion policy (enforced): - lineage: mainnet run commit must be `identical` or `ahead` of staging run commit - strict checks: workflow exits non-zero when any policy check fails +### Dispatch Full Evidence Sequence (CLI) + +From `contracts/`: + +```bash +STAGING_RPC_URL= \ +STAGING_SETTLEMENT_OPERATOR=<0x_operator> \ +MAINNET_RPC_URL= \ +DISPATCH_EXECUTE=true \ +WAIT_FOR_COMPLETION=true \ +make dispatch-release-evidence-sequence +``` + +This will: +1. Dispatch `contracts-staging-rehearsal.yml`. +2. Dispatch `contracts-mainnet-readiness.yml`. +3. Wait for both to succeed. +4. Dispatch `contracts-promotion-checklist.yml` with explicit run IDs. + +Required repository secrets: +- `MARK_STAGING_DEPLOYER_PRIVATE_KEY` +- `MARK_DEPLOYER_PRIVATE_KEY` + ### Evidence Manifest (Hash Integrity Baseline) Workflow: diff --git a/contracts/script/ops/dispatch-release-evidence-sequence.sh b/contracts/script/ops/dispatch-release-evidence-sequence.sh new file mode 100755 index 0000000..5e7a471 --- /dev/null +++ b/contracts/script/ops/dispatch-release-evidence-sequence.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd gh +require_cmd jq +require_cmd git + +infer_repo_from_remote() { + local remote + remote="$(git remote get-url origin)" + if [[ "$remote" =~ ^git@github.com:([^/]+/[^.]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + if [[ "$remote" =~ ^https://github.com/([^/]+/[^.]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + echo "Could not infer GH_REPO from origin: $remote" >&2 + exit 1 +} + +require_nonempty() { + local label="$1" + local value="$2" + if [[ -z "$value" ]]; then + echo "Missing required value: $label" >&2 + exit 1 + fi +} + +require_secret() { + local repo="$1" + local secret_name="$2" + if ! gh api "/repos/$repo/actions/secrets/$secret_name" >/dev/null 2>&1; then + echo "Required GitHub Actions secret missing in $repo: $secret_name" >&2 + exit 1 + fi +} + +fetch_latest_run_id_for_ref() { + local repo="$1" + local workflow="$2" + local ref="$3" + gh run list \ + --repo "$repo" \ + --workflow "$workflow" \ + --branch "$ref" \ + --limit 1 \ + --json databaseId \ + --jq '.[0].databaseId // empty' +} + +fetch_run_conclusion() { + local repo="$1" + local run_id="$2" + gh run view "$run_id" --repo "$repo" --json conclusion --jq '.conclusion // empty' +} + +wait_for_success() { + local repo="$1" + local run_id="$2" + gh run watch "$run_id" --repo "$repo" + local conclusion + conclusion="$(fetch_run_conclusion "$repo" "$run_id")" + if [[ "$conclusion" != "success" ]]; then + echo "Run did not succeed: run_id=$run_id conclusion=$conclusion" >&2 + exit 1 + fi +} + +GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" +GH_REF="${GH_REF:-main}" +DISPATCH_EXECUTE="${DISPATCH_EXECUTE:-false}" +WAIT_FOR_COMPLETION="${WAIT_FOR_COMPLETION:-false}" + +STAGING_WORKFLOW="${STAGING_WORKFLOW:-contracts-staging-rehearsal.yml}" +MAINNET_WORKFLOW="${MAINNET_WORKFLOW:-contracts-mainnet-readiness.yml}" +PROMOTION_WORKFLOW="${PROMOTION_WORKFLOW:-contracts-promotion-checklist.yml}" + +STAGING_RPC_URL="${STAGING_RPC_URL:-}" +STAGING_SETTLEMENT_OPERATOR="${STAGING_SETTLEMENT_OPERATOR:-}" +STAGING_OWNER_ADDRESS="${STAGING_OWNER_ADDRESS:-}" +STAGING_BRIDGE_OPERATOR="${STAGING_BRIDGE_OPERATOR:-}" +STAGING_DESTINATION_CHAIN_ID="${STAGING_DESTINATION_CHAIN_ID:-10}" +STAGING_ATTESTER_ADDRESS="${STAGING_ATTESTER_ADDRESS:-0x0000000000000000000000000000000000000000}" +STAGING_RELEASE_ARTIFACT_PATH="${STAGING_RELEASE_ARTIFACT_PATH:-broadcast/mark-staging-release.json}" +STAGING_REHEARSAL_ARTIFACT_PATH="${STAGING_REHEARSAL_ARTIFACT_PATH:-broadcast/mark-staging-rehearsal.json}" + +MAINNET_RPC_URL="${MAINNET_RPC_URL:-}" +MAINNET_MODE="${MAINNET_MODE:-predeploy}" +MAINNET_ARTIFACT_PATH="${MAINNET_ARTIFACT_PATH:-broadcast/mark-mainnet-gate-ci.json}" + +PROMOTION_FRESHNESS_HOURS="${PROMOTION_FRESHNESS_HOURS:-72}" +PROMOTION_CHECKLIST_PATH="${PROMOTION_CHECKLIST_PATH:-broadcast/mark-promotion-checklist.json}" +PROMOTION_CHECKLIST_MARKDOWN_PATH="${PROMOTION_CHECKLIST_MARKDOWN_PATH:-broadcast/mark-promotion-checklist.md}" + +require_nonempty "STAGING_RPC_URL" "$STAGING_RPC_URL" +require_nonempty "STAGING_SETTLEMENT_OPERATOR" "$STAGING_SETTLEMENT_OPERATOR" +require_nonempty "MAINNET_RPC_URL" "$MAINNET_RPC_URL" + +STAGING_CMD=( + gh workflow run "$STAGING_WORKFLOW" + --repo "$GH_REPO" + --ref "$GH_REF" + -f "rpc_url=$STAGING_RPC_URL" + -f "owner_address=$STAGING_OWNER_ADDRESS" + -f "settlement_operator=$STAGING_SETTLEMENT_OPERATOR" + -f "bridge_operator=$STAGING_BRIDGE_OPERATOR" + -f "destination_chain_id=$STAGING_DESTINATION_CHAIN_ID" + -f "attester_address=$STAGING_ATTESTER_ADDRESS" + -f "release_artifact_path=$STAGING_RELEASE_ARTIFACT_PATH" + -f "rehearsal_artifact_path=$STAGING_REHEARSAL_ARTIFACT_PATH" +) + +MAINNET_CMD=( + gh workflow run "$MAINNET_WORKFLOW" + --repo "$GH_REPO" + --ref "$GH_REF" + -f "mode=$MAINNET_MODE" + -f "rpc_url=$MAINNET_RPC_URL" + -f "artifact_path=$MAINNET_ARTIFACT_PATH" +) + +echo "Dispatch target repo: $GH_REPO" +echo "Dispatch ref: $GH_REF" +echo "Staging workflow: $STAGING_WORKFLOW" +echo "Mainnet workflow: $MAINNET_WORKFLOW" +echo "Promotion workflow: $PROMOTION_WORKFLOW" +echo "wait_for_completion=$WAIT_FOR_COMPLETION" +echo "promotion_freshness_hours=$PROMOTION_FRESHNESS_HOURS" + +if [[ "$DISPATCH_EXECUTE" != "true" ]]; then + echo "Dry run only. Set DISPATCH_EXECUTE=true to run:" + printf ' %q' "${STAGING_CMD[@]}" + printf '\n' + printf ' %q' "${MAINNET_CMD[@]}" + printf '\n' + if [[ "$WAIT_FOR_COMPLETION" == "true" ]]; then + echo " # promotion checklist auto-dispatches after both runs succeed" + else + echo " # then dispatch promotion checklist manually with run IDs" + fi + exit 0 +fi + +require_secret "$GH_REPO" "MARK_STAGING_DEPLOYER_PRIVATE_KEY" +require_secret "$GH_REPO" "MARK_DEPLOYER_PRIVATE_KEY" + +"${STAGING_CMD[@]}" +"${MAINNET_CMD[@]}" + +echo "Staging + mainnet workflows dispatched." + +if [[ "$WAIT_FOR_COMPLETION" != "true" ]]; then + echo "Wait disabled. After both runs succeed, dispatch promotion checklist with:" + echo " gh workflow run $PROMOTION_WORKFLOW --repo $GH_REPO --ref $GH_REF -f staging_run_id= -f mainnet_run_id= -f freshness_hours=$PROMOTION_FRESHNESS_HOURS" + exit 0 +fi + +# Allow GH API indexing time so latest run lookup is stable. +sleep 5 + +staging_run_id="$(fetch_latest_run_id_for_ref "$GH_REPO" "$STAGING_WORKFLOW" "$GH_REF")" +mainnet_run_id="$(fetch_latest_run_id_for_ref "$GH_REPO" "$MAINNET_WORKFLOW" "$GH_REF")" + +require_nonempty "staging_run_id" "$staging_run_id" +require_nonempty "mainnet_run_id" "$mainnet_run_id" + +echo "Watching staging run: $staging_run_id" +wait_for_success "$GH_REPO" "$staging_run_id" + +echo "Watching mainnet run: $mainnet_run_id" +wait_for_success "$GH_REPO" "$mainnet_run_id" + +gh workflow run "$PROMOTION_WORKFLOW" \ + --repo "$GH_REPO" \ + --ref "$GH_REF" \ + -f "staging_run_id=$staging_run_id" \ + -f "mainnet_run_id=$mainnet_run_id" \ + -f "checklist_path=$PROMOTION_CHECKLIST_PATH" \ + -f "checklist_markdown_path=$PROMOTION_CHECKLIST_MARKDOWN_PATH" \ + -f "freshness_hours=$PROMOTION_FRESHNESS_HOURS" + +echo "Promotion checklist workflow dispatched." +echo "Inspect runs with:" +echo " gh run list --repo $GH_REPO --workflow \"$PROMOTION_WORKFLOW\" --limit 5" From a3b2e318b1363082d2105808cd5871c149a5aad8 Mon Sep 17 00:00:00 2001 From: Iko Date: Mon, 27 Apr 2026 01:24:21 +0700 Subject: [PATCH 03/38] ops: add release secret bootstrap helper --- contracts/Makefile | 5 +- contracts/README.md | 9 +++ contracts/RUNBOOK.md | 8 +++ .../script/ops/bootstrap-release-secrets.sh | 65 +++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100755 contracts/script/ops/bootstrap-release-secrets.sh diff --git a/contracts/Makefile b/contracts/Makefile index 5c9f8c3..dc75157 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -1,4 +1,4 @@ -.PHONY: smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature +.PHONY: smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature smoke-production-mode: @./script/ops/smoke-production-mode.sh @@ -17,6 +17,9 @@ dispatch-production-lock-verify: dispatch-release-evidence-sequence: @./script/ops/dispatch-release-evidence-sequence.sh +bootstrap-release-secrets: + @./script/ops/bootstrap-release-secrets.sh + rehearse-production-lock: @./script/ops/rehearse-production-lock.sh diff --git a/contracts/README.md b/contracts/README.md index 7e3e9db..e251c9b 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -241,6 +241,15 @@ Required GitHub secrets for this sequence: - `MARK_STAGING_DEPLOYER_PRIVATE_KEY` - `MARK_DEPLOYER_PRIVATE_KEY` +Bootstrap those secrets from local env values (dry-run by default): + +```bash +MARK_STAGING_DEPLOYER_PRIVATE_KEY=<0x_staging_pk> \ +MARK_DEPLOYER_PRIVATE_KEY=<0x_mainnet_pk> \ +DISPATCH_EXECUTE=true \ +make bootstrap-release-secrets +``` + Staging rehearsal (release + production-lock verify): ```bash diff --git a/contracts/RUNBOOK.md b/contracts/RUNBOOK.md index 114011b..daa090b 100644 --- a/contracts/RUNBOOK.md +++ b/contracts/RUNBOOK.md @@ -137,6 +137,14 @@ Required repository secrets: - `MARK_STAGING_DEPLOYER_PRIVATE_KEY` - `MARK_DEPLOYER_PRIVATE_KEY` +Optional bootstrap helper (from `contracts/`): +```bash +MARK_STAGING_DEPLOYER_PRIVATE_KEY=<0x_staging_pk> \ +MARK_DEPLOYER_PRIVATE_KEY=<0x_mainnet_pk> \ +DISPATCH_EXECUTE=true \ +make bootstrap-release-secrets +``` + ### Evidence Manifest (Hash Integrity Baseline) Workflow: diff --git a/contracts/script/ops/bootstrap-release-secrets.sh b/contracts/script/ops/bootstrap-release-secrets.sh new file mode 100755 index 0000000..030f4b0 --- /dev/null +++ b/contracts/script/ops/bootstrap-release-secrets.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd gh +require_cmd git + +infer_repo_from_remote() { + local remote + remote="$(git remote get-url origin)" + if [[ "$remote" =~ ^git@github.com:([^/]+/[^.]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + if [[ "$remote" =~ ^https://github.com/([^/]+/[^.]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + echo "Could not infer GH_REPO from origin: $remote" >&2 + exit 1 +} + +require_nonempty() { + local label="$1" + local value="$2" + if [[ -z "$value" ]]; then + echo "Missing required value: $label" >&2 + exit 1 + fi +} + +GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" +DISPATCH_EXECUTE="${DISPATCH_EXECUTE:-false}" + +STAGING_PK="${MARK_STAGING_DEPLOYER_PRIVATE_KEY:-${STAGING_DEPLOYER_PRIVATE_KEY:-}}" +MAINNET_PK="${MARK_DEPLOYER_PRIVATE_KEY:-${MAINNET_DEPLOYER_PRIVATE_KEY:-}}" + +echo "Target repo: $GH_REPO" +echo "Will set secrets:" +echo " - MARK_STAGING_DEPLOYER_PRIVATE_KEY" +echo " - MARK_DEPLOYER_PRIVATE_KEY" + +if [[ "$DISPATCH_EXECUTE" != "true" ]]; then + echo "Dry run only." + echo "Set env vars then run with DISPATCH_EXECUTE=true:" + echo " MARK_STAGING_DEPLOYER_PRIVATE_KEY=<0x...> MARK_DEPLOYER_PRIVATE_KEY=<0x...> DISPATCH_EXECUTE=true make bootstrap-release-secrets" + exit 0 +fi + +require_nonempty "MARK_STAGING_DEPLOYER_PRIVATE_KEY (or STAGING_DEPLOYER_PRIVATE_KEY)" "$STAGING_PK" +require_nonempty "MARK_DEPLOYER_PRIVATE_KEY (or MAINNET_DEPLOYER_PRIVATE_KEY)" "$MAINNET_PK" + +printf "%s" "$STAGING_PK" | gh secret set MARK_STAGING_DEPLOYER_PRIVATE_KEY --repo "$GH_REPO" --body - +printf "%s" "$MAINNET_PK" | gh secret set MARK_DEPLOYER_PRIVATE_KEY --repo "$GH_REPO" --body - + +echo "Secrets updated successfully for $GH_REPO." From f77534ded9ca7e3a7c78fe2c23bb4dc5c0c31e32 Mon Sep 17 00:00:00 2001 From: Iko Date: Mon, 27 Apr 2026 02:02:27 +0700 Subject: [PATCH 04/38] ops: harden release dispatch run correlation and strict env checks --- .github/workflows/contracts-env-guard.yml | 5 +- contracts/README.md | 4 + contracts/RUNBOOK.md | 4 + .../ops/dispatch-production-lock-verify.sh | 113 +++++++++++ .../ops/dispatch-release-evidence-sequence.sh | 65 +++++- contracts/script/ops/validate-prod-env.sh | 185 ++++++++++++++++++ .../script/ops/verify-production-lock.sh | 151 ++++++++++++++ 7 files changed, 521 insertions(+), 6 deletions(-) create mode 100755 contracts/script/ops/dispatch-production-lock-verify.sh create mode 100755 contracts/script/ops/validate-prod-env.sh create mode 100755 contracts/script/ops/verify-production-lock.sh diff --git a/.github/workflows/contracts-env-guard.yml b/.github/workflows/contracts-env-guard.yml index 055b970..7142811 100644 --- a/.github/workflows/contracts-env-guard.yml +++ b/.github/workflows/contracts-env-guard.yml @@ -30,10 +30,7 @@ jobs: - name: Bash syntax checks for ops scripts run: | - bash -n script/ops/validate-prod-env.sh - bash -n script/ops/rehearse-production-lock.sh - bash -n script/ops/dispatch-production-lock-verify.sh - bash -n script/ops/verify-production-lock.sh + find script/ops -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n - name: Validate rehearsal env profile run: | diff --git a/contracts/README.md b/contracts/README.md index e251c9b..ed8489a 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -237,6 +237,10 @@ WAIT_FOR_COMPLETION=true \ make dispatch-release-evidence-sequence ``` +Notes: +- dispatcher resolves run IDs using `workflow_dispatch` + actor + branch + dispatch timestamp filters (safer under concurrent runs) +- production dispatch/verify paths enforce `MARK_ENV_STRICT_PLACEHOLDERS=true` to block known placeholder addresses + Required GitHub secrets for this sequence: - `MARK_STAGING_DEPLOYER_PRIVATE_KEY` - `MARK_DEPLOYER_PRIVATE_KEY` diff --git a/contracts/RUNBOOK.md b/contracts/RUNBOOK.md index daa090b..c248467 100644 --- a/contracts/RUNBOOK.md +++ b/contracts/RUNBOOK.md @@ -133,6 +133,10 @@ This will: 3. Wait for both to succeed. 4. Dispatch `contracts-promotion-checklist.yml` with explicit run IDs. +Safety behavior: +- run correlation uses dispatch timestamp + actor + branch filtering (reduces cross-run mismatch risk under concurrent operators) +- production dispatch/verify env validation rejects known placeholder addresses + Required repository secrets: - `MARK_STAGING_DEPLOYER_PRIVATE_KEY` - `MARK_DEPLOYER_PRIVATE_KEY` diff --git a/contracts/script/ops/dispatch-production-lock-verify.sh b/contracts/script/ops/dispatch-production-lock-verify.sh new file mode 100755 index 0000000..0788ec1 --- /dev/null +++ b/contracts/script/ops/dispatch-production-lock-verify.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd gh +require_cmd jq +require_cmd git + +infer_repo_from_remote() { + local remote + remote="$(git remote get-url origin)" + if [[ "$remote" =~ ^git@github.com:([^/]+/[^.]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + if [[ "$remote" =~ ^https://github.com/([^/]+/[^.]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + echo "Could not infer GH_REPO from origin: $remote" >&2 + exit 1 +} + +require_nonempty() { + local label="$1" + local value="$2" + if [[ -z "$value" ]]; then + echo "Missing required value: $label" >&2 + exit 1 + fi +} + +ARTIFACT_PATH="${MARK_RELEASE_ARTIFACT_PATH:-broadcast/mark-release-latest.json}" +WORKFLOW_FILE="${WORKFLOW_FILE:-contracts-production-lock-verify.yml}" +GH_REF="${GH_REF:-main}" +GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" +DISPATCH_EXECUTE="${DISPATCH_EXECUTE:-false}" + +RPC_URL="${RPC_URL:-}" +TOKEN_ADDRESS="${MARK_RYLA_TOKEN:-${VERIFY_MARK_RYLA_TOKEN:-}}" +MODULE_ADDRESS="${MARK_SETTLEMENT_MODULE:-${VERIFY_MARK_SETTLEMENT_MODULE:-}}" +VERIFIER_ADDRESS="${MARK_SETTLEMENT_VERIFIER:-${VERIFY_MARK_SETTLEMENT_VERIFIER:-}}" +OWNER_ADDRESS="${MARK_RYLA_OWNER:-${VERIFY_MARK_RYLA_OWNER:-}}" +SETTLEMENT_OPERATOR="${MARK_SETTLEMENT_OPERATOR:-${VERIFY_MARK_SETTLEMENT_OPERATOR:-}}" +ATTESTER_ADDRESS="${MARK_SETTLEMENT_ATTESTER:-${VERIFY_MARK_SETTLEMENT_ATTESTER:-0x0000000000000000000000000000000000000000}}" + +if [[ -f "$ARTIFACT_PATH" ]]; then + if [[ -z "$TOKEN_ADDRESS" ]]; then TOKEN_ADDRESS="$(jq -r '.token // empty' "$ARTIFACT_PATH")"; fi + if [[ -z "$MODULE_ADDRESS" ]]; then MODULE_ADDRESS="$(jq -r '.module // empty' "$ARTIFACT_PATH")"; fi + if [[ -z "$VERIFIER_ADDRESS" ]]; then VERIFIER_ADDRESS="$(jq -r '.verifier // empty' "$ARTIFACT_PATH")"; fi +fi + +require_nonempty "RPC_URL" "$RPC_URL" +require_nonempty "token_address" "$TOKEN_ADDRESS" +require_nonempty "module_address" "$MODULE_ADDRESS" +require_nonempty "verifier_address" "$VERIFIER_ADDRESS" +require_nonempty "owner_address" "$OWNER_ADDRESS" +require_nonempty "settlement_operator" "$SETTLEMENT_OPERATOR" + +export MARK_RYLA_TOKEN="$TOKEN_ADDRESS" +export MARK_SETTLEMENT_MODULE="$MODULE_ADDRESS" +export MARK_SETTLEMENT_VERIFIER="$VERIFIER_ADDRESS" +export MARK_RYLA_OWNER="$OWNER_ADDRESS" +export MARK_SETTLEMENT_OPERATOR +export MARK_SETTLEMENT_ATTESTER="$ATTESTER_ADDRESS" +export MARK_ENV_STRICT_PLACEHOLDERS=true +VALIDATE_MODE=dispatch ./script/ops/validate-prod-env.sh + +CMD=( + gh workflow run "$WORKFLOW_FILE" + --repo "$GH_REPO" + --ref "$GH_REF" + -f "rpc_url=$RPC_URL" + -f "token_address=$TOKEN_ADDRESS" + -f "module_address=$MODULE_ADDRESS" + -f "verifier_address=$VERIFIER_ADDRESS" + -f "owner_address=$OWNER_ADDRESS" + -f "settlement_operator=$SETTLEMENT_OPERATOR" + -f "attester_address=$ATTESTER_ADDRESS" +) + +echo "Dispatch target repo: $GH_REPO" +echo "Workflow: $WORKFLOW_FILE" +echo "Ref: $GH_REF" +echo "rpc_url=$RPC_URL" +echo "token_address=$TOKEN_ADDRESS" +echo "module_address=$MODULE_ADDRESS" +echo "verifier_address=$VERIFIER_ADDRESS" +echo "owner_address=$OWNER_ADDRESS" +echo "settlement_operator=$SETTLEMENT_OPERATOR" +echo "attester_address=$ATTESTER_ADDRESS" + +if [[ "$DISPATCH_EXECUTE" != "true" ]]; then + echo "Dry run only. Set DISPATCH_EXECUTE=true to run:" + printf ' %q' "${CMD[@]}" + printf '\n' + exit 0 +fi + +"${CMD[@]}" + +echo "Workflow dispatched." +echo "Inspect runs with:" +echo " gh run list --repo $GH_REPO --workflow \"$WORKFLOW_FILE\" --limit 5" diff --git a/contracts/script/ops/dispatch-release-evidence-sequence.sh b/contracts/script/ops/dispatch-release-evidence-sequence.sh index 5e7a471..1c021f2 100755 --- a/contracts/script/ops/dispatch-release-evidence-sequence.sh +++ b/contracts/script/ops/dispatch-release-evidence-sequence.sh @@ -61,6 +61,28 @@ fetch_latest_run_id_for_ref() { --jq '.[0].databaseId // empty' } +fetch_dispatched_run_id_for_ref() { + local repo="$1" + local workflow="$2" + local ref="$3" + local started_at_epoch="$4" + local actor="$5" + gh run list \ + --repo "$repo" \ + --workflow "$workflow" \ + --branch "$ref" \ + --limit 30 \ + --json databaseId,createdAt,event,headBranch,actor \ + --jq \ + "map(select(.event == \"workflow_dispatch\" + and .headBranch == \"$ref\" + and (.actor.login // \"\") == \"$actor\" + and ((.createdAt | fromdateiso8601) >= $started_at_epoch))) + | sort_by(.createdAt) + | last + | .databaseId // empty" +} + fetch_run_conclusion() { local repo="$1" local run_id="$2" @@ -83,6 +105,7 @@ GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" GH_REF="${GH_REF:-main}" DISPATCH_EXECUTE="${DISPATCH_EXECUTE:-false}" WAIT_FOR_COMPLETION="${WAIT_FOR_COMPLETION:-false}" +DISPATCH_DETECT_TIMEOUT_SECONDS="${DISPATCH_DETECT_TIMEOUT_SECONDS:-180}" STAGING_WORKFLOW="${STAGING_WORKFLOW:-contracts-staging-rehearsal.yml}" MAINNET_WORKFLOW="${MAINNET_WORKFLOW:-contracts-mainnet-readiness.yml}" @@ -105,10 +128,16 @@ PROMOTION_FRESHNESS_HOURS="${PROMOTION_FRESHNESS_HOURS:-72}" PROMOTION_CHECKLIST_PATH="${PROMOTION_CHECKLIST_PATH:-broadcast/mark-promotion-checklist.json}" PROMOTION_CHECKLIST_MARKDOWN_PATH="${PROMOTION_CHECKLIST_MARKDOWN_PATH:-broadcast/mark-promotion-checklist.md}" +DISPATCH_START_EPOCH="$(date -u +%s)" +CURRENT_ACTOR_LOGIN="$(gh api user --jq '.login')" +require_nonempty "CURRENT_ACTOR_LOGIN" "$CURRENT_ACTOR_LOGIN" + require_nonempty "STAGING_RPC_URL" "$STAGING_RPC_URL" require_nonempty "STAGING_SETTLEMENT_OPERATOR" "$STAGING_SETTLEMENT_OPERATOR" require_nonempty "MAINNET_RPC_URL" "$MAINNET_RPC_URL" +export MARK_ENV_STRICT_PLACEHOLDERS=true + STAGING_CMD=( gh workflow run "$STAGING_WORKFLOW" --repo "$GH_REPO" @@ -171,8 +200,40 @@ fi # Allow GH API indexing time so latest run lookup is stable. sleep 5 -staging_run_id="$(fetch_latest_run_id_for_ref "$GH_REPO" "$STAGING_WORKFLOW" "$GH_REF")" -mainnet_run_id="$(fetch_latest_run_id_for_ref "$GH_REPO" "$MAINNET_WORKFLOW" "$GH_REF")" +staging_run_id="" +mainnet_run_id="" +deadline_epoch=$((DISPATCH_START_EPOCH + DISPATCH_DETECT_TIMEOUT_SECONDS)) +while [[ -z "$staging_run_id" || -z "$mainnet_run_id" ]]; do + now_epoch="$(date -u +%s)" + if (( now_epoch > deadline_epoch )); then + echo "Timed out resolving dispatched run IDs for actor=$CURRENT_ACTOR_LOGIN ref=$GH_REF" >&2 + echo "Provide explicit run IDs manually for promotion checklist dispatch." >&2 + exit 1 + fi + if [[ -z "$staging_run_id" ]]; then + staging_run_id="$( + fetch_dispatched_run_id_for_ref \ + "$GH_REPO" \ + "$STAGING_WORKFLOW" \ + "$GH_REF" \ + "$DISPATCH_START_EPOCH" \ + "$CURRENT_ACTOR_LOGIN" + )" + fi + if [[ -z "$mainnet_run_id" ]]; then + mainnet_run_id="$( + fetch_dispatched_run_id_for_ref \ + "$GH_REPO" \ + "$MAINNET_WORKFLOW" \ + "$GH_REF" \ + "$DISPATCH_START_EPOCH" \ + "$CURRENT_ACTOR_LOGIN" + )" + fi + if [[ -z "$staging_run_id" || -z "$mainnet_run_id" ]]; then + sleep 5 + fi +done require_nonempty "staging_run_id" "$staging_run_id" require_nonempty "mainnet_run_id" "$mainnet_run_id" diff --git a/contracts/script/ops/validate-prod-env.sh b/contracts/script/ops/validate-prod-env.sh new file mode 100755 index 0000000..29e70d3 --- /dev/null +++ b/contracts/script/ops/validate-prod-env.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env bash +set -euo pipefail + +MODE="${VALIDATE_MODE:-}" +STRICT_PLACEHOLDERS="${MARK_ENV_STRICT_PLACEHOLDERS:-false}" + +if [[ -z "$MODE" ]]; then + echo "VALIDATE_MODE is required (rehearsal | dispatch | verify-lock)" >&2 + exit 1 +fi + +require_env() { + local key="$1" + if [[ -z "${!key:-}" ]]; then + echo "Missing required env var: $key" >&2 + exit 1 + fi +} + +is_address() { + local value="$1" + [[ "$value" =~ ^0x[0-9a-fA-F]{40}$ ]] +} + +is_zero_address() { + local value="$1" + [[ "${value,,}" == "0x0000000000000000000000000000000000000000" ]] +} + +require_address() { + local key="$1" + local value="${!key:-}" + require_env "$key" + if ! is_address "$value"; then + echo "Invalid address format for $key: $value" >&2 + exit 1 + fi +} + +is_strict_mode() { + [[ "$STRICT_PLACEHOLDERS" == "true" || "$STRICT_PLACEHOLDERS" == "1" ]] +} + +is_known_placeholder_address() { + local value="${1,,}" + case "$value" in + 0x1111111111111111111111111111111111111111|\ + 0x2222222222222222222222222222222222222222|\ + 0x3333333333333333333333333333333333333333|\ + 0x4444444444444444444444444444444444444444|\ + 0x5555555555555555555555555555555555555555) + return 0 + ;; + *) + return 1 + ;; + esac +} + +reject_known_placeholder_if_strict() { + local key="$1" + local value="$2" + if is_strict_mode && is_known_placeholder_address "$value"; then + echo "Placeholder address is not allowed for $key when MARK_ENV_STRICT_PLACEHOLDERS=true: $value" >&2 + exit 1 + fi +} + +require_rpc_url() { + require_env RPC_URL + if [[ ! "$RPC_URL" =~ ^(https?|wss?):// ]]; then + echo "RPC_URL must start with http://, https://, ws://, or wss://" >&2 + exit 1 + fi +} + +assert_true_like() { + local key="$1" + local value="${!key:-}" + if [[ "$value" != "true" && "$value" != "1" ]]; then + echo "$key must be true/1 for this mode (got: ${value:-})" >&2 + exit 1 + fi +} + +validate_common_optional_addresses() { + local key + for key in "$@"; do + if [[ -n "${!key:-}" ]] && ! is_address "${!key}"; then + echo "Invalid address format for $key: ${!key}" >&2 + exit 1 + fi + done +} + +validate_rehearsal() { + require_rpc_url + require_env PRIVATE_KEY + require_address MARK_SETTLEMENT_OPERATOR + + validate_common_optional_addresses \ + MARK_RYLA_OWNER \ + MARK_MODULE_OWNER \ + MARK_BRIDGE_OPERATOR \ + MARK_SETTLEMENT_ATTESTER \ + MARK_SETTLEMENT_VERIFIER + + assert_true_like MARK_SETTLEMENT_PROOF_ENABLED + assert_true_like MARK_SETTLEMENT_PRODUCTION_MODE + assert_true_like MARK_RELEASE_EXECUTE + + local verifier="${MARK_SETTLEMENT_VERIFIER:-0x0000000000000000000000000000000000000000}" + local deploy_attested="${MARK_DEPLOY_ATTESTED_VERIFIER:-false}" + if is_zero_address "$verifier" && [[ "$deploy_attested" != "true" && "$deploy_attested" != "1" ]]; then + echo "Production mode requires verifier or MARK_DEPLOY_ATTESTED_VERIFIER=true" >&2 + exit 1 + fi +} + +validate_dispatch() { + require_rpc_url + require_address MARK_RYLA_TOKEN + require_address MARK_SETTLEMENT_MODULE + require_address MARK_SETTLEMENT_VERIFIER + require_address MARK_RYLA_OWNER + require_address MARK_SETTLEMENT_OPERATOR + reject_known_placeholder_if_strict "MARK_RYLA_TOKEN" "$MARK_RYLA_TOKEN" + reject_known_placeholder_if_strict "MARK_SETTLEMENT_MODULE" "$MARK_SETTLEMENT_MODULE" + reject_known_placeholder_if_strict "MARK_SETTLEMENT_VERIFIER" "$MARK_SETTLEMENT_VERIFIER" + reject_known_placeholder_if_strict "MARK_RYLA_OWNER" "$MARK_RYLA_OWNER" + reject_known_placeholder_if_strict "MARK_SETTLEMENT_OPERATOR" "$MARK_SETTLEMENT_OPERATOR" + + if [[ -n "${MARK_SETTLEMENT_ATTESTER:-}" ]] && ! is_address "${MARK_SETTLEMENT_ATTESTER}"; then + echo "Invalid address format for MARK_SETTLEMENT_ATTESTER: ${MARK_SETTLEMENT_ATTESTER}" >&2 + exit 1 + fi + if [[ -n "${MARK_SETTLEMENT_ATTESTER:-}" ]]; then + reject_known_placeholder_if_strict "MARK_SETTLEMENT_ATTESTER" "$MARK_SETTLEMENT_ATTESTER" + fi + + if is_zero_address "$MARK_RYLA_TOKEN" || is_zero_address "$MARK_SETTLEMENT_MODULE" || is_zero_address "$MARK_SETTLEMENT_VERIFIER"; then + echo "Dispatch mode does not allow zero token/module/verifier addresses" >&2 + exit 1 + fi +} + +validate_verify_lock() { + require_rpc_url + require_address MARK_RYLA_TOKEN + require_address MARK_SETTLEMENT_MODULE + require_address MARK_SETTLEMENT_VERIFIER + require_address MARK_RYLA_OWNER + require_address MARK_SETTLEMENT_OPERATOR + reject_known_placeholder_if_strict "MARK_RYLA_TOKEN" "$MARK_RYLA_TOKEN" + reject_known_placeholder_if_strict "MARK_SETTLEMENT_MODULE" "$MARK_SETTLEMENT_MODULE" + reject_known_placeholder_if_strict "MARK_SETTLEMENT_VERIFIER" "$MARK_SETTLEMENT_VERIFIER" + reject_known_placeholder_if_strict "MARK_RYLA_OWNER" "$MARK_RYLA_OWNER" + reject_known_placeholder_if_strict "MARK_SETTLEMENT_OPERATOR" "$MARK_SETTLEMENT_OPERATOR" + + if [[ -n "${MARK_SETTLEMENT_ATTESTER:-}" ]] && ! is_address "${MARK_SETTLEMENT_ATTESTER}"; then + echo "Invalid address format for MARK_SETTLEMENT_ATTESTER: ${MARK_SETTLEMENT_ATTESTER}" >&2 + exit 1 + fi + if [[ -n "${MARK_SETTLEMENT_ATTESTER:-}" ]]; then + reject_known_placeholder_if_strict "MARK_SETTLEMENT_ATTESTER" "$MARK_SETTLEMENT_ATTESTER" + fi +} + +case "$MODE" in + rehearsal) + validate_rehearsal + ;; + dispatch) + validate_dispatch + ;; + verify-lock) + validate_verify_lock + ;; + *) + echo "Unsupported VALIDATE_MODE: $MODE (expected rehearsal | dispatch | verify-lock)" >&2 + exit 1 + ;; +esac + +echo "Env validation PASSED ($MODE)" diff --git a/contracts/script/ops/verify-production-lock.sh b/contracts/script/ops/verify-production-lock.sh new file mode 100755 index 0000000..62b3b58 --- /dev/null +++ b/contracts/script/ops/verify-production-lock.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd cast +require_cmd jq + +require_env() { + local key="$1" + if [[ -z "${!key:-}" ]]; then + echo "Missing required env var: $key" >&2 + exit 1 + fi +} + +norm_addr() { + echo "$1" | tr '[:upper:]' '[:lower:]' +} + +assert_eq() { + local got="$1" + local expected="$2" + local msg="$3" + if [[ "$got" != "$expected" ]]; then + echo "ASSERT FAILED: $msg (got=$got expected=$expected)" >&2 + exit 1 + fi +} + +assert_true() { + local got="$1" + local msg="$2" + if [[ "$got" != "true" ]]; then + echo "ASSERT FAILED: $msg (got=$got expected=true)" >&2 + exit 1 + fi +} + +assert_contract() { + local addr="$1" + local label="$2" + local code + code="$(cast code "$addr" --rpc-url "$RPC_URL")" + if [[ "$code" == "0x" ]]; then + echo "ASSERT FAILED: $label has no code at $addr" >&2 + exit 1 + fi +} + +has_role() { + local contract_addr="$1" + local role="$2" + local account="$3" + cast call "$contract_addr" "hasRole(bytes32,address)(bool)" "$role" "$account" --rpc-url "$RPC_URL" +} + +RPC_URL="${RPC_URL:-}" +ARTIFACT_PATH="${MARK_RELEASE_ARTIFACT_PATH:-}" + +require_env RPC_URL + +TOKEN_ADDRESS="${MARK_RYLA_TOKEN:-${VERIFY_MARK_RYLA_TOKEN:-}}" +MODULE_ADDRESS="${MARK_SETTLEMENT_MODULE:-${VERIFY_MARK_SETTLEMENT_MODULE:-}}" +VERIFIER_ADDRESS="${MARK_SETTLEMENT_VERIFIER:-${VERIFY_MARK_SETTLEMENT_VERIFIER:-}}" + +if [[ -n "$ARTIFACT_PATH" && -f "$ARTIFACT_PATH" ]]; then + if [[ -z "$TOKEN_ADDRESS" ]]; then + TOKEN_ADDRESS="$(jq -r '.token' "$ARTIFACT_PATH")" + fi + if [[ -z "$MODULE_ADDRESS" ]]; then + MODULE_ADDRESS="$(jq -r '.module' "$ARTIFACT_PATH")" + fi + if [[ -z "$VERIFIER_ADDRESS" ]]; then + VERIFIER_ADDRESS="$(jq -r '.verifier' "$ARTIFACT_PATH")" + fi +fi + +EXPECTED_OWNER="${VERIFY_MARK_RYLA_OWNER:-${MARK_RYLA_OWNER:-}}" +EXPECTED_SETTLEMENT_OPERATOR="${VERIFY_MARK_SETTLEMENT_OPERATOR:-${MARK_SETTLEMENT_OPERATOR:-}}" +EXPECTED_VERIFIER="${VERIFY_MARK_SETTLEMENT_VERIFIER:-${MARK_SETTLEMENT_VERIFIER:-$VERIFIER_ADDRESS}}" +EXPECTED_ATTESTER="${VERIFY_MARK_SETTLEMENT_ATTESTER:-${MARK_SETTLEMENT_ATTESTER:-}}" + +require_env TOKEN_ADDRESS +require_env MODULE_ADDRESS +require_env VERIFIER_ADDRESS +require_env EXPECTED_OWNER +require_env EXPECTED_SETTLEMENT_OPERATOR +require_env EXPECTED_VERIFIER + +export MARK_RYLA_TOKEN="$TOKEN_ADDRESS" +export MARK_SETTLEMENT_MODULE="$MODULE_ADDRESS" +export MARK_SETTLEMENT_VERIFIER="$VERIFIER_ADDRESS" +export MARK_RYLA_OWNER="$EXPECTED_OWNER" +export MARK_SETTLEMENT_OPERATOR="$EXPECTED_SETTLEMENT_OPERATOR" +export MARK_SETTLEMENT_ATTESTER="${EXPECTED_ATTESTER:-0x0000000000000000000000000000000000000000}" +export MARK_ENV_STRICT_PLACEHOLDERS=true +VALIDATE_MODE=verify-lock ./script/ops/validate-prod-env.sh + +DEFAULT_ADMIN_ROLE="0x0000000000000000000000000000000000000000000000000000000000000000" + +assert_contract "$TOKEN_ADDRESS" "RYLA token" +assert_contract "$MODULE_ADDRESS" "Settlement module" +assert_contract "$VERIFIER_ADDRESS" "Settlement verifier" + +PROOF_ENABLED="$(cast call "$MODULE_ADDRESS" "proofValidationEnabled()(bool)" --rpc-url "$RPC_URL")" +PRODUCTION_MODE="$(cast call "$MODULE_ADDRESS" "productionMode()(bool)" --rpc-url "$RPC_URL")" +MODULE_VERIFIER="$(cast call "$MODULE_ADDRESS" "verifier()(address)" --rpc-url "$RPC_URL")" +MODULE_OPERATOR_ROLE="$(cast call "$MODULE_ADDRESS" "OPERATOR_ROLE()(bytes32)" --rpc-url "$RPC_URL")" +TOKEN_MINTER_ROLE="$(cast call "$TOKEN_ADDRESS" "MINTER_ROLE()(bytes32)" --rpc-url "$RPC_URL")" +TOKEN_BURNER_ROLE="$(cast call "$TOKEN_ADDRESS" "BURNER_ROLE()(bytes32)" --rpc-url "$RPC_URL")" + +assert_true "$PROOF_ENABLED" "module proofValidationEnabled must be true" +assert_true "$PRODUCTION_MODE" "module productionMode must be true" +assert_eq "$(norm_addr "$MODULE_VERIFIER")" "$(norm_addr "$EXPECTED_VERIFIER")" "module verifier mismatch" +assert_eq "$(norm_addr "$VERIFIER_ADDRESS")" "$(norm_addr "$EXPECTED_VERIFIER")" "configured verifier mismatch" + +TOKEN_OWNER_ADMIN="$(has_role "$TOKEN_ADDRESS" "$DEFAULT_ADMIN_ROLE" "$EXPECTED_OWNER")" +MODULE_OWNER_ADMIN="$(has_role "$MODULE_ADDRESS" "$DEFAULT_ADMIN_ROLE" "$EXPECTED_OWNER")" +MODULE_OPERATOR_SET="$(has_role "$MODULE_ADDRESS" "$MODULE_OPERATOR_ROLE" "$EXPECTED_SETTLEMENT_OPERATOR")" +TOKEN_MINTER_SET="$(has_role "$TOKEN_ADDRESS" "$TOKEN_MINTER_ROLE" "$MODULE_ADDRESS")" +TOKEN_BURNER_SET="$(has_role "$TOKEN_ADDRESS" "$TOKEN_BURNER_ROLE" "$MODULE_ADDRESS")" +VERIFIER_OWNER_ADMIN="$(has_role "$VERIFIER_ADDRESS" "$DEFAULT_ADMIN_ROLE" "$EXPECTED_OWNER")" + +assert_true "$TOKEN_OWNER_ADMIN" "owner must be token default admin" +assert_true "$MODULE_OWNER_ADMIN" "owner must be module default admin" +assert_true "$MODULE_OPERATOR_SET" "expected settlement operator role missing on module" +assert_true "$TOKEN_MINTER_SET" "module must have token minter role" +assert_true "$TOKEN_BURNER_SET" "module must have token burner role" +assert_true "$VERIFIER_OWNER_ADMIN" "owner must be verifier default admin" + +if [[ -n "$EXPECTED_ATTESTER" && "$EXPECTED_ATTESTER" != "0x0000000000000000000000000000000000000000" ]]; then + ATTESTER_ROLE="$(cast call "$VERIFIER_ADDRESS" "ATTESTER_ROLE()(bytes32)" --rpc-url "$RPC_URL")" + VERIFIER_ATTESTER_SET="$(has_role "$VERIFIER_ADDRESS" "$ATTESTER_ROLE" "$EXPECTED_ATTESTER")" + assert_true "$VERIFIER_ATTESTER_SET" "expected attester role missing on verifier" +fi + +echo "Production lock verification PASSED" +echo "Token: $TOKEN_ADDRESS" +echo "Module: $MODULE_ADDRESS" +echo "Verifier: $VERIFIER_ADDRESS" +echo "Owner(admin): $EXPECTED_OWNER" +echo "Settlement operator: $EXPECTED_SETTLEMENT_OPERATOR" From 7909c5835c332059631bad417bb9337d070d0f4b Mon Sep 17 00:00:00 2001 From: Iko Date: Mon, 27 Apr 2026 02:15:52 +0700 Subject: [PATCH 05/38] refactor: centralize core contract custom errors --- contracts/src/errors/BridgeErrors.sol | 11 ++ contracts/src/errors/SettlementErrors.sol | 13 ++ contracts/src/errors/TokenErrors.sol | 7 + contracts/src/protocol/MARKBridgeAdapter.sol | 116 ++++++++++++ .../src/protocol/MARKSettlementModule.sol | 116 ++++++++++++ contracts/src/token/RYLA.sol | 76 ++++++++ contracts/test/e2e/MARKSettlementE2E.t.sol | 154 ++++++++++++++++ contracts/test/unit/MARKBridgeAdapter.t.sol | 112 ++++++++++++ .../test/unit/MARKSettlementModule.t.sol | 167 ++++++++++++++++++ 9 files changed, 772 insertions(+) create mode 100644 contracts/src/errors/BridgeErrors.sol create mode 100644 contracts/src/errors/SettlementErrors.sol create mode 100644 contracts/src/errors/TokenErrors.sol create mode 100644 contracts/src/protocol/MARKBridgeAdapter.sol create mode 100644 contracts/src/protocol/MARKSettlementModule.sol create mode 100644 contracts/src/token/RYLA.sol create mode 100644 contracts/test/e2e/MARKSettlementE2E.t.sol create mode 100644 contracts/test/unit/MARKBridgeAdapter.t.sol create mode 100644 contracts/test/unit/MARKSettlementModule.t.sol diff --git a/contracts/src/errors/BridgeErrors.sol b/contracts/src/errors/BridgeErrors.sol new file mode 100644 index 0000000..a0ea410 --- /dev/null +++ b/contracts/src/errors/BridgeErrors.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Shared bridge adapter custom errors. +abstract contract BridgeErrors { + error InvalidAmount(); + error InvalidChainId(); + error DestinationDisabled(); + error MaxPerTxExceeded(); + error DailyCapExceeded(); +} diff --git a/contracts/src/errors/SettlementErrors.sol b/contracts/src/errors/SettlementErrors.sol new file mode 100644 index 0000000..1bae67c --- /dev/null +++ b/contracts/src/errors/SettlementErrors.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Shared settlement module custom errors. +abstract contract SettlementErrors { + error InvalidAmount(); + error InvalidIntent(); + error IntentAlreadyConsumed(); + error VerifierRequired(); + error VerificationFailed(); + error ProductionModeAlreadyEnabled(); + error ProductionModeRequiresProofValidation(); +} diff --git a/contracts/src/errors/TokenErrors.sol b/contracts/src/errors/TokenErrors.sol new file mode 100644 index 0000000..54be35b --- /dev/null +++ b/contracts/src/errors/TokenErrors.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Shared token-level custom errors. +abstract contract TokenErrors { + error InvalidAmount(); +} diff --git a/contracts/src/protocol/MARKBridgeAdapter.sol b/contracts/src/protocol/MARKBridgeAdapter.sol new file mode 100644 index 0000000..1b3c7fa --- /dev/null +++ b/contracts/src/protocol/MARKBridgeAdapter.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {ISuperchainTokenBridge} from "@interop-lib/interfaces/ISuperchainTokenBridge.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; +import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; +import {BridgeErrors} from "../errors/BridgeErrors.sol"; + +/// @title MARKBridgeAdapter +/// @notice Operator-gated bridge-out adapter for RYLA using SuperchainTokenBridge. +/// @dev Uses destination allowlist and optional per-tx / daily caps. +contract MARKBridgeAdapter is ReentrancyGuard, AccessControlDefaultAdminRules, BridgeErrors { + using SafeERC20 for IERC20; + + event OperatorUpdated(address indexed operator, bool enabled); + event DestinationUpdated(uint256 indexed destinationChainId, bool enabled); + event BridgeLimitsUpdated(uint256 maxPerTx, uint256 dailyCap); + event BridgedOut( + address indexed operator, + address indexed recipient, + uint256 indexed destinationChainId, + uint256 amount, + bytes32 messageHash + ); + + uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + + IERC20 public immutable TOKEN; + ISuperchainTokenBridge public constant SUPERCHAIN_TOKEN_BRIDGE = + ISuperchainTokenBridge(PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE); + + mapping(uint256 => bool) public destinationEnabled; + + uint256 public maxPerTx; + uint256 public dailyCap; + uint64 public dailyCapEpoch; + uint256 public bridgedInDailyCapEpoch; + + constructor(address initialAdmin, address tokenAddress) + AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) + { + if (initialAdmin == address(0)) revert ZeroAddress(); + if (tokenAddress == address(0)) revert ZeroAddress(); + TOKEN = IERC20(tokenAddress); + } + + function setOperator(address operator, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (operator == address(0)) revert ZeroAddress(); + if (enabled) { + _grantRole(OPERATOR_ROLE, operator); + } else { + _revokeRole(OPERATOR_ROLE, operator); + } + emit OperatorUpdated(operator, enabled); + } + + function setDestination(uint256 destinationChainId, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (destinationChainId == 0) revert InvalidChainId(); + destinationEnabled[destinationChainId] = enabled; + emit DestinationUpdated(destinationChainId, enabled); + } + + /// @notice Sets optional bridge limits. Set `0` to disable each limiter. + function setBridgeLimits(uint256 maxPerTx_, uint256 dailyCap_) external onlyRole(DEFAULT_ADMIN_ROLE) { + maxPerTx = maxPerTx_; + dailyCap = dailyCap_; + dailyCapEpoch = 0; + bridgedInDailyCapEpoch = 0; + emit BridgeLimitsUpdated(maxPerTx_, dailyCap_); + } + + function bridgeTo(address recipient, uint256 amount, uint256 destinationChainId) + external + onlyRole(OPERATOR_ROLE) + nonReentrant + returns (bytes32 messageHash) + { + if (recipient == address(0)) revert ZeroAddress(); + if (amount == 0) revert InvalidAmount(); + if (destinationChainId == 0) revert InvalidChainId(); + if (!destinationEnabled[destinationChainId]) revert DestinationDisabled(); + + _consumeLimits(amount); + + TOKEN.safeTransferFrom(msg.sender, address(this), amount); + TOKEN.forceApprove(address(SUPERCHAIN_TOKEN_BRIDGE), amount); + + messageHash = SUPERCHAIN_TOKEN_BRIDGE.sendERC20(address(TOKEN), recipient, amount, destinationChainId); + emit BridgedOut(msg.sender, recipient, destinationChainId, amount, messageHash); + } + + function _consumeLimits(uint256 amount) internal { + uint256 maxPerTx_ = maxPerTx; + if (maxPerTx_ > 0 && amount > maxPerTx_) revert MaxPerTxExceeded(); + + uint256 dailyCap_ = dailyCap; + if (dailyCap_ == 0) return; + + uint64 epoch = uint64(block.timestamp / 1 days); + if (epoch != dailyCapEpoch) { + dailyCapEpoch = epoch; + bridgedInDailyCapEpoch = 0; + } + + uint256 nextBridged = bridgedInDailyCapEpoch + amount; + if (nextBridged > dailyCap_) revert DailyCapExceeded(); + bridgedInDailyCapEpoch = nextBridged; + } +} diff --git a/contracts/src/protocol/MARKSettlementModule.sol b/contracts/src/protocol/MARKSettlementModule.sol new file mode 100644 index 0000000..ee7a17f --- /dev/null +++ b/contracts/src/protocol/MARKSettlementModule.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {RYLA} from "../token/RYLA.sol"; +import {IUTXOSettlementVerifier} from "../interfaces/IUTXOSettlementVerifier.sol"; +import {SettlementErrors} from "../errors/SettlementErrors.sol"; +import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; + +/// @title MARKSettlementModule +/// @notice Boundary module for integrating external UTXO/zk accounting with RYLA mint/burn. +/// @dev Holds RYLA minter and burner roles. Replay protection is enforced via `intentId`. +contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules, SettlementErrors { + using SafeERC20 for IERC20; + event OperatorUpdated(address indexed operator, bool enabled); + event VerifierUpdated(address indexed verifier, bool validationEnabled); + event ProductionModeActivated(address indexed admin); + event MintSettled(bytes32 indexed intentId, address indexed operator, address indexed recipient, uint256 amount); + event BurnSettled(bytes32 indexed intentId, address indexed operator, address indexed account, uint256 amount); + + uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + + RYLA public immutable TOKEN; + + mapping(bytes32 => bool) public consumedIntents; + + IUTXOSettlementVerifier public verifier; + bool public proofValidationEnabled; + bool public productionMode; + + constructor(address initialAdmin, address tokenAddress) + AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) + { + if (initialAdmin == address(0)) revert ZeroAddress(); + if (tokenAddress == address(0)) revert ZeroAddress(); + TOKEN = RYLA(tokenAddress); + } + + function setOperator(address operator, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (operator == address(0)) revert ZeroAddress(); + if (enabled) { + _grantRole(OPERATOR_ROLE, operator); + } else { + _revokeRole(OPERATOR_ROLE, operator); + } + emit OperatorUpdated(operator, enabled); + } + + function setVerifier(address verifierAddress, bool enableValidation) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (productionMode && (!enableValidation || verifierAddress == address(0))) { + revert ProductionModeRequiresProofValidation(); + } + if (enableValidation && verifierAddress == address(0)) revert VerifierRequired(); + if (verifierAddress != address(0) && verifierAddress.code.length == 0) revert VerifierRequired(); + + verifier = IUTXOSettlementVerifier(verifierAddress); + proofValidationEnabled = enableValidation; + emit VerifierUpdated(verifierAddress, enableValidation); + } + + /// @notice Irreversibly enables production mode that forces proof validation to remain active. + function activateProductionMode() external onlyRole(DEFAULT_ADMIN_ROLE) { + if (productionMode) revert ProductionModeAlreadyEnabled(); + if (!proofValidationEnabled || address(verifier) == address(0)) { + revert ProductionModeRequiresProofValidation(); + } + productionMode = true; + emit ProductionModeActivated(msg.sender); + } + + function settleMint(address recipient, uint256 amount, bytes32 intentId, bytes calldata proof) + external + onlyRole(OPERATOR_ROLE) + nonReentrant + { + if (recipient == address(0)) revert ZeroAddress(); + _consumeAndValidate(intentId, recipient, amount, true, proof); + TOKEN.mint(recipient, amount); + emit MintSettled(intentId, msg.sender, recipient, amount); + } + + function settleBurn(address account, uint256 amount, bytes32 intentId, bytes calldata proof) + external + onlyRole(OPERATOR_ROLE) + nonReentrant + { + if (account == address(0)) revert ZeroAddress(); + _consumeAndValidate(intentId, account, amount, false, proof); + IERC20(address(TOKEN)).safeTransferFrom(account, address(this), amount); + TOKEN.burn(amount); + emit BurnSettled(intentId, msg.sender, account, amount); + } + + function _consumeAndValidate(bytes32 intentId, address account, uint256 amount, bool isMint, bytes calldata proof) + internal + { + if (intentId == bytes32(0)) revert InvalidIntent(); + if (amount == 0) revert InvalidAmount(); + if (consumedIntents[intentId]) revert IntentAlreadyConsumed(); + + if (proofValidationEnabled) { + IUTXOSettlementVerifier verifier_ = verifier; + if (address(verifier_) == address(0)) revert VerifierRequired(); + bool ok = verifier_.verifySettlement(intentId, address(this), account, amount, isMint, proof); + if (!ok) revert VerificationFailed(); + } + + consumedIntents[intentId] = true; + } +} diff --git a/contracts/src/token/RYLA.sol b/contracts/src/token/RYLA.sol new file mode 100644 index 0000000..895a3b0 --- /dev/null +++ b/contracts/src/token/RYLA.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +import {SuperchainERC20} from "@interop-lib/SuperchainERC20.sol"; +import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; +import {TokenErrors} from "../errors/TokenErrors.sol"; + +/// @title RYLA (Ʀ) +/// @notice Superchain-compatible standard credit token. +/// @dev Cross-chain mint/burn is handled by SuperchainTokenBridge via SuperchainERC20. +contract RYLA is SuperchainERC20, AccessControlDefaultAdminRules, TokenErrors { + + event MinterUpdated(address indexed account, bool enabled); + event BurnerUpdated(address indexed account, bool enabled); + + uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor(address initialAdmin) AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) { + if (initialAdmin == address(0)) revert ZeroAddress(); + } + + function name() public pure override returns (string memory) { + return "RYLA"; + } + + function symbol() public pure override returns (string memory) { + return "RYLA"; + } + + function supportsInterface(bytes4 interfaceId) + public + view + override(AccessControlDefaultAdminRules, SuperchainERC20) + returns (bool) + { + return AccessControlDefaultAdminRules.supportsInterface(interfaceId) + || SuperchainERC20.supportsInterface(interfaceId); + } + + function setMinter(address account, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (account == address(0)) revert ZeroAddress(); + if (enabled) { + _grantRole(MINTER_ROLE, account); + } else { + _revokeRole(MINTER_ROLE, account); + } + emit MinterUpdated(account, enabled); + } + + function setBurner(address account, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (account == address(0)) revert ZeroAddress(); + if (enabled) { + _grantRole(BURNER_ROLE, account); + } else { + _revokeRole(BURNER_ROLE, account); + } + emit BurnerUpdated(account, enabled); + } + + function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { + if (to == address(0)) revert ZeroAddress(); + if (amount == 0) revert InvalidAmount(); + _mint(to, amount); + } + + /// @notice Burns tokens from the caller balance (typically a burner module). + function burn(uint256 amount) external onlyRole(BURNER_ROLE) { + if (amount == 0) revert InvalidAmount(); + _burn(msg.sender, amount); + } +} diff --git a/contracts/test/e2e/MARKSettlementE2E.t.sol b/contracts/test/e2e/MARKSettlementE2E.t.sol new file mode 100644 index 0000000..41f3075 --- /dev/null +++ b/contracts/test/e2e/MARKSettlementE2E.t.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKSettlementModule} from "../../src/protocol/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../src/verifier/AttestedSettlementVerifier.sol"; +import {SettlementErrors} from "../../src/errors/SettlementErrors.sol"; + +contract MARKSettlementE2ETest is Test { + RYLA internal token; + MARKSettlementModule internal module; + MARKSettlementModule internal moduleB; + AttestedSettlementVerifier internal verifier; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + address internal user = makeAddr("user"); + uint256 internal attesterPk = 0xC0FFEE; + address internal attester; + + bytes32 internal constant INTENT_MINT = keccak256("e2e-mint-1"); + bytes32 internal constant INTENT_BURN = keccak256("e2e-burn-1"); + bytes32 internal constant CONTEXT_MINT = keccak256("e2e-mint-context"); + bytes32 internal constant CONTEXT_BURN = keccak256("e2e-burn-context"); + + function setUp() public { + attester = vm.addr(attesterPk); + + vm.prank(owner); + token = new RYLA(owner); + + vm.prank(owner); + verifier = new AttestedSettlementVerifier(owner); + + vm.prank(owner); + module = new MARKSettlementModule(owner, address(token)); + vm.prank(owner); + moduleB = new MARKSettlementModule(owner, address(token)); + + vm.startPrank(owner); + verifier.setAttester(attester, true); + module.setOperator(operator, true); + moduleB.setOperator(operator, true); + module.setVerifier(address(verifier), true); + moduleB.setVerifier(address(verifier), true); + token.setMinter(address(module), true); + token.setBurner(address(module), true); + token.setMinter(address(moduleB), true); + token.setBurner(address(moduleB), true); + vm.stopPrank(); + } + + function testMintThenBurnLifecycleWithValidationEnabled() public { + uint256 mintAmount = 100 ether; + uint256 mintDeadline = block.timestamp + 1 hours; + bytes memory mintProof = _buildProof(INTENT_MINT, address(module), user, mintAmount, true, CONTEXT_MINT, mintDeadline); + + vm.prank(operator); + module.settleMint(user, mintAmount, INTENT_MINT, mintProof); + assertEq(token.balanceOf(user), mintAmount); + + uint256 burnAmount = 40 ether; + vm.prank(user); + bool ok = token.approve(address(module), burnAmount); + assertTrue(ok); + + uint256 burnDeadline = block.timestamp + 1 hours; + bytes memory burnProof = + _buildProof(INTENT_BURN, address(module), user, burnAmount, false, CONTEXT_BURN, burnDeadline); + + vm.prank(operator); + module.settleBurn(user, burnAmount, INTENT_BURN, burnProof); + + assertEq(token.balanceOf(user), 60 ether); + assertEq(token.totalSupply(), 60 ether); + } + + function testReplayIntentReverts() public { + uint256 deadline = block.timestamp + 1 hours; + bytes memory mintProof = _buildProof(INTENT_MINT, address(module), user, 1 ether, true, CONTEXT_MINT, deadline); + + vm.prank(operator); + module.settleMint(user, 1 ether, INTENT_MINT, mintProof); + + vm.prank(operator); + vm.expectRevert(SettlementErrors.IntentAlreadyConsumed.selector); + module.settleMint(user, 1 ether, INTENT_MINT, mintProof); + } + + function testInvalidAttestationReverts() public { + uint256 deadline = block.timestamp + 1 hours; + bytes32 settlementHash = keccak256( + abi.encode( + verifier.SETTLEMENT_ATTESTATION_DOMAIN(), + address(verifier), + block.chainid, + INTENT_MINT, + address(module), + user, + 1 ether, + true, + CONTEXT_MINT, + deadline + ) + ); + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(0xDEAD, digest); + bytes memory wrongProof = abi.encode(deadline, CONTEXT_MINT, v, r, s); + + vm.prank(operator); + vm.expectRevert(SettlementErrors.VerificationFailed.selector); + module.settleMint(user, 1 ether, INTENT_MINT, wrongProof); + } + + function testProofBoundToModuleAddressRevertsOnDifferentModule() public { + uint256 amount = 3 ether; + uint256 deadline = block.timestamp + 1 hours; + bytes memory proofForModuleA = + _buildProof(INTENT_MINT, address(module), user, amount, true, CONTEXT_MINT, deadline); + + vm.prank(operator); + vm.expectRevert(SettlementErrors.VerificationFailed.selector); + moduleB.settleMint(user, amount, INTENT_MINT, proofForModuleA); + } + + function _buildProof( + bytes32 intentId, + address moduleAddress, + address account, + uint256 amount, + bool isMint, + bytes32 contextHash, + uint256 deadline + ) internal view returns (bytes memory) { + bytes32 settlementHash = keccak256( + abi.encode( + verifier.SETTLEMENT_ATTESTATION_DOMAIN(), + address(verifier), + block.chainid, + intentId, + moduleAddress, + account, + amount, + isMint, + contextHash, + deadline + ) + ); + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(attesterPk, digest); + return abi.encode(deadline, contextHash, v, r, s); + } +} diff --git a/contracts/test/unit/MARKBridgeAdapter.t.sol b/contracts/test/unit/MARKBridgeAdapter.t.sol new file mode 100644 index 0000000..d00a04b --- /dev/null +++ b/contracts/test/unit/MARKBridgeAdapter.t.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {Test} from "forge-std/Test.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../src/protocol/MARKBridgeAdapter.sol"; +import {BridgeErrors} from "../../src/errors/BridgeErrors.sol"; +import {ISuperchainTokenBridge} from "@interop-lib/interfaces/ISuperchainTokenBridge.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; + +contract MARKBridgeAdapterTest is Test { + RYLA internal token; + MARKBridgeAdapter internal adapter; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + address internal recipient = makeAddr("recipient"); + + uint256 internal constant DST_CHAIN_ID = 902; + address internal constant SUPERCHAIN_BRIDGE = PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE; + + function setUp() public { + vm.prank(owner); + token = new RYLA(owner); + + vm.startPrank(owner); + token.setMinter(owner, true); + token.mint(operator, 500 ether); + adapter = new MARKBridgeAdapter(owner, address(token)); + adapter.setOperator(operator, true); + adapter.setDestination(DST_CHAIN_ID, true); + vm.stopPrank(); + + vm.prank(operator); + IERC20(address(token)).approve(address(adapter), type(uint256).max); + } + + function testBridgeToRevertsWhenCallerNotOperator() public { + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), adapter.OPERATOR_ROLE() + ) + ); + adapter.bridgeTo(recipient, 1 ether, DST_CHAIN_ID); + } + + function testBridgeToRevertsWhenDestinationDisabled() public { + vm.prank(owner); + adapter.setDestination(DST_CHAIN_ID, false); + + vm.prank(operator); + vm.expectRevert(BridgeErrors.DestinationDisabled.selector); + adapter.bridgeTo(recipient, 1 ether, DST_CHAIN_ID); + } + + function testBridgeToCallsSuperchainBridge() public { + uint256 amount = 25 ether; + bytes32 expectedHash = keccak256("bridge-hash-1"); + + bytes memory callData = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, amount, DST_CHAIN_ID + ); + vm.mockCall(SUPERCHAIN_BRIDGE, callData, abi.encode(expectedHash)); + vm.expectCall(SUPERCHAIN_BRIDGE, callData); + + vm.prank(operator); + bytes32 messageHash = adapter.bridgeTo(recipient, amount, DST_CHAIN_ID); + + assertEq(messageHash, expectedHash); + assertEq(token.balanceOf(operator), 475 ether); + assertEq(token.balanceOf(address(adapter)), amount); + } + + function testBridgeToEnforcesMaxPerTx() public { + vm.prank(owner); + adapter.setBridgeLimits(10 ether, 0); + + vm.prank(operator); + vm.expectRevert(BridgeErrors.MaxPerTxExceeded.selector); + adapter.bridgeTo(recipient, 11 ether, DST_CHAIN_ID); + } + + function testBridgeToEnforcesAndResetsDailyCap() public { + vm.prank(owner); + adapter.setBridgeLimits(0, 100 ether); + + bytes memory callData40 = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, 40 ether, DST_CHAIN_ID + ); + bytes memory callData60 = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, 60 ether, DST_CHAIN_ID + ); + + vm.mockCall(SUPERCHAIN_BRIDGE, callData40, abi.encode(keccak256("h40"))); + vm.prank(operator); + adapter.bridgeTo(recipient, 40 ether, DST_CHAIN_ID); + + vm.prank(operator); + vm.expectRevert(BridgeErrors.DailyCapExceeded.selector); + adapter.bridgeTo(recipient, 70 ether, DST_CHAIN_ID); + + vm.warp(block.timestamp + 1 days + 1); + + vm.mockCall(SUPERCHAIN_BRIDGE, callData60, abi.encode(keccak256("h60"))); + vm.prank(operator); + adapter.bridgeTo(recipient, 60 ether, DST_CHAIN_ID); + + assertEq(adapter.bridgedInDailyCapEpoch(), 60 ether); + } +} diff --git a/contracts/test/unit/MARKSettlementModule.t.sol b/contracts/test/unit/MARKSettlementModule.t.sol new file mode 100644 index 0000000..f53f221 --- /dev/null +++ b/contracts/test/unit/MARKSettlementModule.t.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKSettlementModule} from "../../src/protocol/MARKSettlementModule.sol"; +import {IUTXOSettlementVerifier} from "../../src/interfaces/IUTXOSettlementVerifier.sol"; +import {SettlementErrors} from "../../src/errors/SettlementErrors.sol"; + +contract MockUTXOSettlementVerifier is IUTXOSettlementVerifier { + bool public shouldVerify = true; + + function setShouldVerify(bool value) external { + shouldVerify = value; + } + + function verifySettlement(bytes32, address, address, uint256, bool, bytes calldata) external view returns (bool) { + return shouldVerify; + } +} + +contract MARKSettlementModuleTest is Test { + RYLA internal token; + MARKSettlementModule internal module; + MockUTXOSettlementVerifier internal verifier; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + address internal user = makeAddr("user"); + bytes32 internal constant INTENT_M1 = keccak256("m-1"); + bytes32 internal constant INTENT_M2 = keccak256("m-2"); + bytes32 internal constant INTENT_M3 = keccak256("m-3"); + bytes32 internal constant INTENT_M4 = keccak256("m-4"); + bytes32 internal constant INTENT_M5 = keccak256("m-5"); + bytes32 internal constant INTENT_B1 = keccak256("b-1"); + bytes32 internal constant INTENT_B2 = keccak256("b-2"); + + function setUp() public { + vm.prank(owner); + token = new RYLA(owner); + + vm.prank(owner); + module = new MARKSettlementModule(owner, address(token)); + + vm.startPrank(owner); + token.setMinter(address(module), true); + token.setBurner(address(module), true); + module.setOperator(operator, true); + vm.stopPrank(); + + verifier = new MockUTXOSettlementVerifier(); + } + + function testSettleMintRevertsWhenCallerNotOperator() public { + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), module.OPERATOR_ROLE() + ) + ); + module.settleMint(user, 1 ether, INTENT_M1, bytes("")); + } + + function testSettleMintConsumesIntentAndMints() public { + vm.prank(operator); + module.settleMint(user, 5 ether, INTENT_M1, bytes("")); + assertEq(token.balanceOf(user), 5 ether); + assertTrue(module.consumedIntents(INTENT_M1)); + + vm.prank(operator); + vm.expectRevert(SettlementErrors.IntentAlreadyConsumed.selector); + module.settleMint(user, 1 ether, INTENT_M1, bytes("")); + } + + function testSettleBurnConsumesIntentAndBurns() public { + vm.prank(operator); + module.settleMint(user, 9 ether, INTENT_M2, bytes("")); + assertEq(token.balanceOf(user), 9 ether); + + vm.prank(user); + bool ok = token.approve(address(module), 4 ether); + assertTrue(ok); + + vm.prank(operator); + module.settleBurn(user, 4 ether, INTENT_B1, bytes("")); + assertEq(token.balanceOf(user), 5 ether); + assertEq(token.totalSupply(), 5 ether); + assertTrue(module.consumedIntents(INTENT_B1)); + } + + function testSetVerifierAndEnforceValidation() public { + vm.prank(owner); + module.setVerifier(address(verifier), true); + + verifier.setShouldVerify(false); + vm.prank(operator); + vm.expectRevert(SettlementErrors.VerificationFailed.selector); + module.settleMint(user, 1 ether, INTENT_M3, hex"1234"); + + verifier.setShouldVerify(true); + vm.prank(operator); + module.settleMint(user, 1 ether, INTENT_M4, hex"1234"); + assertEq(token.balanceOf(user), 1 ether); + } + + function testSetVerifierRejectsValidationWithoutVerifier() public { + vm.prank(owner); + vm.expectRevert(SettlementErrors.VerifierRequired.selector); + module.setVerifier(address(0), true); + } + + function testActivateProductionModeRequiresEnabledVerifier() public { + vm.prank(owner); + vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); + module.activateProductionMode(); + + vm.prank(owner); + module.setVerifier(address(verifier), true); + vm.prank(owner); + module.activateProductionMode(); + assertTrue(module.productionMode()); + + vm.prank(owner); + vm.expectRevert(SettlementErrors.ProductionModeAlreadyEnabled.selector); + module.activateProductionMode(); + } + + function testProductionModePreventsDisablingProofValidation() public { + vm.prank(owner); + module.setVerifier(address(verifier), true); + vm.prank(owner); + module.activateProductionMode(); + + vm.prank(owner); + vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); + module.setVerifier(address(0), false); + } + + function testProductionModePreventsVerifierSwapWithProofDisabled() public { + vm.prank(owner); + module.setVerifier(address(verifier), true); + vm.prank(owner); + module.activateProductionMode(); + + vm.prank(owner); + vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); + module.setVerifier(address(verifier), false); + } + + function testProductionModeStillEnforcesBurnProofValidation() public { + vm.startPrank(owner); + module.setVerifier(address(verifier), true); + module.activateProductionMode(); + vm.stopPrank(); + + vm.prank(operator); + module.settleMint(user, 3 ether, INTENT_M5, hex"1234"); + + vm.prank(user); + assertTrue(token.approve(address(module), 3 ether)); + + verifier.setShouldVerify(false); + vm.prank(operator); + vm.expectRevert(SettlementErrors.VerificationFailed.selector); + module.settleBurn(user, 1 ether, INTENT_B2, hex"1234"); + } +} From c24416aa9deb116b9f3ad853eb8557a1c1bb7ed6 Mon Sep 17 00:00:00 2001 From: Iko Date: Wed, 29 Apr 2026 15:37:12 +0700 Subject: [PATCH 06/38] refactor(contracts): separate bridge/settlement domains and harden settlement flows --- contracts/.env.example | 61 ++++ contracts/ARCHITECTURE.md | 23 ++ contracts/Makefile | 19 +- contracts/README.md | 40 ++- contracts/RUNBOOK.md | 4 +- .../networks/optimism-mainnet.env.example | 11 + .../networks/optimism-sepolia.env.example | 11 + contracts/config/profiles/mainnet.env | 18 ++ contracts/config/profiles/staging.env | 23 ++ contracts/foundry.lock | 11 + contracts/foundry.toml | 15 +- contracts/script/ci/architecture-guard.sh | 44 +++ contracts/script/ci/layering-guard.sh | 49 +++ .../deploy/bridge/DeployMARKStack.s.sol | 53 ++++ .../DeployMARKSettlementModule.s.sol | 88 ++++++ contracts/script/examples/Deploy.s.sol | 42 +++ contracts/script/ops/mainnet-readiness.sh | 101 +++++++ .../script/ops/rehearse-production-lock.sh | 113 +++++++ .../ops/settlement/PostDeployMARKSetup.s.sol | 215 ++++++++++++++ .../settlement/PreflightMARKDeployment.s.sol | 197 ++++++++++++ .../script/ops/settlement/ReleaseMARK.s.sol | 281 ++++++++++++++++++ .../ops/settlement/VerifyMARKDeployment.s.sol | 185 ++++++++++++ contracts/script/ops/smoke-production-mode.sh | 125 ++++++++ contracts/src/bridge/MARKBridgeAdapter.sol | 116 ++++++++ contracts/src/errors/SettlementErrors.sol | 1 + contracts/src/examples/CrossChainCounter.sol | 52 ++++ .../examples/CrossChainCounterIncrementer.sol | 26 ++ .../src/settlement/MARKSettlementModule.sol | 127 ++++++++ .../interfaces/IUTXOSettlementVerifier.sol | 19 ++ .../verifier/AttestedSettlementVerifier.sol | 102 +++++++ contracts/test/CrossChainCounter.t.sol | 2 +- .../e2e/settlement/MARKSettlementE2E.t.sol | 154 ++++++++++ .../settlement/CrossChainIncrementer.t.sol | 48 +++ .../settlement/MARKSettlementInvariants.t.sol | 192 ++++++++++++ contracts/test/unit/MARKAdminDelay.t.sol | 94 ++++++ contracts/test/unit/MARKDeployScripts.t.sol | 118 ++++++++ contracts/test/unit/PostDeployMARKSetup.t.sol | 59 ++++ .../test/unit/PreflightMARKDeployment.t.sol | 162 ++++++++++ contracts/test/unit/RYLA.t.sol | 79 +++++ contracts/test/unit/ReleaseMARK.t.sol | 35 +++ .../test/unit/VerifyMARKDeployment.t.sol | 89 ++++++ .../test/unit/bridge/MARKBridgeAdapter.t.sol | 156 ++++++++++ .../AttestedSettlementVerifier.t.sol | 104 +++++++ .../settlement/MARKSettlementModule.t.sol | 170 +++++++++++ 44 files changed, 3615 insertions(+), 19 deletions(-) create mode 100644 contracts/.env.example create mode 100644 contracts/ARCHITECTURE.md create mode 100644 contracts/config/networks/optimism-mainnet.env.example create mode 100644 contracts/config/networks/optimism-sepolia.env.example create mode 100644 contracts/config/profiles/mainnet.env create mode 100644 contracts/config/profiles/staging.env create mode 100644 contracts/foundry.lock create mode 100755 contracts/script/ci/architecture-guard.sh create mode 100755 contracts/script/ci/layering-guard.sh create mode 100644 contracts/script/deploy/bridge/DeployMARKStack.s.sol create mode 100644 contracts/script/deploy/settlement/DeployMARKSettlementModule.s.sol create mode 100644 contracts/script/examples/Deploy.s.sol create mode 100755 contracts/script/ops/mainnet-readiness.sh create mode 100755 contracts/script/ops/rehearse-production-lock.sh create mode 100644 contracts/script/ops/settlement/PostDeployMARKSetup.s.sol create mode 100644 contracts/script/ops/settlement/PreflightMARKDeployment.s.sol create mode 100644 contracts/script/ops/settlement/ReleaseMARK.s.sol create mode 100644 contracts/script/ops/settlement/VerifyMARKDeployment.s.sol create mode 100755 contracts/script/ops/smoke-production-mode.sh create mode 100644 contracts/src/bridge/MARKBridgeAdapter.sol create mode 100644 contracts/src/examples/CrossChainCounter.sol create mode 100644 contracts/src/examples/CrossChainCounterIncrementer.sol create mode 100644 contracts/src/settlement/MARKSettlementModule.sol create mode 100644 contracts/src/settlement/interfaces/IUTXOSettlementVerifier.sol create mode 100644 contracts/src/settlement/verifier/AttestedSettlementVerifier.sol create mode 100644 contracts/test/e2e/settlement/MARKSettlementE2E.t.sol create mode 100644 contracts/test/integration/settlement/CrossChainIncrementer.t.sol create mode 100644 contracts/test/invariant/settlement/MARKSettlementInvariants.t.sol create mode 100644 contracts/test/unit/MARKAdminDelay.t.sol create mode 100644 contracts/test/unit/MARKDeployScripts.t.sol create mode 100644 contracts/test/unit/PostDeployMARKSetup.t.sol create mode 100644 contracts/test/unit/PreflightMARKDeployment.t.sol create mode 100644 contracts/test/unit/RYLA.t.sol create mode 100644 contracts/test/unit/ReleaseMARK.t.sol create mode 100644 contracts/test/unit/VerifyMARKDeployment.t.sol create mode 100644 contracts/test/unit/bridge/MARKBridgeAdapter.t.sol create mode 100644 contracts/test/unit/settlement/AttestedSettlementVerifier.t.sol create mode 100644 contracts/test/unit/settlement/MARKSettlementModule.t.sol diff --git a/contracts/.env.example b/contracts/.env.example new file mode 100644 index 0000000..504653d --- /dev/null +++ b/contracts/.env.example @@ -0,0 +1,61 @@ +# ----------------------------------------------------------------------------- +# Core deploy inputs +# ----------------------------------------------------------------------------- +PRIVATE_KEY=0xYOUR_PRIVATE_KEY +RPC_URL=https://your-rpc-endpoint + +# RYLA token + MARK bridge adapter deploy script (DeployMARKStack.s.sol) +MARK_RYLA_OWNER=0x0000000000000000000000000000000000000000 +MARK_BRIDGE_OPERATOR=0x0000000000000000000000000000000000000000 +MARK_BRIDGE_DESTINATION_CHAIN_ID=0 + +# MARK settlement module deploy script (DeployMARKSettlementModule.s.sol) +MARK_RYLA_TOKEN=0x0000000000000000000000000000000000000000 +MARK_MODULE_OWNER=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_OPERATOR=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_VERIFIER=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_PROOF_ENABLED=false +MARK_SETTLEMENT_PRODUCTION_MODE=false +MARK_DEPLOY_ATTESTED_VERIFIER=false +MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 + +# MARK post-deploy setup script (PostDeployMARKSetup.s.sol) +MARK_BRIDGE_ADAPTER=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_MODULE=0x0000000000000000000000000000000000000000 + +# MARK preflight script (PreflightMARKDeployment.s.sol) +# 1 = DeployMARKStack, 2 = DeployMARKSettlementModule, 3 = PostDeployMARKSetup +MARK_PREFLIGHT_MODE=0 + +# MARK release orchestrator (ReleaseMARK.s.sol) +MARK_RELEASE_EXECUTE=false +MARK_RELEASE_RUN_POSTDEPLOY=false +MARK_RELEASE_WRITE_ARTIFACT=false +MARK_RELEASE_ARTIFACT_PATH=broadcast/mark-release-latest.json +MARK_GIT_COMMIT=unknown +MARK_RELEASE_STRICT_VERIFY=true +MARK_MAINNET_GATE_ARTIFACT_PATH=broadcast/mark-mainnet-gate.json +MARK_MAINNET_GATE_MODE=predeploy + +# ----------------------------------------------------------------------------- +# Post-deploy verification inputs (VerifyMARKDeployment.s.sol) +# ----------------------------------------------------------------------------- +VERIFY_MARK_RYLA_TOKEN=0x0000000000000000000000000000000000000000 +VERIFY_MARK_RYLA_OWNER=0x0000000000000000000000000000000000000000 +VERIFY_MARK_BRIDGE_ADAPTER=0x0000000000000000000000000000000000000000 +VERIFY_MARK_BRIDGE_OPERATOR=0x0000000000000000000000000000000000000000 +VERIFY_MARK_BRIDGE_DEST_CHAIN=0 +VERIFY_MARK_BRIDGE_MAX_PER_TX=0 +VERIFY_MARK_BRIDGE_DAILY_CAP=0 +VERIFY_MARK_SETTLEMENT_MODULE=0x0000000000000000000000000000000000000000 +VERIFY_MARK_SETTLEMENT_OPERATOR=0x0000000000000000000000000000000000000000 +VERIFY_MARK_SETTLEMENT_PROOF_ENABLED=false +VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE=false +VERIFY_MARK_SETTLEMENT_VERIFIER=0x0000000000000000000000000000000000000000 +VERIFY_MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 + +# ----------------------------------------------------------------------------- +# Local integration test endpoints (supersim) +# ----------------------------------------------------------------------------- +CHAIN_A_RPC_URL=http://127.0.0.1:9545 +CHAIN_B_RPC_URL=http://127.0.0.1:9546 diff --git a/contracts/ARCHITECTURE.md b/contracts/ARCHITECTURE.md new file mode 100644 index 0000000..94953ae --- /dev/null +++ b/contracts/ARCHITECTURE.md @@ -0,0 +1,23 @@ +# Contracts Architecture + +## Domains + +- `src/token`: token primitives (`RYLA`). +- `src/bridge`: bridge adapter domain. +- `src/settlement`: settlement module + verifier domain. +- `src/errors`: shared error types. + +## Dependency Rules + +- `src/bridge/**` must not import from `src/settlement/**`. +- `src/settlement/**` must not import from `src/bridge/**`. +- Cross-domain sharing should be done through narrow interfaces and shared types only. +- `src/token/**` is an allowed dependency for both bridge and settlement domains. + +## Enforcement + +- CI runs `make architecture-guard`. +- Guard implementation: `script/ci/architecture-guard.sh`. +- CI runs `make layering-guard`. +- Guard implementation: `script/ci/layering-guard.sh`. +- Any forbidden cross-domain import causes CI failure. diff --git a/contracts/Makefile b/contracts/Makefile index dc75157..2179aeb 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -1,4 +1,21 @@ -.PHONY: smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature +.PHONY: ci-local ci-fast ci-full architecture-guard layering-guard smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature + +# Fast local loop: guards + default-profile tests (no integration profile tests). +ci-fast: architecture-guard layering-guard + @forge test -q + +# Full local gate: fast checks + explicit production lock regression checks. +ci-full: ci-fast + @$(MAKE) test-production-lock + +# Backward-compatible alias for local usage. +ci-local: ci-fast + +architecture-guard: + @./script/ci/architecture-guard.sh + +layering-guard: + @./script/ci/layering-guard.sh smoke-production-mode: @./script/ops/smoke-production-mode.sh diff --git a/contracts/README.md b/contracts/README.md index ed8489a..a3b8f78 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -13,14 +13,14 @@ Operational procedures (deployment, incident, rollback) are documented in [RUNBO - `MINTER_ROLE` / `BURNER_ROLE` for protocol issuance modules - Uses `AccessControlDefaultAdminRules` for delayed default-admin transfer hardening -### [MARKBridgeAdapter.sol](./src/protocol/MARKBridgeAdapter.sol) +### [MARKBridgeAdapter.sol](./src/bridge/MARKBridgeAdapter.sol) - Operator-gated bridge-out adapter that routes through `SuperchainTokenBridge` - Destination allowlist and optional `maxPerTx` / `dailyCap` risk limits - Keeps token core bridge authority on the canonical predeploy - Uses `AccessControlDefaultAdminRules` (`OPERATOR_ROLE`) -### [MARKSettlementModule.sol](./src/protocol/MARKSettlementModule.sol) +### [MARKSettlementModule.sol](./src/settlement/MARKSettlementModule.sol) - Phase-2 integration boundary for UTXO / zk accounting - Operator-gated settlement with replay protection (`intentId`) @@ -28,7 +28,7 @@ Operational procedures (deployment, incident, rollback) are documented in [RUNBO - Holds token minter/burner roles for controlled mint and burn settlement - Uses `AccessControlDefaultAdminRules` (`OPERATOR_ROLE`) -### [AttestedSettlementVerifier.sol](./src/verifier/AttestedSettlementVerifier.sol) +### [AttestedSettlementVerifier.sol](./src/settlement/verifier/AttestedSettlementVerifier.sol) - Signature-based settlement verifier (`ATTESTER_ROLE` allowlist) - Intended as a concrete verifier baseline before integrating zk circuits @@ -69,12 +69,24 @@ forge build forge test ``` +Run the fast local CI checks (recommended during iteration): + +```bash +make ci-fast +``` + +Run the full local CI checks (includes explicit production-lock checks): + +```bash +make ci-full +``` + Run integration (fork/RPC-dependent) tests only: ```bash CHAIN_A_RPC_URL=http://127.0.0.1:9545 \ CHAIN_B_RPC_URL=http://127.0.0.1:9546 \ -FOUNDRY_PROFILE=integration forge test --match-path 'test/integration/*.t.sol' +FOUNDRY_PROFILE=integration forge test --match-path 'test/integration/**/*.t.sol' ``` ### Deploy @@ -97,21 +109,21 @@ Deploy `RYLA` stack: ```bash set -a && source .env && set +a -forge script script/deploy/DeployMARKStack.s.sol --rpc-url $RPC_URL --broadcast +forge script script/deploy/bridge/DeployMARKStack.s.sol --rpc-url $RPC_URL --broadcast ``` Deploy settlement module: ```bash set -a && source .env && set +a -forge script script/deploy/DeployMARKSettlementModule.s.sol --rpc-url $RPC_URL --broadcast +forge script script/deploy/settlement/DeployMARKSettlementModule.s.sol --rpc-url $RPC_URL --broadcast ``` Deploy settlement module with in-script attested verifier: ```bash set -a && source .env && set +a -forge script script/deploy/DeployMARKSettlementModule.s.sol --rpc-url $RPC_URL --broadcast +forge script script/deploy/settlement/DeployMARKSettlementModule.s.sol --rpc-url $RPC_URL --broadcast ``` ### Post-Deploy Setup @@ -120,7 +132,7 @@ Apply deterministic role/config setup on already deployed contracts: ```bash set -a && source .env && set +a -forge script script/ops/PostDeployMARKSetup.s.sol --rpc-url $RPC_URL --broadcast +forge script script/ops/settlement/PostDeployMARKSetup.s.sol --rpc-url $RPC_URL --broadcast ``` ### Preflight (Recommended Before Broadcast) @@ -129,7 +141,7 @@ Run read-only checks to validate env wiring and admin permissions before deploym ```bash set -a && source .env && set +a -forge script script/ops/PreflightMARKDeployment.s.sol --rpc-url $RPC_URL +forge script script/ops/settlement/PreflightMARKDeployment.s.sol --rpc-url $RPC_URL ``` `MARK_PREFLIGHT_MODE` values: @@ -143,7 +155,7 @@ Run full release pipeline (preflight -> deploy -> optional setup -> verify -> ar ```bash set -a && source .env && set +a -forge script script/ops/ReleaseMARK.s.sol --rpc-url $RPC_URL +forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url $RPC_URL ``` Local production-mode smoke (starts Anvil, deploys verifier, runs strict release verify): @@ -193,7 +205,7 @@ Run read-only checks against deployed contracts and role wiring: ```bash set -a && source .env && set +a -forge script script/ops/VerifyMARKDeployment.s.sol --rpc-url $RPC_URL +forge script script/ops/settlement/VerifyMARKDeployment.s.sol --rpc-url $RPC_URL ``` Optional strict checks supported by verify script: @@ -388,9 +400,9 @@ Run Slither locally on MARK core contracts: cd /Users/iap/mark/contracts slither \ src/token/RYLA.sol \ - src/protocol/MARKBridgeAdapter.sol \ - src/protocol/MARKSettlementModule.sol \ - src/verifier/AttestedSettlementVerifier.sol \ + src/bridge/MARKBridgeAdapter.sol \ + src/settlement/MARKSettlementModule.sol \ + src/settlement/verifier/AttestedSettlementVerifier.sol \ --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ --exclude-dependencies \ --filter-paths "lib|test|script|out|cache" \ diff --git a/contracts/RUNBOOK.md b/contracts/RUNBOOK.md index c248467..bae30fc 100644 --- a/contracts/RUNBOOK.md +++ b/contracts/RUNBOOK.md @@ -196,12 +196,12 @@ RPC_URL= PRIVATE_KEY= ./script/ops/mainnet-readiness.sh ```bash set -a && source .env && set +a MARK_RELEASE_EXECUTE=true MARK_RELEASE_WRITE_ARTIFACT=true \ -forge script script/ops/ReleaseMARK.s.sol --rpc-url $RPC_URL --broadcast +forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url $RPC_URL --broadcast ``` 3. Run post-deploy verify: ```bash set -a && source .env && set +a -forge script script/ops/VerifyMARKDeployment.s.sol --rpc-url $RPC_URL +forge script script/ops/settlement/VerifyMARKDeployment.s.sol --rpc-url $RPC_URL ``` 4. Run production lock assurance: ```bash diff --git a/contracts/config/networks/optimism-mainnet.env.example b/contracts/config/networks/optimism-mainnet.env.example new file mode 100644 index 0000000..d188c4a --- /dev/null +++ b/contracts/config/networks/optimism-mainnet.env.example @@ -0,0 +1,11 @@ +# Optimism mainnet example config +RPC_URL=https://mainnet.optimism.io + +# Governance / role holders +MARK_RYLA_OWNER=0x0000000000000000000000000000000000000000 +MARK_BRIDGE_OPERATOR=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_OPERATOR=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 + +# Optional initial bridge destination chain id +MARK_BRIDGE_DESTINATION_CHAIN_ID=0 diff --git a/contracts/config/networks/optimism-sepolia.env.example b/contracts/config/networks/optimism-sepolia.env.example new file mode 100644 index 0000000..1c1f1c2 --- /dev/null +++ b/contracts/config/networks/optimism-sepolia.env.example @@ -0,0 +1,11 @@ +# Optimism Sepolia example config +RPC_URL=https://sepolia.optimism.io + +# Governance / role holders +MARK_RYLA_OWNER=0x0000000000000000000000000000000000000000 +MARK_BRIDGE_OPERATOR=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_OPERATOR=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 + +# Optional initial bridge destination chain id +MARK_BRIDGE_DESTINATION_CHAIN_ID=0 diff --git a/contracts/config/profiles/mainnet.env b/contracts/config/profiles/mainnet.env new file mode 100644 index 0000000..928f9b9 --- /dev/null +++ b/contracts/config/profiles/mainnet.env @@ -0,0 +1,18 @@ +# MARK mainnet profile (Optimism mainnet) +# Placeholder addresses must be replaced with real deployed values before use. + +RPC_URL=https://mainnet.optimism.io + +MARK_RYLA_TOKEN=0x1111111111111111111111111111111111111111 +MARK_SETTLEMENT_MODULE=0x2222222222222222222222222222222222222222 +MARK_SETTLEMENT_VERIFIER=0x3333333333333333333333333333333333333333 +MARK_RYLA_OWNER=0x4444444444444444444444444444444444444444 +MARK_SETTLEMENT_OPERATOR=0x5555555555555555555555555555555555555555 +MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 + +VERIFY_MARK_RYLA_TOKEN=0x1111111111111111111111111111111111111111 +VERIFY_MARK_SETTLEMENT_MODULE=0x2222222222222222222222222222222222222222 +VERIFY_MARK_SETTLEMENT_VERIFIER=0x3333333333333333333333333333333333333333 +VERIFY_MARK_RYLA_OWNER=0x4444444444444444444444444444444444444444 +VERIFY_MARK_SETTLEMENT_OPERATOR=0x5555555555555555555555555555555555555555 +VERIFY_MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 diff --git a/contracts/config/profiles/staging.env b/contracts/config/profiles/staging.env new file mode 100644 index 0000000..dae0e1d --- /dev/null +++ b/contracts/config/profiles/staging.env @@ -0,0 +1,23 @@ +# MARK staging profile (OP Sepolia) +# Safe by default: execution is disabled unless explicitly overridden. + +RPC_URL=https://sepolia.optimism.io +PRIVATE_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 + +MARK_RYLA_OWNER=0x1111111111111111111111111111111111111111 +MARK_MODULE_OWNER=0x1111111111111111111111111111111111111111 +MARK_BRIDGE_OPERATOR=0x2222222222222222222222222222222222222222 +MARK_SETTLEMENT_OPERATOR=0x3333333333333333333333333333333333333333 +MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 +MARK_BRIDGE_DESTINATION_CHAIN_ID=10 + +MARK_SETTLEMENT_VERIFIER=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_PROOF_ENABLED=true +MARK_SETTLEMENT_PRODUCTION_MODE=true +MARK_DEPLOY_ATTESTED_VERIFIER=true + +MARK_RELEASE_EXECUTE=false +MARK_RELEASE_RUN_POSTDEPLOY=false +MARK_RELEASE_WRITE_ARTIFACT=true +MARK_RELEASE_ARTIFACT_PATH=broadcast/mark-staging-release.json +MARK_REHEARSAL_ARTIFACT_PATH=broadcast/mark-staging-rehearsal.json diff --git a/contracts/foundry.lock b/contracts/foundry.lock new file mode 100644 index 0000000..e0d6d41 --- /dev/null +++ b/contracts/foundry.lock @@ -0,0 +1,11 @@ +{ + "lib/createx": { + "rev": "de53df69f2e15373308ae573b01846158c240535" + }, + "lib/forge-std": { + "rev": "6853b9ec7df5dc0c213b05ae67785ad4f4baa0ea" + }, + "lib/interop-lib": { + "rev": "2a15e8f99c1f845eb5f89778d757f55351954586" + } +} \ No newline at end of file diff --git a/contracts/foundry.toml b/contracts/foundry.toml index cb2815f..74574b3 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -6,9 +6,22 @@ script = "script" cache_path = "cache" broadcast = "broadcast" libs = ["lib"] +no_match_path = "test/integration/**" +fs_permissions = [{ access = "read-write", path = "./broadcast" }] remappings = [ - "@interop-lib/=lib/interop-lib/src/" + "@interop-lib/=lib/interop-lib/src/", + "@openzeppelin/=lib/createx/lib/openzeppelin-contracts/" ] +[profile.integration] +src = "src" +out = "out" +test = "test" +script = "script" +cache_path = "cache" +broadcast = "broadcast" +libs = ["lib"] +no_match_path = "test/never/**" + # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/contracts/script/ci/architecture-guard.sh b/contracts/script/ci/architecture-guard.sh new file mode 100755 index 0000000..70f71ae --- /dev/null +++ b/contracts/script/ci/architecture-guard.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +fail() { + echo "[architecture-guard] $1" >&2 + exit 1 +} + +check_no_imports() { + local domain_dir="$1" + local forbidden_re="$2" + local label="$3" + + local files + files="$(find "$domain_dir" -type f -name '*.sol' | sort)" + if [[ -z "$files" ]]; then + return 0 + fi + + local hits + hits="$(rg -n --no-heading "$forbidden_re" $files || true)" + if [[ -n "$hits" ]]; then + echo "[architecture-guard] Forbidden imports found for rule: $label" >&2 + echo "$hits" >&2 + exit 1 + fi +} + +# Bridge contracts must not depend on settlement concrete contracts. +check_no_imports \ + "src/bridge" \ + '^import\s+.*"(?:\.\./settlement/|\.\./\.\./src/settlement/|src/settlement/)' \ + "bridge -> settlement" + +# Settlement contracts must not depend on bridge concrete contracts. +check_no_imports \ + "src/settlement" \ + '^import\s+.*"(?:\.\./bridge/|\.\./\.\./src/bridge/|src/bridge/)' \ + "settlement -> bridge" + +echo "[architecture-guard] OK" diff --git a/contracts/script/ci/layering-guard.sh b/contracts/script/ci/layering-guard.sh new file mode 100755 index 0000000..3ec19b1 --- /dev/null +++ b/contracts/script/ci/layering-guard.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +check_no_imports() { + local domain_dir="$1" + local forbidden_re="$2" + local label="$3" + + local files + files="$(find "$domain_dir" -type f -name '*.sol' | sort)" + if [[ -z "$files" ]]; then + return 0 + fi + + local hits + hits="$(rg -n --no-heading "$forbidden_re" $files || true)" + if [[ -n "$hits" ]]; then + echo "[layering-guard] Forbidden imports found for rule: $label" >&2 + echo "$hits" >&2 + exit 1 + fi +} + +# Unit tests must stay domain-local at the test-helper layer. +check_no_imports \ + "test/unit/bridge" \ + '^import\s+.*"(?:\.\./settlement/|\.\./\.\./unit/settlement/|\.\./\.\./integration/settlement/|\.\./\.\./invariant/settlement/|\.\./\.\./e2e/settlement/|test/unit/settlement/|test/integration/settlement/|test/invariant/settlement/|test/e2e/settlement/)' \ + "unit/bridge -> settlement test trees" + +check_no_imports \ + "test/unit/settlement" \ + '^import\s+.*"(?:\.\./bridge/|\.\./\.\./unit/bridge/|test/unit/bridge/)' \ + "unit/settlement -> bridge test trees" + +# Deploy scripts should remain domain-local. +check_no_imports \ + "script/deploy/bridge" \ + '^import\s+.*"(?:\.\./settlement/|\.\./\.\./deploy/settlement/|script/deploy/settlement/)' \ + "deploy/bridge -> deploy/settlement" + +check_no_imports \ + "script/deploy/settlement" \ + '^import\s+.*"(?:\.\./bridge/|\.\./\.\./deploy/bridge/|script/deploy/bridge/)' \ + "deploy/settlement -> deploy/bridge" + +echo "[layering-guard] OK" diff --git a/contracts/script/deploy/bridge/DeployMARKStack.s.sol b/contracts/script/deploy/bridge/DeployMARKStack.s.sol new file mode 100644 index 0000000..9aa2995 --- /dev/null +++ b/contracts/script/deploy/bridge/DeployMARKStack.s.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../../src/bridge/MARKBridgeAdapter.sol"; + +/// @notice Deploys RYLA token + MARK bridge adapter with optional initial operator/destination wiring. +contract DeployMARKStack is Script { + bytes32 private constant DEFAULT_ADMIN_ROLE = 0x00; + error MissingAdapterAdminForRequestedConfig(); + + function run() external returns (RYLA token, MARKBridgeAdapter adapter) { + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(deployerKey); + + address owner = vm.envOr("MARK_RYLA_OWNER", deployer); + address initialOperator = vm.envOr("MARK_BRIDGE_OPERATOR", address(0)); + uint256 initialDestinationChain = vm.envOr("MARK_BRIDGE_DESTINATION_CHAIN_ID", uint256(0)); + + vm.startBroadcast(deployerKey); + + token = new RYLA(owner); + adapter = new MARKBridgeAdapter(owner, address(token)); + + bool deployerIsAdapterAdmin = adapter.hasRole(DEFAULT_ADMIN_ROLE, deployer); + bool hasRequestedConfig = initialOperator != address(0) || initialDestinationChain != 0; + + if (hasRequestedConfig && !deployerIsAdapterAdmin) { + revert MissingAdapterAdminForRequestedConfig(); + } + + if (deployerIsAdapterAdmin) { + _configureAdapter(adapter, initialOperator, initialDestinationChain); + } + + vm.stopBroadcast(); + + console.log("RYLA:", address(token)); + console.log("MARKBridgeAdapter:", address(adapter)); + } + + function _configureAdapter(MARKBridgeAdapter adapter, address initialOperator, uint256 initialDestinationChain) + internal + { + if (initialOperator != address(0)) { + adapter.setOperator(initialOperator, true); + } + if (initialDestinationChain != 0) { + adapter.setDestination(initialDestinationChain, true); + } + } +} diff --git a/contracts/script/deploy/settlement/DeployMARKSettlementModule.s.sol b/contracts/script/deploy/settlement/DeployMARKSettlementModule.s.sol new file mode 100644 index 0000000..581cba9 --- /dev/null +++ b/contracts/script/deploy/settlement/DeployMARKSettlementModule.s.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../../src/settlement/verifier/AttestedSettlementVerifier.sol"; + +/// @notice Deploys MARKSettlementModule and optionally wires operator/verifier and token roles. +contract DeployMARKSettlementModule is Script { + bytes32 private constant DEFAULT_ADMIN_ROLE = 0x00; + error MissingVerifierAdminForRequestedAttester(); + error MissingModuleAdminForRequestedConfig(); + error MissingTokenAdminForRoleGrants(); + + struct Config { + uint256 deployerKey; + address deployer; + address tokenAddress; + address owner; + address operator; + address verifierAddress; + bool deployAttestedVerifier; + address verifierAttester; + bool proofEnabled; + } + + function run() external returns (MARKSettlementModule module) { + Config memory cfg = _loadConfig(); + RYLA token = RYLA(cfg.tokenAddress); + + vm.startBroadcast(cfg.deployerKey); + module = new MARKSettlementModule(cfg.owner, cfg.tokenAddress); + + if (cfg.deployAttestedVerifier) { + AttestedSettlementVerifier deployedVerifier = new AttestedSettlementVerifier(cfg.owner); + cfg.verifierAddress = address(deployedVerifier); + bool deployerIsVerifierAdmin = deployedVerifier.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer); + if (cfg.verifierAttester != address(0) && !deployerIsVerifierAdmin) { + revert MissingVerifierAdminForRequestedAttester(); + } + if (deployerIsVerifierAdmin && cfg.verifierAttester != address(0)) { + deployedVerifier.setAttester(cfg.verifierAttester, true); + } + } + + bool deployerIsModuleAdmin = module.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer); + bool moduleNeedsConfig = cfg.operator != address(0) || cfg.verifierAddress != address(0) || cfg.proofEnabled; + if (moduleNeedsConfig && !deployerIsModuleAdmin) { + revert MissingModuleAdminForRequestedConfig(); + } + + if (deployerIsModuleAdmin) { + if (cfg.operator != address(0)) { + module.setOperator(cfg.operator, true); + } + module.setVerifier(cfg.verifierAddress, cfg.proofEnabled); + } + + bool deployerIsTokenAdmin = token.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer); + if (!deployerIsTokenAdmin) { + revert MissingTokenAdminForRoleGrants(); + } + + if (deployerIsTokenAdmin) { + token.setMinter(address(module), true); + token.setBurner(address(module), true); + } + + vm.stopBroadcast(); + + console.log("MARKSettlementModule:", address(module)); + console.log("SettlementVerifier:", cfg.verifierAddress); + } + + function _loadConfig() internal view returns (Config memory cfg) { + cfg.deployerKey = vm.envUint("PRIVATE_KEY"); + cfg.deployer = vm.addr(cfg.deployerKey); + + cfg.tokenAddress = vm.envAddress("MARK_RYLA_TOKEN"); + cfg.owner = vm.envOr("MARK_MODULE_OWNER", cfg.deployer); + cfg.operator = vm.envOr("MARK_SETTLEMENT_OPERATOR", address(0)); + cfg.verifierAddress = vm.envOr("MARK_SETTLEMENT_VERIFIER", address(0)); + cfg.deployAttestedVerifier = vm.envOr("MARK_DEPLOY_ATTESTED_VERIFIER", false); + cfg.verifierAttester = vm.envOr("MARK_SETTLEMENT_ATTESTER", address(0)); + cfg.proofEnabled = vm.envOr("MARK_SETTLEMENT_PROOF_ENABLED", false); + } +} diff --git a/contracts/script/examples/Deploy.s.sol b/contracts/script/examples/Deploy.s.sol new file mode 100644 index 0000000..9bc332e --- /dev/null +++ b/contracts/script/examples/Deploy.s.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {ICreateX} from "createx/ICreateX.sol"; + +import {DeployUtils} from "../../libraries/DeployUtils.sol"; +import {CrossChainCounter} from "../../src/examples/CrossChainCounter.sol"; + +// Example forge script for deploying as an alternative to sup: super-cli (https://github.com/ethereum-optimism/super-cli) +contract Deploy is Script { + /// @notice Array of RPC URLs to deploy to, deploy to supersim 901 and 902 by default. + string[] private rpcUrls = ["http://localhost:9545", "http://localhost:9546"]; + + /// @notice Modifier that wraps a function in broadcasting. + modifier broadcast() { + vm.startBroadcast(msg.sender); + _; + vm.stopBroadcast(); + } + + function run() public { + for (uint256 i = 0; i < rpcUrls.length; i++) { + string memory rpcUrl = rpcUrls[i]; + + console.log("Deploying to RPC: ", rpcUrl); + vm.createSelectFork(rpcUrl); + deployCrossChainCounterContract(); + } + } + + function deployCrossChainCounterContract() public broadcast returns (address addr_) { + bytes memory initCode = abi.encodePacked(type(CrossChainCounter).creationCode); + addr_ = DeployUtils.deployContract("CrossChainCounter", _implSalt(), initCode); + } + + /// @notice The CREATE2 salt to be used when deploying a contract. + function _implSalt() internal view returns (bytes32) { + return keccak256(abi.encodePacked(vm.envOr("DEPLOY_SALT", string("ethers phoenix")))); + } +} diff --git a/contracts/script/ops/mainnet-readiness.sh b/contracts/script/ops/mainnet-readiness.sh new file mode 100755 index 0000000..2332110 --- /dev/null +++ b/contracts/script/ops/mainnet-readiness.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +if ! command -v forge >/dev/null 2>&1; then + echo "forge is required but not found in PATH" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required but not found in PATH" >&2 + exit 1 +fi + +if ! command -v slither >/dev/null 2>&1; then + echo "slither is required but not found in PATH" >&2 + exit 1 +fi + +if [[ -z "${RPC_URL:-}" ]]; then + echo "RPC_URL is required" >&2 + exit 1 +fi + +if [[ -z "${PRIVATE_KEY:-}" ]]; then + echo "PRIVATE_KEY is required" >&2 + exit 1 +fi + +ARTIFACT_PATH="${MARK_MAINNET_GATE_ARTIFACT_PATH:-broadcast/mark-mainnet-gate.json}" +MODE="${MARK_MAINNET_GATE_MODE:-predeploy}" + +case "$MODE" in + predeploy|postdeploy|full) ;; + *) + echo "MARK_MAINNET_GATE_MODE must be one of: predeploy, postdeploy, full" >&2 + exit 1 + ;; +esac + +run_predeploy_checks() { + echo "[1/6] Running contract tests..." + forge test -vv + + echo "[2/6] Running Slither core scan..." + slither \ + src/token/RYLA.sol \ + src/bridge/MARKBridgeAdapter.sol \ + src/settlement/MARKSettlementModule.sol \ + src/settlement/verifier/AttestedSettlementVerifier.sol \ + --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ + --exclude-dependencies \ + --filter-paths "lib|test|script|out|cache" \ + --fail-medium + + echo "[3/6] Running preflight checks for all modes..." + for mode in 1 2 3; do + MARK_PREFLIGHT_MODE="$mode" forge script script/ops/settlement/PreflightMARKDeployment.s.sol --rpc-url "$RPC_URL" -q + done +} + +run_postdeploy_checks() { + echo "[4/6] Running deployment verify checks..." + forge script script/ops/settlement/VerifyMARKDeployment.s.sol --rpc-url "$RPC_URL" -q +} + +generate_and_validate_artifact() { + echo "[5/6] Generating release artifact via dry-run orchestration..." + MARK_RELEASE_EXECUTE=false \ + MARK_RELEASE_WRITE_ARTIFACT=true \ + MARK_RELEASE_ARTIFACT_PATH="$ARTIFACT_PATH" \ + MARK_RELEASE_STRICT_VERIFY=false \ + forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url "$RPC_URL" -q + + echo "[6/6] Validating release artifact schema..." + jq -e ' + .protocol == "MARK" and + .tokenSymbol == "RYLA" and + .execute == false and + .deployer != null and + .chainId != null and + .timestamp != null and + .gitCommit != null + ' "$ARTIFACT_PATH" >/dev/null +} + +if [[ "$MODE" == "predeploy" ]]; then + run_predeploy_checks + generate_and_validate_artifact +elif [[ "$MODE" == "postdeploy" ]]; then + run_postdeploy_checks +else + run_predeploy_checks + run_postdeploy_checks + generate_and_validate_artifact +fi + +echo "Mainnet readiness gate PASSED." +echo "Artifact: $ARTIFACT_PATH" diff --git a/contracts/script/ops/rehearse-production-lock.sh b/contracts/script/ops/rehearse-production-lock.sh new file mode 100755 index 0000000..4651c80 --- /dev/null +++ b/contracts/script/ops/rehearse-production-lock.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_env() { + local key="$1" + if [[ -z "${!key:-}" ]]; then + echo "Missing required env var: $key" >&2 + exit 1 + fi +} + +require_cmd forge +require_cmd cast +require_cmd jq + +require_env RPC_URL +require_env PRIVATE_KEY +require_env MARK_SETTLEMENT_OPERATOR + +DEPLOYER_ADDRESS="$(cast wallet address --private-key "$PRIVATE_KEY")" + +MARK_RYLA_OWNER="${MARK_RYLA_OWNER:-$DEPLOYER_ADDRESS}" +MARK_MODULE_OWNER="${MARK_MODULE_OWNER:-$MARK_RYLA_OWNER}" +MARK_BRIDGE_OPERATOR="${MARK_BRIDGE_OPERATOR:-$MARK_SETTLEMENT_OPERATOR}" +MARK_BRIDGE_DESTINATION_CHAIN_ID="${MARK_BRIDGE_DESTINATION_CHAIN_ID:-10}" +MARK_SETTLEMENT_ATTESTER="${MARK_SETTLEMENT_ATTESTER:-0x0000000000000000000000000000000000000000}" + +RELEASE_ARTIFACT_PATH="${MARK_RELEASE_ARTIFACT_PATH:-broadcast/mark-staging-release.json}" +REHEARSAL_ARTIFACT_PATH="${MARK_REHEARSAL_ARTIFACT_PATH:-broadcast/mark-staging-rehearsal.json}" +MARK_GIT_COMMIT="${MARK_GIT_COMMIT:-unknown}" + +export MARK_RYLA_OWNER +export MARK_MODULE_OWNER +export MARK_BRIDGE_OPERATOR +export MARK_BRIDGE_DESTINATION_CHAIN_ID +export MARK_SETTLEMENT_OPERATOR +export MARK_SETTLEMENT_ATTESTER + +export MARK_RELEASE_EXECUTE=true +export MARK_RELEASE_RUN_POSTDEPLOY=false +export MARK_RELEASE_WRITE_ARTIFACT=true +export MARK_RELEASE_ARTIFACT_PATH="$RELEASE_ARTIFACT_PATH" +export MARK_RELEASE_STRICT_VERIFY=false +export MARK_GIT_COMMIT + +export MARK_SETTLEMENT_PROOF_ENABLED=true +export MARK_SETTLEMENT_PRODUCTION_MODE=true +export MARK_DEPLOY_ATTESTED_VERIFIER=true +export MARK_SETTLEMENT_VERIFIER="${MARK_SETTLEMENT_VERIFIER:-0x0000000000000000000000000000000000000000}" + +VALIDATE_MODE=rehearsal ./script/ops/validate-prod-env.sh + +echo "Running staging rehearsal release..." +forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url "$RPC_URL" --broadcast -q + +if [[ ! -f "$RELEASE_ARTIFACT_PATH" ]]; then + echo "Missing release artifact: $RELEASE_ARTIFACT_PATH" >&2 + exit 1 +fi + +TOKEN_ADDRESS="$(jq -r '.token' "$RELEASE_ARTIFACT_PATH")" +MODULE_ADDRESS="$(jq -r '.module' "$RELEASE_ARTIFACT_PATH")" +VERIFIER_ADDRESS="$(jq -r '.verifier' "$RELEASE_ARTIFACT_PATH")" +CHAIN_ID="$(cast chain-id --rpc-url "$RPC_URL")" + +export MARK_RYLA_TOKEN="$TOKEN_ADDRESS" +export MARK_SETTLEMENT_MODULE="$MODULE_ADDRESS" +export MARK_SETTLEMENT_VERIFIER="$VERIFIER_ADDRESS" + +echo "Running production lock verification..." +./script/ops/verify-production-lock.sh + +mkdir -p "$(dirname "$REHEARSAL_ARTIFACT_PATH")" +jq -n \ + --arg rpcUrl "$RPC_URL" \ + --arg chainId "$CHAIN_ID" \ + --arg deployer "$DEPLOYER_ADDRESS" \ + --arg owner "$MARK_RYLA_OWNER" \ + --arg settlementOperator "$MARK_SETTLEMENT_OPERATOR" \ + --arg token "$TOKEN_ADDRESS" \ + --arg module "$MODULE_ADDRESS" \ + --arg verifier "$VERIFIER_ADDRESS" \ + --arg releaseArtifact "$RELEASE_ARTIFACT_PATH" \ + --arg verifyMode "production-lock" \ + --arg commit "$MARK_GIT_COMMIT" \ + '{ + status: "passed", + verifyMode: $verifyMode, + rpcUrl: $rpcUrl, + chainId: $chainId, + deployer: $deployer, + owner: $owner, + settlementOperator: $settlementOperator, + token: $token, + module: $module, + verifier: $verifier, + releaseArtifact: $releaseArtifact, + gitCommit: $commit + }' >"$REHEARSAL_ARTIFACT_PATH" + +echo "Staging rehearsal PASSED" +echo "Release artifact: $RELEASE_ARTIFACT_PATH" +echo "Rehearsal artifact: $REHEARSAL_ARTIFACT_PATH" diff --git a/contracts/script/ops/settlement/PostDeployMARKSetup.s.sol b/contracts/script/ops/settlement/PostDeployMARKSetup.s.sol new file mode 100644 index 0000000..ad7994f --- /dev/null +++ b/contracts/script/ops/settlement/PostDeployMARKSetup.s.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../../src/settlement/verifier/AttestedSettlementVerifier.sol"; + +/// @notice Deterministic post-deploy configuration for existing MARK protocol contracts. +/// @dev Applies requested role/config updates and verifies resulting state in one broadcast. +contract PostDeployMARKSetup is Script { + bytes32 private constant DEFAULT_ADMIN_ROLE = 0x00; + + error MissingTokenAdminForRequestedConfig(); + error MissingAdapterAdminForRequestedConfig(); + error MissingModuleAdminForRequestedConfig(); + error MissingVerifierAdminForRequestedConfig(); + error ModuleAddressRequiredForVerifierConfig(); + error ProductionModeRequiresProofValidation(); + error ProductionModeRequiresVerifier(); + + struct Config { + uint256 deployerKey; + address deployer; + address tokenAddress; + address adapterAddress; + address moduleAddress; + address verifierAddress; + address bridgeOperator; + uint256 destinationChainId; + address settlementOperator; + address settlementAttester; + bool proofEnabled; + bool settlementProductionMode; + } + + struct Contracts { + RYLA token; + MARKBridgeAdapter adapter; + MARKSettlementModule module; + AttestedSettlementVerifier verifier; + } + + function run() external { + Config memory cfg = _loadConfig(); + Contracts memory ctr = _bindContracts(cfg); + + _validateConfig(cfg, ctr); + + vm.startBroadcast(cfg.deployerKey); + _applyConfig(cfg, ctr); + vm.stopBroadcast(); + + _assertConfig(cfg, ctr); + _logSummary(cfg); + } + + function validateWithConfig(Config memory cfg) external view { + Contracts memory ctr = _bindContracts(cfg); + _validateConfig(cfg, ctr); + } + + function _loadConfig() internal view returns (Config memory cfg) { + cfg.deployerKey = vm.envUint("PRIVATE_KEY"); + cfg.deployer = vm.addr(cfg.deployerKey); + + cfg.tokenAddress = vm.envAddress("MARK_RYLA_TOKEN"); + + cfg.adapterAddress = vm.envOr("MARK_BRIDGE_ADAPTER", address(0)); + if (cfg.adapterAddress == address(0)) { + cfg.adapterAddress = vm.envOr("VERIFY_MARK_BRIDGE_ADAPTER", address(0)); + } + + cfg.moduleAddress = vm.envOr("MARK_SETTLEMENT_MODULE", address(0)); + if (cfg.moduleAddress == address(0)) { + cfg.moduleAddress = vm.envOr("VERIFY_MARK_SETTLEMENT_MODULE", address(0)); + } + + cfg.verifierAddress = vm.envOr("MARK_SETTLEMENT_VERIFIER", address(0)); + cfg.proofEnabled = vm.envOr("MARK_SETTLEMENT_PROOF_ENABLED", false); + + cfg.bridgeOperator = vm.envOr("MARK_BRIDGE_OPERATOR", address(0)); + cfg.destinationChainId = vm.envOr("MARK_BRIDGE_DESTINATION_CHAIN_ID", uint256(0)); + + cfg.settlementOperator = vm.envOr("MARK_SETTLEMENT_OPERATOR", address(0)); + cfg.settlementAttester = vm.envOr("MARK_SETTLEMENT_ATTESTER", address(0)); + cfg.settlementProductionMode = vm.envOr("MARK_SETTLEMENT_PRODUCTION_MODE", false); + } + + function _bindContracts(Config memory cfg) internal pure returns (Contracts memory ctr) { + ctr.token = RYLA(cfg.tokenAddress); + if (cfg.adapterAddress != address(0)) { + ctr.adapter = MARKBridgeAdapter(cfg.adapterAddress); + } + if (cfg.moduleAddress != address(0)) { + ctr.module = MARKSettlementModule(cfg.moduleAddress); + } + if (cfg.verifierAddress != address(0)) { + ctr.verifier = AttestedSettlementVerifier(cfg.verifierAddress); + } + } + + function _validateConfig(Config memory cfg, Contracts memory ctr) internal view { + bool needsTokenConfig = cfg.moduleAddress != address(0); + bool needsAdapterConfig = + cfg.adapterAddress != address(0) && (cfg.bridgeOperator != address(0) || cfg.destinationChainId != 0); + bool needsModuleConfig = cfg.moduleAddress != address(0) + && ( + cfg.settlementOperator != address(0) + || cfg.verifierAddress != address(0) + || cfg.proofEnabled + || cfg.settlementProductionMode + ); + bool needsVerifierConfig = cfg.verifierAddress != address(0) && cfg.settlementAttester != address(0); + + if (cfg.verifierAddress != address(0) && cfg.moduleAddress == address(0)) { + revert ModuleAddressRequiredForVerifierConfig(); + } + if (cfg.settlementProductionMode && !cfg.proofEnabled) { + revert ProductionModeRequiresProofValidation(); + } + if (cfg.settlementProductionMode && cfg.verifierAddress == address(0)) { + revert ProductionModeRequiresVerifier(); + } + + if (needsTokenConfig && !ctr.token.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingTokenAdminForRequestedConfig(); + } + if (needsAdapterConfig && !ctr.adapter.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingAdapterAdminForRequestedConfig(); + } + if (needsModuleConfig && !ctr.module.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingModuleAdminForRequestedConfig(); + } + if (needsVerifierConfig && !ctr.verifier.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingVerifierAdminForRequestedConfig(); + } + } + + function _applyConfig(Config memory cfg, Contracts memory ctr) internal { + if (cfg.adapterAddress != address(0)) { + if (cfg.bridgeOperator != address(0)) { + ctr.adapter.setOperator(cfg.bridgeOperator, true); + } + if (cfg.destinationChainId != 0) { + ctr.adapter.setDestination(cfg.destinationChainId, true); + } + } + + if (cfg.moduleAddress != address(0)) { + ctr.token.setMinter(cfg.moduleAddress, true); + ctr.token.setBurner(cfg.moduleAddress, true); + + if (cfg.settlementOperator != address(0)) { + ctr.module.setOperator(cfg.settlementOperator, true); + } + if (cfg.verifierAddress != address(0) || cfg.proofEnabled) { + ctr.module.setVerifier(cfg.verifierAddress, cfg.proofEnabled); + } + if (cfg.settlementProductionMode) { + ctr.module.activateProductionMode(); + } + } + + if (cfg.verifierAddress != address(0) && cfg.settlementAttester != address(0)) { + ctr.verifier.setAttester(cfg.settlementAttester, true); + } + } + + function _assertConfig(Config memory cfg, Contracts memory ctr) internal view { + if (cfg.moduleAddress != address(0)) { + _assertTrue(ctr.token.hasRole(ctr.token.MINTER_ROLE(), cfg.moduleAddress), "Token missing module minter role"); + _assertTrue(ctr.token.hasRole(ctr.token.BURNER_ROLE(), cfg.moduleAddress), "Token missing module burner role"); + } + if (cfg.adapterAddress != address(0) && cfg.bridgeOperator != address(0)) { + _assertTrue(ctr.adapter.hasRole(ctr.adapter.OPERATOR_ROLE(), cfg.bridgeOperator), "Adapter missing requested operator role"); + } + if (cfg.adapterAddress != address(0) && cfg.destinationChainId != 0) { + _assertTrue(ctr.adapter.destinationEnabled(cfg.destinationChainId), "Adapter destination not enabled"); + } + if (cfg.moduleAddress != address(0) && cfg.settlementOperator != address(0)) { + _assertTrue(ctr.module.hasRole(ctr.module.OPERATOR_ROLE(), cfg.settlementOperator), "Module missing requested operator role"); + } + if (cfg.moduleAddress != address(0) && (cfg.verifierAddress != address(0) || cfg.proofEnabled)) { + _assertEq(address(ctr.module.verifier()), cfg.verifierAddress, "Module verifier mismatch"); + _assertEq(ctr.module.proofValidationEnabled(), cfg.proofEnabled, "Module proof setting mismatch"); + } + if (cfg.moduleAddress != address(0) && cfg.settlementProductionMode) { + _assertTrue(ctr.module.productionMode(), "Module production mode not enabled"); + } + if (cfg.verifierAddress != address(0) && cfg.settlementAttester != address(0)) { + _assertTrue(ctr.verifier.hasRole(ctr.verifier.ATTESTER_ROLE(), cfg.settlementAttester), "Verifier missing requested attester role"); + } + } + + function _logSummary(Config memory cfg) internal pure { + console.log("PostDeployMARKSetup completed for token:", cfg.tokenAddress); + if (cfg.adapterAddress != address(0)) console.log("Configured MARKBridgeAdapter:", cfg.adapterAddress); + if (cfg.moduleAddress != address(0)) console.log("Configured MARKSettlementModule:", cfg.moduleAddress); + if (cfg.verifierAddress != address(0)) console.log("Configured settlement verifier:", cfg.verifierAddress); + } + + function _assertEq(address left, address right, string memory err) internal pure { + if (left != right) revert(err); + } + + function _assertEq(bool left, bool right, string memory err) internal pure { + if (left != right) revert(err); + } + + function _assertTrue(bool condition, string memory err) internal pure { + if (!condition) revert(err); + } +} diff --git a/contracts/script/ops/settlement/PreflightMARKDeployment.s.sol b/contracts/script/ops/settlement/PreflightMARKDeployment.s.sol new file mode 100644 index 0000000..bf7630d --- /dev/null +++ b/contracts/script/ops/settlement/PreflightMARKDeployment.s.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../../src/settlement/verifier/AttestedSettlementVerifier.sol"; + +/// @notice Read-only preflight checks for deploy/setup operations. +/// @dev Set MARK_PREFLIGHT_MODE to: +/// 1 => DeployMARKStack +/// 2 => DeployMARKSettlementModule +/// 3 => PostDeployMARKSetup +contract PreflightMARKDeployment is Script { + bytes32 private constant DEFAULT_ADMIN_ROLE = 0x00; + + uint256 internal constant MODE_STACK = 1; + uint256 internal constant MODE_SETTLEMENT = 2; + uint256 internal constant MODE_POSTDEPLOY = 3; + + error InvalidPreflightMode(); + error OwnerRequired(); + error TokenAddressRequired(); + error AdapterAddressRequired(); + error ModuleAddressRequired(); + error VerifierAddressRequired(); + error AddressMustBeContract(); + error MissingAdapterAdminForRequestedConfig(); + error MissingModuleAdminForRequestedConfig(); + error MissingVerifierAdminForRequestedAttester(); + error MissingTokenAdminForRoleGrants(); + error MissingTokenAdminForRequestedConfig(); + error MissingAdapterAdminForRequestedSetup(); + error MissingModuleAdminForRequestedSetup(); + error MissingVerifierAdminForRequestedSetup(); + error ModuleAddressRequiredForVerifierConfig(); + error VerifierRequiredWhenProofEnabled(); + error VerifierRequiredWhenProductionModeEnabled(); + error ProofValidationRequiredWhenProductionModeEnabled(); + error AmbiguousVerifierConfig(); + + struct Config { + uint256 mode; + uint256 deployerKey; + address deployer; + address owner; + address tokenAddress; + address adapterAddress; + address moduleAddress; + address verifierAddress; + address bridgeOperator; + uint256 destinationChainId; + address settlementOperator; + address settlementAttester; + bool proofEnabled; + bool deployAttestedVerifier; + bool settlementProductionMode; + } + + function run() external view { + Config memory cfg = _loadConfig(); + preflightWithConfig(cfg); + } + + function preflightWithConfig(Config memory cfg) public view { + if (cfg.mode == MODE_STACK) { + _preflightStack(cfg); + } else if (cfg.mode == MODE_SETTLEMENT) { + _preflightSettlement(cfg); + } else if (cfg.mode == MODE_POSTDEPLOY) { + _preflightPostDeploy(cfg); + } else { + revert InvalidPreflightMode(); + } + + console.log("Preflight checks passed. Mode:", cfg.mode); + } + + function _loadConfig() internal view returns (Config memory cfg) { + cfg.mode = vm.envOr("MARK_PREFLIGHT_MODE", uint256(0)); + cfg.deployerKey = vm.envUint("PRIVATE_KEY"); + cfg.deployer = vm.addr(cfg.deployerKey); + + cfg.owner = vm.envOr("MARK_RYLA_OWNER", cfg.deployer); + cfg.tokenAddress = vm.envOr("MARK_RYLA_TOKEN", address(0)); + cfg.adapterAddress = vm.envOr("MARK_BRIDGE_ADAPTER", vm.envOr("VERIFY_MARK_BRIDGE_ADAPTER", address(0))); + cfg.moduleAddress = vm.envOr("MARK_SETTLEMENT_MODULE", vm.envOr("VERIFY_MARK_SETTLEMENT_MODULE", address(0))); + cfg.verifierAddress = vm.envOr("MARK_SETTLEMENT_VERIFIER", address(0)); + cfg.bridgeOperator = vm.envOr("MARK_BRIDGE_OPERATOR", address(0)); + cfg.destinationChainId = vm.envOr("MARK_BRIDGE_DESTINATION_CHAIN_ID", uint256(0)); + cfg.settlementOperator = vm.envOr("MARK_SETTLEMENT_OPERATOR", address(0)); + cfg.settlementAttester = vm.envOr("MARK_SETTLEMENT_ATTESTER", address(0)); + cfg.proofEnabled = vm.envOr("MARK_SETTLEMENT_PROOF_ENABLED", false); + cfg.deployAttestedVerifier = vm.envOr("MARK_DEPLOY_ATTESTED_VERIFIER", false); + cfg.settlementProductionMode = vm.envOr("MARK_SETTLEMENT_PRODUCTION_MODE", false); + } + + function _preflightStack(Config memory cfg) internal pure { + if (cfg.owner == address(0)) revert OwnerRequired(); + + bool requestedAdapterConfig = cfg.bridgeOperator != address(0) || cfg.destinationChainId != 0; + if (requestedAdapterConfig && cfg.owner != cfg.deployer) { + revert MissingAdapterAdminForRequestedConfig(); + } + } + + function _preflightSettlement(Config memory cfg) internal view { + if (cfg.owner == address(0)) revert OwnerRequired(); + if (cfg.tokenAddress == address(0)) revert TokenAddressRequired(); + _assertContract(cfg.tokenAddress); + + if (cfg.proofEnabled && cfg.verifierAddress == address(0) && !cfg.deployAttestedVerifier) { + revert VerifierRequiredWhenProofEnabled(); + } + if (cfg.settlementProductionMode && cfg.verifierAddress == address(0) && !cfg.deployAttestedVerifier) { + revert VerifierRequiredWhenProductionModeEnabled(); + } + if (cfg.settlementProductionMode && !cfg.proofEnabled) { + revert ProofValidationRequiredWhenProductionModeEnabled(); + } + if (cfg.deployAttestedVerifier && cfg.verifierAddress != address(0)) { + revert AmbiguousVerifierConfig(); + } + if (cfg.verifierAddress != address(0)) { + _assertContract(cfg.verifierAddress); + } + + bool requestedModuleConfig = cfg.settlementOperator != address(0) || cfg.verifierAddress != address(0) || cfg.proofEnabled; + if (requestedModuleConfig && cfg.owner != cfg.deployer) { + revert MissingModuleAdminForRequestedConfig(); + } + if (cfg.deployAttestedVerifier && cfg.settlementAttester != address(0) && cfg.owner != cfg.deployer) { + revert MissingVerifierAdminForRequestedAttester(); + } + + if (!RYLA(cfg.tokenAddress).hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingTokenAdminForRoleGrants(); + } + } + + function _preflightPostDeploy(Config memory cfg) internal view { + if (cfg.tokenAddress == address(0)) revert TokenAddressRequired(); + if (cfg.adapterAddress == address(0)) revert AdapterAddressRequired(); + if (cfg.moduleAddress == address(0)) revert ModuleAddressRequired(); + + _assertContract(cfg.tokenAddress); + _assertContract(cfg.adapterAddress); + _assertContract(cfg.moduleAddress); + + RYLA token = RYLA(cfg.tokenAddress); + MARKBridgeAdapter adapter = MARKBridgeAdapter(cfg.adapterAddress); + MARKSettlementModule module = MARKSettlementModule(cfg.moduleAddress); + + if (cfg.verifierAddress != address(0)) { + _assertContract(cfg.verifierAddress); + if (cfg.moduleAddress == address(0)) revert ModuleAddressRequiredForVerifierConfig(); + } + if (cfg.proofEnabled && cfg.verifierAddress == address(0)) { + revert VerifierRequiredWhenProofEnabled(); + } + if (cfg.settlementProductionMode && cfg.verifierAddress == address(0)) { + revert VerifierRequiredWhenProductionModeEnabled(); + } + if (cfg.settlementProductionMode && !cfg.proofEnabled) { + revert ProofValidationRequiredWhenProductionModeEnabled(); + } + + bool needsTokenSetup = true; // setup always grants module mint/burn roles + bool needsAdapterSetup = cfg.bridgeOperator != address(0) || cfg.destinationChainId != 0; + bool needsModuleSetup = cfg.settlementOperator != address(0) + || cfg.verifierAddress != address(0) + || cfg.proofEnabled + || cfg.settlementProductionMode; + bool needsVerifierSetup = cfg.verifierAddress != address(0) && cfg.settlementAttester != address(0); + + if (needsTokenSetup && !token.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingTokenAdminForRequestedConfig(); + } + if (needsAdapterSetup && !adapter.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingAdapterAdminForRequestedSetup(); + } + if (needsModuleSetup && !module.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingModuleAdminForRequestedSetup(); + } + if (needsVerifierSetup) { + if (cfg.verifierAddress == address(0)) revert VerifierAddressRequired(); + if (!AttestedSettlementVerifier(cfg.verifierAddress).hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) { + revert MissingVerifierAdminForRequestedSetup(); + } + } + } + + function _assertContract(address target) internal view { + if (target.code.length == 0) revert AddressMustBeContract(); + } +} diff --git a/contracts/script/ops/settlement/ReleaseMARK.s.sol b/contracts/script/ops/settlement/ReleaseMARK.s.sol new file mode 100644 index 0000000..53eb768 --- /dev/null +++ b/contracts/script/ops/settlement/ReleaseMARK.s.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {DeployMARKStack} from "../../deploy/bridge/DeployMARKStack.s.sol"; +import {DeployMARKSettlementModule} from "../../deploy/settlement/DeployMARKSettlementModule.s.sol"; +import {PostDeployMARKSetup} from "./PostDeployMARKSetup.s.sol"; +import {PreflightMARKDeployment} from "./PreflightMARKDeployment.s.sol"; +import {VerifyMARKDeployment} from "./VerifyMARKDeployment.s.sol"; + +/// @notice Release orchestrator for MARK deployments. +/// @dev Sequence: preflight -> deploy -> optional postdeploy setup -> verify -> artifact. +contract ReleaseMARK is Script { + uint256 internal constant MODE_STACK = 1; + uint256 internal constant MODE_SETTLEMENT = 2; + uint256 internal constant MODE_POSTDEPLOY = 3; + + struct ReleaseResult { + bool execute; + bool runPostDeploy; + address deployer; + address token; + address adapter; + address module; + address verifier; + } + + function dryRunWithConfig( + address deployer, + address owner, + address bridgeOperator, + uint256 destinationChainId, + bool runPostDeploy + ) + external + returns (ReleaseResult memory result) + { + PreflightMARKDeployment preflight = new PreflightMARKDeployment(); + _runStackPreflightFromValues(preflight, 0, deployer, owner, bridgeOperator, destinationChainId); + + result = ReleaseResult({ + execute: false, + runPostDeploy: runPostDeploy, + deployer: deployer, + token: address(0), + adapter: address(0), + module: address(0), + verifier: address(0) + }); + } + + function run() external { + bool execute = vm.envOr("MARK_RELEASE_EXECUTE", false); + bool runPostDeploy = vm.envOr("MARK_RELEASE_RUN_POSTDEPLOY", false); + bool writeArtifact = vm.envOr("MARK_RELEASE_WRITE_ARTIFACT", false); + + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(deployerKey); + + PreflightMARKDeployment preflight = new PreflightMARKDeployment(); + VerifyMARKDeployment verifyScript = new VerifyMARKDeployment(); + + _runStackPreflight(preflight, deployerKey, deployer); + + if (!execute) { + console.log("MARK_RELEASE_EXECUTE=false. Dry-run complete (no transactions broadcast)."); + if (writeArtifact) { + _writeArtifact( + ReleaseResult({ + execute: false, + runPostDeploy: runPostDeploy, + deployer: deployer, + token: address(0), + adapter: address(0), + module: address(0), + verifier: address(0) + }) + ); + } else { + console.log("Artifact write disabled (MARK_RELEASE_WRITE_ARTIFACT=false)."); + } + return; + } + + DeployMARKStack deployStack = new DeployMARKStack(); + (RYLA token, MARKBridgeAdapter adapter) = deployStack.run(); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(token))); + + _runSettlementPreflight(preflight, deployerKey, deployer, address(token)); + + DeployMARKSettlementModule deploySettlement = new DeployMARKSettlementModule(); + MARKSettlementModule module = deploySettlement.run(); + + if (runPostDeploy) { + vm.setEnv("MARK_BRIDGE_ADAPTER", vm.toString(address(adapter))); + vm.setEnv("MARK_SETTLEMENT_MODULE", vm.toString(address(module))); + + _runPostDeployPreflight(preflight, deployerKey, deployer, address(token), address(adapter), address(module)); + + PostDeployMARKSetup setup = new PostDeployMARKSetup(); + setup.run(); + } + + _runVerify(verifyScript, address(token), address(adapter), address(module)); + + ReleaseResult memory result = ReleaseResult({ + execute: true, + runPostDeploy: runPostDeploy, + deployer: deployer, + token: address(token), + adapter: address(adapter), + module: address(module), + verifier: address(module.verifier()) + }); + if (writeArtifact) { + _writeArtifact(result); + } else { + console.log("Artifact write disabled (MARK_RELEASE_WRITE_ARTIFACT=false)."); + } + } + + function _runStackPreflight(PreflightMARKDeployment preflight, uint256 deployerKey, address deployer) internal view { + address owner = vm.envOr("MARK_RYLA_OWNER", deployer); + address bridgeOperator = vm.envOr("MARK_BRIDGE_OPERATOR", address(0)); + uint256 destinationChainId = vm.envOr("MARK_BRIDGE_DESTINATION_CHAIN_ID", uint256(0)); + _runStackPreflightFromValues(preflight, deployerKey, deployer, owner, bridgeOperator, destinationChainId); + } + + function _runStackPreflightFromValues( + PreflightMARKDeployment preflight, + uint256 deployerKey, + address deployer, + address owner, + address bridgeOperator, + uint256 destinationChainId + ) + internal + view + { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_STACK; + cfg.deployerKey = deployerKey; + cfg.deployer = deployer; + cfg.owner = owner; + cfg.bridgeOperator = bridgeOperator; + cfg.destinationChainId = destinationChainId; + preflight.preflightWithConfig(cfg); + } + + function _runSettlementPreflight( + PreflightMARKDeployment preflight, + uint256 deployerKey, + address deployer, + address tokenAddress + ) + internal + view + { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_SETTLEMENT; + cfg.deployerKey = deployerKey; + cfg.deployer = deployer; + cfg.owner = vm.envOr("MARK_MODULE_OWNER", deployer); + cfg.tokenAddress = tokenAddress; + cfg.settlementOperator = vm.envOr("MARK_SETTLEMENT_OPERATOR", address(0)); + cfg.verifierAddress = vm.envOr("MARK_SETTLEMENT_VERIFIER", address(0)); + cfg.settlementAttester = vm.envOr("MARK_SETTLEMENT_ATTESTER", address(0)); + cfg.proofEnabled = vm.envOr("MARK_SETTLEMENT_PROOF_ENABLED", false); + cfg.deployAttestedVerifier = vm.envOr("MARK_DEPLOY_ATTESTED_VERIFIER", false); + cfg.settlementProductionMode = vm.envOr("MARK_SETTLEMENT_PRODUCTION_MODE", false); + preflight.preflightWithConfig(cfg); + } + + function _runPostDeployPreflight( + PreflightMARKDeployment preflight, + uint256 deployerKey, + address deployer, + address tokenAddress, + address adapterAddress, + address moduleAddress + ) + internal + view + { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_POSTDEPLOY; + cfg.deployerKey = deployerKey; + cfg.deployer = deployer; + cfg.tokenAddress = tokenAddress; + cfg.adapterAddress = adapterAddress; + cfg.moduleAddress = moduleAddress; + cfg.bridgeOperator = vm.envOr("MARK_BRIDGE_OPERATOR", address(0)); + cfg.destinationChainId = vm.envOr("MARK_BRIDGE_DESTINATION_CHAIN_ID", uint256(0)); + cfg.settlementOperator = vm.envOr("MARK_SETTLEMENT_OPERATOR", address(0)); + cfg.verifierAddress = vm.envOr("MARK_SETTLEMENT_VERIFIER", address(0)); + cfg.settlementAttester = vm.envOr("MARK_SETTLEMENT_ATTESTER", address(0)); + cfg.proofEnabled = vm.envOr("MARK_SETTLEMENT_PROOF_ENABLED", false); + cfg.settlementProductionMode = vm.envOr("MARK_SETTLEMENT_PRODUCTION_MODE", false); + preflight.preflightWithConfig(cfg); + } + + function _runVerify( + VerifyMARKDeployment verifyScript, + address tokenAddress, + address adapterAddress, + address moduleAddress + ) + internal + view + { + bool strictVerify = vm.envOr("MARK_RELEASE_STRICT_VERIFY", true); + VerifyMARKDeployment.ExpectedConfig memory cfg; + cfg.tokenAddress = tokenAddress; + cfg.expectedOwner = vm.envOr("VERIFY_MARK_RYLA_OWNER", vm.envOr("MARK_RYLA_OWNER", address(0))); + cfg.adapterAddress = adapterAddress; + cfg.expectedBridgeOperator = vm.envOr("VERIFY_MARK_BRIDGE_OPERATOR", address(0)); + cfg.expectedDestinationChain = vm.envOr("VERIFY_MARK_BRIDGE_DEST_CHAIN", uint256(0)); + cfg.expectedBridgeMaxPerTx = vm.envOr("VERIFY_MARK_BRIDGE_MAX_PER_TX", uint256(0)); + cfg.expectedBridgeDailyCap = vm.envOr("VERIFY_MARK_BRIDGE_DAILY_CAP", uint256(0)); + cfg.moduleAddress = moduleAddress; + cfg.expectedSettlementOperator = vm.envOr("VERIFY_MARK_SETTLEMENT_OPERATOR", address(0)); + + MARKSettlementModule module = MARKSettlementModule(moduleAddress); + if (strictVerify) { + cfg.expectedProofEnabled = vm.envBool("VERIFY_MARK_SETTLEMENT_PROOF_ENABLED"); + cfg.expectedProductionMode = vm.envBool("VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE"); + cfg.expectedVerifier = vm.envAddress("VERIFY_MARK_SETTLEMENT_VERIFIER"); + } else { + cfg.expectedProofEnabled = module.proofValidationEnabled(); + cfg.expectedProductionMode = module.productionMode(); + cfg.expectedVerifier = address(module.verifier()); + } + cfg.expectedAttester = vm.envOr("VERIFY_MARK_SETTLEMENT_ATTESTER", address(0)); + + verifyScript.runWithConfig(cfg); + } + + function _writeArtifact(ReleaseResult memory result) internal { + string memory path = vm.envOr("MARK_RELEASE_ARTIFACT_PATH", string("broadcast/mark-release-latest.json")); + string memory root = "release"; + _ensureParentDir(path); + + vm.serializeString(root, "protocol", "MARK"); + vm.serializeString(root, "tokenSymbol", "RYLA"); + vm.serializeBool(root, "execute", result.execute); + vm.serializeBool(root, "runPostDeploy", result.runPostDeploy); + vm.serializeAddress(root, "deployer", result.deployer); + vm.serializeAddress(root, "token", result.token); + vm.serializeAddress(root, "adapter", result.adapter); + vm.serializeAddress(root, "module", result.module); + vm.serializeAddress(root, "verifier", result.verifier); + vm.serializeUint(root, "chainId", block.chainid); + vm.serializeUint(root, "timestamp", block.timestamp); + string memory json = vm.serializeString(root, "gitCommit", vm.envOr("MARK_GIT_COMMIT", string("unknown"))); + vm.writeJson(json, path); + + console.log("Release artifact written:", path); + } + + function _ensureParentDir(string memory path) internal { + bytes memory raw = bytes(path); + uint256 split = type(uint256).max; + for (uint256 i = raw.length; i > 0; i--) { + if (raw[i - 1] == "/") { + split = i - 1; + break; + } + } + if (split == type(uint256).max || split == 0) return; + + bytes memory parent = new bytes(split); + for (uint256 j = 0; j < split; j++) { + parent[j] = raw[j]; + } + vm.createDir(string(parent), true); + } +} diff --git a/contracts/script/ops/settlement/VerifyMARKDeployment.s.sol b/contracts/script/ops/settlement/VerifyMARKDeployment.s.sol new file mode 100644 index 0000000..c557c95 --- /dev/null +++ b/contracts/script/ops/settlement/VerifyMARKDeployment.s.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../../src/settlement/verifier/AttestedSettlementVerifier.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; + +/// @notice Read-only deployment verification script for the MARK protocol stack (RYLA token + MARK modules). +/// @dev Run with `forge script ... --rpc-url ` after setting VERIFY_* env vars. +contract VerifyMARKDeployment is Script { + bytes32 private constant DEFAULT_ADMIN_ROLE = 0x00; + uint48 private constant EXPECTED_ADMIN_DELAY = 1 days; + + struct ExpectedConfig { + address tokenAddress; + address expectedOwner; + address adapterAddress; + address expectedBridgeOperator; + uint256 expectedDestinationChain; + uint256 expectedBridgeMaxPerTx; + uint256 expectedBridgeDailyCap; + address moduleAddress; + address expectedSettlementOperator; + bool expectedProofEnabled; + bool expectedProductionMode; + address expectedVerifier; + address expectedAttester; + } + + function run() external view { + ExpectedConfig memory cfg = _loadConfig(); + _runWithConfig(cfg); + } + + function runWithConfig(ExpectedConfig memory cfg) external view { + _runWithConfig(cfg); + } + + function _runWithConfig(ExpectedConfig memory cfg) internal view { + RYLA token = RYLA(cfg.tokenAddress); + + _assertEq(token.name(), "RYLA", "Token name mismatch"); + _assertEq(token.symbol(), "RYLA", "Token symbol mismatch"); + _assertEq(uint256(token.defaultAdminDelay()), uint256(EXPECTED_ADMIN_DELAY), "Token admin delay mismatch"); + + if (cfg.expectedOwner != address(0)) { + _assertTrue(token.hasRole(DEFAULT_ADMIN_ROLE, cfg.expectedOwner), "Token admin missing expected owner"); + } + + console.log("Verified token metadata and owner role:", cfg.tokenAddress); + + if (cfg.adapterAddress != address(0)) { + MARKBridgeAdapter adapter = MARKBridgeAdapter(cfg.adapterAddress); + _assertEq(address(adapter.TOKEN()), cfg.tokenAddress, "Bridge adapter token mismatch"); + _assertEq( + address(adapter.SUPERCHAIN_TOKEN_BRIDGE()), + PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE, + "Bridge adapter predeploy mismatch" + ); + + if (cfg.expectedOwner != address(0)) { + _assertTrue(adapter.hasRole(DEFAULT_ADMIN_ROLE, cfg.expectedOwner), "Adapter admin missing expected owner"); + } + _assertEq( + uint256(adapter.defaultAdminDelay()), uint256(EXPECTED_ADMIN_DELAY), "Adapter admin delay mismatch" + ); + if (cfg.expectedBridgeOperator != address(0)) { + _assertTrue( + adapter.hasRole(adapter.OPERATOR_ROLE(), cfg.expectedBridgeOperator), "Adapter missing expected operator" + ); + } + if (cfg.expectedDestinationChain != 0) { + _assertTrue( + adapter.destinationEnabled(cfg.expectedDestinationChain), "Adapter destination chain not enabled" + ); + } + if (cfg.expectedBridgeMaxPerTx != 0) { + _assertEq(adapter.maxPerTx(), cfg.expectedBridgeMaxPerTx, "Bridge adapter maxPerTx mismatch"); + } + if (cfg.expectedBridgeDailyCap != 0) { + _assertEq(adapter.dailyCap(), cfg.expectedBridgeDailyCap, "Bridge adapter dailyCap mismatch"); + } + + console.log("Verified bridge adapter wiring:", cfg.adapterAddress); + } + + if (cfg.moduleAddress != address(0)) { + MARKSettlementModule module = MARKSettlementModule(cfg.moduleAddress); + _assertEq(address(module.TOKEN()), cfg.tokenAddress, "Settlement module token mismatch"); + _assertTrue(token.hasRole(token.MINTER_ROLE(), cfg.moduleAddress), "Token missing module minter role"); + _assertTrue(token.hasRole(token.BURNER_ROLE(), cfg.moduleAddress), "Token missing module burner role"); + + if (cfg.expectedOwner != address(0)) { + _assertTrue(module.hasRole(DEFAULT_ADMIN_ROLE, cfg.expectedOwner), "Module admin missing expected owner"); + } + _assertEq( + uint256(module.defaultAdminDelay()), uint256(EXPECTED_ADMIN_DELAY), "Module admin delay mismatch" + ); + if (cfg.expectedSettlementOperator != address(0)) { + _assertTrue( + module.hasRole(module.OPERATOR_ROLE(), cfg.expectedSettlementOperator), + "Module missing expected operator" + ); + } + + _assertEq(module.proofValidationEnabled(), cfg.expectedProofEnabled, "Module proof toggle mismatch"); + _assertEq(module.productionMode(), cfg.expectedProductionMode, "Module production mode mismatch"); + if (module.proofValidationEnabled()) { + _assertTrue(address(module.verifier()) != address(0), "Module proof enabled but verifier is zero"); + } + + if (cfg.expectedVerifier != address(0)) { + _assertEq(address(module.verifier()), cfg.expectedVerifier, "Module verifier mismatch"); + } + + console.log("Verified settlement module wiring:", cfg.moduleAddress); + } + + if (cfg.expectedVerifier != address(0) && cfg.expectedAttester != address(0)) { + AttestedSettlementVerifier verifier = AttestedSettlementVerifier(cfg.expectedVerifier); + _assertEq( + uint256(verifier.defaultAdminDelay()), uint256(EXPECTED_ADMIN_DELAY), "Verifier admin delay mismatch" + ); + if (cfg.expectedOwner != address(0)) { + _assertTrue(verifier.hasRole(DEFAULT_ADMIN_ROLE, cfg.expectedOwner), "Verifier admin missing expected owner"); + } + _assertTrue( + verifier.hasRole(verifier.ATTESTER_ROLE(), cfg.expectedAttester), "Verifier missing expected attester role" + ); + console.log("Verified verifier attester role:", cfg.expectedVerifier); + } + + console.log("MARK protocol deployment verification complete"); + } + + function _loadConfig() internal view returns (ExpectedConfig memory cfg) { + cfg.tokenAddress = vm.envAddress("VERIFY_MARK_RYLA_TOKEN"); + cfg.expectedOwner = vm.envOr("VERIFY_MARK_RYLA_OWNER", address(0)); + cfg.adapterAddress = vm.envOr("VERIFY_MARK_BRIDGE_ADAPTER", address(0)); + cfg.expectedBridgeOperator = vm.envOr("VERIFY_MARK_BRIDGE_OPERATOR", address(0)); + cfg.expectedDestinationChain = vm.envOr("VERIFY_MARK_BRIDGE_DEST_CHAIN", uint256(0)); + cfg.expectedBridgeMaxPerTx = vm.envOr("VERIFY_MARK_BRIDGE_MAX_PER_TX", uint256(0)); + cfg.expectedBridgeDailyCap = vm.envOr("VERIFY_MARK_BRIDGE_DAILY_CAP", uint256(0)); + cfg.moduleAddress = vm.envOr("VERIFY_MARK_SETTLEMENT_MODULE", address(0)); + cfg.expectedSettlementOperator = vm.envOr("VERIFY_MARK_SETTLEMENT_OPERATOR", address(0)); + cfg.expectedProofEnabled = vm.envOr("VERIFY_MARK_SETTLEMENT_PROOF_ENABLED", false); + cfg.expectedProductionMode = vm.envOr("VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE", false); + cfg.expectedVerifier = vm.envOr("VERIFY_MARK_SETTLEMENT_VERIFIER", address(0)); + cfg.expectedAttester = vm.envOr("VERIFY_MARK_SETTLEMENT_ATTESTER", address(0)); + } + + function _assertEq(string memory left, string memory right, string memory err) internal pure { + if (keccak256(bytes(left)) != keccak256(bytes(right))) { + revert(err); + } + } + + function _assertEq(address left, address right, string memory err) internal pure { + if (left != right) { + revert(err); + } + } + + function _assertEq(bool left, bool right, string memory err) internal pure { + if (left != right) { + revert(err); + } + } + + function _assertEq(uint256 left, uint256 right, string memory err) internal pure { + if (left != right) { + revert(err); + } + } + + + function _assertTrue(bool condition, string memory err) internal pure { + if (!condition) { + revert(err); + } + } +} diff --git a/contracts/script/ops/smoke-production-mode.sh b/contracts/script/ops/smoke-production-mode.sh new file mode 100755 index 0000000..bf124b4 --- /dev/null +++ b/contracts/script/ops/smoke-production-mode.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd anvil +require_cmd forge +require_cmd cast +require_cmd jq + +ANVIL_HOST="${ANVIL_HOST:-127.0.0.1}" +ANVIL_PORT="${ANVIL_PORT:-8545}" +HTTP_RPC_URL="${HTTP_RPC_URL:-http://${ANVIL_HOST}:${ANVIL_PORT}}" +RPC_URL="${RPC_URL:-ws://${ANVIL_HOST}:${ANVIL_PORT}}" +START_LOCAL_ANVIL="${START_LOCAL_ANVIL:-true}" + +PRIVATE_KEY="${PRIVATE_KEY:-0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80}" +OWNER="${MARK_RYLA_OWNER:-0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266}" +BRIDGE_OPERATOR="${MARK_BRIDGE_OPERATOR:-0x70997970C51812dc3A010C7d01b50e0d17dc79C8}" +SETTLEMENT_OPERATOR="${MARK_SETTLEMENT_OPERATOR:-0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC}" +BRIDGE_DEST_CHAIN="${MARK_BRIDGE_DESTINATION_CHAIN_ID:-10}" +ARTIFACT_PATH="${MARK_RELEASE_ARTIFACT_PATH:-broadcast/mark-release-prodmode-ci.json}" + +ANVIL_PID="" +cleanup() { + if [[ -n "$ANVIL_PID" ]]; then + kill "$ANVIL_PID" >/dev/null 2>&1 || true + fi +} +trap cleanup EXIT + +if [[ "$START_LOCAL_ANVIL" == "true" ]]; then + anvil --host "$ANVIL_HOST" --port "$ANVIL_PORT" >/tmp/mark-anvil.log 2>&1 & + ANVIL_PID=$! +fi + +for _ in $(seq 1 30); do + if /usr/bin/curl -sS -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \ + "$HTTP_RPC_URL" >/dev/null 2>&1; then + break + fi + sleep 1 +done + +/usr/bin/curl -sS -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \ + "$HTTP_RPC_URL" >/dev/null + +# Deploy an attested verifier with cast to avoid local forge create broadcast quirks. +BYTECODE="$(forge inspect src/settlement/verifier/AttestedSettlementVerifier.sol:AttestedSettlementVerifier bytecode)" +ARGS="$(cast abi-encode "constructor(address)" "$OWNER")" +DATA="${BYTECODE}${ARGS#0x}" +SEND_JSON="$(cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" --create "$DATA" --json 2>/dev/null || true)" +VERIFIER_TX="$(echo "$SEND_JSON" | jq -r '.transactionHash' 2>/dev/null || true)" +if [[ -z "$VERIFIER_TX" || "$VERIFIER_TX" == "null" ]]; then + SEND_OUT="$(cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" --create "$DATA")" + VERIFIER_TX="$(echo "$SEND_OUT" | rg -o '0x[0-9a-fA-F]{64}' | head -n1 || true)" +fi +VERIFIER_ADDRESS="$(cast receipt "$VERIFIER_TX" --rpc-url "$RPC_URL" --json | jq -r '.contractAddress')" + +if [[ "$VERIFIER_ADDRESS" == "0x0000000000000000000000000000000000000000" ]]; then + echo "Verifier deployment failed: zero address" >&2 + exit 1 +fi + +export PRIVATE_KEY RPC_URL +export MARK_RELEASE_EXECUTE=true +export MARK_RELEASE_RUN_POSTDEPLOY=true +export MARK_RELEASE_WRITE_ARTIFACT=true +export MARK_RELEASE_ARTIFACT_PATH="$ARTIFACT_PATH" +export MARK_RELEASE_STRICT_VERIFY=true +export MARK_GIT_COMMIT="${MARK_GIT_COMMIT:-local-prodmode-smoke}" + +export MARK_RYLA_OWNER="$OWNER" +export MARK_MODULE_OWNER="$OWNER" +export MARK_BRIDGE_OPERATOR="$BRIDGE_OPERATOR" +export MARK_BRIDGE_DESTINATION_CHAIN_ID="$BRIDGE_DEST_CHAIN" +export MARK_SETTLEMENT_OPERATOR="$SETTLEMENT_OPERATOR" +export MARK_SETTLEMENT_VERIFIER="$VERIFIER_ADDRESS" +export MARK_SETTLEMENT_PROOF_ENABLED=true +export MARK_SETTLEMENT_PRODUCTION_MODE=true +export MARK_SETTLEMENT_ATTESTER="${MARK_SETTLEMENT_ATTESTER:-0x0000000000000000000000000000000000000000}" + +export VERIFY_MARK_RYLA_OWNER="$OWNER" +export VERIFY_MARK_BRIDGE_OPERATOR="$BRIDGE_OPERATOR" +export VERIFY_MARK_BRIDGE_DEST_CHAIN="$BRIDGE_DEST_CHAIN" +export VERIFY_MARK_SETTLEMENT_OPERATOR="$SETTLEMENT_OPERATOR" +export VERIFY_MARK_SETTLEMENT_PROOF_ENABLED=true +export VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE=true +export VERIFY_MARK_SETTLEMENT_VERIFIER="$VERIFIER_ADDRESS" + +forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url "$RPC_URL" --broadcast -q + +if [[ ! -f "$ARTIFACT_PATH" ]]; then + echo "Missing artifact: $ARTIFACT_PATH" >&2 + exit 1 +fi + +TOKEN="$(jq -r '.token' "$ARTIFACT_PATH")" +ADAPTER="$(jq -r '.adapter' "$ARTIFACT_PATH")" +MODULE="$(jq -r '.module' "$ARTIFACT_PATH")" +VERIFIER_ARTIFACT="$(jq -r '.verifier' "$ARTIFACT_PATH")" +PROOF="$(cast call "$MODULE" "proofValidationEnabled()(bool)" --rpc-url "$RPC_URL")" +PRODUCTION_MODE="$(cast call "$MODULE" "productionMode()(bool)" --rpc-url "$RPC_URL")" +ONCHAIN_VERIFIER="$(cast call "$MODULE" "verifier()(address)" --rpc-url "$RPC_URL")" + +echo "Production mode smoke PASSED" +echo "Artifact: $ARTIFACT_PATH" +echo "Verifier predeploy: $VERIFIER_ADDRESS" +echo "Token: $TOKEN" +echo "Adapter: $ADAPTER" +echo "Module: $MODULE" +echo "Verifier (artifact): $VERIFIER_ARTIFACT" +echo "Proof enabled: $PROOF" +echo "Production mode: $PRODUCTION_MODE" +echo "Verifier (onchain): $ONCHAIN_VERIFIER" diff --git a/contracts/src/bridge/MARKBridgeAdapter.sol b/contracts/src/bridge/MARKBridgeAdapter.sol new file mode 100644 index 0000000..1b3c7fa --- /dev/null +++ b/contracts/src/bridge/MARKBridgeAdapter.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {ISuperchainTokenBridge} from "@interop-lib/interfaces/ISuperchainTokenBridge.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; +import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; +import {BridgeErrors} from "../errors/BridgeErrors.sol"; + +/// @title MARKBridgeAdapter +/// @notice Operator-gated bridge-out adapter for RYLA using SuperchainTokenBridge. +/// @dev Uses destination allowlist and optional per-tx / daily caps. +contract MARKBridgeAdapter is ReentrancyGuard, AccessControlDefaultAdminRules, BridgeErrors { + using SafeERC20 for IERC20; + + event OperatorUpdated(address indexed operator, bool enabled); + event DestinationUpdated(uint256 indexed destinationChainId, bool enabled); + event BridgeLimitsUpdated(uint256 maxPerTx, uint256 dailyCap); + event BridgedOut( + address indexed operator, + address indexed recipient, + uint256 indexed destinationChainId, + uint256 amount, + bytes32 messageHash + ); + + uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + + IERC20 public immutable TOKEN; + ISuperchainTokenBridge public constant SUPERCHAIN_TOKEN_BRIDGE = + ISuperchainTokenBridge(PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE); + + mapping(uint256 => bool) public destinationEnabled; + + uint256 public maxPerTx; + uint256 public dailyCap; + uint64 public dailyCapEpoch; + uint256 public bridgedInDailyCapEpoch; + + constructor(address initialAdmin, address tokenAddress) + AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) + { + if (initialAdmin == address(0)) revert ZeroAddress(); + if (tokenAddress == address(0)) revert ZeroAddress(); + TOKEN = IERC20(tokenAddress); + } + + function setOperator(address operator, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (operator == address(0)) revert ZeroAddress(); + if (enabled) { + _grantRole(OPERATOR_ROLE, operator); + } else { + _revokeRole(OPERATOR_ROLE, operator); + } + emit OperatorUpdated(operator, enabled); + } + + function setDestination(uint256 destinationChainId, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (destinationChainId == 0) revert InvalidChainId(); + destinationEnabled[destinationChainId] = enabled; + emit DestinationUpdated(destinationChainId, enabled); + } + + /// @notice Sets optional bridge limits. Set `0` to disable each limiter. + function setBridgeLimits(uint256 maxPerTx_, uint256 dailyCap_) external onlyRole(DEFAULT_ADMIN_ROLE) { + maxPerTx = maxPerTx_; + dailyCap = dailyCap_; + dailyCapEpoch = 0; + bridgedInDailyCapEpoch = 0; + emit BridgeLimitsUpdated(maxPerTx_, dailyCap_); + } + + function bridgeTo(address recipient, uint256 amount, uint256 destinationChainId) + external + onlyRole(OPERATOR_ROLE) + nonReentrant + returns (bytes32 messageHash) + { + if (recipient == address(0)) revert ZeroAddress(); + if (amount == 0) revert InvalidAmount(); + if (destinationChainId == 0) revert InvalidChainId(); + if (!destinationEnabled[destinationChainId]) revert DestinationDisabled(); + + _consumeLimits(amount); + + TOKEN.safeTransferFrom(msg.sender, address(this), amount); + TOKEN.forceApprove(address(SUPERCHAIN_TOKEN_BRIDGE), amount); + + messageHash = SUPERCHAIN_TOKEN_BRIDGE.sendERC20(address(TOKEN), recipient, amount, destinationChainId); + emit BridgedOut(msg.sender, recipient, destinationChainId, amount, messageHash); + } + + function _consumeLimits(uint256 amount) internal { + uint256 maxPerTx_ = maxPerTx; + if (maxPerTx_ > 0 && amount > maxPerTx_) revert MaxPerTxExceeded(); + + uint256 dailyCap_ = dailyCap; + if (dailyCap_ == 0) return; + + uint64 epoch = uint64(block.timestamp / 1 days); + if (epoch != dailyCapEpoch) { + dailyCapEpoch = epoch; + bridgedInDailyCapEpoch = 0; + } + + uint256 nextBridged = bridgedInDailyCapEpoch + amount; + if (nextBridged > dailyCap_) revert DailyCapExceeded(); + bridgedInDailyCapEpoch = nextBridged; + } +} diff --git a/contracts/src/errors/SettlementErrors.sol b/contracts/src/errors/SettlementErrors.sol index 1bae67c..458c00a 100644 --- a/contracts/src/errors/SettlementErrors.sol +++ b/contracts/src/errors/SettlementErrors.sol @@ -6,6 +6,7 @@ abstract contract SettlementErrors { error InvalidAmount(); error InvalidIntent(); error IntentAlreadyConsumed(); + error BurnEscrowInvariantFailed(); error VerifierRequired(); error VerificationFailed(); error ProductionModeAlreadyEnabled(); diff --git a/contracts/src/examples/CrossChainCounter.sol b/contracts/src/examples/CrossChainCounter.sol new file mode 100644 index 0000000..02d9658 --- /dev/null +++ b/contracts/src/examples/CrossChainCounter.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; +import {CrossDomainMessageLib} from "@interop-lib/libraries/CrossDomainMessageLib.sol"; + +/// @title CrossChainCounter +/// @notice A simple Counter that can only be incremented **from other chains** +contract CrossChainCounter { + /// @notice Emitted when the counter is incremented from another chain + /// @param senderChainId The chain ID where the increment request originated + /// @param sender The address that requested the increment on the origin chain + /// @param newValue The new value of the counter after incrementing + event CounterIncremented(uint256 indexed senderChainId, address indexed sender, uint256 newValue); + + /// @notice Struct to store the last incrementer's details + struct Incrementer { + uint256 chainId; // The chain ID where the increment originated + address sender; // The address that triggered the increment + } + + /// @notice The current value of the counter + uint256 public number; + + /// @notice Details about the last address to increment the counter + Incrementer public lastIncrementer; + + /// @notice Increments the counter by 1 + /// @dev Can only be called through the L2ToL2CrossDomainMessenger contract + function increment() public { + // Verify that this function is being called by the L2ToL2CrossDomainMessenger contract + CrossDomainMessageLib.requireCallerIsCrossDomainMessenger(); + + // Get the original sender's address from the origin chain + address sender = + IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageSender(); + + // Get the chain ID where the increment request originated + uint256 senderChainId = + IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageSource(); + + // Increment the counter + number += 1; + + // Store the incrementer's details + lastIncrementer = Incrementer(senderChainId, sender); + + // Emit an event for off-chain tracking and indexing + emit CounterIncremented(senderChainId, sender, number); + } +} diff --git a/contracts/src/examples/CrossChainCounterIncrementer.sol b/contracts/src/examples/CrossChainCounterIncrementer.sol new file mode 100644 index 0000000..099370b --- /dev/null +++ b/contracts/src/examples/CrossChainCounterIncrementer.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; +import {CrossDomainMessageLib} from "@interop-lib/libraries/CrossDomainMessageLib.sol"; + +import {CrossChainCounter} from "./CrossChainCounter.sol"; + +/// @title CrossChainCounterIncrementer +/// @notice A contract that sends cross-chain messages to increment a counter on another chain +contract CrossChainCounterIncrementer { + /// @dev The L2 to L2 cross domain messenger predeploy to handle message passing + IL2ToL2CrossDomainMessenger internal messenger = + IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + /// @notice Sends a message to increment a counter on another chain + /// @param counterChainId The chain ID where the target counter contract is deployed + /// @param counterAddress The address of the counter contract on the target chain + function increment(uint256 counterChainId, address counterAddress) public { + // Send a cross-chain message to increment the counter on the target chain + messenger.sendMessage( + counterChainId, counterAddress, abi.encodeWithSelector(CrossChainCounter.increment.selector) + ); + } +} diff --git a/contracts/src/settlement/MARKSettlementModule.sol b/contracts/src/settlement/MARKSettlementModule.sol new file mode 100644 index 0000000..02a0fea --- /dev/null +++ b/contracts/src/settlement/MARKSettlementModule.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {RYLA} from "../token/RYLA.sol"; +import {IUTXOSettlementVerifier} from "./interfaces/IUTXOSettlementVerifier.sol"; +import {SettlementErrors} from "../errors/SettlementErrors.sol"; +import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; + +/// @title MARKSettlementModule +/// @notice Boundary module for integrating external UTXO/zk accounting with RYLA mint/burn. +/// @dev Holds RYLA minter and burner roles. Replay protection is enforced via `intentId`. +contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules, SettlementErrors { + using SafeERC20 for IERC20; + event OperatorUpdated(address indexed operator, bool enabled); + event VerifierUpdated(address indexed verifier, bool validationEnabled); + event ProductionModeActivated(address indexed admin); + event MintSettled(bytes32 indexed intentId, address indexed operator, address indexed recipient, uint256 amount); + event BurnSettled(bytes32 indexed intentId, address indexed operator, address indexed account, uint256 amount); + + uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + + RYLA public immutable TOKEN; + + mapping(bytes32 => bool) public consumedIntents; + uint256 public totalSettledMint; + uint256 public totalSettledBurn; + + IUTXOSettlementVerifier public verifier; + bool public proofValidationEnabled; + bool public productionMode; + + constructor(address initialAdmin, address tokenAddress) + AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) + { + if (initialAdmin == address(0)) revert ZeroAddress(); + if (tokenAddress == address(0)) revert ZeroAddress(); + TOKEN = RYLA(tokenAddress); + } + + function setOperator(address operator, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (operator == address(0)) revert ZeroAddress(); + if (enabled) { + _grantRole(OPERATOR_ROLE, operator); + } else { + _revokeRole(OPERATOR_ROLE, operator); + } + emit OperatorUpdated(operator, enabled); + } + + function setVerifier(address verifierAddress, bool enableValidation) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (productionMode && (!enableValidation || verifierAddress == address(0))) { + revert ProductionModeRequiresProofValidation(); + } + if (enableValidation && verifierAddress == address(0)) revert VerifierRequired(); + if (verifierAddress != address(0) && verifierAddress.code.length == 0) revert VerifierRequired(); + + verifier = IUTXOSettlementVerifier(verifierAddress); + proofValidationEnabled = enableValidation; + emit VerifierUpdated(verifierAddress, enableValidation); + } + + /// @notice Irreversibly enables production mode that forces proof validation to remain active. + function activateProductionMode() external onlyRole(DEFAULT_ADMIN_ROLE) { + if (productionMode) revert ProductionModeAlreadyEnabled(); + if (!proofValidationEnabled || address(verifier) == address(0)) { + revert ProductionModeRequiresProofValidation(); + } + productionMode = true; + emit ProductionModeActivated(msg.sender); + } + + function settleMint(address recipient, uint256 amount, bytes32 intentId, bytes calldata proof) + external + onlyRole(OPERATOR_ROLE) + nonReentrant + { + if (recipient == address(0)) revert ZeroAddress(); + _consumeAndValidate(intentId, recipient, amount, true, proof); + TOKEN.mint(recipient, amount); + totalSettledMint += amount; + emit MintSettled(intentId, msg.sender, recipient, amount); + } + + function settleBurn(address account, uint256 amount, bytes32 intentId, bytes calldata proof) + external + onlyRole(OPERATOR_ROLE) + nonReentrant + { + if (account == address(0)) revert ZeroAddress(); + _consumeAndValidate(intentId, account, amount, false, proof); + + uint256 moduleBalanceBefore = TOKEN.balanceOf(address(this)); + IERC20(address(TOKEN)).safeTransferFrom(account, address(this), amount); + uint256 moduleBalanceAfterTransfer = TOKEN.balanceOf(address(this)); + if (moduleBalanceAfterTransfer != moduleBalanceBefore + amount) revert BurnEscrowInvariantFailed(); + + TOKEN.burn(amount); + uint256 moduleBalanceAfterBurn = TOKEN.balanceOf(address(this)); + if (moduleBalanceAfterBurn != moduleBalanceBefore) revert BurnEscrowInvariantFailed(); + totalSettledBurn += amount; + emit BurnSettled(intentId, msg.sender, account, amount); + } + + function _consumeAndValidate(bytes32 intentId, address account, uint256 amount, bool isMint, bytes calldata proof) + internal + { + if (intentId == bytes32(0)) revert InvalidIntent(); + if (amount == 0) revert InvalidAmount(); + if (consumedIntents[intentId]) revert IntentAlreadyConsumed(); + + if (proofValidationEnabled) { + IUTXOSettlementVerifier verifier_ = verifier; + if (address(verifier_) == address(0)) revert VerifierRequired(); + bool ok = verifier_.verifySettlement(intentId, address(this), account, amount, isMint, proof); + if (!ok) revert VerificationFailed(); + } + + consumedIntents[intentId] = true; + } +} diff --git a/contracts/src/settlement/interfaces/IUTXOSettlementVerifier.sol b/contracts/src/settlement/interfaces/IUTXOSettlementVerifier.sol new file mode 100644 index 0000000..3b4b747 --- /dev/null +++ b/contracts/src/settlement/interfaces/IUTXOSettlementVerifier.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Verifier interface for UTXO settlement proofs. +/// @dev Kept generic so different verifier backends can be swapped in. +/// Implementations should bind proofs to `settlementModule` to avoid cross-module replay. +interface IUTXOSettlementVerifier { + function verifySettlement( + bytes32 intentId, + address settlementModule, + address account, + uint256 amount, + bool isMint, + bytes calldata proof + ) + external + view + returns (bool); +} diff --git a/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol b/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol new file mode 100644 index 0000000..d7dc557 --- /dev/null +++ b/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {IUTXOSettlementVerifier} from "../interfaces/IUTXOSettlementVerifier.sol"; +import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; + +/// @title AttestedSettlementVerifier +/// @notice Signature-based verifier for settlement intents. +/// @dev Intended as a production-safe bridge step before zk verifier integration. +/// Proof encoding: +/// `abi.encode(uint256 deadline, bytes32 contextHash, uint8 v, bytes32 r, bytes32 s)`. +/// Settlement digest binds to the calling settlement module address. +contract AttestedSettlementVerifier is IUTXOSettlementVerifier, AccessControlDefaultAdminRules { + using MessageHashUtils for bytes32; + + bytes32 public constant SETTLEMENT_ATTESTATION_DOMAIN = keccak256("AttestedSettlementVerifier.v1"); + uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; + bytes32 public constant ATTESTER_ROLE = keccak256("ATTESTER_ROLE"); + + event AttesterUpdated(address indexed attester, bool enabled); + + struct Attestation { + uint256 deadline; + bytes32 contextHash; + uint8 v; + bytes32 r; + bytes32 s; + } + + constructor(address initialAdmin) AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) { + if (initialAdmin == address(0)) revert ZeroAddress(); + } + + function setAttester(address attester, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (attester == address(0)) revert ZeroAddress(); + if (enabled) { + _grantRole(ATTESTER_ROLE, attester); + } else { + _revokeRole(ATTESTER_ROLE, attester); + } + emit AttesterUpdated(attester, enabled); + } + + function verifySettlement( + bytes32 intentId, + address settlementModule, + address account, + uint256 amount, + bool isMint, + bytes calldata proof + ) + external + view + override + returns (bool) + { + if (intentId == bytes32(0) || settlementModule == address(0) || account == address(0) || amount == 0) return false; + if (proof.length != 160) return false; + + Attestation memory att = abi.decode(proof, (Attestation)); + if (att.deadline < block.timestamp) return false; + + bytes32 digest = + _settlementDigest(intentId, settlementModule, account, amount, isMint, att.contextHash, att.deadline); + bytes memory signature = abi.encodePacked(att.r, att.s, att.v); + (address signer, ECDSA.RecoverError err,) = ECDSA.tryRecover(digest, signature); + if (err != ECDSA.RecoverError.NoError || signer == address(0)) return false; + + return hasRole(ATTESTER_ROLE, signer); + } + + function _settlementDigest( + bytes32 intentId, + address settlementModule, + address account, + uint256 amount, + bool isMint, + bytes32 contextHash, + uint256 deadline + ) internal view returns (bytes32) { + bytes32 settlementHash = keccak256( + abi.encode( + SETTLEMENT_ATTESTATION_DOMAIN, + address(this), + block.chainid, + intentId, + settlementModule, + account, + amount, + isMint, + contextHash, + deadline + ) + ); + return settlementHash.toEthSignedMessageHash(); + } +} diff --git a/contracts/test/CrossChainCounter.t.sol b/contracts/test/CrossChainCounter.t.sol index 8f8b87d..93222bd 100644 --- a/contracts/test/CrossChainCounter.t.sol +++ b/contracts/test/CrossChainCounter.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.26; import {Test} from "forge-std/Test.sol"; import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {CrossChainCounter} from "../src/CrossChainCounter.sol"; +import {CrossChainCounter} from "../src/examples/CrossChainCounter.sol"; contract CrossChainCounterTest is Test { CrossChainCounter public counter; diff --git a/contracts/test/e2e/settlement/MARKSettlementE2E.t.sol b/contracts/test/e2e/settlement/MARKSettlementE2E.t.sol new file mode 100644 index 0000000..ba09220 --- /dev/null +++ b/contracts/test/e2e/settlement/MARKSettlementE2E.t.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../../src/settlement/verifier/AttestedSettlementVerifier.sol"; +import {SettlementErrors} from "../../../src/errors/SettlementErrors.sol"; + +contract MARKSettlementE2ETest is Test { + RYLA internal token; + MARKSettlementModule internal module; + MARKSettlementModule internal moduleB; + AttestedSettlementVerifier internal verifier; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + address internal user = makeAddr("user"); + uint256 internal attesterPk = 0xC0FFEE; + address internal attester; + + bytes32 internal constant INTENT_MINT = keccak256("e2e-mint-1"); + bytes32 internal constant INTENT_BURN = keccak256("e2e-burn-1"); + bytes32 internal constant CONTEXT_MINT = keccak256("e2e-mint-context"); + bytes32 internal constant CONTEXT_BURN = keccak256("e2e-burn-context"); + + function setUp() public { + attester = vm.addr(attesterPk); + + vm.prank(owner); + token = new RYLA(owner); + + vm.prank(owner); + verifier = new AttestedSettlementVerifier(owner); + + vm.prank(owner); + module = new MARKSettlementModule(owner, address(token)); + vm.prank(owner); + moduleB = new MARKSettlementModule(owner, address(token)); + + vm.startPrank(owner); + verifier.setAttester(attester, true); + module.setOperator(operator, true); + moduleB.setOperator(operator, true); + module.setVerifier(address(verifier), true); + moduleB.setVerifier(address(verifier), true); + token.setMinter(address(module), true); + token.setBurner(address(module), true); + token.setMinter(address(moduleB), true); + token.setBurner(address(moduleB), true); + vm.stopPrank(); + } + + function testMintThenBurnLifecycleWithValidationEnabled() public { + uint256 mintAmount = 100 ether; + uint256 mintDeadline = block.timestamp + 1 hours; + bytes memory mintProof = _buildProof(INTENT_MINT, address(module), user, mintAmount, true, CONTEXT_MINT, mintDeadline); + + vm.prank(operator); + module.settleMint(user, mintAmount, INTENT_MINT, mintProof); + assertEq(token.balanceOf(user), mintAmount); + + uint256 burnAmount = 40 ether; + vm.prank(user); + bool ok = token.approve(address(module), burnAmount); + assertTrue(ok); + + uint256 burnDeadline = block.timestamp + 1 hours; + bytes memory burnProof = + _buildProof(INTENT_BURN, address(module), user, burnAmount, false, CONTEXT_BURN, burnDeadline); + + vm.prank(operator); + module.settleBurn(user, burnAmount, INTENT_BURN, burnProof); + + assertEq(token.balanceOf(user), 60 ether); + assertEq(token.totalSupply(), 60 ether); + } + + function testReplayIntentReverts() public { + uint256 deadline = block.timestamp + 1 hours; + bytes memory mintProof = _buildProof(INTENT_MINT, address(module), user, 1 ether, true, CONTEXT_MINT, deadline); + + vm.prank(operator); + module.settleMint(user, 1 ether, INTENT_MINT, mintProof); + + vm.prank(operator); + vm.expectRevert(SettlementErrors.IntentAlreadyConsumed.selector); + module.settleMint(user, 1 ether, INTENT_MINT, mintProof); + } + + function testInvalidAttestationReverts() public { + uint256 deadline = block.timestamp + 1 hours; + bytes32 settlementHash = keccak256( + abi.encode( + verifier.SETTLEMENT_ATTESTATION_DOMAIN(), + address(verifier), + block.chainid, + INTENT_MINT, + address(module), + user, + 1 ether, + true, + CONTEXT_MINT, + deadline + ) + ); + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(0xDEAD, digest); + bytes memory wrongProof = abi.encode(deadline, CONTEXT_MINT, v, r, s); + + vm.prank(operator); + vm.expectRevert(SettlementErrors.VerificationFailed.selector); + module.settleMint(user, 1 ether, INTENT_MINT, wrongProof); + } + + function testProofBoundToModuleAddressRevertsOnDifferentModule() public { + uint256 amount = 3 ether; + uint256 deadline = block.timestamp + 1 hours; + bytes memory proofForModuleA = + _buildProof(INTENT_MINT, address(module), user, amount, true, CONTEXT_MINT, deadline); + + vm.prank(operator); + vm.expectRevert(SettlementErrors.VerificationFailed.selector); + moduleB.settleMint(user, amount, INTENT_MINT, proofForModuleA); + } + + function _buildProof( + bytes32 intentId, + address moduleAddress, + address account, + uint256 amount, + bool isMint, + bytes32 contextHash, + uint256 deadline + ) internal view returns (bytes memory) { + bytes32 settlementHash = keccak256( + abi.encode( + verifier.SETTLEMENT_ATTESTATION_DOMAIN(), + address(verifier), + block.chainid, + intentId, + moduleAddress, + account, + amount, + isMint, + contextHash, + deadline + ) + ); + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(attesterPk, digest); + return abi.encode(deadline, contextHash, v, r, s); + } +} diff --git a/contracts/test/integration/settlement/CrossChainIncrementer.t.sol b/contracts/test/integration/settlement/CrossChainIncrementer.t.sol new file mode 100644 index 0000000..6732123 --- /dev/null +++ b/contracts/test/integration/settlement/CrossChainIncrementer.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {console} from "forge-std/console.sol"; +import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; +import {Relayer} from "@interop-lib/test/Relayer.sol"; +import {CrossChainCounterIncrementer} from "../../../src/examples/CrossChainCounterIncrementer.sol"; +import {CrossChainCounter} from "../../../src/examples/CrossChainCounter.sol"; + +contract CrossChainIncrementerTest is Relayer, Test { + CrossChainCounterIncrementer public incrementer; + CrossChainCounter public counter; + + string[] private rpcUrls = [ + vm.envString("CHAIN_A_RPC_URL"), + vm.envString("CHAIN_B_RPC_URL") + ]; + + constructor() Relayer(rpcUrls) {} + + function setUp() public { + vm.selectFork(forkIds[0]); + incrementer = new CrossChainCounterIncrementer{salt: bytes32(0)}(); + + vm.selectFork(forkIds[1]); + counter = new CrossChainCounter{salt: bytes32(0)}(); + } + + // Test incrementing from a valid cross-chain message + function test_increment_crossDomain_succeeds() public { + vm.selectFork(forkIds[0]); + + incrementer.increment(chainIdByForkId[forkIds[1]], address(counter)); + + // verify counter has not been incremented on chainB + vm.selectFork(forkIds[1]); + assertEq(counter.number(), 0); + + relayAllMessages(); + + // verify counter has been incremented on chainB + vm.selectFork(forkIds[1]); + assertEq(counter.number(), 1); + } +} diff --git a/contracts/test/invariant/settlement/MARKSettlementInvariants.t.sol b/contracts/test/invariant/settlement/MARKSettlementInvariants.t.sol new file mode 100644 index 0000000..2554949 --- /dev/null +++ b/contracts/test/invariant/settlement/MARKSettlementInvariants.t.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {StdInvariant} from "forge-std/StdInvariant.sol"; +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {SettlementErrors} from "../../../src/errors/SettlementErrors.sol"; + +contract MARKSettlementHandler is Test { + RYLA public immutable token; + MARKSettlementModule public immutable module; + address public immutable operator; + + address[] internal users; + + uint256 public totalMinted; + uint256 public totalBurned; + uint256 public successfulMintCount; + uint256 public successfulBurnCount; + uint256 internal nonce; + bytes32 internal lastMintIntentId; + address internal lastMintRecipient; + uint256 internal lastMintAmount; + + constructor(RYLA _token, MARKSettlementModule _module, address _operator) { + token = _token; + module = _module; + operator = _operator; + + users.push(makeAddr("userA")); + users.push(makeAddr("userB")); + users.push(makeAddr("userC")); + } + + function settleMint(uint96 rawAmount, uint8 userSeed) external { + uint256 amount = _clamp(uint256(rawAmount), 1, 1_000_000 ether); + address recipient = _user(userSeed); + bytes32 intentId = keccak256(abi.encodePacked("mint", nonce++)); + + vm.prank(operator); + module.settleMint(recipient, amount, intentId, bytes("")); + + totalMinted += amount; + successfulMintCount += 1; + lastMintIntentId = intentId; + lastMintRecipient = recipient; + lastMintAmount = amount; + } + + function settleBurn(uint96 rawAmount, uint8 userSeed) external { + address account = _user(userSeed); + uint256 balance = token.balanceOf(account); + if (balance == 0) return; + + uint256 amount = _clamp(uint256(rawAmount), 1, balance); + bytes32 intentId = keccak256(abi.encodePacked("burn", nonce++)); + + vm.prank(account); + token.approve(address(module), amount); + + vm.prank(operator); + module.settleBurn(account, amount, intentId, bytes("")); + + totalBurned += amount; + successfulBurnCount += 1; + } + + function replayLastMintIntent() external { + if (lastMintIntentId == bytes32(0)) return; + + vm.prank(operator); + vm.expectRevert(SettlementErrors.IntentAlreadyConsumed.selector); + module.settleMint(lastMintRecipient, lastMintAmount, lastMintIntentId, bytes("")); + } + + function unauthorizedSettleMint(uint96 rawAmount, uint8 userSeed) external { + uint256 amount = _clamp(uint256(rawAmount), 1, 1_000_000 ether); + address recipient = _user(userSeed); + bytes32 intentId = keccak256(abi.encodePacked("unauthMint", nonce++)); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), module.OPERATOR_ROLE() + ) + ); + module.settleMint(recipient, amount, intentId, bytes("")); + } + + function unauthorizedTokenMint(uint96 rawAmount, uint8 userSeed) external { + uint256 amount = _clamp(uint256(rawAmount), 1, 1_000_000 ether); + address recipient = _user(userSeed); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), token.MINTER_ROLE() + ) + ); + token.mint(recipient, amount); + } + + function unauthorizedTokenBurn(uint96 rawAmount) external { + uint256 amount = _clamp(uint256(rawAmount), 1, 1_000_000 ether); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), token.BURNER_ROLE() + ) + ); + token.burn(amount); + } + + function trackedUserBalanceSum() external view returns (uint256 sum) { + uint256 length = users.length; + for (uint256 i = 0; i < length; ++i) { + sum += token.balanceOf(users[i]); + } + } + + function _user(uint8 seed) internal view returns (address) { + return users[uint256(seed) % users.length]; + } + + function _clamp(uint256 x, uint256 min, uint256 max) internal pure returns (uint256) { + if (max <= min) return min; + unchecked { + return min + (x % (max - min + 1)); + } + } +} + +contract MARKSettlementInvariants is StdInvariant, Test { + RYLA internal token; + MARKSettlementModule internal module; + MARKSettlementHandler internal handler; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + + function setUp() public { + vm.prank(owner); + token = new RYLA(owner); + + vm.prank(owner); + module = new MARKSettlementModule(owner, address(token)); + + vm.startPrank(owner); + token.setMinter(address(module), true); + token.setBurner(address(module), true); + module.setOperator(operator, true); + vm.stopPrank(); + + handler = new MARKSettlementHandler(token, module, operator); + + bytes4[] memory selectors = new bytes4[](6); + selectors[0] = MARKSettlementHandler.settleMint.selector; + selectors[1] = MARKSettlementHandler.settleBurn.selector; + selectors[2] = MARKSettlementHandler.unauthorizedSettleMint.selector; + selectors[3] = MARKSettlementHandler.unauthorizedTokenMint.selector; + selectors[4] = MARKSettlementHandler.unauthorizedTokenBurn.selector; + selectors[5] = MARKSettlementHandler.replayLastMintIntent.selector; + + targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); + targetContract(address(handler)); + } + + function invariant_totalSupplyMatchesNetSettlements() public view { + assertEq(token.totalSupply(), handler.totalMinted() - handler.totalBurned(), "supply != minted-burned"); + } + + function invariant_totalSupplyMatchesTrackedBalances() public view { + assertEq(token.totalSupply(), handler.trackedUserBalanceSum(), "supply != tracked balances"); + } + + function invariant_settlementCountersStayConsistent() public view { + assertEq(module.totalSettledMint(), handler.totalMinted(), "module mint total mismatch"); + assertEq(module.totalSettledBurn(), handler.totalBurned(), "module burn total mismatch"); + assertGe(module.totalSettledMint(), module.totalSettledBurn(), "burn exceeds mint"); + } + + function invariant_moduleRemainsExclusiveIssuer() public view { + assertTrue(token.hasRole(token.MINTER_ROLE(), address(module)), "module missing minter role"); + assertTrue(token.hasRole(token.BURNER_ROLE(), address(module)), "module missing burner role"); + assertFalse(token.hasRole(token.MINTER_ROLE(), address(handler)), "handler unexpectedly minter"); + assertFalse(token.hasRole(token.BURNER_ROLE(), address(handler)), "handler unexpectedly burner"); + } + + function invariant_moduleDoesNotAccumulateTokens() public view { + assertEq(token.balanceOf(address(module)), 0, "module balance leak"); + } +} diff --git a/contracts/test/unit/MARKAdminDelay.t.sol b/contracts/test/unit/MARKAdminDelay.t.sol new file mode 100644 index 0000000..6df6553 --- /dev/null +++ b/contracts/test/unit/MARKAdminDelay.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../src/settlement/verifier/AttestedSettlementVerifier.sol"; + +contract MARKAdminDelayTest is Test { + bytes32 internal constant DEFAULT_ADMIN_ROLE = 0x00; + + address internal owner = makeAddr("owner"); + address internal newOwner = makeAddr("newOwner"); + address internal tokenAddress; + + RYLA internal token; + MARKBridgeAdapter internal adapter; + MARKSettlementModule internal module; + AttestedSettlementVerifier internal verifier; + + function setUp() public { + vm.prank(owner); + token = new RYLA(owner); + tokenAddress = address(token); + + vm.prank(owner); + adapter = new MARKBridgeAdapter(owner, tokenAddress); + + vm.prank(owner); + module = new MARKSettlementModule(owner, tokenAddress); + + vm.prank(owner); + verifier = new AttestedSettlementVerifier(owner); + } + + function testAdminTransferRequiresDelayAcrossCoreContracts() public { + _assertAdminTransfer(address(token)); + _assertAdminTransfer(address(adapter)); + _assertAdminTransfer(address(module)); + _assertAdminTransfer(address(verifier)); + } + + function testCancelAdminTransferKeepsCurrentAdmin() public { + vm.startPrank(owner); + token.beginDefaultAdminTransfer(newOwner); + token.cancelDefaultAdminTransfer(); + vm.stopPrank(); + + (address pendingAdmin, uint48 schedule) = token.pendingDefaultAdmin(); + assertEq(pendingAdmin, address(0)); + assertEq(schedule, 0); + + vm.warp(block.timestamp + token.defaultAdminDelay() + 1); + vm.prank(newOwner); + vm.expectRevert(); + token.acceptDefaultAdminTransfer(); + + assertTrue(token.hasRole(DEFAULT_ADMIN_ROLE, owner)); + assertFalse(token.hasRole(DEFAULT_ADMIN_ROLE, newOwner)); + } + + function _assertAdminTransfer(address contractAddress) internal { + RYLALike target = RYLALike(contractAddress); + + vm.prank(owner); + target.beginDefaultAdminTransfer(newOwner); + + (address pendingAdmin, uint48 schedule) = target.pendingDefaultAdmin(); + assertEq(pendingAdmin, newOwner); + assertEq(schedule, uint48(block.timestamp + target.defaultAdminDelay())); + + vm.prank(newOwner); + vm.expectRevert(); + target.acceptDefaultAdminTransfer(); + + vm.warp(block.timestamp + target.defaultAdminDelay() + 1); + vm.prank(newOwner); + target.acceptDefaultAdminTransfer(); + + assertFalse(target.hasRole(DEFAULT_ADMIN_ROLE, owner)); + assertTrue(target.hasRole(DEFAULT_ADMIN_ROLE, newOwner)); + } +} + +interface RYLALike { + function hasRole(bytes32 role, address account) external view returns (bool); + function beginDefaultAdminTransfer(address newAdmin) external; + function cancelDefaultAdminTransfer() external; + function acceptDefaultAdminTransfer() external; + function pendingDefaultAdmin() external view returns (address newAdmin, uint48 schedule); + function defaultAdminDelay() external view returns (uint48); +} + diff --git a/contracts/test/unit/MARKDeployScripts.t.sol b/contracts/test/unit/MARKDeployScripts.t.sol new file mode 100644 index 0000000..ad9704b --- /dev/null +++ b/contracts/test/unit/MARKDeployScripts.t.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../src/settlement/MARKSettlementModule.sol"; +import {DeployMARKStack} from "../../script/deploy/bridge/DeployMARKStack.s.sol"; +import {DeployMARKSettlementModule} from "../../script/deploy/settlement/DeployMARKSettlementModule.s.sol"; + +contract MARKDeployScriptsTest is Test { + uint256 internal constant DEPLOYER_PK = 0xA11CE; + + DeployMARKStack internal deployStack; + DeployMARKSettlementModule internal deploySettlement; + + address internal deployer; + address internal owner; + address internal operator; + address internal outsider; + + function setUp() public { + deployStack = new DeployMARKStack(); + deploySettlement = new DeployMARKSettlementModule(); + + deployer = vm.addr(DEPLOYER_PK); + owner = makeAddr("owner"); + operator = makeAddr("operator"); + outsider = makeAddr("outsider"); + + vm.setEnv("PRIVATE_KEY", vm.toString(DEPLOYER_PK)); + + // Ensure test isolation for script env-driven behavior. + vm.setEnv("MARK_RYLA_OWNER", vm.toString(address(0))); + vm.setEnv("MARK_BRIDGE_OPERATOR", vm.toString(address(0))); + vm.setEnv("MARK_BRIDGE_DESTINATION_CHAIN_ID", "0"); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(0))); + vm.setEnv("MARK_MODULE_OWNER", vm.toString(address(0))); + vm.setEnv("MARK_SETTLEMENT_OPERATOR", vm.toString(address(0))); + vm.setEnv("MARK_SETTLEMENT_VERIFIER", vm.toString(address(0))); + vm.setEnv("MARK_SETTLEMENT_PROOF_ENABLED", "false"); + vm.setEnv("MARK_DEPLOY_ATTESTED_VERIFIER", "false"); + vm.setEnv("MARK_SETTLEMENT_ATTESTER", vm.toString(address(0))); + } + + function testDeployMARKStackRevertsWhenConfigRequestedWithoutAdapterAdmin() public { + vm.setEnv("MARK_RYLA_OWNER", vm.toString(owner)); + vm.setEnv("MARK_BRIDGE_OPERATOR", vm.toString(operator)); + vm.setEnv("MARK_BRIDGE_DESTINATION_CHAIN_ID", "902"); + + vm.expectRevert(DeployMARKStack.MissingAdapterAdminForRequestedConfig.selector); + deployStack.run(); + } + + function testDeployMARKStackConfiguresWhenDeployerIsAdmin() public { + vm.setEnv("MARK_RYLA_OWNER", vm.toString(deployer)); + vm.setEnv("MARK_BRIDGE_OPERATOR", vm.toString(operator)); + vm.setEnv("MARK_BRIDGE_DESTINATION_CHAIN_ID", "902"); + + (RYLA token, MARKBridgeAdapter adapter) = deployStack.run(); + + assertTrue(token.hasRole(0x00, deployer)); + assertTrue(adapter.hasRole(adapter.OPERATOR_ROLE(), operator)); + assertTrue(adapter.destinationEnabled(902)); + } + + function testDeployMARKSettlementRevertsWhenConfigRequestedWithoutModuleAdmin() public { + vm.prank(deployer); + RYLA token = new RYLA(deployer); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(token))); + vm.setEnv("MARK_MODULE_OWNER", vm.toString(owner)); + vm.setEnv("MARK_SETTLEMENT_OPERATOR", vm.toString(operator)); + vm.setEnv("MARK_SETTLEMENT_VERIFIER", vm.toString(address(0))); + vm.setEnv("MARK_SETTLEMENT_PROOF_ENABLED", "false"); + vm.setEnv("MARK_DEPLOY_ATTESTED_VERIFIER", "false"); + vm.setEnv("MARK_SETTLEMENT_ATTESTER", vm.toString(address(0))); + + vm.expectRevert(DeployMARKSettlementModule.MissingModuleAdminForRequestedConfig.selector); + deploySettlement.run(); + } + + function testDeployMARKSettlementRevertsWhenMissingTokenAdmin() public { + vm.prank(outsider); + RYLA token = new RYLA(outsider); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(token))); + vm.setEnv("MARK_MODULE_OWNER", vm.toString(deployer)); + vm.setEnv("MARK_SETTLEMENT_OPERATOR", vm.toString(address(0))); + vm.setEnv("MARK_SETTLEMENT_VERIFIER", vm.toString(address(0))); + vm.setEnv("MARK_SETTLEMENT_PROOF_ENABLED", "false"); + vm.setEnv("MARK_DEPLOY_ATTESTED_VERIFIER", "false"); + vm.setEnv("MARK_SETTLEMENT_ATTESTER", vm.toString(address(0))); + + vm.expectRevert(); + deploySettlement.run(); + } + + function testDeployMARKSettlementConfiguresAndGrantsRoles() public { + vm.prank(deployer); + RYLA token = new RYLA(deployer); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(token))); + vm.setEnv("MARK_MODULE_OWNER", vm.toString(deployer)); + vm.setEnv("MARK_SETTLEMENT_OPERATOR", vm.toString(operator)); + vm.setEnv("MARK_SETTLEMENT_VERIFIER", vm.toString(address(0))); + vm.setEnv("MARK_SETTLEMENT_PROOF_ENABLED", "false"); + vm.setEnv("MARK_DEPLOY_ATTESTED_VERIFIER", "false"); + vm.setEnv("MARK_SETTLEMENT_ATTESTER", vm.toString(address(0))); + + MARKSettlementModule module = deploySettlement.run(); + + assertTrue(module.hasRole(module.OPERATOR_ROLE(), operator)); + assertTrue(token.hasRole(token.MINTER_ROLE(), address(module))); + assertTrue(token.hasRole(token.BURNER_ROLE(), address(module))); + } +} diff --git a/contracts/test/unit/PostDeployMARKSetup.t.sol b/contracts/test/unit/PostDeployMARKSetup.t.sol new file mode 100644 index 0000000..f10e100 --- /dev/null +++ b/contracts/test/unit/PostDeployMARKSetup.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../src/settlement/verifier/AttestedSettlementVerifier.sol"; +import {PostDeployMARKSetup} from "../../script/ops/settlement/PostDeployMARKSetup.s.sol"; + +contract PostDeployMARKSetupTest is Test { + PostDeployMARKSetup internal setupScript; + RYLA internal token; + MARKBridgeAdapter internal adapter; + MARKSettlementModule internal module; + AttestedSettlementVerifier internal verifier; + + address internal deployer = makeAddr("deployer"); + + function setUp() public { + setupScript = new PostDeployMARKSetup(); + + vm.prank(deployer); + token = new RYLA(deployer); + vm.prank(deployer); + adapter = new MARKBridgeAdapter(deployer, address(token)); + vm.prank(deployer); + module = new MARKSettlementModule(deployer, address(token)); + vm.prank(deployer); + verifier = new AttestedSettlementVerifier(deployer); + } + + function testPostDeploySetupRevertsWhenProductionModeWithoutProof() public { + PostDeployMARKSetup.Config memory cfg = _baseConfig(); + cfg.settlementProductionMode = true; + cfg.proofEnabled = false; + cfg.verifierAddress = address(verifier); + + vm.expectRevert(PostDeployMARKSetup.ProductionModeRequiresProofValidation.selector); + setupScript.validateWithConfig(cfg); + } + + function testPostDeploySetupRevertsWhenProductionModeWithoutVerifier() public { + PostDeployMARKSetup.Config memory cfg = _baseConfig(); + cfg.settlementProductionMode = true; + cfg.proofEnabled = true; + cfg.verifierAddress = address(0); + + vm.expectRevert(PostDeployMARKSetup.ProductionModeRequiresVerifier.selector); + setupScript.validateWithConfig(cfg); + } + + function _baseConfig() internal view returns (PostDeployMARKSetup.Config memory cfg) { + cfg.deployer = deployer; + cfg.tokenAddress = address(token); + cfg.adapterAddress = address(adapter); + cfg.moduleAddress = address(module); + } +} diff --git a/contracts/test/unit/PreflightMARKDeployment.t.sol b/contracts/test/unit/PreflightMARKDeployment.t.sol new file mode 100644 index 0000000..d516e28 --- /dev/null +++ b/contracts/test/unit/PreflightMARKDeployment.t.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../src/settlement/verifier/AttestedSettlementVerifier.sol"; +import {PreflightMARKDeployment} from "../../script/ops/settlement/PreflightMARKDeployment.s.sol"; + +contract PreflightMARKDeploymentTest is Test { + uint256 internal constant MODE_STACK = 1; + uint256 internal constant MODE_SETTLEMENT = 2; + uint256 internal constant MODE_POSTDEPLOY = 3; + + address internal deployer = makeAddr("deployer"); + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + address internal attester = makeAddr("attester"); + + PreflightMARKDeployment internal preflight; + RYLA internal token; + MARKBridgeAdapter internal adapter; + MARKSettlementModule internal module; + AttestedSettlementVerifier internal verifier; + + function setUp() public { + preflight = new PreflightMARKDeployment(); + + vm.prank(deployer); + token = new RYLA(deployer); + vm.prank(deployer); + adapter = new MARKBridgeAdapter(deployer, address(token)); + vm.prank(deployer); + module = new MARKSettlementModule(deployer, address(token)); + vm.prank(deployer); + verifier = new AttestedSettlementVerifier(deployer); + } + + function testStackPreflightPassesForOwnerDeployer() public view { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_STACK; + cfg.deployer = deployer; + cfg.owner = deployer; + cfg.bridgeOperator = operator; + cfg.destinationChainId = 10; + + preflight.preflightWithConfig(cfg); + } + + function testStackPreflightRevertsWhenConfigNeedsAdminButOwnerDiffers() public { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_STACK; + cfg.deployer = deployer; + cfg.owner = owner; + cfg.bridgeOperator = operator; + + vm.expectRevert(PreflightMARKDeployment.MissingAdapterAdminForRequestedConfig.selector); + preflight.preflightWithConfig(cfg); + } + + function testSettlementPreflightPassesWithValidConfig() public view { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_SETTLEMENT; + cfg.deployer = deployer; + cfg.owner = deployer; + cfg.tokenAddress = address(token); + cfg.settlementOperator = operator; + cfg.verifierAddress = address(verifier); + cfg.proofEnabled = true; + + preflight.preflightWithConfig(cfg); + } + + function testSettlementPreflightRevertsWhenProofEnabledWithoutVerifier() public { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_SETTLEMENT; + cfg.deployer = deployer; + cfg.owner = deployer; + cfg.tokenAddress = address(token); + cfg.proofEnabled = true; + + vm.expectRevert(PreflightMARKDeployment.VerifierRequiredWhenProofEnabled.selector); + preflight.preflightWithConfig(cfg); + } + + function testSettlementPreflightRevertsWhenProductionModeWithoutProof() public { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_SETTLEMENT; + cfg.deployer = deployer; + cfg.owner = deployer; + cfg.tokenAddress = address(token); + cfg.verifierAddress = address(verifier); + cfg.settlementProductionMode = true; + cfg.proofEnabled = false; + + vm.expectRevert(PreflightMARKDeployment.ProofValidationRequiredWhenProductionModeEnabled.selector); + preflight.preflightWithConfig(cfg); + } + + function testSettlementPreflightRevertsWhenProductionModeWithoutVerifier() public { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_SETTLEMENT; + cfg.deployer = deployer; + cfg.owner = deployer; + cfg.tokenAddress = address(token); + cfg.settlementProductionMode = true; + cfg.proofEnabled = true; + + vm.expectRevert(PreflightMARKDeployment.VerifierRequiredWhenProofEnabled.selector); + preflight.preflightWithConfig(cfg); + } + + function testPostDeployPreflightPassesWithConfiguredContracts() public view { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_POSTDEPLOY; + cfg.deployer = deployer; + cfg.tokenAddress = address(token); + cfg.adapterAddress = address(adapter); + cfg.moduleAddress = address(module); + cfg.bridgeOperator = operator; + cfg.destinationChainId = 10; + cfg.settlementOperator = operator; + cfg.verifierAddress = address(verifier); + cfg.settlementAttester = attester; + cfg.proofEnabled = true; + + preflight.preflightWithConfig(cfg); + } + + function testPostDeployPreflightRevertsWhenVerifierAdminMissing() public { + vm.prank(deployer); + AttestedSettlementVerifier externalVerifier = new AttestedSettlementVerifier(owner); + + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_POSTDEPLOY; + cfg.deployer = deployer; + cfg.tokenAddress = address(token); + cfg.adapterAddress = address(adapter); + cfg.moduleAddress = address(module); + cfg.verifierAddress = address(externalVerifier); + cfg.settlementAttester = attester; + cfg.proofEnabled = true; + + vm.expectRevert(PreflightMARKDeployment.MissingVerifierAdminForRequestedSetup.selector); + preflight.preflightWithConfig(cfg); + } + + function testPostDeployPreflightRevertsWhenProductionModeWithoutVerifier() public { + PreflightMARKDeployment.Config memory cfg; + cfg.mode = MODE_POSTDEPLOY; + cfg.deployer = deployer; + cfg.tokenAddress = address(token); + cfg.adapterAddress = address(adapter); + cfg.moduleAddress = address(module); + cfg.settlementProductionMode = true; + cfg.proofEnabled = true; + + vm.expectRevert(PreflightMARKDeployment.VerifierRequiredWhenProofEnabled.selector); + preflight.preflightWithConfig(cfg); + } +} diff --git a/contracts/test/unit/RYLA.t.sol b/contracts/test/unit/RYLA.t.sol new file mode 100644 index 0000000..d378875 --- /dev/null +++ b/contracts/test/unit/RYLA.t.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; +import {Unauthorized} from "@interop-lib/libraries/errors/CommonErrors.sol"; + +contract RYLATest is Test { + RYLA internal token; + + address internal owner = makeAddr("owner"); + address internal minter = makeAddr("minter"); + address internal burner = makeAddr("burner"); + address internal user = makeAddr("user"); + + function setUp() public { + vm.prank(owner); + token = new RYLA(owner); + } + + function testMetadata() public view { + assertEq(token.name(), "RYLA"); + assertEq(token.symbol(), "RYLA"); + assertEq(token.decimals(), 18); + } + + function testOwnerCanSetRolesAndMintBurn() public { + vm.startPrank(owner); + token.setMinter(minter, true); + token.setBurner(burner, true); + vm.stopPrank(); + + vm.prank(minter); + token.mint(user, 10 ether); + assertEq(token.balanceOf(user), 10 ether); + + vm.prank(user); + bool ok = token.transfer(burner, 3 ether); + assertTrue(ok); + assertEq(token.balanceOf(burner), 3 ether); + + vm.prank(burner); + token.burn(3 ether); + assertEq(token.balanceOf(burner), 0); + assertEq(token.totalSupply(), 7 ether); + } + + function testMintRevertsForUnauthorizedCaller() public { + vm.expectRevert( + abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, user, token.MINTER_ROLE()) + ); + vm.prank(user); + token.mint(user, 1 ether); + } + + function testCrosschainMintOnlySuperchainBridge() public { + vm.expectRevert(Unauthorized.selector); + token.crosschainMint(user, 1 ether); + + vm.prank(PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE); + token.crosschainMint(user, 2 ether); + assertEq(token.balanceOf(user), 2 ether); + } + + function testCrosschainBurnOnlySuperchainBridge() public { + vm.prank(PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE); + token.crosschainMint(user, 5 ether); + assertEq(token.balanceOf(user), 5 ether); + + vm.expectRevert(Unauthorized.selector); + token.crosschainBurn(user, 1 ether); + + vm.prank(PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE); + token.crosschainBurn(user, 2 ether); + assertEq(token.balanceOf(user), 3 ether); + } +} diff --git a/contracts/test/unit/ReleaseMARK.t.sol b/contracts/test/unit/ReleaseMARK.t.sol new file mode 100644 index 0000000..4e8f30b --- /dev/null +++ b/contracts/test/unit/ReleaseMARK.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {ReleaseMARK} from "../../script/ops/settlement/ReleaseMARK.s.sol"; +import {PreflightMARKDeployment} from "../../script/ops/settlement/PreflightMARKDeployment.s.sol"; + +contract ReleaseMARKTest is Test { + ReleaseMARK internal release; + + address internal deployer = makeAddr("deployer"); + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + + function setUp() public { + release = new ReleaseMARK(); + } + + function testDryRunWithConfigPassesAndReturnsExpectedShape() public { + ReleaseMARK.ReleaseResult memory result = release.dryRunWithConfig(deployer, deployer, operator, 10, true); + + assertFalse(result.execute); + assertTrue(result.runPostDeploy); + assertEq(result.deployer, deployer); + assertEq(result.token, address(0)); + assertEq(result.adapter, address(0)); + assertEq(result.module, address(0)); + assertEq(result.verifier, address(0)); + } + + function testDryRunWithConfigRevertsWhenAdapterAdminWouldBeMissing() public { + vm.expectRevert(PreflightMARKDeployment.MissingAdapterAdminForRequestedConfig.selector); + release.dryRunWithConfig(deployer, owner, operator, 10, false); + } +} diff --git a/contracts/test/unit/VerifyMARKDeployment.t.sol b/contracts/test/unit/VerifyMARKDeployment.t.sol new file mode 100644 index 0000000..4969d30 --- /dev/null +++ b/contracts/test/unit/VerifyMARKDeployment.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../src/bridge/MARKBridgeAdapter.sol"; +import {MARKSettlementModule} from "../../src/settlement/MARKSettlementModule.sol"; +import {AttestedSettlementVerifier} from "../../src/settlement/verifier/AttestedSettlementVerifier.sol"; +import {VerifyMARKDeployment} from "../../script/ops/settlement/VerifyMARKDeployment.s.sol"; + +contract VerifyMARKDeploymentTest is Test { + VerifyMARKDeployment internal verifyScript; + VerifyMARKDeployment.ExpectedConfig internal cfg; + + address internal owner = makeAddr("owner"); + address internal bridgeOperator = makeAddr("bridgeOperator"); + address internal settlementOperator = makeAddr("settlementOperator"); + address internal attester = makeAddr("attester"); + + RYLA internal token; + MARKBridgeAdapter internal adapter; + MARKSettlementModule internal module; + AttestedSettlementVerifier internal verifier; + + function setUp() public { + verifyScript = new VerifyMARKDeployment(); + + vm.prank(owner); + token = new RYLA(owner); + vm.prank(owner); + adapter = new MARKBridgeAdapter(owner, address(token)); + vm.prank(owner); + module = new MARKSettlementModule(owner, address(token)); + vm.prank(owner); + verifier = new AttestedSettlementVerifier(owner); + + vm.startPrank(owner); + token.setMinter(address(module), true); + token.setBurner(address(module), true); + adapter.setOperator(bridgeOperator, true); + adapter.setDestination(902, true); + adapter.setBridgeLimits(100 ether, 1_000 ether); + module.setOperator(settlementOperator, true); + module.setVerifier(address(verifier), true); + verifier.setAttester(attester, true); + vm.stopPrank(); + + cfg.tokenAddress = address(token); + cfg.expectedOwner = owner; + cfg.adapterAddress = address(adapter); + cfg.expectedBridgeOperator = bridgeOperator; + cfg.expectedDestinationChain = 902; + cfg.expectedBridgeMaxPerTx = 100 ether; + cfg.expectedBridgeDailyCap = 1_000 ether; + cfg.moduleAddress = address(module); + cfg.expectedSettlementOperator = settlementOperator; + cfg.expectedProofEnabled = true; + cfg.expectedProductionMode = false; + cfg.expectedVerifier = address(verifier); + cfg.expectedAttester = attester; + } + + function testVerifyDeploymentPassesWhenWiringMatches() public view { + verifyScript.runWithConfig(cfg); + } + + function testVerifyDeploymentRevertsOnBridgeLimitMismatch() public { + cfg.expectedBridgeMaxPerTx = 99 ether; + + vm.expectRevert(bytes("Bridge adapter maxPerTx mismatch")); + verifyScript.runWithConfig(cfg); + } + + function testVerifyDeploymentPassesWithProductionModeEnabled() public { + vm.prank(owner); + module.activateProductionMode(); + + cfg.expectedProductionMode = true; + verifyScript.runWithConfig(cfg); + } + + function testVerifyDeploymentRevertsOnProductionModeMismatch() public { + vm.prank(owner); + module.activateProductionMode(); + + vm.expectRevert(bytes("Module production mode mismatch")); + verifyScript.runWithConfig(cfg); + } +} diff --git a/contracts/test/unit/bridge/MARKBridgeAdapter.t.sol b/contracts/test/unit/bridge/MARKBridgeAdapter.t.sol new file mode 100644 index 0000000..3ba2e96 --- /dev/null +++ b/contracts/test/unit/bridge/MARKBridgeAdapter.t.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {Test} from "forge-std/Test.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../../src/bridge/MARKBridgeAdapter.sol"; +import {BridgeErrors} from "../../../src/errors/BridgeErrors.sol"; +import {ISuperchainTokenBridge} from "@interop-lib/interfaces/ISuperchainTokenBridge.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; + +contract MARKBridgeAdapterTest is Test { + RYLA internal token; + MARKBridgeAdapter internal adapter; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + address internal recipient = makeAddr("recipient"); + + uint256 internal constant DST_CHAIN_ID = 902; + address internal constant SUPERCHAIN_BRIDGE = PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE; + + function setUp() public { + vm.prank(owner); + token = new RYLA(owner); + + vm.startPrank(owner); + token.setMinter(owner, true); + token.mint(operator, 500 ether); + adapter = new MARKBridgeAdapter(owner, address(token)); + adapter.setOperator(operator, true); + adapter.setDestination(DST_CHAIN_ID, true); + vm.stopPrank(); + + vm.prank(operator); + IERC20(address(token)).approve(address(adapter), type(uint256).max); + } + + function testBridgeToRevertsWhenCallerNotOperator() public { + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), adapter.OPERATOR_ROLE() + ) + ); + adapter.bridgeTo(recipient, 1 ether, DST_CHAIN_ID); + } + + function testBridgeToRevertsWhenDestinationDisabled() public { + vm.prank(owner); + adapter.setDestination(DST_CHAIN_ID, false); + + vm.prank(operator); + vm.expectRevert(BridgeErrors.DestinationDisabled.selector); + adapter.bridgeTo(recipient, 1 ether, DST_CHAIN_ID); + } + + function testBridgeToCallsSuperchainBridge() public { + uint256 amount = 25 ether; + bytes32 expectedHash = keccak256("bridge-hash-1"); + + bytes memory callData = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, amount, DST_CHAIN_ID + ); + vm.mockCall(SUPERCHAIN_BRIDGE, callData, abi.encode(expectedHash)); + vm.expectCall(SUPERCHAIN_BRIDGE, callData); + + vm.prank(operator); + bytes32 messageHash = adapter.bridgeTo(recipient, amount, DST_CHAIN_ID); + + assertEq(messageHash, expectedHash); + assertEq(token.balanceOf(operator), 475 ether); + assertEq(token.balanceOf(address(adapter)), amount); + } + + function testBridgeToEnforcesMaxPerTx() public { + vm.prank(owner); + adapter.setBridgeLimits(10 ether, 0); + + vm.prank(operator); + vm.expectRevert(BridgeErrors.MaxPerTxExceeded.selector); + adapter.bridgeTo(recipient, 11 ether, DST_CHAIN_ID); + } + + function testBridgeToEnforcesAndResetsDailyCap() public { + vm.prank(owner); + adapter.setBridgeLimits(0, 100 ether); + + bytes memory callData40 = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, 40 ether, DST_CHAIN_ID + ); + bytes memory callData60 = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, 60 ether, DST_CHAIN_ID + ); + + vm.mockCall(SUPERCHAIN_BRIDGE, callData40, abi.encode(keccak256("h40"))); + vm.prank(operator); + adapter.bridgeTo(recipient, 40 ether, DST_CHAIN_ID); + + vm.prank(operator); + vm.expectRevert(BridgeErrors.DailyCapExceeded.selector); + adapter.bridgeTo(recipient, 70 ether, DST_CHAIN_ID); + + vm.warp(block.timestamp + 1 days + 1); + + vm.mockCall(SUPERCHAIN_BRIDGE, callData60, abi.encode(keccak256("h60"))); + vm.prank(operator); + adapter.bridgeTo(recipient, 60 ether, DST_CHAIN_ID); + + assertEq(adapter.bridgedInDailyCapEpoch(), 60 ether); + } + + function testFuzz_BridgeLimitsRespectCapsAndDayReset( + uint96 firstRaw, + uint96 secondRaw, + uint96 thirdRaw + ) public { + vm.prank(owner); + adapter.setBridgeLimits(100 ether, 150 ether); + + uint256 first = bound(uint256(firstRaw), 1, 100 ether); + uint256 second = bound(uint256(secondRaw), 1, 100 ether); + uint256 third = bound(uint256(thirdRaw), 1, 100 ether); + + bytes memory callData1 = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, first, DST_CHAIN_ID + ); + vm.mockCall(SUPERCHAIN_BRIDGE, callData1, abi.encode(keccak256("f1"))); + vm.prank(operator); + adapter.bridgeTo(recipient, first, DST_CHAIN_ID); + + bytes memory callData2 = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, second, DST_CHAIN_ID + ); + if (first + second > 150 ether) { + vm.prank(operator); + vm.expectRevert(BridgeErrors.DailyCapExceeded.selector); + adapter.bridgeTo(recipient, second, DST_CHAIN_ID); + } else { + vm.mockCall(SUPERCHAIN_BRIDGE, callData2, abi.encode(keccak256("f2"))); + vm.prank(operator); + adapter.bridgeTo(recipient, second, DST_CHAIN_ID); + assertEq(adapter.bridgedInDailyCapEpoch(), first + second); + } + + vm.warp(block.timestamp + 1 days + 1); + + bytes memory callData3 = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, third, DST_CHAIN_ID + ); + vm.mockCall(SUPERCHAIN_BRIDGE, callData3, abi.encode(keccak256("f3"))); + vm.prank(operator); + adapter.bridgeTo(recipient, third, DST_CHAIN_ID); + assertEq(adapter.bridgedInDailyCapEpoch(), third); + } +} diff --git a/contracts/test/unit/settlement/AttestedSettlementVerifier.t.sol b/contracts/test/unit/settlement/AttestedSettlementVerifier.t.sol new file mode 100644 index 0000000..fc1495b --- /dev/null +++ b/contracts/test/unit/settlement/AttestedSettlementVerifier.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {AttestedSettlementVerifier} from "../../../src/settlement/verifier/AttestedSettlementVerifier.sol"; + +contract AttestedSettlementVerifierTest is Test { + AttestedSettlementVerifier internal verifier; + + address internal owner = makeAddr("owner"); + address internal settlementModule = makeAddr("settlementModule"); + address internal user = makeAddr("user"); + uint256 internal attesterPk = 0xA11CE; + address internal attester; + + bytes32 internal constant INTENT = keccak256("intent-1"); + bytes32 internal constant CONTEXT = keccak256("ctx-1"); + + function setUp() public { + attester = vm.addr(attesterPk); + vm.prank(owner); + verifier = new AttestedSettlementVerifier(owner); + vm.prank(owner); + verifier.setAttester(attester, true); + } + + function testVerifySettlementReturnsTrueForValidAttestation() public view { + uint256 amount = 25 ether; + uint256 deadline = block.timestamp + 1 hours; + bytes memory proof = _buildProof(INTENT, settlementModule, user, amount, true, CONTEXT, deadline, attesterPk); + bool ok = verifier.verifySettlement(INTENT, settlementModule, user, amount, true, proof); + assertTrue(ok); + } + + function testVerifySettlementReturnsFalseForExpiredAttestation() public view { + uint256 amount = 25 ether; + uint256 deadline = block.timestamp - 1; + bytes memory proof = _buildProof(INTENT, settlementModule, user, amount, true, CONTEXT, deadline, attesterPk); + bool ok = verifier.verifySettlement(INTENT, settlementModule, user, amount, true, proof); + assertFalse(ok); + } + + function testVerifySettlementReturnsFalseForUnauthorizedSigner() public view { + uint256 amount = 25 ether; + uint256 deadline = block.timestamp + 1 hours; + bytes memory proof = _buildProof(INTENT, settlementModule, user, amount, true, CONTEXT, deadline, 0xB0B); + bool ok = verifier.verifySettlement(INTENT, settlementModule, user, amount, true, proof); + assertFalse(ok); + } + + function testVerifySettlementReturnsFalseForWrongAmount() public view { + uint256 amount = 25 ether; + uint256 deadline = block.timestamp + 1 hours; + bytes memory proof = _buildProof(INTENT, settlementModule, user, amount, true, CONTEXT, deadline, attesterPk); + bool ok = verifier.verifySettlement(INTENT, settlementModule, user, amount + 1, true, proof); + assertFalse(ok); + } + + function testVerifySettlementReturnsFalseForMalformedProof() public view { + bool ok = verifier.verifySettlement(INTENT, settlementModule, user, 1 ether, true, hex"1234"); + assertFalse(ok); + } + + function _buildProof( + bytes32 intentId, + address moduleAddress, + address account, + uint256 amount, + bool isMint, + bytes32 contextHash, + uint256 deadline, + uint256 signerPk + ) internal view returns (bytes memory proof) { + bytes32 digest = _settlementDigest(intentId, moduleAddress, account, amount, isMint, contextHash, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, digest); + proof = abi.encode(deadline, contextHash, v, r, s); + } + + function _settlementDigest( + bytes32 intentId, + address moduleAddress, + address account, + uint256 amount, + bool isMint, + bytes32 contextHash, + uint256 deadline + ) internal view returns (bytes32) { + bytes32 settlementHash = keccak256( + abi.encode( + verifier.SETTLEMENT_ATTESTATION_DOMAIN(), + address(verifier), + block.chainid, + intentId, + moduleAddress, + account, + amount, + isMint, + contextHash, + deadline + ) + ); + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); + } +} diff --git a/contracts/test/unit/settlement/MARKSettlementModule.t.sol b/contracts/test/unit/settlement/MARKSettlementModule.t.sol new file mode 100644 index 0000000..03390de --- /dev/null +++ b/contracts/test/unit/settlement/MARKSettlementModule.t.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {IUTXOSettlementVerifier} from "../../../src/settlement/interfaces/IUTXOSettlementVerifier.sol"; +import {SettlementErrors} from "../../../src/errors/SettlementErrors.sol"; + +contract MockUTXOSettlementVerifier is IUTXOSettlementVerifier { + bool public shouldVerify = true; + + function setShouldVerify(bool value) external { + shouldVerify = value; + } + + function verifySettlement(bytes32, address, address, uint256, bool, bytes calldata) external view returns (bool) { + return shouldVerify; + } +} + +contract MARKSettlementModuleTest is Test { + RYLA internal token; + MARKSettlementModule internal module; + MockUTXOSettlementVerifier internal verifier; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + address internal user = makeAddr("user"); + bytes32 internal constant INTENT_M1 = keccak256("m-1"); + bytes32 internal constant INTENT_M2 = keccak256("m-2"); + bytes32 internal constant INTENT_M3 = keccak256("m-3"); + bytes32 internal constant INTENT_M4 = keccak256("m-4"); + bytes32 internal constant INTENT_M5 = keccak256("m-5"); + bytes32 internal constant INTENT_B1 = keccak256("b-1"); + bytes32 internal constant INTENT_B2 = keccak256("b-2"); + + function setUp() public { + vm.prank(owner); + token = new RYLA(owner); + + vm.prank(owner); + module = new MARKSettlementModule(owner, address(token)); + + vm.startPrank(owner); + token.setMinter(address(module), true); + token.setBurner(address(module), true); + module.setOperator(operator, true); + vm.stopPrank(); + + verifier = new MockUTXOSettlementVerifier(); + } + + function testSettleMintRevertsWhenCallerNotOperator() public { + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), module.OPERATOR_ROLE() + ) + ); + module.settleMint(user, 1 ether, INTENT_M1, bytes("")); + } + + function testSettleMintConsumesIntentAndMints() public { + vm.prank(operator); + module.settleMint(user, 5 ether, INTENT_M1, bytes("")); + assertEq(token.balanceOf(user), 5 ether); + assertEq(module.totalSettledMint(), 5 ether); + assertTrue(module.consumedIntents(INTENT_M1)); + + vm.prank(operator); + vm.expectRevert(SettlementErrors.IntentAlreadyConsumed.selector); + module.settleMint(user, 1 ether, INTENT_M1, bytes("")); + } + + function testSettleBurnConsumesIntentAndBurns() public { + vm.prank(operator); + module.settleMint(user, 9 ether, INTENT_M2, bytes("")); + assertEq(token.balanceOf(user), 9 ether); + + vm.prank(user); + bool ok = token.approve(address(module), 4 ether); + assertTrue(ok); + + vm.prank(operator); + module.settleBurn(user, 4 ether, INTENT_B1, bytes("")); + assertEq(token.balanceOf(user), 5 ether); + assertEq(token.totalSupply(), 5 ether); + assertEq(token.balanceOf(address(module)), 0); + assertEq(module.totalSettledBurn(), 4 ether); + assertTrue(module.consumedIntents(INTENT_B1)); + } + + function testSetVerifierAndEnforceValidation() public { + vm.prank(owner); + module.setVerifier(address(verifier), true); + + verifier.setShouldVerify(false); + vm.prank(operator); + vm.expectRevert(SettlementErrors.VerificationFailed.selector); + module.settleMint(user, 1 ether, INTENT_M3, hex"1234"); + + verifier.setShouldVerify(true); + vm.prank(operator); + module.settleMint(user, 1 ether, INTENT_M4, hex"1234"); + assertEq(token.balanceOf(user), 1 ether); + } + + function testSetVerifierRejectsValidationWithoutVerifier() public { + vm.prank(owner); + vm.expectRevert(SettlementErrors.VerifierRequired.selector); + module.setVerifier(address(0), true); + } + + function testActivateProductionModeRequiresEnabledVerifier() public { + vm.prank(owner); + vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); + module.activateProductionMode(); + + vm.prank(owner); + module.setVerifier(address(verifier), true); + vm.prank(owner); + module.activateProductionMode(); + assertTrue(module.productionMode()); + + vm.prank(owner); + vm.expectRevert(SettlementErrors.ProductionModeAlreadyEnabled.selector); + module.activateProductionMode(); + } + + function testProductionModePreventsDisablingProofValidation() public { + vm.prank(owner); + module.setVerifier(address(verifier), true); + vm.prank(owner); + module.activateProductionMode(); + + vm.prank(owner); + vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); + module.setVerifier(address(0), false); + } + + function testProductionModePreventsVerifierSwapWithProofDisabled() public { + vm.prank(owner); + module.setVerifier(address(verifier), true); + vm.prank(owner); + module.activateProductionMode(); + + vm.prank(owner); + vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); + module.setVerifier(address(verifier), false); + } + + function testProductionModeStillEnforcesBurnProofValidation() public { + vm.startPrank(owner); + module.setVerifier(address(verifier), true); + module.activateProductionMode(); + vm.stopPrank(); + + vm.prank(operator); + module.settleMint(user, 3 ether, INTENT_M5, hex"1234"); + + vm.prank(user); + assertTrue(token.approve(address(module), 3 ether)); + + verifier.setShouldVerify(false); + vm.prank(operator); + vm.expectRevert(SettlementErrors.VerificationFailed.selector); + module.settleBurn(user, 1 ether, INTENT_B2, hex"1234"); + } +} From 3b7b9f412eae711725b7bb49025a76f0438d1477 Mon Sep 17 00:00:00 2001 From: Iko Date: Wed, 29 Apr 2026 15:37:18 +0700 Subject: [PATCH 07/38] ci(contracts): enforce architecture/layering guards and fix refactor paths --- .github/workflows/contracts-ci.yml | 12 +++++++++--- .github/workflows/contracts-slither.yml | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index e0b27a5..31d4989 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -37,6 +37,12 @@ jobs: - name: Setup Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Enforce architecture boundaries + run: make architecture-guard + + - name: Enforce test/script layering boundaries + run: make layering-guard + - name: Run deterministic test suite run: forge test -vv @@ -71,7 +77,7 @@ jobs: run: anvil --host 127.0.0.1 --port 8545 > /tmp/anvil.log 2>&1 & - name: Run release orchestrator dry-run - run: forge script script/ops/ReleaseMARK.s.sol --rpc-url $RPC_URL -vv + run: forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url $RPC_URL -vv - name: Run release orchestrator execute smoke (local Anvil) env: @@ -82,7 +88,7 @@ jobs: VERIFY_MARK_SETTLEMENT_PROOF_ENABLED: "false" VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE: "false" VERIFY_MARK_SETTLEMENT_VERIFIER: "0x0000000000000000000000000000000000000000" - run: forge script script/ops/ReleaseMARK.s.sol --rpc-url $RPC_URL --broadcast -vv + run: forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url $RPC_URL --broadcast -vv - name: Assert release artifact schema run: | @@ -170,7 +176,7 @@ jobs: working-directory: . - name: Run integration suite - run: FOUNDRY_PROFILE=integration forge test --match-path 'test/integration/*.t.sol' -vv + run: FOUNDRY_PROFILE=integration forge test --match-path 'test/integration/**/*.t.sol' -vv - name: Print supersim logs on failure if: failure() diff --git a/.github/workflows/contracts-slither.yml b/.github/workflows/contracts-slither.yml index 082fd84..93770b3 100644 --- a/.github/workflows/contracts-slither.yml +++ b/.github/workflows/contracts-slither.yml @@ -42,9 +42,9 @@ jobs: run: | slither \ src/token/RYLA.sol \ - src/protocol/MARKBridgeAdapter.sol \ - src/protocol/MARKSettlementModule.sol \ - src/verifier/AttestedSettlementVerifier.sol \ + src/bridge/MARKBridgeAdapter.sol \ + src/settlement/MARKSettlementModule.sol \ + src/settlement/verifier/AttestedSettlementVerifier.sol \ --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ --exclude-dependencies \ --filter-paths "lib|test|script|out|cache" \ From 079ba13023fff517cc41572e30f2fd736fd0b2a6 Mon Sep 17 00:00:00 2001 From: Iko Date: Wed, 29 Apr 2026 15:37:38 +0700 Subject: [PATCH 08/38] chore(contracts): remove legacy files replaced by domain refactor --- contracts/script/Deploy.s.sol | 42 ----- contracts/src/CrossChainCounter.sol | 52 ------ .../src/CrossChainCounterIncrementer.sol | 26 --- contracts/src/protocol/MARKBridgeAdapter.sol | 116 ------------ .../src/protocol/MARKSettlementModule.sol | 116 ------------ contracts/test/CrossChainIncrementer.t.sol | 48 ----- contracts/test/e2e/MARKSettlementE2E.t.sol | 154 ---------------- contracts/test/unit/MARKBridgeAdapter.t.sol | 112 ------------ .../test/unit/MARKSettlementModule.t.sol | 167 ------------------ 9 files changed, 833 deletions(-) delete mode 100644 contracts/script/Deploy.s.sol delete mode 100644 contracts/src/CrossChainCounter.sol delete mode 100644 contracts/src/CrossChainCounterIncrementer.sol delete mode 100644 contracts/src/protocol/MARKBridgeAdapter.sol delete mode 100644 contracts/src/protocol/MARKSettlementModule.sol delete mode 100644 contracts/test/CrossChainIncrementer.t.sol delete mode 100644 contracts/test/e2e/MARKSettlementE2E.t.sol delete mode 100644 contracts/test/unit/MARKBridgeAdapter.t.sol delete mode 100644 contracts/test/unit/MARKSettlementModule.t.sol diff --git a/contracts/script/Deploy.s.sol b/contracts/script/Deploy.s.sol deleted file mode 100644 index 5178b10..0000000 --- a/contracts/script/Deploy.s.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Script, console} from "forge-std/Script.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {ICreateX} from "createx/ICreateX.sol"; - -import {DeployUtils} from "../libraries/DeployUtils.sol"; -import {CrossChainCounter} from "../src/CrossChainCounter.sol"; - -// Example forge script for deploying as an alternative to sup: super-cli (https://github.com/ethereum-optimism/super-cli) -contract Deploy is Script { - /// @notice Array of RPC URLs to deploy to, deploy to supersim 901 and 902 by default. - string[] private rpcUrls = ["http://localhost:9545", "http://localhost:9546"]; - - /// @notice Modifier that wraps a function in broadcasting. - modifier broadcast() { - vm.startBroadcast(msg.sender); - _; - vm.stopBroadcast(); - } - - function run() public { - for (uint256 i = 0; i < rpcUrls.length; i++) { - string memory rpcUrl = rpcUrls[i]; - - console.log("Deploying to RPC: ", rpcUrl); - vm.createSelectFork(rpcUrl); - deployCrossChainCounterContract(); - } - } - - function deployCrossChainCounterContract() public broadcast returns (address addr_) { - bytes memory initCode = abi.encodePacked(type(CrossChainCounter).creationCode); - addr_ = DeployUtils.deployContract("CrossChainCounter", _implSalt(), initCode); - } - - /// @notice The CREATE2 salt to be used when deploying a contract. - function _implSalt() internal view returns (bytes32) { - return keccak256(abi.encodePacked(vm.envOr("DEPLOY_SALT", string("ethers phoenix")))); - } -} diff --git a/contracts/src/CrossChainCounter.sol b/contracts/src/CrossChainCounter.sol deleted file mode 100644 index 02d9658..0000000 --- a/contracts/src/CrossChainCounter.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {CrossDomainMessageLib} from "@interop-lib/libraries/CrossDomainMessageLib.sol"; - -/// @title CrossChainCounter -/// @notice A simple Counter that can only be incremented **from other chains** -contract CrossChainCounter { - /// @notice Emitted when the counter is incremented from another chain - /// @param senderChainId The chain ID where the increment request originated - /// @param sender The address that requested the increment on the origin chain - /// @param newValue The new value of the counter after incrementing - event CounterIncremented(uint256 indexed senderChainId, address indexed sender, uint256 newValue); - - /// @notice Struct to store the last incrementer's details - struct Incrementer { - uint256 chainId; // The chain ID where the increment originated - address sender; // The address that triggered the increment - } - - /// @notice The current value of the counter - uint256 public number; - - /// @notice Details about the last address to increment the counter - Incrementer public lastIncrementer; - - /// @notice Increments the counter by 1 - /// @dev Can only be called through the L2ToL2CrossDomainMessenger contract - function increment() public { - // Verify that this function is being called by the L2ToL2CrossDomainMessenger contract - CrossDomainMessageLib.requireCallerIsCrossDomainMessenger(); - - // Get the original sender's address from the origin chain - address sender = - IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageSender(); - - // Get the chain ID where the increment request originated - uint256 senderChainId = - IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageSource(); - - // Increment the counter - number += 1; - - // Store the incrementer's details - lastIncrementer = Incrementer(senderChainId, sender); - - // Emit an event for off-chain tracking and indexing - emit CounterIncremented(senderChainId, sender, number); - } -} diff --git a/contracts/src/CrossChainCounterIncrementer.sol b/contracts/src/CrossChainCounterIncrementer.sol deleted file mode 100644 index 099370b..0000000 --- a/contracts/src/CrossChainCounterIncrementer.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {CrossDomainMessageLib} from "@interop-lib/libraries/CrossDomainMessageLib.sol"; - -import {CrossChainCounter} from "./CrossChainCounter.sol"; - -/// @title CrossChainCounterIncrementer -/// @notice A contract that sends cross-chain messages to increment a counter on another chain -contract CrossChainCounterIncrementer { - /// @dev The L2 to L2 cross domain messenger predeploy to handle message passing - IL2ToL2CrossDomainMessenger internal messenger = - IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - - /// @notice Sends a message to increment a counter on another chain - /// @param counterChainId The chain ID where the target counter contract is deployed - /// @param counterAddress The address of the counter contract on the target chain - function increment(uint256 counterChainId, address counterAddress) public { - // Send a cross-chain message to increment the counter on the target chain - messenger.sendMessage( - counterChainId, counterAddress, abi.encodeWithSelector(CrossChainCounter.increment.selector) - ); - } -} diff --git a/contracts/src/protocol/MARKBridgeAdapter.sol b/contracts/src/protocol/MARKBridgeAdapter.sol deleted file mode 100644 index 1b3c7fa..0000000 --- a/contracts/src/protocol/MARKBridgeAdapter.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { - AccessControlDefaultAdminRules -} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {ISuperchainTokenBridge} from "@interop-lib/interfaces/ISuperchainTokenBridge.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; -import {BridgeErrors} from "../errors/BridgeErrors.sol"; - -/// @title MARKBridgeAdapter -/// @notice Operator-gated bridge-out adapter for RYLA using SuperchainTokenBridge. -/// @dev Uses destination allowlist and optional per-tx / daily caps. -contract MARKBridgeAdapter is ReentrancyGuard, AccessControlDefaultAdminRules, BridgeErrors { - using SafeERC20 for IERC20; - - event OperatorUpdated(address indexed operator, bool enabled); - event DestinationUpdated(uint256 indexed destinationChainId, bool enabled); - event BridgeLimitsUpdated(uint256 maxPerTx, uint256 dailyCap); - event BridgedOut( - address indexed operator, - address indexed recipient, - uint256 indexed destinationChainId, - uint256 amount, - bytes32 messageHash - ); - - uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; - bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); - - IERC20 public immutable TOKEN; - ISuperchainTokenBridge public constant SUPERCHAIN_TOKEN_BRIDGE = - ISuperchainTokenBridge(PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE); - - mapping(uint256 => bool) public destinationEnabled; - - uint256 public maxPerTx; - uint256 public dailyCap; - uint64 public dailyCapEpoch; - uint256 public bridgedInDailyCapEpoch; - - constructor(address initialAdmin, address tokenAddress) - AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) - { - if (initialAdmin == address(0)) revert ZeroAddress(); - if (tokenAddress == address(0)) revert ZeroAddress(); - TOKEN = IERC20(tokenAddress); - } - - function setOperator(address operator, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { - if (operator == address(0)) revert ZeroAddress(); - if (enabled) { - _grantRole(OPERATOR_ROLE, operator); - } else { - _revokeRole(OPERATOR_ROLE, operator); - } - emit OperatorUpdated(operator, enabled); - } - - function setDestination(uint256 destinationChainId, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { - if (destinationChainId == 0) revert InvalidChainId(); - destinationEnabled[destinationChainId] = enabled; - emit DestinationUpdated(destinationChainId, enabled); - } - - /// @notice Sets optional bridge limits. Set `0` to disable each limiter. - function setBridgeLimits(uint256 maxPerTx_, uint256 dailyCap_) external onlyRole(DEFAULT_ADMIN_ROLE) { - maxPerTx = maxPerTx_; - dailyCap = dailyCap_; - dailyCapEpoch = 0; - bridgedInDailyCapEpoch = 0; - emit BridgeLimitsUpdated(maxPerTx_, dailyCap_); - } - - function bridgeTo(address recipient, uint256 amount, uint256 destinationChainId) - external - onlyRole(OPERATOR_ROLE) - nonReentrant - returns (bytes32 messageHash) - { - if (recipient == address(0)) revert ZeroAddress(); - if (amount == 0) revert InvalidAmount(); - if (destinationChainId == 0) revert InvalidChainId(); - if (!destinationEnabled[destinationChainId]) revert DestinationDisabled(); - - _consumeLimits(amount); - - TOKEN.safeTransferFrom(msg.sender, address(this), amount); - TOKEN.forceApprove(address(SUPERCHAIN_TOKEN_BRIDGE), amount); - - messageHash = SUPERCHAIN_TOKEN_BRIDGE.sendERC20(address(TOKEN), recipient, amount, destinationChainId); - emit BridgedOut(msg.sender, recipient, destinationChainId, amount, messageHash); - } - - function _consumeLimits(uint256 amount) internal { - uint256 maxPerTx_ = maxPerTx; - if (maxPerTx_ > 0 && amount > maxPerTx_) revert MaxPerTxExceeded(); - - uint256 dailyCap_ = dailyCap; - if (dailyCap_ == 0) return; - - uint64 epoch = uint64(block.timestamp / 1 days); - if (epoch != dailyCapEpoch) { - dailyCapEpoch = epoch; - bridgedInDailyCapEpoch = 0; - } - - uint256 nextBridged = bridgedInDailyCapEpoch + amount; - if (nextBridged > dailyCap_) revert DailyCapExceeded(); - bridgedInDailyCapEpoch = nextBridged; - } -} diff --git a/contracts/src/protocol/MARKSettlementModule.sol b/contracts/src/protocol/MARKSettlementModule.sol deleted file mode 100644 index ee7a17f..0000000 --- a/contracts/src/protocol/MARKSettlementModule.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { - AccessControlDefaultAdminRules -} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {RYLA} from "../token/RYLA.sol"; -import {IUTXOSettlementVerifier} from "../interfaces/IUTXOSettlementVerifier.sol"; -import {SettlementErrors} from "../errors/SettlementErrors.sol"; -import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; - -/// @title MARKSettlementModule -/// @notice Boundary module for integrating external UTXO/zk accounting with RYLA mint/burn. -/// @dev Holds RYLA minter and burner roles. Replay protection is enforced via `intentId`. -contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules, SettlementErrors { - using SafeERC20 for IERC20; - event OperatorUpdated(address indexed operator, bool enabled); - event VerifierUpdated(address indexed verifier, bool validationEnabled); - event ProductionModeActivated(address indexed admin); - event MintSettled(bytes32 indexed intentId, address indexed operator, address indexed recipient, uint256 amount); - event BurnSettled(bytes32 indexed intentId, address indexed operator, address indexed account, uint256 amount); - - uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; - bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); - - RYLA public immutable TOKEN; - - mapping(bytes32 => bool) public consumedIntents; - - IUTXOSettlementVerifier public verifier; - bool public proofValidationEnabled; - bool public productionMode; - - constructor(address initialAdmin, address tokenAddress) - AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) - { - if (initialAdmin == address(0)) revert ZeroAddress(); - if (tokenAddress == address(0)) revert ZeroAddress(); - TOKEN = RYLA(tokenAddress); - } - - function setOperator(address operator, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { - if (operator == address(0)) revert ZeroAddress(); - if (enabled) { - _grantRole(OPERATOR_ROLE, operator); - } else { - _revokeRole(OPERATOR_ROLE, operator); - } - emit OperatorUpdated(operator, enabled); - } - - function setVerifier(address verifierAddress, bool enableValidation) external onlyRole(DEFAULT_ADMIN_ROLE) { - if (productionMode && (!enableValidation || verifierAddress == address(0))) { - revert ProductionModeRequiresProofValidation(); - } - if (enableValidation && verifierAddress == address(0)) revert VerifierRequired(); - if (verifierAddress != address(0) && verifierAddress.code.length == 0) revert VerifierRequired(); - - verifier = IUTXOSettlementVerifier(verifierAddress); - proofValidationEnabled = enableValidation; - emit VerifierUpdated(verifierAddress, enableValidation); - } - - /// @notice Irreversibly enables production mode that forces proof validation to remain active. - function activateProductionMode() external onlyRole(DEFAULT_ADMIN_ROLE) { - if (productionMode) revert ProductionModeAlreadyEnabled(); - if (!proofValidationEnabled || address(verifier) == address(0)) { - revert ProductionModeRequiresProofValidation(); - } - productionMode = true; - emit ProductionModeActivated(msg.sender); - } - - function settleMint(address recipient, uint256 amount, bytes32 intentId, bytes calldata proof) - external - onlyRole(OPERATOR_ROLE) - nonReentrant - { - if (recipient == address(0)) revert ZeroAddress(); - _consumeAndValidate(intentId, recipient, amount, true, proof); - TOKEN.mint(recipient, amount); - emit MintSettled(intentId, msg.sender, recipient, amount); - } - - function settleBurn(address account, uint256 amount, bytes32 intentId, bytes calldata proof) - external - onlyRole(OPERATOR_ROLE) - nonReentrant - { - if (account == address(0)) revert ZeroAddress(); - _consumeAndValidate(intentId, account, amount, false, proof); - IERC20(address(TOKEN)).safeTransferFrom(account, address(this), amount); - TOKEN.burn(amount); - emit BurnSettled(intentId, msg.sender, account, amount); - } - - function _consumeAndValidate(bytes32 intentId, address account, uint256 amount, bool isMint, bytes calldata proof) - internal - { - if (intentId == bytes32(0)) revert InvalidIntent(); - if (amount == 0) revert InvalidAmount(); - if (consumedIntents[intentId]) revert IntentAlreadyConsumed(); - - if (proofValidationEnabled) { - IUTXOSettlementVerifier verifier_ = verifier; - if (address(verifier_) == address(0)) revert VerifierRequired(); - bool ok = verifier_.verifySettlement(intentId, address(this), account, amount, isMint, proof); - if (!ok) revert VerificationFailed(); - } - - consumedIntents[intentId] = true; - } -} diff --git a/contracts/test/CrossChainIncrementer.t.sol b/contracts/test/CrossChainIncrementer.t.sol deleted file mode 100644 index 17909fe..0000000 --- a/contracts/test/CrossChainIncrementer.t.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Test} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {console} from "forge-std/console.sol"; -import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {Relayer} from "@interop-lib/test/Relayer.sol"; -import {CrossChainCounterIncrementer} from "../src/CrossChainCounterIncrementer.sol"; -import {CrossChainCounter} from "../src/CrossChainCounter.sol"; - -contract CrossChainIncrementerTest is Relayer, Test { - CrossChainCounterIncrementer public incrementer; - CrossChainCounter public counter; - - string[] private rpcUrls = [ - vm.envOr("CHAIN_A_RPC_URL", string("https://interop-rc-alpha-0.optimism.io/")), - vm.envOr("CHAIN_B_RPC_URL", string("https://interop-rc-alpha-1.optimism.io/")) - ]; - - constructor() Relayer(rpcUrls) {} - - function setUp() public { - vm.selectFork(forkIds[0]); - incrementer = new CrossChainCounterIncrementer{salt: bytes32(0)}(); - - vm.selectFork(forkIds[1]); - counter = new CrossChainCounter{salt: bytes32(0)}(); - } - - // Test incrementing from a valid cross-chain message - function test_increment_crossDomain_succeeds() public { - vm.selectFork(forkIds[0]); - - incrementer.increment(chainIdByForkId[forkIds[1]], address(counter)); - - // verify counter has not been incremented on chainB - vm.selectFork(forkIds[1]); - assertEq(counter.number(), 0); - - relayAllMessages(); - - // verify counter has been incremented on chainB - vm.selectFork(forkIds[1]); - assertEq(counter.number(), 1); - } -} diff --git a/contracts/test/e2e/MARKSettlementE2E.t.sol b/contracts/test/e2e/MARKSettlementE2E.t.sol deleted file mode 100644 index 41f3075..0000000 --- a/contracts/test/e2e/MARKSettlementE2E.t.sol +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Test} from "forge-std/Test.sol"; -import {RYLA} from "../../src/token/RYLA.sol"; -import {MARKSettlementModule} from "../../src/protocol/MARKSettlementModule.sol"; -import {AttestedSettlementVerifier} from "../../src/verifier/AttestedSettlementVerifier.sol"; -import {SettlementErrors} from "../../src/errors/SettlementErrors.sol"; - -contract MARKSettlementE2ETest is Test { - RYLA internal token; - MARKSettlementModule internal module; - MARKSettlementModule internal moduleB; - AttestedSettlementVerifier internal verifier; - - address internal owner = makeAddr("owner"); - address internal operator = makeAddr("operator"); - address internal user = makeAddr("user"); - uint256 internal attesterPk = 0xC0FFEE; - address internal attester; - - bytes32 internal constant INTENT_MINT = keccak256("e2e-mint-1"); - bytes32 internal constant INTENT_BURN = keccak256("e2e-burn-1"); - bytes32 internal constant CONTEXT_MINT = keccak256("e2e-mint-context"); - bytes32 internal constant CONTEXT_BURN = keccak256("e2e-burn-context"); - - function setUp() public { - attester = vm.addr(attesterPk); - - vm.prank(owner); - token = new RYLA(owner); - - vm.prank(owner); - verifier = new AttestedSettlementVerifier(owner); - - vm.prank(owner); - module = new MARKSettlementModule(owner, address(token)); - vm.prank(owner); - moduleB = new MARKSettlementModule(owner, address(token)); - - vm.startPrank(owner); - verifier.setAttester(attester, true); - module.setOperator(operator, true); - moduleB.setOperator(operator, true); - module.setVerifier(address(verifier), true); - moduleB.setVerifier(address(verifier), true); - token.setMinter(address(module), true); - token.setBurner(address(module), true); - token.setMinter(address(moduleB), true); - token.setBurner(address(moduleB), true); - vm.stopPrank(); - } - - function testMintThenBurnLifecycleWithValidationEnabled() public { - uint256 mintAmount = 100 ether; - uint256 mintDeadline = block.timestamp + 1 hours; - bytes memory mintProof = _buildProof(INTENT_MINT, address(module), user, mintAmount, true, CONTEXT_MINT, mintDeadline); - - vm.prank(operator); - module.settleMint(user, mintAmount, INTENT_MINT, mintProof); - assertEq(token.balanceOf(user), mintAmount); - - uint256 burnAmount = 40 ether; - vm.prank(user); - bool ok = token.approve(address(module), burnAmount); - assertTrue(ok); - - uint256 burnDeadline = block.timestamp + 1 hours; - bytes memory burnProof = - _buildProof(INTENT_BURN, address(module), user, burnAmount, false, CONTEXT_BURN, burnDeadline); - - vm.prank(operator); - module.settleBurn(user, burnAmount, INTENT_BURN, burnProof); - - assertEq(token.balanceOf(user), 60 ether); - assertEq(token.totalSupply(), 60 ether); - } - - function testReplayIntentReverts() public { - uint256 deadline = block.timestamp + 1 hours; - bytes memory mintProof = _buildProof(INTENT_MINT, address(module), user, 1 ether, true, CONTEXT_MINT, deadline); - - vm.prank(operator); - module.settleMint(user, 1 ether, INTENT_MINT, mintProof); - - vm.prank(operator); - vm.expectRevert(SettlementErrors.IntentAlreadyConsumed.selector); - module.settleMint(user, 1 ether, INTENT_MINT, mintProof); - } - - function testInvalidAttestationReverts() public { - uint256 deadline = block.timestamp + 1 hours; - bytes32 settlementHash = keccak256( - abi.encode( - verifier.SETTLEMENT_ATTESTATION_DOMAIN(), - address(verifier), - block.chainid, - INTENT_MINT, - address(module), - user, - 1 ether, - true, - CONTEXT_MINT, - deadline - ) - ); - bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(0xDEAD, digest); - bytes memory wrongProof = abi.encode(deadline, CONTEXT_MINT, v, r, s); - - vm.prank(operator); - vm.expectRevert(SettlementErrors.VerificationFailed.selector); - module.settleMint(user, 1 ether, INTENT_MINT, wrongProof); - } - - function testProofBoundToModuleAddressRevertsOnDifferentModule() public { - uint256 amount = 3 ether; - uint256 deadline = block.timestamp + 1 hours; - bytes memory proofForModuleA = - _buildProof(INTENT_MINT, address(module), user, amount, true, CONTEXT_MINT, deadline); - - vm.prank(operator); - vm.expectRevert(SettlementErrors.VerificationFailed.selector); - moduleB.settleMint(user, amount, INTENT_MINT, proofForModuleA); - } - - function _buildProof( - bytes32 intentId, - address moduleAddress, - address account, - uint256 amount, - bool isMint, - bytes32 contextHash, - uint256 deadline - ) internal view returns (bytes memory) { - bytes32 settlementHash = keccak256( - abi.encode( - verifier.SETTLEMENT_ATTESTATION_DOMAIN(), - address(verifier), - block.chainid, - intentId, - moduleAddress, - account, - amount, - isMint, - contextHash, - deadline - ) - ); - bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(attesterPk, digest); - return abi.encode(deadline, contextHash, v, r, s); - } -} diff --git a/contracts/test/unit/MARKBridgeAdapter.t.sol b/contracts/test/unit/MARKBridgeAdapter.t.sol deleted file mode 100644 index d00a04b..0000000 --- a/contracts/test/unit/MARKBridgeAdapter.t.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -import {Test} from "forge-std/Test.sol"; -import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {RYLA} from "../../src/token/RYLA.sol"; -import {MARKBridgeAdapter} from "../../src/protocol/MARKBridgeAdapter.sol"; -import {BridgeErrors} from "../../src/errors/BridgeErrors.sol"; -import {ISuperchainTokenBridge} from "@interop-lib/interfaces/ISuperchainTokenBridge.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; - -contract MARKBridgeAdapterTest is Test { - RYLA internal token; - MARKBridgeAdapter internal adapter; - - address internal owner = makeAddr("owner"); - address internal operator = makeAddr("operator"); - address internal recipient = makeAddr("recipient"); - - uint256 internal constant DST_CHAIN_ID = 902; - address internal constant SUPERCHAIN_BRIDGE = PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE; - - function setUp() public { - vm.prank(owner); - token = new RYLA(owner); - - vm.startPrank(owner); - token.setMinter(owner, true); - token.mint(operator, 500 ether); - adapter = new MARKBridgeAdapter(owner, address(token)); - adapter.setOperator(operator, true); - adapter.setDestination(DST_CHAIN_ID, true); - vm.stopPrank(); - - vm.prank(operator); - IERC20(address(token)).approve(address(adapter), type(uint256).max); - } - - function testBridgeToRevertsWhenCallerNotOperator() public { - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), adapter.OPERATOR_ROLE() - ) - ); - adapter.bridgeTo(recipient, 1 ether, DST_CHAIN_ID); - } - - function testBridgeToRevertsWhenDestinationDisabled() public { - vm.prank(owner); - adapter.setDestination(DST_CHAIN_ID, false); - - vm.prank(operator); - vm.expectRevert(BridgeErrors.DestinationDisabled.selector); - adapter.bridgeTo(recipient, 1 ether, DST_CHAIN_ID); - } - - function testBridgeToCallsSuperchainBridge() public { - uint256 amount = 25 ether; - bytes32 expectedHash = keccak256("bridge-hash-1"); - - bytes memory callData = abi.encodeWithSelector( - ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, amount, DST_CHAIN_ID - ); - vm.mockCall(SUPERCHAIN_BRIDGE, callData, abi.encode(expectedHash)); - vm.expectCall(SUPERCHAIN_BRIDGE, callData); - - vm.prank(operator); - bytes32 messageHash = adapter.bridgeTo(recipient, amount, DST_CHAIN_ID); - - assertEq(messageHash, expectedHash); - assertEq(token.balanceOf(operator), 475 ether); - assertEq(token.balanceOf(address(adapter)), amount); - } - - function testBridgeToEnforcesMaxPerTx() public { - vm.prank(owner); - adapter.setBridgeLimits(10 ether, 0); - - vm.prank(operator); - vm.expectRevert(BridgeErrors.MaxPerTxExceeded.selector); - adapter.bridgeTo(recipient, 11 ether, DST_CHAIN_ID); - } - - function testBridgeToEnforcesAndResetsDailyCap() public { - vm.prank(owner); - adapter.setBridgeLimits(0, 100 ether); - - bytes memory callData40 = abi.encodeWithSelector( - ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, 40 ether, DST_CHAIN_ID - ); - bytes memory callData60 = abi.encodeWithSelector( - ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, 60 ether, DST_CHAIN_ID - ); - - vm.mockCall(SUPERCHAIN_BRIDGE, callData40, abi.encode(keccak256("h40"))); - vm.prank(operator); - adapter.bridgeTo(recipient, 40 ether, DST_CHAIN_ID); - - vm.prank(operator); - vm.expectRevert(BridgeErrors.DailyCapExceeded.selector); - adapter.bridgeTo(recipient, 70 ether, DST_CHAIN_ID); - - vm.warp(block.timestamp + 1 days + 1); - - vm.mockCall(SUPERCHAIN_BRIDGE, callData60, abi.encode(keccak256("h60"))); - vm.prank(operator); - adapter.bridgeTo(recipient, 60 ether, DST_CHAIN_ID); - - assertEq(adapter.bridgedInDailyCapEpoch(), 60 ether); - } -} diff --git a/contracts/test/unit/MARKSettlementModule.t.sol b/contracts/test/unit/MARKSettlementModule.t.sol deleted file mode 100644 index f53f221..0000000 --- a/contracts/test/unit/MARKSettlementModule.t.sol +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -import {Test} from "forge-std/Test.sol"; -import {RYLA} from "../../src/token/RYLA.sol"; -import {MARKSettlementModule} from "../../src/protocol/MARKSettlementModule.sol"; -import {IUTXOSettlementVerifier} from "../../src/interfaces/IUTXOSettlementVerifier.sol"; -import {SettlementErrors} from "../../src/errors/SettlementErrors.sol"; - -contract MockUTXOSettlementVerifier is IUTXOSettlementVerifier { - bool public shouldVerify = true; - - function setShouldVerify(bool value) external { - shouldVerify = value; - } - - function verifySettlement(bytes32, address, address, uint256, bool, bytes calldata) external view returns (bool) { - return shouldVerify; - } -} - -contract MARKSettlementModuleTest is Test { - RYLA internal token; - MARKSettlementModule internal module; - MockUTXOSettlementVerifier internal verifier; - - address internal owner = makeAddr("owner"); - address internal operator = makeAddr("operator"); - address internal user = makeAddr("user"); - bytes32 internal constant INTENT_M1 = keccak256("m-1"); - bytes32 internal constant INTENT_M2 = keccak256("m-2"); - bytes32 internal constant INTENT_M3 = keccak256("m-3"); - bytes32 internal constant INTENT_M4 = keccak256("m-4"); - bytes32 internal constant INTENT_M5 = keccak256("m-5"); - bytes32 internal constant INTENT_B1 = keccak256("b-1"); - bytes32 internal constant INTENT_B2 = keccak256("b-2"); - - function setUp() public { - vm.prank(owner); - token = new RYLA(owner); - - vm.prank(owner); - module = new MARKSettlementModule(owner, address(token)); - - vm.startPrank(owner); - token.setMinter(address(module), true); - token.setBurner(address(module), true); - module.setOperator(operator, true); - vm.stopPrank(); - - verifier = new MockUTXOSettlementVerifier(); - } - - function testSettleMintRevertsWhenCallerNotOperator() public { - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), module.OPERATOR_ROLE() - ) - ); - module.settleMint(user, 1 ether, INTENT_M1, bytes("")); - } - - function testSettleMintConsumesIntentAndMints() public { - vm.prank(operator); - module.settleMint(user, 5 ether, INTENT_M1, bytes("")); - assertEq(token.balanceOf(user), 5 ether); - assertTrue(module.consumedIntents(INTENT_M1)); - - vm.prank(operator); - vm.expectRevert(SettlementErrors.IntentAlreadyConsumed.selector); - module.settleMint(user, 1 ether, INTENT_M1, bytes("")); - } - - function testSettleBurnConsumesIntentAndBurns() public { - vm.prank(operator); - module.settleMint(user, 9 ether, INTENT_M2, bytes("")); - assertEq(token.balanceOf(user), 9 ether); - - vm.prank(user); - bool ok = token.approve(address(module), 4 ether); - assertTrue(ok); - - vm.prank(operator); - module.settleBurn(user, 4 ether, INTENT_B1, bytes("")); - assertEq(token.balanceOf(user), 5 ether); - assertEq(token.totalSupply(), 5 ether); - assertTrue(module.consumedIntents(INTENT_B1)); - } - - function testSetVerifierAndEnforceValidation() public { - vm.prank(owner); - module.setVerifier(address(verifier), true); - - verifier.setShouldVerify(false); - vm.prank(operator); - vm.expectRevert(SettlementErrors.VerificationFailed.selector); - module.settleMint(user, 1 ether, INTENT_M3, hex"1234"); - - verifier.setShouldVerify(true); - vm.prank(operator); - module.settleMint(user, 1 ether, INTENT_M4, hex"1234"); - assertEq(token.balanceOf(user), 1 ether); - } - - function testSetVerifierRejectsValidationWithoutVerifier() public { - vm.prank(owner); - vm.expectRevert(SettlementErrors.VerifierRequired.selector); - module.setVerifier(address(0), true); - } - - function testActivateProductionModeRequiresEnabledVerifier() public { - vm.prank(owner); - vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); - module.activateProductionMode(); - - vm.prank(owner); - module.setVerifier(address(verifier), true); - vm.prank(owner); - module.activateProductionMode(); - assertTrue(module.productionMode()); - - vm.prank(owner); - vm.expectRevert(SettlementErrors.ProductionModeAlreadyEnabled.selector); - module.activateProductionMode(); - } - - function testProductionModePreventsDisablingProofValidation() public { - vm.prank(owner); - module.setVerifier(address(verifier), true); - vm.prank(owner); - module.activateProductionMode(); - - vm.prank(owner); - vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); - module.setVerifier(address(0), false); - } - - function testProductionModePreventsVerifierSwapWithProofDisabled() public { - vm.prank(owner); - module.setVerifier(address(verifier), true); - vm.prank(owner); - module.activateProductionMode(); - - vm.prank(owner); - vm.expectRevert(SettlementErrors.ProductionModeRequiresProofValidation.selector); - module.setVerifier(address(verifier), false); - } - - function testProductionModeStillEnforcesBurnProofValidation() public { - vm.startPrank(owner); - module.setVerifier(address(verifier), true); - module.activateProductionMode(); - vm.stopPrank(); - - vm.prank(operator); - module.settleMint(user, 3 ether, INTENT_M5, hex"1234"); - - vm.prank(user); - assertTrue(token.approve(address(module), 3 ether)); - - verifier.setShouldVerify(false); - vm.prank(operator); - vm.expectRevert(SettlementErrors.VerificationFailed.selector); - module.settleBurn(user, 1 ether, INTENT_B2, hex"1234"); - } -} From e8a7ae9f1d1cce24cf8c4ff74680f82a1c132d1f Mon Sep 17 00:00:00 2001 From: Iko Date: Wed, 29 Apr 2026 16:12:37 +0700 Subject: [PATCH 09/38] feat(ops): add canonical release-gate workflow with evidence artifact --- contracts/Makefile | 5 +- contracts/README.md | 12 +++++ contracts/script/ci/release-gate.sh | 84 +++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100755 contracts/script/ci/release-gate.sh diff --git a/contracts/Makefile b/contracts/Makefile index 2179aeb..12fff95 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -1,4 +1,4 @@ -.PHONY: ci-local ci-fast ci-full architecture-guard layering-guard smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature +.PHONY: ci-local ci-fast ci-full release-gate architecture-guard layering-guard smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature # Fast local loop: guards + default-profile tests (no integration profile tests). ci-fast: architecture-guard layering-guard @@ -11,6 +11,9 @@ ci-full: ci-fast # Backward-compatible alias for local usage. ci-local: ci-fast +release-gate: + @./script/ci/release-gate.sh + architecture-guard: @./script/ci/architecture-guard.sh diff --git a/contracts/README.md b/contracts/README.md index a3b8f78..aec0845 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -81,6 +81,18 @@ Run the full local CI checks (includes explicit production-lock checks): make ci-full ``` +Run canonical release gate checks and emit a timestamped evidence artifact: + +```bash +make release-gate +``` + +For remote/mainnet-style verification (requires `RPC_URL`, `PRIVATE_KEY`, and strict `VERIFY_*` envs): + +```bash +MARK_RELEASE_GATE_MODE=remote make release-gate +``` + Run integration (fork/RPC-dependent) tests only: ```bash diff --git a/contracts/script/ci/release-gate.sh b/contracts/script/ci/release-gate.sh new file mode 100755 index 0000000..c0b3dc2 --- /dev/null +++ b/contracts/script/ci/release-gate.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "release-gate: required command not found: $1" >&2 + exit 1 + fi +} + +require_cmd forge +require_cmd jq +require_cmd git + +MODE="${MARK_RELEASE_GATE_MODE:-local}" # local | remote +OUT_DIR="${MARK_RELEASE_GATE_OUT_DIR:-broadcast/release-gate}" +TIMESTAMP_UTC="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" +STAMP="$(date -u +"%Y%m%dT%H%M%SZ")" +GIT_COMMIT="$(git rev-parse --short HEAD)" +ARTIFACT_PATH="$OUT_DIR/release-gate-$STAMP.json" + +mkdir -p "$OUT_DIR" + +echo "[release-gate] mode=$MODE" +echo "[release-gate] step 1/3: make ci-full" +make ci-full + +READINESS_STATUS="skipped" +VERIFY_STATUS="skipped" + +if [[ "$MODE" == "remote" ]]; then + if [[ -z "${RPC_URL:-}" ]]; then + echo "release-gate: RPC_URL is required for remote mode" >&2 + exit 1 + fi + if [[ -z "${PRIVATE_KEY:-}" ]]; then + echo "release-gate: PRIVATE_KEY is required for remote mode" >&2 + exit 1 + fi + + echo "[release-gate] step 2/3: mainnet-readiness (dry checks)" + MARK_MAINNET_GATE_MODE="${MARK_MAINNET_GATE_MODE:-predeploy}" \ + MARK_MAINNET_GATE_ARTIFACT_PATH="${MARK_MAINNET_GATE_ARTIFACT_PATH:-$OUT_DIR/mainnet-gate-$STAMP.json}" \ + ./script/ops/mainnet-readiness.sh + READINESS_STATUS="passed" + + echo "[release-gate] step 3/3: strict deployment verification" + # Strict mode: require explicit VERIFY_* env instead of implicit defaults. + : "${VERIFY_MARK_RYLA_TOKEN:?VERIFY_MARK_RYLA_TOKEN is required in remote mode}" + : "${VERIFY_MARK_RYLA_OWNER:?VERIFY_MARK_RYLA_OWNER is required in remote mode}" + : "${VERIFY_MARK_SETTLEMENT_MODULE:?VERIFY_MARK_SETTLEMENT_MODULE is required in remote mode}" + : "${VERIFY_MARK_SETTLEMENT_OPERATOR:?VERIFY_MARK_SETTLEMENT_OPERATOR is required in remote mode}" + : "${VERIFY_MARK_SETTLEMENT_PROOF_ENABLED:?VERIFY_MARK_SETTLEMENT_PROOF_ENABLED is required in remote mode}" + : "${VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE:?VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE is required in remote mode}" + : "${VERIFY_MARK_SETTLEMENT_VERIFIER:?VERIFY_MARK_SETTLEMENT_VERIFIER is required in remote mode}" + forge script script/ops/settlement/VerifyMARKDeployment.s.sol --rpc-url "$RPC_URL" -q + VERIFY_STATUS="passed" +fi + +jq -n \ + --arg gate "release-gate" \ + --arg mode "$MODE" \ + --arg timestamp "$TIMESTAMP_UTC" \ + --arg commit "$GIT_COMMIT" \ + --arg ciFull "passed" \ + --arg readiness "$READINESS_STATUS" \ + --arg verify "$VERIFY_STATUS" \ + '{ + gate: $gate, + mode: $mode, + timestamp: $timestamp, + gitCommit: $commit, + checks: { + ciFull: $ciFull, + mainnetReadiness: $readiness, + strictDeploymentVerify: $verify + } + }' > "$ARTIFACT_PATH" + +echo "[release-gate] PASSED" +echo "[release-gate] artifact: $ARTIFACT_PATH" From 73baf439eadbb51442f4cd50ac4c699e09e86534 Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 21:10:51 +0700 Subject: [PATCH 10/38] chore(governance): align release flow and policy guard with canary promotion 1. Update release PR template to canary -> main 2. Extend governance-policy-guard push triggers to include canary 3. Document explicit release promotion path dev -> canary -> main in root README 4. Clarify retirement of legacy CrossChainCounter examples/tests in contracts README 5. Keep governance consistency validator passing after updates --- .github/PULL_REQUEST_TEMPLATE/release.md | 46 +++++ .github/workflows/governance-policy-guard.yml | 32 +++ README.md | 190 +++++------------- contracts/README.md | 47 ++--- 4 files changed, 150 insertions(+), 165 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE/release.md create mode 100644 .github/workflows/governance-policy-guard.yml diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md new file mode 100644 index 0000000..d5b343c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -0,0 +1,46 @@ +## Release PR (`canary` -> `main`) + +Use this template only for production candidate merges. + +## Release Scope + +- Release tag/version candidate: +- Commit range / PR range: +- Contracts affected: + +## Required Evidence + +- [ ] `Contracts Unit + Invariant` CI passed +- [ ] `Contracts Release Check (Dry-Run + Execute Smoke)` CI passed +- [ ] `Slither Core Contracts` CI passed +- [ ] `Contracts Mainnet Readiness` run from `main` branch +- [ ] Readiness artifact uploaded and reviewed +- [ ] Verify output reviewed (role/config expectations) + +Evidence links/values: +- Mainnet readiness run URL: +- Readiness artifact SHA256: + +## Security + Ops Sign-off + +- [ ] Protocol owner/admin signer approval +- [ ] Security reviewer approval +- [ ] Deployment operator approval + +## Deployment Inputs + +- RPC target: +- Artifact path: +- `MARK_GIT_COMMIT` value: +- Environment used: `production` + +## Go / No-Go + +- [ ] Go +- [ ] No-Go (reason) + +## Post-Merge Plan + +- [ ] Run/confirm deployment sequence in `contracts/RUNBOOK.md` +- [ ] Tag release on `main` +- [ ] Back-merge any hotfixes into `dev` (if applicable) diff --git a/.github/workflows/governance-policy-guard.yml b/.github/workflows/governance-policy-guard.yml new file mode 100644 index 0000000..7021d4d --- /dev/null +++ b/.github/workflows/governance-policy-guard.yml @@ -0,0 +1,32 @@ +name: Governance Policy Guard + +on: + pull_request: + paths: + - "scripts/github/apply-governance.sh" + - "BRANCHING.md" + - ".github/PRODUCTION_GOVERNANCE_CHECKLIST.md" + - ".github/workflows/governance-policy-guard.yml" + push: + branches: + - main + - canary + - dev + paths: + - "scripts/github/apply-governance.sh" + - "BRANCHING.md" + - ".github/PRODUCTION_GOVERNANCE_CHECKLIST.md" + - ".github/workflows/governance-policy-guard.yml" + workflow_dispatch: + +jobs: + validate-governance-policy: + name: Validate Governance Policy Consistency + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run governance policy validator + run: bash scripts/ci/validate-governance-policy.sh diff --git a/README.md b/README.md index abdea34..eb68b31 100644 --- a/README.md +++ b/README.md @@ -1,201 +1,113 @@ -# Superchain Starter Kit +# Mark Protocol -A lightweight, focused starting point for prototyping/building on the Superchain, featuring +Decentralised and privacy-first by design. Leveraging zero-knowledge proofs for secure, scalable settlement on the Superchain. -- 🛠 [foundry](https://github.com/foundry-rs/foundry), [supersim](https://github.com/ethereum-optimism/supersim), [super-cli](https://github.com/ethereum-optimism/super-cli) -- 🎨 wagmi, viem -- [@eth-optimism/viem](https://github.com/ethereum-optimism/ecosystem/tree/main/packages/viem), [@eth-optimism/wagmi](https://github.com/ethereum-optimism/ecosystem/tree/main/packages/wagmi) - viem/wagmi extensions for the Superchain -- 💡 simple example app - CrossChainCounter +## Getting Started -Screenshot 2025-02-17 at 8 09 02 PM +### Prerequisites -## 🚀 Getting started +- [Foundry](https://book.getfoundry.sh/getting-started/installation) +- Node.js + pnpm -Get prototyping Superchain apps in under < 1 min! ❤️‍🔥 - -### Prerequisites: Foundry & Node - -Follow [this guide](https://book.getfoundry.sh/getting-started/installation) to install Foundry - -### 1. Create a new repository using this template: - -Click the "Use this template" button above on GitHub, or [generate directly](https://github.com/new?template_name=superchain-starter&template_owner=ethereum-optimism) - -### 2. Clone your new repository +### 1. Clone the repository ```bash -git clone -cd +git clone +cd ``` -### 3. Install dependencies +### 2. Install dependencies ```bash pnpm i ``` -### 4. Get started +### 3. Start development ```bash pnpm dev ``` -This command will: - -- Start a local Superchain network (1 L1 chain and 2 L2 chains) using [supersim](https://github.com/ethereum-optimism/supersim) -- Launch the frontend development server at (http://localhost:5173) -- Deploy the smart contracts to your local network +This will: -Start building on the Superchain! +- Start a local Superchain network (1 L1 + 2 L2 chains) via [supersim](https://github.com/ethereum-optimism/supersim) +- Launch the frontend at http://localhost:5173 +- Deploy contracts to the local network -## Deploying contracts +## Branching Policy -The starter kit uses `super-cli` (or `sup`) to streamline contract deployment across the Superchain ecosystem. `sup` works great with Foundry projects while eliminating common multichain friction points: +Full policy is documented in [BRANCHING.md](./BRANCHING.md). -- 🔄 **Foundry Compatible**: Seamlessly works with your existing Foundry setup and artifacts -- ⛓️ **Multi-Chain**: Deploy to multiple chains with a single command and pre-configured RPCs -- ⛽ **Gasless Deployments**: Instead of having to bridge to `n` chains -- 🎯 **Interactive mode**: No more complex command-line arguments +- `dev` — active integration and feature work +- `canary` — stabilisation, maps to staging deployment +- `main` — production-ready only +- Release promotion path: `dev -> canary -> main` +- Production readiness workflow is gated to `main` -Alternatively, if you want to use Forge scripts directly, follow the multichain deployment example at [`contracts/script/Deploy.s.sol`](contracts/script/Deploy.s.sol) +## Deploying Contracts -### Deploying +Mark uses `super-cli` (`sup`) for contract deployment across the Superchain. -Once you're ready to deploy, start `sup` in interactive mode +### Interactive mode ```bash pnpm sup ``` -Then you can follow the steps to deploy to `supersim` or the `interop-alpha` devnet - -```bash - -🚀 Deploy Create2 Wizard - - ✓ Enter Foundry Project Path ./contracts/ - ✓ Select Contract CrossChainCounter.sol - ✓ Configure Constructor Arguments - ✓ Configure Salt ethers phoenix - ✓ Select Network interop-alpha - ✓ Select Chains interop-alpha-0, interop-alpha-1 - > Verify Contract - -Press ← to go back - - Do you want to verify the contract on the block explorer? Y/n - -``` - ### Non-interactive mode -You can also skip the interactive mode entirely by passing the necessary arguments - ```bash -pnpm sup deploy create2 --chains supersiml2a,supersiml2b --salt ethers phoenix --forge-artifact-path contracts/out/CrossChainCounter.sol/CrossChainCounter.json --network supersim --private-key 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a +pnpm sup deploy create2 --chains supersiml2a,supersiml2b --salt ethers phoenix --forge-artifact-path contracts/out/.sol/.json --network supersim --private-key ``` -### `--prepare` mode - -To "prepare" a command without running it, run `sup` with the prepare mode. This will print the command instead of running it. Then you can run the prepared command directly to run immediately in non-interactive mode. +### Prepare mode (print command without running) ```bash pnpm sup --prepare ``` -### Make sure to build before deploying - -`sup` assumes that your foundry project has already been built. Make sure to build before attempting to deploy +### Build before deploying -``` +```bash pnpm build:contracts ``` -## 👀 Overview - -### Example app - -#### `CrossChainCounter` contract - -- Simple `Hello world` for Superchain Interop -- Unlike the [single chain Counter](https://github.com/foundry-rs/foundry/blob/master/crates/forge/assets/CounterTemplate.sol), this one can only be incremented via cross-chain messages -- Learn more about this contract [here](./contracts/README.md) +## Overview ### Tools -- **[supersim](https://github.com/ethereum-optimism/supersim)**: Local test environment with 1 L1 and multiple L2 chains, includes pre-deployed Superchain contracts -- **[sup (super-cli)](https://github.com/ethereum-optimism/super-cli)**: Deploy and verify contracts across multiple chains, with sponsored transactions -- **foundry**: Blazing fast smart contract development framework -- **wagmi / viem**: Best in class Typescript library for the EVM, -- **vite / tailwind / shadcn**: Frontend development tools and UI components - -### 📁 Directory structure +- **[supersim](https://github.com/ethereum-optimism/supersim)** — local Superchain test environment with pre-deployed contracts +- **[sup (super-cli)](https://github.com/ethereum-optimism/super-cli)** — multi-chain deployment with sponsored transactions +- **foundry** — smart contract development framework +- **wagmi / viem** — TypeScript libraries for the EVM +- **vite / tailwind / shadcn** — frontend tooling and UI components -This starter kit is organized to get you building on the Superchain as quickly as possible. Solidity code goes in `/contracts`, and the typescript frontend goes in `/src` +### Directory Structure ``` -superchain-starter/ -├── contracts/ # Smart contract code (Foundry) -├── src/ # Frontend code (vite, tailwind, shadcn, wagmi, viem) -│ └── App.tsx # Main application component -├── public/ # Static assets for the frontend -├── supersim-logs/ # Local supersim logs -├── package.json # Project dependencies and scripts -└── mprocs.yaml # Run multiple commands using mprocs +mark/ +├── contracts/ # Smart contract code (Foundry) +├── src/ # Frontend code (vite, tailwind, shadcn, wagmi, viem) +│ └── App.tsx # Main application component +├── public/ # Static assets +├── supersim-logs/ # Local supersim logs +├── package.json # Project dependencies and scripts +└── mprocs.yaml # Multi-process dev runner ``` -### A note on project structure +## Debugging -While this structure is great for getting started and building proof of concepts, it's worth noting that many production applications eventually migrate to separate repositories for contracts and frontend code. - -For reference, here are some examples of this separation in production applications: - -- Uniswap: [Uniswap contracts](https://github.com/Uniswap/v4-core), [Uniswap frontend](https://github.com/Uniswap/interface) -- Across: [Across contracts](https://github.com/across-protocol/contracts), [Across frontend](https://github.com/across-protocol/frontend) -- Farcaster: [Farcaster contracts](https://github.com/farcasterxyz/contracts) - -## 🐛 Debugging - -Use the error selectors below to identify the cause of reverts. - -- For a complete list of error signatures from interoperability contracts, see [abi-signatures.md](https://github.com/ethereum-optimism/ecosystem/blob/main/packages/viem/docs/abi-signatures.md) -- Examples: +- Full interoperability error signatures: [abi-signatures.md](https://github.com/ethereum-optimism/ecosystem/blob/main/packages/viem/docs/abi-signatures.md) +- Common errors: - `TargetCallFailed()`: `0xeda86850` - `MessageAlreadyRelayed`: `0x9ca9480b` - `Unauthorized()`: `0x82b42900` - - -## 📚 More resources -- Interop recipes / guides: https://docs.optimism.io/app-developers/tutorials/interop +## Resources + +- Interop guides: https://docs.optimism.io/app-developers/tutorials/interop - Superchain Dev Console: https://console.optimism.io/ -## 😎 Moooaaar examples - -Want to see more? Here are more example crosschain apps for inspiration / patterns! - -- ⚡ [Crosschain Flash Loan](https://github.com/ethereum-optimism/superchain-starter-xchain-flash-loan-example) - - Dependent cross-chain messages (compose multiple cross-domain messages) - - Using SuperchainTokenBridge for cross-chain ERC20 transfers - - Multichain lending vaults using `L2ToL2CrossDomainMessenger` -- 💸 [Multitransfer](https://github.com/ethereum-optimism/superchain-starter-xchain-eth-multitransfer) - - How to set up cross-chain callbacks (contract calling itself on another chain) - - Using SuperchainETHBridge for cross-chain ETH transfers - - Dependent cross-chain messages (compose multiple cross-domain messages) -- 🪙 [SuperchainERC20](https://github.com/ethereum-optimism/superchain-starter-superchainerc20) - - Using ERC-7802 interface for SuperchainERC20 tokens - - How to upgrade existing ERC20s into SuperchainERC20 - - Minting supply on only one chain - - Deterministic address deployment on all chains -- 🏓 [CrossChainPingPong](https://docs.optimism.io/app-developers/tutorials/interop/contract-calls) - - Simple example of passing state between multiple chains using cross domain messenger - - How to set up cross-chain callbacks (contract calling itself on another chain) -- 🕹️ [CrossChainTicTacToe](https://docs.optimism.io/app-developers/tutorials/interop/event-reads) - - Allows players to play each other from any chain **without** cross-chain calls, instead relying on cross-chain event reading - - Creating horizontally scalable apps with interop - -## ⚖️ License +## License Files are licensed under the [MIT license](./LICENSE). - -License information diff --git a/contracts/README.md b/contracts/README.md index aec0845..12c4cc1 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -37,20 +37,10 @@ Operational procedures (deployment, incident, rollback) are documented in [RUNBO - Proof format: - `abi.encode(uint256 deadline, bytes32 contextHash, uint8 v, bytes32 r, bytes32 s)` -### [CrossChainCounter.sol](./src/examples/CrossChainCounter.sol) - -- Counter that can only be incremented through cross-chain messages -- Uses `L2ToL2CrossDomainMessenger` for message verification -- Tracks last incrementer's chain ID and address -- Events emitted for all increments with source chain details - -### [CrossChainCounterIncrementer.sol](./src/examples/CrossChainCounterIncrementer.sol) - -- Sends cross-chain increment messages to `CrossChainCounter` instances -- Uses `L2ToL2CrossDomainMessenger` for message passing - ## Development +Note: legacy CrossChainCounter example contracts and tests were retired in favor of MARK protocol deployment/ops flows. Current CI and release gates focus on MARK stack contracts and governance evidence artifacts. + ### Dependencies ```bash @@ -87,7 +77,17 @@ Run canonical release gate checks and emit a timestamped evidence artifact: make release-gate ``` -For remote/mainnet-style verification (requires `RPC_URL`, `PRIVATE_KEY`, and strict `VERIFY_*` envs): +For remote/mainnet-style verification, run with: +- `RPC_URL` and `PRIVATE_KEY` +- anchored release artifact via `MARK_RELEASE_VERIFY_ARTIFACT_PATH` or `MARK_RELEASE_ARTIFACT_PATH` +- signed evidence verification inputs (default-on): + - `VERIFY_PUBLIC_KEY_FILE` or `VERIFY_PUBLIC_KEY_PEM` + - optional manifest path overrides: + - `MARK_RELEASE_VERIFY_MANIFEST_PATH` + - `MARK_RELEASE_VERIFY_SIGNATURE_PATH` + - `MARK_RELEASE_VERIFY_SIGNATURE_META_PATH` + +Set `MARK_RELEASE_VERIFY_REQUIRE_SIGNED_MANIFEST=false` only for controlled break-glass scenarios. ```bash MARK_RELEASE_GATE_MODE=remote make release-gate @@ -111,10 +111,11 @@ Deploy to multiple chains using either: cd ../ && pnpm sup ``` -2. Direct Forge script: +2. Direct Forge script (MARK stack): ```bash -forge script script/examples/Deploy.s.sol --rpc-url $RPC_URL --broadcast +set -a && source .env && set +a +forge script script/deploy/bridge/DeployMARKStack.s.sol --rpc-url $RPC_URL --broadcast ``` Deploy `RYLA` stack: @@ -379,18 +380,12 @@ Optional: cancel pending transfer before acceptance ## Architecture -### Cross-Chain Messaging Flow (1) - -1. User calls `increment(chainId, counterAddress)` on `CrossChainCounterIncrementer` -2. `CrossChainCounterIncrementer` sends message via `L2ToL2CrossDomainMessenger` -3. Target chain's messenger delivers message to `CrossChainCounter` -4. `CrossChainCounter` verifies messenger and executes increment - -### Cross-Chain Messaging Flow (2) +### MARK Settlement Flow -1. User calls `increment(chainId, counterAddress)` on `CrossChainCounterIncrementer` by directly ending a message through `L2ToL2CrossDomainMessenger` -2. Target chain's messenger delivers message to `CrossChainCounter` -3. `CrossChainCounter` verifies messenger and executes increment +1. Operators submit settlement intents through `MARKSettlementModule`. +2. Optional verifier (`AttestedSettlementVerifier` or custom `IUTXOSettlementVerifier`) validates intent proof material. +3. Settlement module mints/burns `RYLA` under role-constrained rules. +4. Bridge adapter enforces destination/risk controls for cross-chain transfers. ## Testing From 2b67e2d1c43c8ba36f496218808d78054360ea9c Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 21:11:32 +0700 Subject: [PATCH 11/38] feat(release): harden CI gates and retire cross-chain demo artifacts 1. Enable canary across contracts CI, env guard, slither, and secrets drift workflows 2. Add canary push-driven staging rehearsal defaults and stricter required input checks 3. Strengthen release gate with signed evidence-manifest verification and artifact-anchored deployment verification 4. Add evidence tooling scripts (generate/sign/verify manifest and signature, verify-from-artifact) 5. Retire legacy CrossChainCounter example contracts, ABIs, deploy script, and associated tests 6. Update app shell and package scripts toward MARK protocol operations workflow --- .github/CODEOWNERS | 14 + .github/PRODUCTION_GOVERNANCE_CHECKLIST.md | 131 ++++++ .github/pull_request_template.md | 35 ++ .github/workflows/contracts-ci.yml | 1 + .github/workflows/contracts-env-guard.yml | 1 + .github/workflows/contracts-slither.yml | 1 + .../workflows/contracts-staging-rehearsal.yml | 39 +- .github/workflows/secrets-drift-guard.yml | 1 + BRANCHING.md | 139 ++++++ contracts/script/ci/release-gate.sh | 51 ++- contracts/script/ci/verify-from-artifact.sh | 180 ++++++++ contracts/script/examples/Deploy.s.sol | 42 -- .../script/ops/generate-evidence-manifest.sh | 90 ++++ .../script/ops/settlement/ReleaseMARK.s.sol | 10 + .../script/ops/sign-evidence-manifest.sh | 86 ++++ .../script/ops/verify-evidence-manifest.sh | 71 ++++ .../script/ops/verify-evidence-signature.sh | 102 +++++ contracts/src/examples/CrossChainCounter.sol | 52 --- .../examples/CrossChainCounterIncrementer.sol | 26 -- contracts/test/CrossChainCounter.t.sol | 115 ----- .../settlement/CrossChainIncrementer.t.sol | 48 --- package.json | 10 +- scripts/ci/validate-governance-policy.sh | 91 ++++ scripts/github/apply-governance.sh | 276 ++++++++++++ src/App.tsx | 399 +++--------------- src/abi/crossChainCounterAbi.ts | 31 -- src/abi/crossChainCounterIncrementerAbi.ts | 12 - 27 files changed, 1372 insertions(+), 682 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/PRODUCTION_GOVERNANCE_CHECKLIST.md create mode 100644 .github/pull_request_template.md create mode 100644 BRANCHING.md create mode 100755 contracts/script/ci/verify-from-artifact.sh delete mode 100644 contracts/script/examples/Deploy.s.sol create mode 100755 contracts/script/ops/generate-evidence-manifest.sh create mode 100755 contracts/script/ops/sign-evidence-manifest.sh create mode 100755 contracts/script/ops/verify-evidence-manifest.sh create mode 100755 contracts/script/ops/verify-evidence-signature.sh delete mode 100644 contracts/src/examples/CrossChainCounter.sol delete mode 100644 contracts/src/examples/CrossChainCounterIncrementer.sol delete mode 100644 contracts/test/CrossChainCounter.t.sol delete mode 100644 contracts/test/integration/settlement/CrossChainIncrementer.t.sol create mode 100644 scripts/ci/validate-governance-policy.sh create mode 100755 scripts/github/apply-governance.sh delete mode 100644 src/abi/crossChainCounterAbi.ts delete mode 100644 src/abi/crossChainCounterIncrementerAbi.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..234ec4e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,14 @@ +# Global default owner +* @iap + +# Protocol-critical scope +/contracts/src/** @iap +/contracts/script/** @iap +/contracts/test/** @iap +/contracts/RUNBOOK.md @iap +/contracts/README.md @iap + +# CI and governance +/.github/workflows/** @iap +/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md @iap +/BRANCHING.md @iap diff --git a/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md b/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md new file mode 100644 index 0000000..fbbd5c3 --- /dev/null +++ b/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md @@ -0,0 +1,131 @@ +# Production Governance Checklist (GitHub) + +Use this checklist to apply repository settings that enforce the `dev` -> `canary` -> `main` release process. + +## 1) Protect `main` branch + +GitHub path: `Settings -> Branches -> Add branch protection rule` + +- Branch name pattern: `main` +- Enable `Require a pull request before merging` +- Enable `Require approvals` and set minimum to `2` (or `1` if your team is small) +- Enable `Dismiss stale pull request approvals when new commits are pushed` +- Enable `Require status checks to pass before merging` +- Add required checks: + - `Contracts Unit + Invariant` + - `Contracts Release Check (Dry-Run + Execute Smoke)` + - `Slither Core Contracts` + - `Secrets Drift Guard` + - `Validate Release PR Checklist` + - `Validate Release Evidence` +- Governance policy PR rule: + - If PR changes `scripts/github/apply-governance.sh`, `BRANCHING.md`, or this checklist, ensure `Validate Governance Policy Consistency` passes before merge. +- Enable `Require branches to be up to date before merging` +- Enable `Restrict who can push to matching branches` (maintainers only) +- Enable `Do not allow bypassing the above settings` + +## 2) Protect `canary` branch + +GitHub path: `Settings -> Branches -> Add branch protection rule` + +- Branch name pattern: `canary` +- Enable `Require a pull request before merging` +- Enable `Require approvals` and set minimum to `1` +- Enable `Dismiss stale pull request approvals when new commits are pushed` +- Enable `Require status checks to pass before merging` +- Add required checks: + - `Contracts Unit + Invariant` + - `Contracts Release Check (Dry-Run + Execute Smoke)` + - `Slither Core Contracts` + - `Secrets Drift Guard` +- Governance policy PR rule: + - If PR changes `scripts/github/apply-governance.sh`, `BRANCHING.md`, or this checklist, ensure `Validate Governance Policy Consistency` passes before merge. +- Enable `Require branches to be up to date before merging` + +## 3) Protect `dev` branch + +GitHub path: `Settings -> Branches -> Add branch protection rule` + +- Branch name pattern: `dev` +- Enable `Require a pull request before merging` +- Enable `Require status checks to pass before merging` +- Add required checks: + - `Contracts Unit + Invariant` + - `Contracts Release Check (Dry-Run + Execute Smoke)` + - `Slither Core Contracts` + - `Secrets Drift Guard` +- Governance policy PR rule: + - If PR changes `scripts/github/apply-governance.sh`, `BRANCHING.md`, or this checklist, ensure `Validate Governance Policy Consistency` passes before merge. +- Choose one model: + - Strict model: also restrict direct push to maintainers only + - Fast model: allow maintainer direct push for emergency dev iteration + +## 4) Configure `production` environment + +GitHub path: `Settings -> Environments -> New environment` + +- Environment name: `production` +- Enable required reviewers (at least `1`, recommended `2`) +- Add secret: + - `MARK_DEPLOYER_PRIVATE_KEY` +- Optional environment variables: + - `MARK_MAINNET_GATE_MODE=predeploy` (default input still controls mode) + +Notes: +- `contracts-mainnet-readiness.yml` already binds to `environment: production`. +- The workflow already enforces `main` branch execution. + +## 5) Restrict release tagging + +GitHub path: `Settings -> Rules -> Rulesets` (or tag protection in legacy settings) + +- Protect tag pattern: `v*` +- Restrict create/update/delete tag permissions to maintainers/release managers. + +## 6) Validation run (one-time) + +1. Open a small PR to `dev` changing docs only. +2. Confirm required checks run and pass. +3. Merge PR to `dev`. +4. Open PR `dev -> canary`; confirm staging rehearsal triggers automatically. +5. Open PR `canary -> main`. +6. Confirm required checks are enforced on `main`. +7. Merge into `main`. +8. Run workflow `Contracts Mainnet Readiness` from `main`. +9. Confirm: + - workflow requests/uses `production` environment approvals + - run succeeds + - readiness artifact uploads + +## 7) Ongoing operational rule + +- No production deployment from `dev` or `canary`. +- Production readiness + deployment sign-off only from `main`. +- Any emergency `main` hotfix must be back-merged into `canary` and `dev` immediately after release. + +## 8) Optional automation (API) + +You can apply most settings via script: + +```bash +cd /path/to/mark +export GH_PAT= +# optional: +# export GH_REPO=iap/mark +# export MAIN_REVIEW_COUNT=2 +# export DEV_REVIEW_COUNT=1 +# export MAIN_PUSH_ALLOW_USERS=iap +# export MAIN_PUSH_ALLOW_TEAMS=release-managers +# export CANARY_PUSH_ALLOW_USERS=iap +# export DEV_PUSH_ALLOW_USERS=iap +# export PRODUCTION_REVIEWER_IDS=12345,67890 +./scripts/github/apply-governance.sh +``` + +What this script applies: +- `main` branch protection (PR + checks + stale review dismissal) +- `canary` branch protection (PR + checks) +- `dev` branch protection (PR + checks) +- `production` environment creation +- optional production required reviewers by user ID +- optional direct-push restrictions via `*_PUSH_ALLOW_*` allowlists diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..703b206 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,35 @@ +# Summary + +Describe the change and why it is needed. + +## Scope + +- [ ] Contracts +- [ ] Scripts/ops +- [ ] Workflows/CI +- [ ] Docs/runbook + +## Verification + +- [ ] `forge build` passes locally +- [ ] `forge test` passes locally +- [ ] If contracts changed: slither scan reviewed +- [ ] No secrets/private keys added + +## Risk Review + +- [ ] Access control changes reviewed +- [ ] Upgrade/deployment behavior reviewed +- [ ] Backward compatibility impact reviewed +- [ ] No unintended changes to production deployment flow + +## Governance + +- [ ] Target branch is correct (`dev` for normal work, `main` for production release PR) +- [ ] CODEOWNER review requested +- [ ] Relevant runbook/docs updated + +## Linked Context + +- Issue / task: +- Related PRs: diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index 31d4989..a49c85d 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -8,6 +8,7 @@ on: push: branches: - main + - canary - dev paths: - "contracts/**" diff --git a/.github/workflows/contracts-env-guard.yml b/.github/workflows/contracts-env-guard.yml index 7142811..4956abf 100644 --- a/.github/workflows/contracts-env-guard.yml +++ b/.github/workflows/contracts-env-guard.yml @@ -8,6 +8,7 @@ on: push: branches: - main + - canary - dev paths: - "contracts/**" diff --git a/.github/workflows/contracts-slither.yml b/.github/workflows/contracts-slither.yml index 93770b3..56133f5 100644 --- a/.github/workflows/contracts-slither.yml +++ b/.github/workflows/contracts-slither.yml @@ -9,6 +9,7 @@ on: push: branches: - main + - canary - dev paths: - "contracts/src/**" diff --git a/.github/workflows/contracts-staging-rehearsal.yml b/.github/workflows/contracts-staging-rehearsal.yml index 548e48f..a7ef426 100644 --- a/.github/workflows/contracts-staging-rehearsal.yml +++ b/.github/workflows/contracts-staging-rehearsal.yml @@ -1,6 +1,11 @@ name: Contracts Staging Rehearsal on: + push: + branches: + - canary + paths: + - "contracts/**" workflow_dispatch: inputs: rpc_url: @@ -51,22 +56,22 @@ jobs: run: working-directory: contracts env: - RPC_URL: ${{ inputs.rpc_url }} + RPC_URL: ${{ github.event_name == 'push' && vars.MARK_STAGING_RPC_URL || inputs.rpc_url }} PRIVATE_KEY: ${{ secrets.MARK_STAGING_DEPLOYER_PRIVATE_KEY }} - MARK_RYLA_OWNER: ${{ inputs.owner_address }} - MARK_SETTLEMENT_OPERATOR: ${{ inputs.settlement_operator }} - MARK_BRIDGE_OPERATOR: ${{ inputs.bridge_operator }} - MARK_BRIDGE_DESTINATION_CHAIN_ID: ${{ inputs.destination_chain_id }} - MARK_SETTLEMENT_ATTESTER: ${{ inputs.attester_address }} - MARK_RELEASE_ARTIFACT_PATH: ${{ inputs.release_artifact_path }} - MARK_REHEARSAL_ARTIFACT_PATH: ${{ inputs.rehearsal_artifact_path }} + MARK_RYLA_OWNER: ${{ github.event_name == 'push' && vars.MARK_STAGING_OWNER_ADDRESS || inputs.owner_address }} + MARK_SETTLEMENT_OPERATOR: ${{ github.event_name == 'push' && vars.MARK_STAGING_SETTLEMENT_OPERATOR || inputs.settlement_operator }} + MARK_BRIDGE_OPERATOR: ${{ github.event_name == 'push' && vars.MARK_STAGING_BRIDGE_OPERATOR || inputs.bridge_operator }} + MARK_BRIDGE_DESTINATION_CHAIN_ID: ${{ github.event_name == 'push' && vars.MARK_STAGING_DESTINATION_CHAIN_ID || inputs.destination_chain_id }} + MARK_SETTLEMENT_ATTESTER: ${{ github.event_name == 'push' && vars.MARK_STAGING_ATTESTER_ADDRESS || inputs.attester_address }} + MARK_RELEASE_ARTIFACT_PATH: ${{ github.event_name == 'push' && 'broadcast/mark-staging-release.json' || inputs.release_artifact_path }} + MARK_REHEARSAL_ARTIFACT_PATH: ${{ github.event_name == 'push' && 'broadcast/mark-staging-rehearsal.json' || inputs.rehearsal_artifact_path }} MARK_GIT_COMMIT: ${{ github.sha }} steps: - - name: Enforce dev or main branch for rehearsal + - name: Enforce canary or dev or main branch for rehearsal run: | - if [ "${GITHUB_REF_NAME}" != "dev" ] && [ "${GITHUB_REF_NAME}" != "main" ]; then - echo "Staging rehearsal must run from dev or main branch. Current: ${GITHUB_REF_NAME}" + if [ "${GITHUB_REF_NAME}" != "canary" ] && [ "${GITHUB_REF_NAME}" != "dev" ] && [ "${GITHUB_REF_NAME}" != "main" ]; then + echo "Staging rehearsal must run from canary, dev, or main branch. Current: ${GITHUB_REF_NAME}" exit 1 fi @@ -81,6 +86,14 @@ jobs: echo "MARK_STAGING_DEPLOYER_PRIVATE_KEY secret is required"; exit 1; } + test -n "${RPC_URL}" || { + echo "RPC_URL is required (for canary push use repository variable MARK_STAGING_RPC_URL, for manual run use rpc_url input)"; + exit 1; + } + test -n "${MARK_SETTLEMENT_OPERATOR}" || { + echo "MARK_SETTLEMENT_OPERATOR is required (for canary push use MARK_STAGING_SETTLEMENT_OPERATOR variable)"; + exit 1; + } - name: Setup Foundry uses: foundry-rs/foundry-toolchain@v1 @@ -93,7 +106,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: mark-staging-release - path: contracts/${{ inputs.release_artifact_path }} + path: contracts/${{ env.MARK_RELEASE_ARTIFACT_PATH }} if-no-files-found: ignore - name: Upload rehearsal artifact @@ -101,5 +114,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: mark-staging-rehearsal - path: contracts/${{ inputs.rehearsal_artifact_path }} + path: contracts/${{ env.MARK_REHEARSAL_ARTIFACT_PATH }} if-no-files-found: ignore diff --git a/.github/workflows/secrets-drift-guard.yml b/.github/workflows/secrets-drift-guard.yml index b20c2d5..d8c94de 100644 --- a/.github/workflows/secrets-drift-guard.yml +++ b/.github/workflows/secrets-drift-guard.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - dev + - canary - main permissions: diff --git a/BRANCHING.md b/BRANCHING.md new file mode 100644 index 0000000..f26db6f --- /dev/null +++ b/BRANCHING.md @@ -0,0 +1,139 @@ +# Branch Strategy + +This repository uses a three-track branch model: + +- `dev`: active integration — default target for all feature work +- `canary`: stabilisation and staging — maps to testnet/staging deployment +- `main`: production-ready only — source of truth for mainnet releases + +## Branch Roles + +### `dev` +- Default target for feature branches and iterative changes. +- May include ongoing refactors and config updates not yet ready for staging. +- Must stay buildable and testable at all times. + +### `canary` +- Stabilisation branch between `dev` and `main`. +- Automatically triggers staging rehearsal deployment (OP Sepolia). +- Code here is considered release-candidate quality. +- No direct feature work — only PRs from `dev` or `hotfix/*`. + +### `main` +- Contains only reviewed, release-ready code. +- Used for production deployment preparation and release tags. +- Must pass full contract checks before merge. + +## Working Branches + +- `feature/`: regular feature or refactor work, branch from `dev`, merge into `dev`. +- `hotfix/`: urgent production fix, branch from `main`, merge to `main` and `canary`, then back-merge to `dev`. +- `release/` (optional): additional stabilisation before merging `canary` into `main`. + +## CI and Deployment Policy + +- `contracts-ci` runs on pushes to `dev`, `canary`, and `main`, and on PRs touching contracts. +- `contracts-slither` runs on pushes to `dev`, `canary`, and `main`, and on PRs touching core contracts. +- `contracts-env-guard` runs on pushes to `dev`, `canary`, and `main`, and on PRs touching contracts. +- `secrets-drift-guard` runs on all PRs into `dev`, `canary`, and `main`. +- `contracts-staging-rehearsal` is automatically triggered on push to `canary`. +- `contracts-mainnet-readiness` is production-gated: + - manual only (`workflow_dispatch`) + - enforced to run from `main` branch + - tied to `production` environment +- `governance-policy-guard` validates required-check consistency across: + - `scripts/github/apply-governance.sh` + - `BRANCHING.md` + - `.github/PRODUCTION_GOVERNANCE_CHECKLIST.md` +- GitHub settings implementation checklist: + - `.github/PRODUCTION_GOVERNANCE_CHECKLIST.md` +- Review/PR governance files: + - `.github/CODEOWNERS` + - `.github/pull_request_template.md` + - `.github/PULL_REQUEST_TEMPLATE/release.md` + +## Required Checks Matrix + +Use this matrix as the merge baseline. + +### PRs into `dev` + +- `Contracts Unit + Invariant` +- `Contracts Release Check (Dry-Run + Execute Smoke)` +- `Slither Core Contracts` +- `Secrets Drift Guard` +- If PR touches governance policy files (`apply-governance.sh`, `BRANCHING.md`, governance checklist): `Validate Governance Policy Consistency` + +### PRs into `canary` + +- `Contracts Unit + Invariant` +- `Contracts Release Check (Dry-Run + Execute Smoke)` +- `Slither Core Contracts` +- `Secrets Drift Guard` +- If PR touches governance policy files (`apply-governance.sh`, `BRANCHING.md`, governance checklist): `Validate Governance Policy Consistency` + +### PRs into `main` (release candidate) + +- `Contracts Unit + Invariant` +- `Contracts Release Check (Dry-Run + Execute Smoke)` +- `Slither Core Contracts` +- `Secrets Drift Guard` +- `Validate Release PR Checklist` +- `Validate Release Evidence` +- If PR touches governance policy files (`apply-governance.sh`, `BRANCHING.md`, governance checklist): `Validate Governance Policy Consistency` + +### After merge to `main` (pre-deploy gate) + +- Manually run `Contracts Mainnet Readiness` from `main` +- Capture readiness artifact and run URL in release records + +## Required GitHub Branch Protection (Recommended) + +Apply these repository settings: + +1. Protect `main` +- Require pull request before merge. +- Require status checks: + - `Contracts Unit + Invariant` + - `Contracts Release Check (Dry-Run + Execute Smoke)` + - `Slither Core Contracts` + - `Secrets Drift Guard` + - `Validate Release PR Checklist` + - `Validate Release Evidence` +- Require at least 1-2 approvals. +- Dismiss stale approvals on new commits. +- Restrict direct push. + +2. Protect `canary` +- Require pull request before merge. +- Require status checks: + - `Contracts Unit + Invariant` + - `Contracts Release Check (Dry-Run + Execute Smoke)` + - `Slither Core Contracts` + - `Secrets Drift Guard` +- Require at least 1 approval. +- Dismiss stale approvals on new commits. + +3. Protect `dev` +- Require pull request before merge (or allow maintainers direct push if desired). +- Require status checks: + - `Contracts Unit + Invariant` + - `Contracts Release Check (Dry-Run + Execute Smoke)` + - `Slither Core Contracts` + - `Secrets Drift Guard` + +Notes: +- Do not add `Validate Governance Policy Consistency` as a global required branch-protection check because it is intentionally path-filtered; require it only on governance-touching PRs. + +4. Protect tags +- Reserve release tags (for example `v*`) to maintainers only. + +## Merge Flow + +1. Create `feature/*` from `dev`. +2. Open PR into `dev`; resolve feedback and green checks. +3. Open PR `dev -> canary` for staging stabilisation. +4. Staging rehearsal runs automatically on push to `canary`. +5. Open PR `canary -> main` once staging rehearsal passes. +6. Run production readiness workflow from `main`. +7. Tag release on `main` after approval. diff --git a/contracts/script/ci/release-gate.sh b/contracts/script/ci/release-gate.sh index c0b3dc2..f13668e 100755 --- a/contracts/script/ci/release-gate.sh +++ b/contracts/script/ci/release-gate.sh @@ -17,6 +17,7 @@ require_cmd git MODE="${MARK_RELEASE_GATE_MODE:-local}" # local | remote OUT_DIR="${MARK_RELEASE_GATE_OUT_DIR:-broadcast/release-gate}" +VERIFY_REQUIRE_SIGNED_MANIFEST="${MARK_RELEASE_VERIFY_REQUIRE_SIGNED_MANIFEST:-true}" TIMESTAMP_UTC="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" STAMP="$(date -u +"%Y%m%dT%H%M%SZ")" GIT_COMMIT="$(git rev-parse --short HEAD)" @@ -30,6 +31,7 @@ make ci-full READINESS_STATUS="skipped" VERIFY_STATUS="skipped" +MANIFEST_VERIFY_STATUS="skipped" if [[ "$MODE" == "remote" ]]; then if [[ -z "${RPC_URL:-}" ]]; then @@ -47,16 +49,43 @@ if [[ "$MODE" == "remote" ]]; then ./script/ops/mainnet-readiness.sh READINESS_STATUS="passed" - echo "[release-gate] step 3/3: strict deployment verification" - # Strict mode: require explicit VERIFY_* env instead of implicit defaults. - : "${VERIFY_MARK_RYLA_TOKEN:?VERIFY_MARK_RYLA_TOKEN is required in remote mode}" - : "${VERIFY_MARK_RYLA_OWNER:?VERIFY_MARK_RYLA_OWNER is required in remote mode}" - : "${VERIFY_MARK_SETTLEMENT_MODULE:?VERIFY_MARK_SETTLEMENT_MODULE is required in remote mode}" - : "${VERIFY_MARK_SETTLEMENT_OPERATOR:?VERIFY_MARK_SETTLEMENT_OPERATOR is required in remote mode}" - : "${VERIFY_MARK_SETTLEMENT_PROOF_ENABLED:?VERIFY_MARK_SETTLEMENT_PROOF_ENABLED is required in remote mode}" - : "${VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE:?VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE is required in remote mode}" - : "${VERIFY_MARK_SETTLEMENT_VERIFIER:?VERIFY_MARK_SETTLEMENT_VERIFIER is required in remote mode}" - forge script script/ops/settlement/VerifyMARKDeployment.s.sol --rpc-url "$RPC_URL" -q + RELEASE_VERIFY_ARTIFACT_PATH="${MARK_RELEASE_VERIFY_ARTIFACT_PATH:-${MARK_RELEASE_ARTIFACT_PATH:-}}" + if [[ -z "$RELEASE_VERIFY_ARTIFACT_PATH" ]]; then + echo "release-gate: MARK_RELEASE_VERIFY_ARTIFACT_PATH or MARK_RELEASE_ARTIFACT_PATH is required in remote mode" >&2 + exit 1 + fi + + if [[ "$VERIFY_REQUIRE_SIGNED_MANIFEST" == "true" ]]; then + echo "[release-gate] step 3/4: verify signed evidence manifest" + VERIFY_MANIFEST_PATH="${MARK_RELEASE_VERIFY_MANIFEST_PATH:-broadcast/mark-evidence-manifest.json}" + VERIFY_SIGNATURE_PATH="${MARK_RELEASE_VERIFY_SIGNATURE_PATH:-broadcast/mark-evidence-manifest.sig}" + VERIFY_SIGNATURE_META_PATH="${MARK_RELEASE_VERIFY_SIGNATURE_META_PATH:-broadcast/mark-evidence-signature.json}" + + if [[ -z "${VERIFY_PUBLIC_KEY_FILE:-}" && -z "${VERIFY_PUBLIC_KEY_PEM:-}" ]]; then + echo "release-gate: VERIFY_PUBLIC_KEY_FILE or VERIFY_PUBLIC_KEY_PEM is required when MARK_RELEASE_VERIFY_REQUIRE_SIGNED_MANIFEST=true" >&2 + exit 1 + fi + + MANIFEST_PATH="$VERIFY_MANIFEST_PATH" ./script/ops/verify-evidence-manifest.sh + MANIFEST_PATH="$VERIFY_MANIFEST_PATH" \ + SIGNATURE_PATH="$VERIFY_SIGNATURE_PATH" \ + SIGNATURE_META_PATH="$VERIFY_SIGNATURE_META_PATH" \ + VERIFY_PUBLIC_KEY_FILE="${VERIFY_PUBLIC_KEY_FILE:-}" \ + VERIFY_PUBLIC_KEY_PEM="${VERIFY_PUBLIC_KEY_PEM:-}" \ + ./script/ops/verify-evidence-signature.sh + + if ! jq -e --arg path "$RELEASE_VERIFY_ARTIFACT_PATH" '.artifacts[] | select(.id == "release" and .path == $path)' "$VERIFY_MANIFEST_PATH" >/dev/null; then + echo "release-gate: release artifact path is not anchored in manifest (id=release, path=$RELEASE_VERIFY_ARTIFACT_PATH)" >&2 + exit 1 + fi + MANIFEST_VERIFY_STATUS="passed" + else + echo "[release-gate] step 3/4: signed evidence manifest verification skipped (MARK_RELEASE_VERIFY_REQUIRE_SIGNED_MANIFEST=false)" + fi + + echo "[release-gate] step 4/4: strict deployment verification" + MARK_RELEASE_VERIFY_ARTIFACT_PATH="$RELEASE_VERIFY_ARTIFACT_PATH" \ + ./script/ci/verify-from-artifact.sh VERIFY_STATUS="passed" fi @@ -67,6 +96,7 @@ jq -n \ --arg commit "$GIT_COMMIT" \ --arg ciFull "passed" \ --arg readiness "$READINESS_STATUS" \ + --arg manifestVerify "$MANIFEST_VERIFY_STATUS" \ --arg verify "$VERIFY_STATUS" \ '{ gate: $gate, @@ -76,6 +106,7 @@ jq -n \ checks: { ciFull: $ciFull, mainnetReadiness: $readiness, + signedManifestVerify: $manifestVerify, strictDeploymentVerify: $verify } }' > "$ARTIFACT_PATH" diff --git a/contracts/script/ci/verify-from-artifact.sh b/contracts/script/ci/verify-from-artifact.sh new file mode 100755 index 0000000..c8fb8a0 --- /dev/null +++ b/contracts/script/ci/verify-from-artifact.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "verify-from-artifact: required command not found: $1" >&2 + exit 1 + fi +} + +require_cmd jq +require_cmd forge + +require_env() { + local key="$1" + if [[ -z "${!key:-}" ]]; then + echo "verify-from-artifact: missing required env var: $key" >&2 + exit 1 + fi +} + +norm_addr() { + echo "$1" | tr '[:upper:]' '[:lower:]' +} + +assert_addr_eq() { + local label="$1" + local left="$2" + local right="$3" + if [[ "$(norm_addr "$left")" != "$(norm_addr "$right")" ]]; then + echo "verify-from-artifact: $label mismatch (env=$left artifact=$right)" >&2 + exit 1 + fi +} + +assert_scalar_eq() { + local label="$1" + local left="$2" + local right="$3" + if [[ "$left" != "$right" ]]; then + echo "verify-from-artifact: $label mismatch (env=$left artifact=$right)" >&2 + exit 1 + fi +} + +ARTIFACT_PATH="${MARK_RELEASE_VERIFY_ARTIFACT_PATH:-${MARK_RELEASE_ARTIFACT_PATH:-}}" +require_env ARTIFACT_PATH +if [[ ! -f "$ARTIFACT_PATH" ]]; then + echo "verify-from-artifact: artifact not found: $ARTIFACT_PATH" >&2 + exit 1 +fi + +token="$(jq -r '.token // empty' "$ARTIFACT_PATH")" +adapter="$(jq -r '.adapter // empty' "$ARTIFACT_PATH")" +module="$(jq -r '.module // empty' "$ARTIFACT_PATH")" +protocol="$(jq -r '.protocol // empty' "$ARTIFACT_PATH")" +tokenSymbol="$(jq -r '.tokenSymbol // empty' "$ARTIFACT_PATH")" +chainId="$(jq -r '.chainId // 0' "$ARTIFACT_PATH")" +expectedOwner="$(jq -r '.expectedOwner // empty' "$ARTIFACT_PATH")" +expectedBridgeOperator="$(jq -r '.expectedBridgeOperator // "0x0000000000000000000000000000000000000000"' "$ARTIFACT_PATH")" +expectedBridgeDestinationChain="$(jq -r '.expectedBridgeDestinationChain // 0' "$ARTIFACT_PATH")" +expectedBridgeMaxPerTx="$(jq -r '.expectedBridgeMaxPerTx // 0' "$ARTIFACT_PATH")" +expectedBridgeDailyCap="$(jq -r '.expectedBridgeDailyCap // 0' "$ARTIFACT_PATH")" +expectedSettlementOperator="$(jq -r '.expectedSettlementOperator // empty' "$ARTIFACT_PATH")" +expectedProofEnabled="$(jq -r '.expectedProofEnabled // empty' "$ARTIFACT_PATH")" +expectedProductionMode="$(jq -r '.expectedProductionMode // empty' "$ARTIFACT_PATH")" +expectedVerifier="$(jq -r '.expectedVerifier // empty' "$ARTIFACT_PATH")" +expectedAttester="$(jq -r '.expectedAttester // "0x0000000000000000000000000000000000000000"' "$ARTIFACT_PATH")" + +if [[ -z "$token" || -z "$module" ]]; then + echo "verify-from-artifact: artifact missing token/module fields" >&2 + exit 1 +fi +if [[ "$protocol" != "MARK" ]]; then + echo "verify-from-artifact: artifact protocol must be MARK (got: $protocol)" >&2 + exit 1 +fi +if [[ "$tokenSymbol" != "RYLA" ]]; then + echo "verify-from-artifact: artifact tokenSymbol must be RYLA (got: $tokenSymbol)" >&2 + exit 1 +fi +if [[ "$chainId" == "0" ]]; then + echo "verify-from-artifact: artifact chainId must be non-zero" >&2 + exit 1 +fi + +# If env is preset, enforce equality with artifact. Otherwise set from artifact. +if [[ -n "${VERIFY_MARK_RYLA_TOKEN:-}" ]]; then + assert_addr_eq "VERIFY_MARK_RYLA_TOKEN" "${VERIFY_MARK_RYLA_TOKEN}" "$token" +else + export VERIFY_MARK_RYLA_TOKEN="$token" +fi + +if [[ -n "${VERIFY_MARK_BRIDGE_ADAPTER:-}" && -n "$adapter" ]]; then + assert_addr_eq "VERIFY_MARK_BRIDGE_ADAPTER" "${VERIFY_MARK_BRIDGE_ADAPTER}" "$adapter" +else + export VERIFY_MARK_BRIDGE_ADAPTER="${VERIFY_MARK_BRIDGE_ADAPTER:-$adapter}" +fi + +if [[ -n "${VERIFY_MARK_SETTLEMENT_MODULE:-}" ]]; then + assert_addr_eq "VERIFY_MARK_SETTLEMENT_MODULE" "${VERIFY_MARK_SETTLEMENT_MODULE}" "$module" +else + export VERIFY_MARK_SETTLEMENT_MODULE="$module" +fi + +if [[ -n "${VERIFY_MARK_RYLA_OWNER:-}" && -n "$expectedOwner" ]]; then + assert_addr_eq "VERIFY_MARK_RYLA_OWNER" "${VERIFY_MARK_RYLA_OWNER}" "$expectedOwner" +else + export VERIFY_MARK_RYLA_OWNER="${VERIFY_MARK_RYLA_OWNER:-$expectedOwner}" +fi + +if [[ -n "${VERIFY_MARK_BRIDGE_OPERATOR:-}" ]]; then + assert_addr_eq "VERIFY_MARK_BRIDGE_OPERATOR" "${VERIFY_MARK_BRIDGE_OPERATOR}" "$expectedBridgeOperator" +else + export VERIFY_MARK_BRIDGE_OPERATOR="$expectedBridgeOperator" +fi + +if [[ -n "${VERIFY_MARK_BRIDGE_DEST_CHAIN:-}" ]]; then + assert_scalar_eq "VERIFY_MARK_BRIDGE_DEST_CHAIN" "${VERIFY_MARK_BRIDGE_DEST_CHAIN}" "$expectedBridgeDestinationChain" +else + export VERIFY_MARK_BRIDGE_DEST_CHAIN="$expectedBridgeDestinationChain" +fi + +if [[ -n "${VERIFY_MARK_BRIDGE_MAX_PER_TX:-}" ]]; then + assert_scalar_eq "VERIFY_MARK_BRIDGE_MAX_PER_TX" "${VERIFY_MARK_BRIDGE_MAX_PER_TX}" "$expectedBridgeMaxPerTx" +else + export VERIFY_MARK_BRIDGE_MAX_PER_TX="$expectedBridgeMaxPerTx" +fi + +if [[ -n "${VERIFY_MARK_BRIDGE_DAILY_CAP:-}" ]]; then + assert_scalar_eq "VERIFY_MARK_BRIDGE_DAILY_CAP" "${VERIFY_MARK_BRIDGE_DAILY_CAP}" "$expectedBridgeDailyCap" +else + export VERIFY_MARK_BRIDGE_DAILY_CAP="$expectedBridgeDailyCap" +fi + +if [[ -n "${VERIFY_MARK_SETTLEMENT_OPERATOR:-}" && -n "$expectedSettlementOperator" ]]; then + assert_addr_eq "VERIFY_MARK_SETTLEMENT_OPERATOR" "${VERIFY_MARK_SETTLEMENT_OPERATOR}" "$expectedSettlementOperator" +else + export VERIFY_MARK_SETTLEMENT_OPERATOR="${VERIFY_MARK_SETTLEMENT_OPERATOR:-$expectedSettlementOperator}" +fi + +if [[ -n "${VERIFY_MARK_SETTLEMENT_PROOF_ENABLED:-}" && -n "$expectedProofEnabled" ]]; then + assert_scalar_eq "VERIFY_MARK_SETTLEMENT_PROOF_ENABLED" "${VERIFY_MARK_SETTLEMENT_PROOF_ENABLED}" "$expectedProofEnabled" +else + export VERIFY_MARK_SETTLEMENT_PROOF_ENABLED="${VERIFY_MARK_SETTLEMENT_PROOF_ENABLED:-$expectedProofEnabled}" +fi + +if [[ -n "${VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE:-}" && -n "$expectedProductionMode" ]]; then + assert_scalar_eq "VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE" "${VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE}" "$expectedProductionMode" +else + export VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE="${VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE:-$expectedProductionMode}" +fi + +if [[ -n "${VERIFY_MARK_SETTLEMENT_VERIFIER:-}" && -n "$expectedVerifier" ]]; then + assert_addr_eq "VERIFY_MARK_SETTLEMENT_VERIFIER" "${VERIFY_MARK_SETTLEMENT_VERIFIER}" "$expectedVerifier" +else + export VERIFY_MARK_SETTLEMENT_VERIFIER="${VERIFY_MARK_SETTLEMENT_VERIFIER:-$expectedVerifier}" +fi + +if [[ -n "${VERIFY_MARK_SETTLEMENT_ATTESTER:-}" ]]; then + assert_addr_eq "VERIFY_MARK_SETTLEMENT_ATTESTER" "${VERIFY_MARK_SETTLEMENT_ATTESTER}" "$expectedAttester" +else + export VERIFY_MARK_SETTLEMENT_ATTESTER="$expectedAttester" +fi + +require_env RPC_URL +require_env VERIFY_MARK_RYLA_TOKEN +require_env VERIFY_MARK_SETTLEMENT_MODULE +require_env VERIFY_MARK_RYLA_OWNER +require_env VERIFY_MARK_SETTLEMENT_OPERATOR +require_env VERIFY_MARK_SETTLEMENT_PROOF_ENABLED +require_env VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE +require_env VERIFY_MARK_SETTLEMENT_VERIFIER + +echo "[verify-from-artifact] running onchain verify against anchored artifact values" +forge script script/ops/settlement/VerifyMARKDeployment.s.sol --rpc-url "$RPC_URL" -q +echo "[verify-from-artifact] PASSED" diff --git a/contracts/script/examples/Deploy.s.sol b/contracts/script/examples/Deploy.s.sol deleted file mode 100644 index 9bc332e..0000000 --- a/contracts/script/examples/Deploy.s.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Script, console} from "forge-std/Script.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {ICreateX} from "createx/ICreateX.sol"; - -import {DeployUtils} from "../../libraries/DeployUtils.sol"; -import {CrossChainCounter} from "../../src/examples/CrossChainCounter.sol"; - -// Example forge script for deploying as an alternative to sup: super-cli (https://github.com/ethereum-optimism/super-cli) -contract Deploy is Script { - /// @notice Array of RPC URLs to deploy to, deploy to supersim 901 and 902 by default. - string[] private rpcUrls = ["http://localhost:9545", "http://localhost:9546"]; - - /// @notice Modifier that wraps a function in broadcasting. - modifier broadcast() { - vm.startBroadcast(msg.sender); - _; - vm.stopBroadcast(); - } - - function run() public { - for (uint256 i = 0; i < rpcUrls.length; i++) { - string memory rpcUrl = rpcUrls[i]; - - console.log("Deploying to RPC: ", rpcUrl); - vm.createSelectFork(rpcUrl); - deployCrossChainCounterContract(); - } - } - - function deployCrossChainCounterContract() public broadcast returns (address addr_) { - bytes memory initCode = abi.encodePacked(type(CrossChainCounter).creationCode); - addr_ = DeployUtils.deployContract("CrossChainCounter", _implSalt(), initCode); - } - - /// @notice The CREATE2 salt to be used when deploying a contract. - function _implSalt() internal view returns (bytes32) { - return keccak256(abi.encodePacked(vm.envOr("DEPLOY_SALT", string("ethers phoenix")))); - } -} diff --git a/contracts/script/ops/generate-evidence-manifest.sh b/contracts/script/ops/generate-evidence-manifest.sh new file mode 100755 index 0000000..fa9f949 --- /dev/null +++ b/contracts/script/ops/generate-evidence-manifest.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +sha256_file() { + local path="$1" + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$path" | awk '{print $1}' + return + fi + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$path" | awk '{print $1}' + return + fi + echo "No SHA-256 tool found (need shasum or sha256sum)" >&2 + exit 1 +} + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_file() { + local label="$1" + local path="$2" + if [[ ! -f "$path" ]]; then + echo "Missing required $label file: $path" >&2 + exit 1 + fi +} + +require_cmd jq +require_cmd git + +RELEASE_ARTIFACT_PATH="${RELEASE_ARTIFACT_PATH:-broadcast/mark-release-prodmode-ci.json}" +PRODUCTION_LOCK_ARTIFACT_PATH="${PRODUCTION_LOCK_ARTIFACT_PATH:-broadcast/mark-production-lock-verify.json}" +STAGING_REHEARSAL_ARTIFACT_PATH="${STAGING_REHEARSAL_ARTIFACT_PATH:-broadcast/mark-staging-rehearsal.json}" +PROMOTION_CHECKLIST_PATH="${PROMOTION_CHECKLIST_PATH:-broadcast/mark-promotion-checklist.json}" +MANIFEST_PATH="${MANIFEST_PATH:-broadcast/mark-evidence-manifest.json}" +MANIFEST_GENERATED_BY="${MANIFEST_GENERATED_BY:-manual}" +MANIFEST_NOTE="${MANIFEST_NOTE:-}" + +require_file "release artifact" "$RELEASE_ARTIFACT_PATH" +require_file "production-lock artifact" "$PRODUCTION_LOCK_ARTIFACT_PATH" +require_file "staging-rehearsal artifact" "$STAGING_REHEARSAL_ARTIFACT_PATH" +require_file "promotion-checklist artifact" "$PROMOTION_CHECKLIST_PATH" + +mkdir -p "$(dirname "$MANIFEST_PATH")" + +RELEASE_SHA="$(sha256_file "$RELEASE_ARTIFACT_PATH")" +LOCK_SHA="$(sha256_file "$PRODUCTION_LOCK_ARTIFACT_PATH")" +STAGING_SHA="$(sha256_file "$STAGING_REHEARSAL_ARTIFACT_PATH")" +CHECKLIST_SHA="$(sha256_file "$PROMOTION_CHECKLIST_PATH")" + +GIT_COMMIT="${MARK_GIT_COMMIT:-$(git rev-parse --short=12 HEAD 2>/dev/null || echo unknown)}" + +jq -n \ + --arg generatedAt "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ + --arg generatedBy "$MANIFEST_GENERATED_BY" \ + --arg note "$MANIFEST_NOTE" \ + --arg gitCommit "$GIT_COMMIT" \ + --arg releasePath "$RELEASE_ARTIFACT_PATH" \ + --arg releaseSha "$RELEASE_SHA" \ + --arg lockPath "$PRODUCTION_LOCK_ARTIFACT_PATH" \ + --arg lockSha "$LOCK_SHA" \ + --arg stagingPath "$STAGING_REHEARSAL_ARTIFACT_PATH" \ + --arg stagingSha "$STAGING_SHA" \ + --arg checklistPath "$PROMOTION_CHECKLIST_PATH" \ + --arg checklistSha "$CHECKLIST_SHA" \ + '{ + schemaVersion: 1, + generatedAt: $generatedAt, + generatedBy: $generatedBy, + note: $note, + gitCommit: $gitCommit, + artifacts: [ + {id: "release", path: $releasePath, sha256: $releaseSha}, + {id: "production-lock-verify", path: $lockPath, sha256: $lockSha}, + {id: "staging-rehearsal", path: $stagingPath, sha256: $stagingSha}, + {id: "promotion-checklist", path: $checklistPath, sha256: $checklistSha} + ] + }' >"$MANIFEST_PATH" + +echo "Evidence manifest generated:" +echo " $MANIFEST_PATH" diff --git a/contracts/script/ops/settlement/ReleaseMARK.s.sol b/contracts/script/ops/settlement/ReleaseMARK.s.sol index 53eb768..dbb395f 100644 --- a/contracts/script/ops/settlement/ReleaseMARK.s.sol +++ b/contracts/script/ops/settlement/ReleaseMARK.s.sol @@ -253,6 +253,16 @@ contract ReleaseMARK is Script { vm.serializeAddress(root, "adapter", result.adapter); vm.serializeAddress(root, "module", result.module); vm.serializeAddress(root, "verifier", result.verifier); + vm.serializeAddress(root, "expectedOwner", vm.envOr("VERIFY_MARK_RYLA_OWNER", vm.envOr("MARK_RYLA_OWNER", address(0)))); + vm.serializeAddress(root, "expectedBridgeOperator", vm.envOr("VERIFY_MARK_BRIDGE_OPERATOR", address(0))); + vm.serializeUint(root, "expectedBridgeDestinationChain", vm.envOr("VERIFY_MARK_BRIDGE_DEST_CHAIN", uint256(0))); + vm.serializeUint(root, "expectedBridgeMaxPerTx", vm.envOr("VERIFY_MARK_BRIDGE_MAX_PER_TX", uint256(0))); + vm.serializeUint(root, "expectedBridgeDailyCap", vm.envOr("VERIFY_MARK_BRIDGE_DAILY_CAP", uint256(0))); + vm.serializeAddress(root, "expectedSettlementOperator", vm.envOr("VERIFY_MARK_SETTLEMENT_OPERATOR", address(0))); + vm.serializeBool(root, "expectedProofEnabled", vm.envOr("VERIFY_MARK_SETTLEMENT_PROOF_ENABLED", false)); + vm.serializeBool(root, "expectedProductionMode", vm.envOr("VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE", false)); + vm.serializeAddress(root, "expectedVerifier", vm.envOr("VERIFY_MARK_SETTLEMENT_VERIFIER", address(0))); + vm.serializeAddress(root, "expectedAttester", vm.envOr("VERIFY_MARK_SETTLEMENT_ATTESTER", address(0))); vm.serializeUint(root, "chainId", block.chainid); vm.serializeUint(root, "timestamp", block.timestamp); string memory json = vm.serializeString(root, "gitCommit", vm.envOr("MARK_GIT_COMMIT", string("unknown"))); diff --git a/contracts/script/ops/sign-evidence-manifest.sh b/contracts/script/ops/sign-evidence-manifest.sh new file mode 100755 index 0000000..ff2b15f --- /dev/null +++ b/contracts/script/ops/sign-evidence-manifest.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd openssl +require_cmd jq + +MANIFEST_PATH="${MANIFEST_PATH:-broadcast/mark-evidence-manifest.json}" +SIGNATURE_PATH="${SIGNATURE_PATH:-broadcast/mark-evidence-manifest.sig}" +SIGNATURE_META_PATH="${SIGNATURE_META_PATH:-broadcast/mark-evidence-signature.json}" +SIGNING_KEY_FILE="${SIGNING_KEY_FILE:-}" +SIGNING_KEY_PEM="${SIGNING_KEY_PEM:-}" +SIGNING_KEY_ID="${SIGNING_KEY_ID:-manual-openssl-key}" +SIGNATURE_ALGORITHM="${SIGNATURE_ALGORITHM:-sha256-rsa-pkcs1v15}" + +if [[ ! -f "$MANIFEST_PATH" ]]; then + echo "Missing manifest file: $MANIFEST_PATH" >&2 + exit 1 +fi + +if [[ -z "$SIGNING_KEY_FILE" && -z "$SIGNING_KEY_PEM" ]]; then + echo "Provide SIGNING_KEY_FILE or SIGNING_KEY_PEM" >&2 + exit 1 +fi + +KEY_FILE_TO_USE="$SIGNING_KEY_FILE" +TMP_KEY_FILE="" +if [[ -z "$KEY_FILE_TO_USE" ]]; then + TMP_KEY_FILE="$(mktemp)" + printf '%s\n' "$SIGNING_KEY_PEM" >"$TMP_KEY_FILE" + KEY_FILE_TO_USE="$TMP_KEY_FILE" +fi + +cleanup() { + if [[ -n "$TMP_KEY_FILE" ]]; then + rm -f "$TMP_KEY_FILE" + fi +} +trap cleanup EXIT + +mkdir -p "$(dirname "$SIGNATURE_PATH")" "$(dirname "$SIGNATURE_META_PATH")" + +openssl dgst -sha256 -sign "$KEY_FILE_TO_USE" -out "$SIGNATURE_PATH" "$MANIFEST_PATH" + +if command -v shasum >/dev/null 2>&1; then + manifest_sha="$(shasum -a 256 "$MANIFEST_PATH" | awk '{print $1}')" + signature_sha="$(shasum -a 256 "$SIGNATURE_PATH" | awk '{print $1}')" +elif command -v sha256sum >/dev/null 2>&1; then + manifest_sha="$(sha256sum "$MANIFEST_PATH" | awk '{print $1}')" + signature_sha="$(sha256sum "$SIGNATURE_PATH" | awk '{print $1}')" +else + echo "No SHA-256 tool found (need shasum or sha256sum)" >&2 + exit 1 +fi + +jq -n \ + --arg generatedAt "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ + --arg manifestPath "$MANIFEST_PATH" \ + --arg signaturePath "$SIGNATURE_PATH" \ + --arg manifestSha "$manifest_sha" \ + --arg signatureSha "$signature_sha" \ + --arg algorithm "$SIGNATURE_ALGORITHM" \ + --arg keyId "$SIGNING_KEY_ID" \ + '{ + schemaVersion: 1, + generatedAt: $generatedAt, + manifestPath: $manifestPath, + signaturePath: $signaturePath, + algorithm: $algorithm, + keyId: $keyId, + manifestSha256: $manifestSha, + signatureSha256: $signatureSha + }' >"$SIGNATURE_META_PATH" + +echo "Manifest signature generated:" +echo " Signature: $SIGNATURE_PATH" +echo " Metadata: $SIGNATURE_META_PATH" diff --git a/contracts/script/ops/verify-evidence-manifest.sh b/contracts/script/ops/verify-evidence-manifest.sh new file mode 100755 index 0000000..db3f389 --- /dev/null +++ b/contracts/script/ops/verify-evidence-manifest.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +sha256_file() { + local path="$1" + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$path" | awk '{print $1}' + return + fi + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$path" | awk '{print $1}' + return + fi + echo "No SHA-256 tool found (need shasum or sha256sum)" >&2 + exit 1 +} + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd jq + +MANIFEST_PATH="${MANIFEST_PATH:-broadcast/mark-evidence-manifest.json}" + +if [[ ! -f "$MANIFEST_PATH" ]]; then + echo "Missing manifest file: $MANIFEST_PATH" >&2 + exit 1 +fi + +SCHEMA_VERSION="$(jq -r '.schemaVersion // empty' "$MANIFEST_PATH")" +if [[ "$SCHEMA_VERSION" != "1" ]]; then + echo "Unsupported or missing schemaVersion in manifest: $SCHEMA_VERSION" >&2 + exit 1 +fi + +ARTIFACT_COUNT="$(jq '.artifacts | length' "$MANIFEST_PATH")" +if [[ "$ARTIFACT_COUNT" -lt 1 ]]; then + echo "Manifest has no artifacts entries" >&2 + exit 1 +fi + +mapfile -t ENTRIES < <(jq -r '.artifacts[] | [.id, .path, .sha256] | @tsv' "$MANIFEST_PATH") + +for entry in "${ENTRIES[@]}"; do + IFS=$'\t' read -r id path expected_sha <<<"$entry" + if [[ -z "$id" || -z "$path" || -z "$expected_sha" ]]; then + echo "Malformed manifest entry: $entry" >&2 + exit 1 + fi + if [[ ! -f "$path" ]]; then + echo "Missing artifact file for $id: $path" >&2 + exit 1 + fi + actual_sha="$(sha256_file "$path")" + if [[ "$actual_sha" != "$expected_sha" ]]; then + echo "SHA mismatch for $id ($path)" >&2 + echo " expected: $expected_sha" >&2 + echo " actual: $actual_sha" >&2 + exit 1 + fi +done + +echo "Evidence manifest verification PASSED" +echo "Manifest: $MANIFEST_PATH" diff --git a/contracts/script/ops/verify-evidence-signature.sh b/contracts/script/ops/verify-evidence-signature.sh new file mode 100755 index 0000000..50ff8bd --- /dev/null +++ b/contracts/script/ops/verify-evidence-signature.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required but not found in PATH" >&2 + exit 1 + fi +} + +require_cmd openssl +require_cmd jq + +MANIFEST_PATH="${MANIFEST_PATH:-broadcast/mark-evidence-manifest.json}" +SIGNATURE_PATH="${SIGNATURE_PATH:-broadcast/mark-evidence-manifest.sig}" +SIGNATURE_META_PATH="${SIGNATURE_META_PATH:-broadcast/mark-evidence-signature.json}" +VERIFY_PUBLIC_KEY_FILE="${VERIFY_PUBLIC_KEY_FILE:-}" +VERIFY_PUBLIC_KEY_PEM="${VERIFY_PUBLIC_KEY_PEM:-}" + +if [[ ! -f "$MANIFEST_PATH" ]]; then + echo "Missing manifest file: $MANIFEST_PATH" >&2 + exit 1 +fi +if [[ ! -f "$SIGNATURE_PATH" ]]; then + echo "Missing signature file: $SIGNATURE_PATH" >&2 + exit 1 +fi +if [[ ! -f "$SIGNATURE_META_PATH" ]]; then + echo "Missing signature metadata file: $SIGNATURE_META_PATH" >&2 + exit 1 +fi + +if [[ -z "$VERIFY_PUBLIC_KEY_FILE" && -z "$VERIFY_PUBLIC_KEY_PEM" ]]; then + echo "Provide VERIFY_PUBLIC_KEY_FILE or VERIFY_PUBLIC_KEY_PEM" >&2 + exit 1 +fi + +KEY_FILE_TO_USE="$VERIFY_PUBLIC_KEY_FILE" +TMP_KEY_FILE="" +if [[ -z "$KEY_FILE_TO_USE" ]]; then + TMP_KEY_FILE="$(mktemp)" + printf '%s\n' "$VERIFY_PUBLIC_KEY_PEM" >"$TMP_KEY_FILE" + KEY_FILE_TO_USE="$TMP_KEY_FILE" +fi + +cleanup() { + if [[ -n "$TMP_KEY_FILE" ]]; then + rm -f "$TMP_KEY_FILE" + fi +} +trap cleanup EXIT + +meta_manifest_path="$(jq -r '.manifestPath // empty' "$SIGNATURE_META_PATH")" +meta_signature_path="$(jq -r '.signaturePath // empty' "$SIGNATURE_META_PATH")" +meta_schema="$(jq -r '.schemaVersion // empty' "$SIGNATURE_META_PATH")" + +if [[ "$meta_schema" != "1" ]]; then + echo "Unsupported or missing schemaVersion in signature metadata: $meta_schema" >&2 + exit 1 +fi + +if [[ "$meta_manifest_path" != "$MANIFEST_PATH" ]]; then + echo "Signature metadata manifestPath mismatch: $meta_manifest_path != $MANIFEST_PATH" >&2 + exit 1 +fi +if [[ "$meta_signature_path" != "$SIGNATURE_PATH" ]]; then + echo "Signature metadata signaturePath mismatch: $meta_signature_path != $SIGNATURE_PATH" >&2 + exit 1 +fi + +openssl dgst -sha256 -verify "$KEY_FILE_TO_USE" -signature "$SIGNATURE_PATH" "$MANIFEST_PATH" >/dev/null + +if command -v shasum >/dev/null 2>&1; then + manifest_sha="$(shasum -a 256 "$MANIFEST_PATH" | awk '{print $1}')" + signature_sha="$(shasum -a 256 "$SIGNATURE_PATH" | awk '{print $1}')" +elif command -v sha256sum >/dev/null 2>&1; then + manifest_sha="$(sha256sum "$MANIFEST_PATH" | awk '{print $1}')" + signature_sha="$(sha256sum "$SIGNATURE_PATH" | awk '{print $1}')" +else + echo "No SHA-256 tool found (need shasum or sha256sum)" >&2 + exit 1 +fi + +meta_manifest_sha="$(jq -r '.manifestSha256 // empty' "$SIGNATURE_META_PATH")" +meta_signature_sha="$(jq -r '.signatureSha256 // empty' "$SIGNATURE_META_PATH")" + +if [[ "$manifest_sha" != "$meta_manifest_sha" ]]; then + echo "Manifest SHA mismatch in signature metadata" >&2 + exit 1 +fi +if [[ "$signature_sha" != "$meta_signature_sha" ]]; then + echo "Signature SHA mismatch in signature metadata" >&2 + exit 1 +fi + +echo "Evidence signature verification PASSED" +echo "Manifest: $MANIFEST_PATH" +echo "Signature: $SIGNATURE_PATH" +echo "Metadata: $SIGNATURE_META_PATH" diff --git a/contracts/src/examples/CrossChainCounter.sol b/contracts/src/examples/CrossChainCounter.sol deleted file mode 100644 index 02d9658..0000000 --- a/contracts/src/examples/CrossChainCounter.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {CrossDomainMessageLib} from "@interop-lib/libraries/CrossDomainMessageLib.sol"; - -/// @title CrossChainCounter -/// @notice A simple Counter that can only be incremented **from other chains** -contract CrossChainCounter { - /// @notice Emitted when the counter is incremented from another chain - /// @param senderChainId The chain ID where the increment request originated - /// @param sender The address that requested the increment on the origin chain - /// @param newValue The new value of the counter after incrementing - event CounterIncremented(uint256 indexed senderChainId, address indexed sender, uint256 newValue); - - /// @notice Struct to store the last incrementer's details - struct Incrementer { - uint256 chainId; // The chain ID where the increment originated - address sender; // The address that triggered the increment - } - - /// @notice The current value of the counter - uint256 public number; - - /// @notice Details about the last address to increment the counter - Incrementer public lastIncrementer; - - /// @notice Increments the counter by 1 - /// @dev Can only be called through the L2ToL2CrossDomainMessenger contract - function increment() public { - // Verify that this function is being called by the L2ToL2CrossDomainMessenger contract - CrossDomainMessageLib.requireCallerIsCrossDomainMessenger(); - - // Get the original sender's address from the origin chain - address sender = - IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageSender(); - - // Get the chain ID where the increment request originated - uint256 senderChainId = - IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageSource(); - - // Increment the counter - number += 1; - - // Store the incrementer's details - lastIncrementer = Incrementer(senderChainId, sender); - - // Emit an event for off-chain tracking and indexing - emit CounterIncremented(senderChainId, sender, number); - } -} diff --git a/contracts/src/examples/CrossChainCounterIncrementer.sol b/contracts/src/examples/CrossChainCounterIncrementer.sol deleted file mode 100644 index 099370b..0000000 --- a/contracts/src/examples/CrossChainCounterIncrementer.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {CrossDomainMessageLib} from "@interop-lib/libraries/CrossDomainMessageLib.sol"; - -import {CrossChainCounter} from "./CrossChainCounter.sol"; - -/// @title CrossChainCounterIncrementer -/// @notice A contract that sends cross-chain messages to increment a counter on another chain -contract CrossChainCounterIncrementer { - /// @dev The L2 to L2 cross domain messenger predeploy to handle message passing - IL2ToL2CrossDomainMessenger internal messenger = - IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - - /// @notice Sends a message to increment a counter on another chain - /// @param counterChainId The chain ID where the target counter contract is deployed - /// @param counterAddress The address of the counter contract on the target chain - function increment(uint256 counterChainId, address counterAddress) public { - // Send a cross-chain message to increment the counter on the target chain - messenger.sendMessage( - counterChainId, counterAddress, abi.encodeWithSelector(CrossChainCounter.increment.selector) - ); - } -} diff --git a/contracts/test/CrossChainCounter.t.sol b/contracts/test/CrossChainCounter.t.sol deleted file mode 100644 index 93222bd..0000000 --- a/contracts/test/CrossChainCounter.t.sol +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {CrossChainCounter} from "../src/examples/CrossChainCounter.sol"; - -contract CrossChainCounterTest is Test { - CrossChainCounter public counter; - address bob; - - // Helper function to mock and expect a call - function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { - vm.mockCall(_receiver, _calldata, _returned); - vm.expectCall(_receiver, _calldata); - } - - function setUp() public { - counter = new CrossChainCounter(); - bob = vm.addr(1); - } - - // Test incrementing from a valid cross-chain message - function test_increment_crossDomain_succeeds() public { - uint256 fromChainId = 901; - vm.chainId(902); // Current chain - - // Mock the cross-domain message sender validation - _mockAndExpect( - PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(bob) - ); - - // Mock the cross-domain message source - _mockAndExpect( - PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), - abi.encode(fromChainId) - ); - - // Expect the CounterIncremented event - vm.expectEmit(true, true, true, true, address(counter)); - emit CrossChainCounter.CounterIncremented(fromChainId, bob, 1); - - // Call increment as if from the L2_TO_L2_CROSS_DOMAIN_MESSENGER - vm.prank(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - counter.increment(); - - // Verify the counter was incremented - assertEq(counter.number(), 1); - - // Verify the last incrementer details - (uint256 lastChainId, address lastSender) = counter.lastIncrementer(); - assertEq(lastChainId, fromChainId); - assertEq(lastSender, bob); - } - - // Test incrementing with an invalid sender (not the cross-domain messenger) - function test_increment_invalidSender_reverts() public { - vm.expectRevert(); // Will revert with CallerNotCrossDomainMessenger - counter.increment(); - } - - // Test multiple increments from different chains - function test_increment_multipleChains_succeeds() public { - // First increment from chain 901 - uint256 firstChainId = 901; - address firstSender = vm.addr(2); - vm.chainId(902); - - _mockAndExpect( - PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(firstSender) - ); - - _mockAndExpect( - PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), - abi.encode(firstChainId) - ); - - vm.prank(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - counter.increment(); - - // Second increment from chain 903 - uint256 secondChainId = 903; - address secondSender = vm.addr(3); - - _mockAndExpect( - PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(secondSender) - ); - - _mockAndExpect( - PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), - abi.encode(secondChainId) - ); - - vm.prank(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - counter.increment(); - - // Verify final counter value - assertEq(counter.number(), 2); - - // Verify last incrementer is from the second chain - (uint256 lastChainId, address lastSender) = counter.lastIncrementer(); - assertEq(lastChainId, secondChainId); - assertEq(lastSender, secondSender); - } -} diff --git a/contracts/test/integration/settlement/CrossChainIncrementer.t.sol b/contracts/test/integration/settlement/CrossChainIncrementer.t.sol deleted file mode 100644 index 6732123..0000000 --- a/contracts/test/integration/settlement/CrossChainIncrementer.t.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Test} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {console} from "forge-std/console.sol"; -import {IL2ToL2CrossDomainMessenger} from "@interop-lib/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; -import {Relayer} from "@interop-lib/test/Relayer.sol"; -import {CrossChainCounterIncrementer} from "../../../src/examples/CrossChainCounterIncrementer.sol"; -import {CrossChainCounter} from "../../../src/examples/CrossChainCounter.sol"; - -contract CrossChainIncrementerTest is Relayer, Test { - CrossChainCounterIncrementer public incrementer; - CrossChainCounter public counter; - - string[] private rpcUrls = [ - vm.envString("CHAIN_A_RPC_URL"), - vm.envString("CHAIN_B_RPC_URL") - ]; - - constructor() Relayer(rpcUrls) {} - - function setUp() public { - vm.selectFork(forkIds[0]); - incrementer = new CrossChainCounterIncrementer{salt: bytes32(0)}(); - - vm.selectFork(forkIds[1]); - counter = new CrossChainCounter{salt: bytes32(0)}(); - } - - // Test incrementing from a valid cross-chain message - function test_increment_crossDomain_succeeds() public { - vm.selectFork(forkIds[0]); - - incrementer.increment(chainIdByForkId[forkIds[1]], address(counter)); - - // verify counter has not been incremented on chainB - vm.selectFork(forkIds[1]); - assertEq(counter.number(), 0); - - relayAllMessages(); - - // verify counter has been incremented on chainB - vm.selectFork(forkIds[1]); - assertEq(counter.number(), 1); - } -} diff --git a/package.json b/package.json index 5a3854d..875f6a5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "superchain-starter", + "name": "mark", "private": true, "version": "0.0.0", "type": "module", @@ -11,9 +11,11 @@ "dev:supersim": "pnpm supersim --interop.autorelay --logs.directory supersim-logs", "build:frontend": "vite build", "build:contracts": "forge build --root contracts", - "deploy:supersim": "pnpm build:contracts && sup deploy create2 --chains supersiml2a,supersiml2b --salt ethers phoenix --forge-artifact-path contracts/out/CrossChainCounter.sol/CrossChainCounter.json --network supersim --private-key 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", - "deploy:counter-incrementer:supersim": "pnpm build:contracts && sup deploy create2 --chains supersiml2a,supersiml2b --salt ethers phoenix --forge-artifact-path contracts/out/CrossChainCounterIncrementer.sol/CrossChainCounterIncrementer.json --network supersim --private-key 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", - "deploy:interop-alpha": "pnpm build:contracts && sup deploy create2 --chains interop-alpha-0,interop-alpha-1 --salt ethers phoenix --forge-artifact-path contracts/out/CrossChainCounter.sol/CrossChainCounter.json --network interop-alpha --verify", + "contracts:ci-fast": "cd contracts && make ci-fast", + "contracts:ci-full": "cd contracts && make ci-full", + "contracts:release-gate": "cd contracts && make release-gate", + "contracts:rehearse-staging": "cd contracts && make rehearse-production-lock", + "contracts:evidence-manifest": "cd contracts && make generate-evidence-manifest", "typecheck": "tsc --noEmit", "lint": "eslint .", "lint:fix": "eslint --fix .", diff --git a/scripts/ci/validate-governance-policy.sh b/scripts/ci/validate-governance-policy.sh new file mode 100644 index 0000000..5e2a83e --- /dev/null +++ b/scripts/ci/validate-governance-policy.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +python3 - <<'PY' +from pathlib import Path +import re +import sys + +apply_governance = Path("scripts/github/apply-governance.sh").read_text() +branching = Path("BRANCHING.md").read_text() +checklist = Path(".github/PRODUCTION_GOVERNANCE_CHECKLIST.md").read_text() + + +def parse_checks(var_name: str) -> list[str]: + pattern = rf"{var_name}='\[(.*?)\]'" + m = re.search(pattern, apply_governance, re.S) + if not m: + raise RuntimeError(f"Could not parse {var_name} from apply-governance.sh") + block = m.group(1) + return re.findall(r'"([^"]+)"', block) + + +def parse_branching_section(title: str) -> list[str]: + # Capture bullet lines after a section header until the next header. + pattern = rf"### {re.escape(title)}\n\n(.*?)(?:\n### |\n## |\Z)" + m = re.search(pattern, branching, re.S) + if not m: + raise RuntimeError(f"Could not parse section '{title}' from BRANCHING.md") + block = m.group(1) + return [line.strip()[3:-1] if line.strip().startswith("- `") and line.strip().endswith("`") else line.strip()[2:] + for line in block.splitlines() if line.strip().startswith("- ")] + + +def parse_checklist_section(title: str) -> list[str]: + pattern = rf"## {re.escape(title)}\n\n(.*?)(?:\n## |\Z)" + m = re.search(pattern, checklist, re.S) + if not m: + raise RuntimeError(f"Could not parse section '{title}' from checklist") + block = m.group(1) + in_checks = False + checks = [] + for raw in block.splitlines(): + line = raw.strip() + if line == "- Add required checks:": + in_checks = True + continue + if in_checks: + if line.startswith("- `") and line.endswith("`"): + checks.append(line[3:-1]) + continue + if line.startswith("- ") and not line.startswith("- `"): + break + return checks + + +def ensure_contains_all(container: list[str], expected: list[str], label: str) -> list[str]: + missing = [x for x in expected if x not in container] + if missing: + return [f"{label} missing: {x}" for x in missing] + return [] + + +errors = [] +dev_expected = parse_checks("DEV_CHECKS_JSON") +main_expected = parse_checks("MAIN_CHECKS_JSON") + +branching_dev = parse_branching_section("PRs into `dev`") +branching_canary = parse_branching_section("PRs into `canary`") +branching_main = parse_branching_section("PRs into `main` (release candidate)") +errors += ensure_contains_all(branching_dev, dev_expected, "BRANCHING.md dev matrix") +errors += ensure_contains_all(branching_canary, dev_expected, "BRANCHING.md canary matrix") +errors += ensure_contains_all(branching_main, main_expected, "BRANCHING.md main matrix") + +checklist_main = parse_checklist_section("1) Protect `main` branch") +checklist_canary = parse_checklist_section("2) Protect `canary` branch") +checklist_dev = parse_checklist_section("3) Protect `dev` branch") +errors += ensure_contains_all(checklist_main, main_expected, "Governance checklist main required checks") +errors += ensure_contains_all(checklist_canary, dev_expected, "Governance checklist canary required checks") +errors += ensure_contains_all(checklist_dev, dev_expected, "Governance checklist dev required checks") + +if errors: + print("Governance policy drift detected:") + for err in errors: + print(f"- {err}") + sys.exit(1) + +print("Governance policy validation PASSED") +PY diff --git a/scripts/github/apply-governance.sh b/scripts/github/apply-governance.sh new file mode 100755 index 0000000..713389c --- /dev/null +++ b/scripts/github/apply-governance.sh @@ -0,0 +1,276 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Applies repository governance defaults: +# - branch protection for main and dev +# - creates/updates production environment +# +# Required env: +# GH_PAT= +# Optional env: +# GH_REPO=owner/repo (default: inferred from git remote origin) +# MAIN_REVIEW_COUNT=2 +# DEV_REVIEW_COUNT=1 +# PRODUCTION_REVIEWER_IDS=12345,67890 # GitHub user IDs +# MAIN_PUSH_ALLOW_USERS=user1,user2 # optional login allowlist for direct push +# MAIN_PUSH_ALLOW_TEAMS=team-slug # optional team slug allowlist +# MAIN_PUSH_ALLOW_APPS=app-slug # optional GitHub App allowlist +# DEV_PUSH_ALLOW_USERS=user1,user2 # optional login allowlist for direct push +# DEV_PUSH_ALLOW_TEAMS=team-slug # optional team slug allowlist +# DEV_PUSH_ALLOW_APPS=app-slug # optional GitHub App allowlist + +if ! command -v curl >/dev/null 2>&1; then + echo "curl is required" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required" >&2 + exit 1 +fi + +if [[ -z "${GH_PAT:-}" ]]; then + echo "GH_PAT is required" >&2 + exit 1 +fi + +infer_repo_from_remote() { + local remote + remote="$(git remote get-url origin)" + # Supports: + # git@github.com:owner/repo.git + # https://github.com/owner/repo.git + if [[ "$remote" =~ ^git@github.com:([^/]+/[^/]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + if [[ "$remote" =~ ^https://github.com/([^/]+/[^/]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + echo "Could not infer GH_REPO from origin: $remote" >&2 + exit 1 +} + +GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" +MAIN_REVIEW_COUNT="${MAIN_REVIEW_COUNT:-2}" +DEV_REVIEW_COUNT="${DEV_REVIEW_COUNT:-1}" + +owner="${GH_REPO%%/*}" +repo="${GH_REPO##*/}" +api="https://api.github.com/repos/${owner}/${repo}" + +auth_headers=( + -H "Authorization: Bearer ${GH_PAT}" + -H "Accept: application/vnd.github+json" + -H "X-GitHub-Api-Version: 2022-11-28" +) + +echo "Applying governance to ${GH_REPO}..." + +csv_to_json_array() { + local csv="${1:-}" + if [[ -z "$csv" ]]; then + echo "[]" + return + fi + + awk -v csv="$csv" 'BEGIN { + n=split(csv, a, ","); + printf("["); + first=1; + for (i=1; i<=n; i++) { + gsub(/^[ \t]+|[ \t]+$/, "", a[i]); + if (a[i] == "") continue; + gsub(/\\/,"\\\\",a[i]); + gsub(/"/,"\\\"",a[i]); + if (!first) printf(","); + printf("\"%s\"", a[i]); + first=0; + } + printf("]"); + }' +} + +build_restrictions_json() { + local users_csv="${1:-}" + local teams_csv="${2:-}" + local apps_csv="${3:-}" + local users_json teams_json apps_json + users_json="$(csv_to_json_array "$users_csv")" + teams_json="$(csv_to_json_array "$teams_csv")" + apps_json="$(csv_to_json_array "$apps_csv")" + + if [[ "$users_json" == "[]" && "$teams_json" == "[]" && "$apps_json" == "[]" ]]; then + echo "null" + return + fi + + jq -n \ + --argjson users "$users_json" \ + --argjson teams "$teams_json" \ + --argjson apps "$apps_json" \ + '{users: $users, teams: $teams, apps: $apps}' +} + +apply_branch_protection() { + local branch="$1" + local review_count="$2" + local checks_json="$3" + local restrictions_json="$4" + + local payload + payload="$( + jq -n \ + --argjson review_count "$review_count" \ + --argjson checks "$checks_json" \ + --argjson restrictions "${restrictions_json}" \ + '{ + required_status_checks: { + strict: true, + checks: ($checks | map({ context: . })) + }, + enforce_admins: true, + required_pull_request_reviews: { + dismissal_restrictions: {}, + dismiss_stale_reviews: true, + require_code_owner_reviews: false, + required_approving_review_count: $review_count, + require_last_push_approval: false + }, + restrictions: $restrictions, + required_linear_history: false, + allow_force_pushes: false, + allow_deletions: false, + block_creations: false, + required_conversation_resolution: true, + lock_branch: false, + allow_fork_syncing: false + }' + )" + + echo " - protecting branch: ${branch}" + local tmp_body + tmp_body="$(mktemp)" + local http_code + http_code="$( + curl -sS -o "${tmp_body}" -w "%{http_code}" -X PUT \ + "${auth_headers[@]}" \ + "${api}/branches/${branch}/protection" \ + -d "${payload}" + )" + + if [[ "${http_code}" == "200" ]]; then + rm -f "${tmp_body}" + return + fi + + if [[ "${http_code}" == "403" ]] && grep -q "Upgrade to GitHub Pro" "${tmp_body}"; then + echo " ! skipped: branch protection requires GitHub Pro/Team on private repos" + rm -f "${tmp_body}" + return + fi + + echo " ! failed (${http_code}) while protecting ${branch}:" + cat "${tmp_body}" + rm -f "${tmp_body}" + return 1 +} + +ensure_environment() { + local env_name="$1" + local payload="$2" + local tmp_body + tmp_body="$(mktemp)" + local http_code + http_code="$( + curl -sS -o "${tmp_body}" -w "%{http_code}" -X PUT \ + "${auth_headers[@]}" \ + "${api}/environments/${env_name}" \ + -d "${payload}" + )" + + if [[ "${http_code}" == "200" ]]; then + rm -f "${tmp_body}" + return + fi + + if [[ "${http_code}" == "422" ]] && grep -q "billing plan supports the required reviewers protection rule" "${tmp_body}"; then + echo " ! skipped: required reviewers rule not available on current billing plan" + rm -f "${tmp_body}" + return + fi + + echo " ! failed (${http_code}) while configuring environment ${env_name}:" + cat "${tmp_body}" + rm -f "${tmp_body}" + return 1 +} + +# Baseline checks for dev, canary, and main. +DEV_CHECKS_JSON='[ + "Contracts Unit + Invariant", + "Contracts Release Check (Dry-Run + Execute Smoke)", + "Slither Core Contracts", + "Secrets Drift Guard" +]' +CANARY_CHECKS_JSON='[ + "Contracts Unit + Invariant", + "Contracts Release Check (Dry-Run + Execute Smoke)", + "Slither Core Contracts", + "Secrets Drift Guard" +]' +MAIN_CHECKS_JSON='[ + "Contracts Unit + Invariant", + "Contracts Release Check (Dry-Run + Execute Smoke)", + "Slither Core Contracts", + "Secrets Drift Guard", + "Validate Release PR Checklist", + "Validate Release Evidence" +]' + +MAIN_RESTRICTIONS_JSON="$(build_restrictions_json "${MAIN_PUSH_ALLOW_USERS:-}" "${MAIN_PUSH_ALLOW_TEAMS:-}" "${MAIN_PUSH_ALLOW_APPS:-}")" +CANARY_RESTRICTIONS_JSON="$(build_restrictions_json "${CANARY_PUSH_ALLOW_USERS:-}" "${CANARY_PUSH_ALLOW_TEAMS:-}" "${CANARY_PUSH_ALLOW_APPS:-}")" +DEV_RESTRICTIONS_JSON="$(build_restrictions_json "${DEV_PUSH_ALLOW_USERS:-}" "${DEV_PUSH_ALLOW_TEAMS:-}" "${DEV_PUSH_ALLOW_APPS:-}")" + +if [[ "$MAIN_RESTRICTIONS_JSON" == "null" ]]; then + echo " - note: MAIN push restrictions not set (provide MAIN_PUSH_ALLOW_* to restrict direct push safely)" +fi + +# main: strict, no direct pushes +apply_branch_protection "main" "${MAIN_REVIEW_COUNT}" "$MAIN_CHECKS_JSON" "$MAIN_RESTRICTIONS_JSON" + +# canary: PR + checks; stabilisation track +apply_branch_protection "canary" "1" "$CANARY_CHECKS_JSON" "$CANARY_RESTRICTIONS_JSON" + +# dev: PR + checks; direct pushes configurable by changing restrict_pushes here +apply_branch_protection "dev" "${DEV_REVIEW_COUNT}" "$DEV_CHECKS_JSON" "$DEV_RESTRICTIONS_JSON" + +# Ensure production environment exists +echo " - ensuring environment: production" +ensure_environment "production" '{}' + +# Optional: set required reviewers for production environment +if [[ -n "${PRODUCTION_REVIEWER_IDS:-}" ]]; then + reviewers_json="$( + awk -v ids="${PRODUCTION_REVIEWER_IDS}" 'BEGIN{ + n=split(ids,a,","); + printf("["); + for(i=1;i<=n;i++){ + gsub(/^[ \t]+|[ \t]+$/, "", a[i]); + if (a[i] ~ /^[0-9]+$/) { + if (i>1) printf(","); + printf("{\"type\":\"User\",\"id\":%s}", a[i]); + } + } + printf("]"); + }' + )" + + env_payload="$(jq -n --argjson reviewers "${reviewers_json}" '{reviewers: $reviewers, wait_timer: 0}')" + echo " - applying production reviewers" + ensure_environment "production" "${env_payload}" +fi + +echo "Done." +echo "Next: manually verify branch protection toggles in GitHub UI." diff --git a/src/App.tsx b/src/App.tsx index c51eeb6..de2b02d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,352 +1,93 @@ -import { useMemo, useState } from 'react'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - CardFooter, -} from '@/components/ui/card'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; -import { Loader2 } from 'lucide-react'; -import { - useReadContracts, - useWaitForTransactionReceipt, - useWatchContractEvent, - useWriteContract, -} from 'wagmi'; import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'; -import { contracts, l2ToL2CrossDomainMessengerAbi } from '@eth-optimism/viem'; -import { privateKeyToAccount } from 'viem/accounts'; -import { sortBy } from '@/lib/utils'; -import { encodeFunctionData } from 'viem'; -import { crossChainCounterAbi } from '@/abi/crossChainCounterAbi'; -import { crossChainCounterIncrementerAbi } from '@/abi/crossChainCounterIncrementerAbi'; - -// ============================================================================ -// Configuration -// ============================================================================ - -const CONFIG = { - devAccount: privateKeyToAccount( - '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' - ), - sourceChain: supersimL2A, - destinationChain: supersimL2B, - contracts: { - counter: { - address: '0x704dd06427a37b65a1557d9a166f3c1d79026386', - }, - counterIncrementer: { - address: '0xa63abb212e7e2fa1238d7f0a5baccdba66d02392', - }, - }, -} as const; - -// ============================================================================ -// Source Chain Components (Chain A) -// ============================================================================ - -const CounterIncrementer = () => { - const { data, writeContract, isPending } = useWriteContract(); - const { isLoading: isWaitingForReceipt } = useWaitForTransactionReceipt({ - hash: data, - chainId: CONFIG.sourceChain.id, - pollingInterval: 1000, - }); - - const buttonText = isWaitingForReceipt - ? 'Waiting for confirmation...' - : isPending - ? 'Sending...' - : 'Increment'; - - return ( - <> -
- Method 1: Increment the counter by calling the increment{' '} - function on the CrossChainCounterIncrementer contract. -
- - - CrossChainCounterIncrementer - - {CONFIG.contracts.counterIncrementer.address} - - - -
-
-
- increment(uint256 counterChainId, address counterAddress) -
-
-
counterChainId: {CONFIG.destinationChain.id}
-
counterAddress: {CONFIG.contracts.counter.address}
-
-
-
-
- - - -
- - ); +type ChainInfo = { + name: string; + id: number; + role: string; }; -const DirectMessengerCall = () => { - const { writeContract, isPending, data } = useWriteContract(); - const { isLoading: isWaitingForReceipt } = useWaitForTransactionReceipt({ - hash: data, - chainId: CONFIG.sourceChain.id, - pollingInterval: 1000, - }); - - const buttonText = isWaitingForReceipt - ? 'Waiting for confirmation...' - : isPending - ? 'Sending...' - : 'Send Message'; - - const incrementFunctionData = encodeFunctionData({ - abi: crossChainCounterAbi, - functionName: 'increment', - }); +const MARK_FLOW = [ + 'Preflight deployment checks', + 'Release orchestration and artifact generation', + 'Staging rehearsal on canary', + 'Mainnet readiness gate on main', + 'Evidence manifest and signature verification', +]; + +const CHAINS: ChainInfo[] = [ + { name: supersimL2A.name, id: supersimL2A.id, role: 'source lane' }, + { name: supersimL2B.name, id: supersimL2B.id, role: 'destination lane' }, +]; + +const QuickCommand = ({ label, cmd }: { label: string; cmd: string }) => ( +
+
{label}
+
{cmd}
+
+); +function App() { return ( - <> -
- Method 2: Increment the counter by sending message by directly calling the{' '} - sendMessage function on the{' '} - L2ToL2CrossDomainMessenger contract. -
+
- L2ToL2CrossDomainMessenger - - {contracts.l2ToL2CrossDomainMessenger.address} + MARK Protocol Workspace + + This app now tracks protocol operations and release flow. - -
-
-
- sendMessage(uint256 _destination, address _target, bytes calldata _message) -
-
-
_destination: {CONFIG.destinationChain.id}
-
_address: {CONFIG.contracts.counter.address}
-
- _message: {incrementFunctionData} increment() -
-
-
-
-
- - -
- - ); -}; -const SourceChain = () => ( -
-
- Chain: {CONFIG.sourceChain.name} ({CONFIG.sourceChain.id}) -
- - - -
-); - -// ============================================================================ -// Destination Chain Components (Chain B) -// ============================================================================ - -const DestinationChain = () => { - const [logs, setLogs] = useState< - Array<{ - senderChainId: bigint; - sender: string; - newValue: bigint; - transactionHash: string; - blockNumber: bigint; - }> - >([]); - - const { data, refetch } = useReadContracts({ - contracts: [ - { - address: CONFIG.contracts.counter.address, - abi: crossChainCounterAbi, - functionName: 'number', - chainId: CONFIG.destinationChain.id, - }, - { - address: CONFIG.contracts.counter.address, - abi: crossChainCounterAbi, - functionName: 'lastIncrementer', - chainId: CONFIG.destinationChain.id, - }, - ], - }); - - useWatchContractEvent({ - address: CONFIG.contracts.counter.address, - abi: crossChainCounterAbi, - eventName: 'CounterIncremented', - chainId: CONFIG.destinationChain.id, - onLogs: newLogs => { - setLogs(prevLogs => [ - ...prevLogs, - ...newLogs.map(log => ({ - senderChainId: log.args.senderChainId!, - sender: log.args.sender!, - newValue: log.args.newValue!, - transactionHash: log.transactionHash, - blockNumber: log.blockNumber, - })), - ]); - refetch(); - }, - }); +
+ + + Superchain Lanes + Local development network topology. + + + {CHAINS.map(chain => ( +
+
+ {chain.name} ({chain.id}) +
+
{chain.role}
+
+ ))} +
+
+ + + + Release Flow + Canonical MARK release checkpoints. + + +
    + {MARK_FLOW.map(step => ( +
  1. {step}
  2. + ))} +
+
+
+
- const sortedLogs = useMemo(() => sortBy(logs, log => -log.blockNumber), [logs]); + - return ( -
-
- Chain: {CONFIG.destinationChain.name} ({CONFIG.destinationChain.id}) -
-
- Watch for state changes as the counter is incremented from the source chain. -
- - CrossChainCounter - {CONFIG.contracts.counter.address} + + Quick Commands + Use contract Make targets for protocol operations. - - {/* Counter Current State */} - -
- number: - {data?.[0]?.result?.toString() ?? '—'} -
-
-
lastIncrementer:
-
-
- chainId: - - {data?.[1]?.result?.[0]?.toString() ?? '—'} - -
-
- sender: - - {data?.[1]?.result?.[1] ?? '—'} - -
-
-
+ + + + + - - - - {/* Event Logs */} - -
-
- -
- Listening for CounterIncremented events... -
-
-
- {sortedLogs.length > 0 && ( -
-
blockNumber
-
senderChainId
-
sender
-
newValue
-
- )} - {sortedLogs.map(log => ( -
-
{log.blockNumber.toString()}
-
{log.senderChainId.toString()}
-
{log.sender}
-
{log.newValue.toString()}
-
- ))} -
-
-
); -}; - -// ============================================================================ -// Main App -// ============================================================================ - -function App() { - return ( -
- - -
- ); } export default App; diff --git a/src/abi/crossChainCounterAbi.ts b/src/abi/crossChainCounterAbi.ts deleted file mode 100644 index 4b13714..0000000 --- a/src/abi/crossChainCounterAbi.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const crossChainCounterAbi = [ - { type: 'function', name: 'increment', inputs: [], outputs: [], stateMutability: 'nonpayable' }, - { - type: 'function', - name: 'lastIncrementer', - inputs: [], - outputs: [ - { name: 'chainId', type: 'uint256', internalType: 'uint256' }, - { name: 'sender', type: 'address', internalType: 'address' }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'number', - inputs: [], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'event', - name: 'CounterIncremented', - inputs: [ - { name: 'senderChainId', type: 'uint256', indexed: true, internalType: 'uint256' }, - { name: 'sender', type: 'address', indexed: true, internalType: 'address' }, - { name: 'newValue', type: 'uint256', indexed: false, internalType: 'uint256' }, - ], - anonymous: false, - }, - { type: 'error', name: 'CallerNotL2ToL2CrossDomainMessenger', inputs: [] }, -] as const; diff --git a/src/abi/crossChainCounterIncrementerAbi.ts b/src/abi/crossChainCounterIncrementerAbi.ts deleted file mode 100644 index c473cce..0000000 --- a/src/abi/crossChainCounterIncrementerAbi.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const crossChainCounterIncrementerAbi = [ - { - type: 'function', - name: 'increment', - inputs: [ - { name: 'counterChainId', type: 'uint256', internalType: 'uint256' }, - { name: 'counterAddress', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, -] as const; From d030dfdc3b1bb68e443d134234e88051e473b1c3 Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 22:25:47 +0700 Subject: [PATCH 12/38] chore(ci): stabilize local test and lint signal 1. Exclude vendored/generated contract directories from root ESLint scope 2. Split fast core tests and invariant tests in contracts Makefile 3. Bound local invariant runs for predictable ci-full runtime --- contracts/Makefile | 17 +++++++++++------ eslint.config.js | 10 +++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/contracts/Makefile b/contracts/Makefile index 12fff95..37f168a 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -1,11 +1,16 @@ -.PHONY: ci-local ci-fast ci-full release-gate architecture-guard layering-guard smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature +.PHONY: ci-local ci-fast ci-full test-core test-invariants release-gate architecture-guard layering-guard smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature -# Fast local loop: guards + default-profile tests (no integration profile tests). -ci-fast: architecture-guard layering-guard - @forge test -q +# Fast local loop: guards + unit/e2e tests (excludes invariants and integration profile tests). +ci-fast: architecture-guard layering-guard test-core -# Full local gate: fast checks + explicit production lock regression checks. -ci-full: ci-fast +test-core: + @FOUNDRY_OFFLINE=true forge test --no-match-path 'test/invariant/**' -q + +test-invariants: + @FOUNDRY_OFFLINE=true FOUNDRY_INVARIANT_RUNS=64 forge test --match-path 'test/invariant/**/*.t.sol' -q + +# Full local gate: fast checks + invariants + explicit production lock regression checks. +ci-full: ci-fast test-invariants @$(MAKE) test-production-lock # Backward-compatible alias for local usage. diff --git a/eslint.config.js b/eslint.config.js index c973d7f..06dfbc3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,15 @@ import reactRefresh from 'eslint-plugin-react-refresh'; import tseslint from 'typescript-eslint'; export default tseslint.config( - { ignores: ['dist'] }, + { + ignores: [ + 'dist', + 'contracts/lib/**', + 'contracts/out/**', + 'contracts/cache/**', + 'contracts/broadcast/**', + ], + }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], files: ['**/*.{ts,tsx}'], From b13ecbf86b6bac502db59c28898a5419ff0d4e8f Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 22:31:38 +0700 Subject: [PATCH 13/38] fix(ci): repair contracts workflow execution on GitHub 1. Fix contracts integration job condition to use github.event.inputs 2. Run slither per target contract instead of invalid multi-target invocation --- .github/workflows/contracts-ci.yml | 2 +- .github/workflows/contracts-slither.yml | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index a49c85d..6061ec7 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -136,7 +136,7 @@ jobs: name: Contracts Integration (RPC) runs-on: ubuntu-latest needs: [contracts-unit-invariant, contracts-release-check, contracts-production-mode-smoke] - if: ${{ github.event_name == 'workflow_dispatch' && inputs.run_integration }} + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.run_integration == 'true' }} defaults: run: working-directory: contracts diff --git a/.github/workflows/contracts-slither.yml b/.github/workflows/contracts-slither.yml index 56133f5..67070a4 100644 --- a/.github/workflows/contracts-slither.yml +++ b/.github/workflows/contracts-slither.yml @@ -41,12 +41,15 @@ jobs: - name: Run Slither on MARK core contracts working-directory: contracts run: | - slither \ + for target in \ src/token/RYLA.sol \ src/bridge/MARKBridgeAdapter.sol \ src/settlement/MARKSettlementModule.sol \ - src/settlement/verifier/AttestedSettlementVerifier.sol \ - --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ - --exclude-dependencies \ - --filter-paths "lib|test|script|out|cache" \ - --fail-medium + src/settlement/verifier/AttestedSettlementVerifier.sol + do + slither "$target" \ + --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ + --exclude-dependencies \ + --filter-paths "lib|test|script|out|cache" \ + --fail-medium + done From c5bf691c1183fa1916bc95ce069fc828f61fb31d Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 22:32:27 +0700 Subject: [PATCH 14/38] fix(ci): quote static private key in contracts-ci workflow env --- .github/workflows/contracts-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index 6061ec7..515634b 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -58,7 +58,7 @@ jobs: run: working-directory: contracts env: - PRIVATE_KEY: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + PRIVATE_KEY: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" RPC_URL: http://127.0.0.1:8545 MARK_RELEASE_EXECUTE: "false" MARK_RELEASE_RUN_POSTDEPLOY: "false" From b08e5c20c301ac20ddbeb019dfe576ec3505eb43 Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 22:36:00 +0700 Subject: [PATCH 15/38] fix(slither): codify accepted detector exclusions for MARK contracts Exclude known/accepted findings (naming convention, timestamp epoching, operator-gated transferFrom pattern, and benign reentrancy patterns) while keeping fail-medium enforcement for remaining detectors. --- .github/workflows/contracts-slither.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/contracts-slither.yml b/.github/workflows/contracts-slither.yml index 67070a4..333420e 100644 --- a/.github/workflows/contracts-slither.yml +++ b/.github/workflows/contracts-slither.yml @@ -50,6 +50,7 @@ jobs: slither "$target" \ --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ --exclude-dependencies \ + --exclude "naming-convention,timestamp,arbitrary-send-erc20,reentrancy-balance,reentrancy-benign" \ --filter-paths "lib|test|script|out|cache" \ --fail-medium done From 202c70ae541b7ba2e18c1205b1d142f265c43890 Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 23:25:03 +0700 Subject: [PATCH 16/38] chore(ci): harden workflow runtime compatibility and add frontend node matrix 1. Upgrade actions/checkout from v4 to v5 across workflows 2. Upgrade actions/setup-python from v5 to v6 in python-based workflows 3. Add frontend CI workflow with Node 20/22 matrix for typecheck, lint, and build validation --- .github/workflows/contracts-ci.yml | 8 +-- .github/workflows/contracts-env-guard.yml | 2 +- .../workflows/contracts-evidence-manifest.yml | 4 +- .../workflows/contracts-mainnet-readiness.yml | 4 +- .../contracts-production-lock-verify.yml | 2 +- .../contracts-promotion-checklist.yml | 2 +- .github/workflows/contracts-slither.yml | 4 +- .../workflows/contracts-staging-rehearsal.yml | 2 +- .github/workflows/frontend-ci.yml | 66 +++++++++++++++++++ .github/workflows/governance-policy-guard.yml | 2 +- .github/workflows/secrets-drift-guard.yml | 2 +- 11 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/frontend-ci.yml diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index 515634b..a1d4c37 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -67,7 +67,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -118,7 +118,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -146,7 +146,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/contracts-env-guard.yml b/.github/workflows/contracts-env-guard.yml index 4956abf..75a802e 100644 --- a/.github/workflows/contracts-env-guard.yml +++ b/.github/workflows/contracts-env-guard.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/contracts-evidence-manifest.yml b/.github/workflows/contracts-evidence-manifest.yml index 31612d0..ff3cb6a 100644 --- a/.github/workflows/contracts-evidence-manifest.yml +++ b/.github/workflows/contracts-evidence-manifest.yml @@ -59,7 +59,7 @@ jobs: working-directory: contracts steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -149,7 +149,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/contracts-mainnet-readiness.yml b/.github/workflows/contracts-mainnet-readiness.yml index d1895ac..4f9c148 100644 --- a/.github/workflows/contracts-mainnet-readiness.yml +++ b/.github/workflows/contracts-mainnet-readiness.yml @@ -46,7 +46,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -58,7 +58,7 @@ jobs: } - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" diff --git a/.github/workflows/contracts-production-lock-verify.yml b/.github/workflows/contracts-production-lock-verify.yml index 2041d7a..03b5bf5 100644 --- a/.github/workflows/contracts-production-lock-verify.yml +++ b/.github/workflows/contracts-production-lock-verify.yml @@ -59,7 +59,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/contracts-promotion-checklist.yml b/.github/workflows/contracts-promotion-checklist.yml index 43de2f8..e644862 100644 --- a/.github/workflows/contracts-promotion-checklist.yml +++ b/.github/workflows/contracts-promotion-checklist.yml @@ -60,7 +60,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/contracts-slither.yml b/.github/workflows/contracts-slither.yml index 333420e..e2fe0af 100644 --- a/.github/workflows/contracts-slither.yml +++ b/.github/workflows/contracts-slither.yml @@ -23,12 +23,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" diff --git a/.github/workflows/contracts-staging-rehearsal.yml b/.github/workflows/contracts-staging-rehearsal.yml index a7ef426..92c916e 100644 --- a/.github/workflows/contracts-staging-rehearsal.yml +++ b/.github/workflows/contracts-staging-rehearsal.yml @@ -76,7 +76,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml new file mode 100644 index 0000000..e868de8 --- /dev/null +++ b/.github/workflows/frontend-ci.yml @@ -0,0 +1,66 @@ +name: Frontend CI + +on: + pull_request: + paths: + - "src/**" + - "public/**" + - "package.json" + - "pnpm-lock.yaml" + - "tsconfig*.json" + - "vite.config.*" + - "eslint.config.*" + - ".github/workflows/frontend-ci.yml" + push: + branches: + - main + - canary + - dev + paths: + - "src/**" + - "public/**" + - "package.json" + - "pnpm-lock.yaml" + - "tsconfig*.json" + - "vite.config.*" + - "eslint.config.*" + - ".github/workflows/frontend-ci.yml" + workflow_dispatch: + +jobs: + frontend-checks: + name: Frontend Checks (Node ${{ matrix.node }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: ["20", "22"] + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + submodules: recursive + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: "pnpm" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm -s typecheck + + - name: Lint + run: pnpm -s lint + + - name: Build frontend + run: pnpm -s build:frontend diff --git a/.github/workflows/governance-policy-guard.yml b/.github/workflows/governance-policy-guard.yml index 7021d4d..cbd6034 100644 --- a/.github/workflows/governance-policy-guard.yml +++ b/.github/workflows/governance-policy-guard.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Run governance policy validator run: bash scripts/ci/validate-governance-policy.sh diff --git a/.github/workflows/secrets-drift-guard.yml b/.github/workflows/secrets-drift-guard.yml index d8c94de..6677ad3 100644 --- a/.github/workflows/secrets-drift-guard.yml +++ b/.github/workflows/secrets-drift-guard.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 From 4bc94ec3d8109b64536adba07c232335070172f7 Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 23:26:00 +0700 Subject: [PATCH 17/38] fix(frontend-ci): ensure pnpm setup works with node matrix Use actions/setup-node@v5 and remove premature pnpm cache wiring so pnpm/action-setup can install pnpm before dependency install. --- .github/workflows/frontend-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index e868de8..7784fa6 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -43,10 +43,9 @@ jobs: submodules: recursive - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node }} - cache: "pnpm" - name: Setup pnpm uses: pnpm/action-setup@v4 From c0a98c125743cda94beee5c45c05dbd851b7943d Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 23:26:51 +0700 Subject: [PATCH 18/38] fix(frontend-ci): install pnpm before setup-node auto-cache check --- .github/workflows/frontend-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 7784fa6..9f718ba 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -42,16 +42,16 @@ jobs: with: submodules: recursive - - name: Setup Node.js - uses: actions/setup-node@v5 - with: - node-version: ${{ matrix.node }} - - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 9 + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node }} + - name: Install dependencies run: pnpm install --frozen-lockfile From 36df5bad6b21ddc63057c0a4487244d48535aabe Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 23:27:29 +0700 Subject: [PATCH 19/38] fix(frontend-ci): rely on packageManager-pinned pnpm version --- .github/workflows/frontend-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 9f718ba..bae8736 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -44,8 +44,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: Setup Node.js uses: actions/setup-node@v5 From 623b91b531d517ae50a6a7d906732bbe6dbdfa55 Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 23:35:57 +0700 Subject: [PATCH 20/38] chore(ci): replace pnpm action with corepack-pinned bootstrap 1. Remove pnpm/action-setup usage from frontend and contracts integration workflows 2. Use corepack with pinned pnpm@9.0.2 from project policy 3. Disable setup-node package-manager auto-cache probing to avoid pnpm bootstrap race --- .github/workflows/contracts-ci.yml | 13 +++++++------ .github/workflows/frontend-ci.yml | 10 +++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index a1d4c37..7fc2f5b 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -151,15 +151,16 @@ jobs: submodules: recursive - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: "20" - cache: "pnpm" + package-manager-cache: false - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 + - name: Setup pnpm (corepack) + run: | + corepack enable + corepack prepare pnpm@9.0.2 --activate + pnpm --version - name: Install JS dependencies run: pnpm install --frozen-lockfile diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index bae8736..1a54291 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -42,13 +42,17 @@ jobs: with: submodules: recursive - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - name: Setup Node.js uses: actions/setup-node@v5 with: node-version: ${{ matrix.node }} + package-manager-cache: false + + - name: Setup pnpm (corepack) + run: | + corepack enable + corepack prepare pnpm@9.0.2 --activate + pnpm --version - name: Install dependencies run: pnpm install --frozen-lockfile From e22bc22734da8c67c1a852c57f910b19b93f7925 Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 23:40:54 +0700 Subject: [PATCH 21/38] fix(contracts-ci): wait for anvil before release dry-run --- .github/workflows/contracts-ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index 7fc2f5b..9523615 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -77,6 +77,18 @@ jobs: - name: Start anvil run: anvil --host 127.0.0.1 --port 8545 > /tmp/anvil.log 2>&1 & + - name: Wait for anvil readiness + run: | + for _ in $(seq 1 30); do + if nc -z 127.0.0.1 8545; then + exit 0 + fi + sleep 1 + done + echo "anvil did not become ready on 127.0.0.1:8545" >&2 + tail -n 200 /tmp/anvil.log || true + exit 1 + - name: Run release orchestrator dry-run run: forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url $RPC_URL -vv From 01d275e252d2bde89be60de112406e19a247a41e Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 23:50:02 +0700 Subject: [PATCH 22/38] chore(deps): add dependabot config for actions and npm --- .github/dependabot.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ba50c1b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "03:00" + timezone: "UTC" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "ci" + commit-message: + prefix: "chore(deps)" + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "03:30" + timezone: "UTC" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "frontend" + groups: + frontend-minor-patch: + update-types: + - "minor" + - "patch" + commit-message: + prefix: "chore(deps)" From 30722fe6ce01aab0a50612dc2eb0c70cd0324b8d Mon Sep 17 00:00:00 2001 From: Iko Date: Sat, 2 May 2026 23:50:02 +0700 Subject: [PATCH 23/38] chore(deps): add dependabot config for actions and npm --- .github/dependabot.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ba50c1b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "03:00" + timezone: "UTC" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "ci" + commit-message: + prefix: "chore(deps)" + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "03:30" + timezone: "UTC" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "frontend" + groups: + frontend-minor-patch: + update-types: + - "minor" + - "patch" + commit-message: + prefix: "chore(deps)" From b8ef2759df74e5e9bbc8707afb37b725f29d6dfd Mon Sep 17 00:00:00 2001 From: Iko Date: Sun, 3 May 2026 22:31:14 +0700 Subject: [PATCH 24/38] chore(coderabbit): add repository-level review configuration --- .coderabbit.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..ca31c7a --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: "en-US" +early_access: false + +reviews: + profile: "chill" + request_changes_workflow: false + high_level_summary: true + review_status: true + collapse_walkthrough: false + auto_review: + enabled: true + drafts: false + +chat: + auto_reply: true + +knowledge_base: + code_guidelines: + enabled: true From da727f46ad042e1fd57c78952fa883f05715f33b Mon Sep 17 00:00:00 2001 From: Iko Date: Sun, 3 May 2026 23:18:26 +0700 Subject: [PATCH 25/38] fix(readiness): run pre-checks before contracts working directory exists --- .github/workflows/contracts-mainnet-readiness.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/contracts-mainnet-readiness.yml b/.github/workflows/contracts-mainnet-readiness.yml index 4f9c148..c8229ab 100644 --- a/.github/workflows/contracts-mainnet-readiness.yml +++ b/.github/workflows/contracts-mainnet-readiness.yml @@ -27,9 +27,6 @@ jobs: name: Mainnet Readiness Gate runs-on: ubuntu-latest environment: production - defaults: - run: - working-directory: contracts env: MARK_MAINNET_GATE_MODE: ${{ inputs.mode }} RPC_URL: ${{ inputs.rpc_url }} @@ -69,6 +66,7 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 - name: Run mainnet readiness gate + working-directory: contracts run: ./script/ops/mainnet-readiness.sh - name: Upload readiness artifact From 1f318fdc536aa4eceb16716f413b754c4769874c Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Sun, 3 May 2026 23:51:28 +0700 Subject: [PATCH 26/38] chore: promote dev to canary (ci and quality sync) (#15) * chore(deps): bump actions/setup-node from 5 to 6 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/upload-artifact from 4 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/github-script from 7 to 9 Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 9. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v9) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump the frontend-minor-patch group with 13 updates Bumps the frontend-minor-patch group with 13 updates: | Package | From | To | | --- | --- | --- | | [@eth-optimism/viem](https://github.com/ethereum-optimism/ecosystem/tree/HEAD/packages/viem) | `0.3.2` | `0.4.15` | | [@radix-ui/react-separator](https://github.com/radix-ui/primitives) | `1.1.2` | `1.1.8` | | [@radix-ui/react-slot](https://github.com/radix-ui/primitives) | `1.1.2` | `1.2.4` | | [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.0.6` | `4.2.4` | | [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.66.0` | `5.100.8` | | [abitype](https://github.com/wevm/abitype) | `1.0.8` | `1.2.4` | | [tailwind-merge](https://github.com/dcastil/tailwind-merge) | `3.0.1` | `3.5.0` | | [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.0.6` | `4.2.4` | | [viem](https://github.com/wevm/viem) | `2.23.1` | `2.48.8` | | [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.4.19` | `0.5.2` | | [mprocs](https://github.com/pvolok/mprocs) | `0.7.2` | `0.9.2` | | [prettier](https://github.com/prettier/prettier) | `3.5.0` | `3.8.3` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.24.0` | `8.59.1` | Updates `@eth-optimism/viem` from 0.3.2 to 0.4.15 - [Changelog](https://github.com/ethereum-optimism/ecosystem/blob/main/packages/viem/CHANGELOG.md) - [Commits](https://github.com/ethereum-optimism/ecosystem/commits/HEAD/packages/viem) Updates `@radix-ui/react-separator` from 1.1.2 to 1.1.8 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@radix-ui/react-slot` from 1.1.2 to 1.2.4 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@tailwindcss/vite` from 4.0.6 to 4.2.4 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.4/packages/@tailwindcss-vite) Updates `@tanstack/react-query` from 5.66.0 to 5.100.8 - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.100.8/packages/react-query) Updates `abitype` from 1.0.8 to 1.2.4 - [Release notes](https://github.com/wevm/abitype/releases) - [Commits](https://github.com/wevm/abitype/compare/abitype@1.0.8...abitype@1.2.4) Updates `tailwind-merge` from 3.0.1 to 3.5.0 - [Release notes](https://github.com/dcastil/tailwind-merge/releases) - [Commits](https://github.com/dcastil/tailwind-merge/compare/v3.0.1...v3.5.0) Updates `tailwindcss` from 4.0.6 to 4.2.4 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.4/packages/tailwindcss) Updates `viem` from 2.23.1 to 2.48.8 - [Release notes](https://github.com/wevm/viem/releases) - [Commits](https://github.com/wevm/viem/compare/viem@2.23.1...viem@2.48.8) Updates `eslint-plugin-react-refresh` from 0.4.19 to 0.5.2 - [Release notes](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/releases) - [Changelog](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/main/CHANGELOG.md) - [Commits](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/compare/v0.4.19...v0.5.2) Updates `mprocs` from 0.7.2 to 0.9.2 - [Release notes](https://github.com/pvolok/mprocs/releases) - [Changelog](https://github.com/pvolok/mprocs/blob/master/CHANGELOG.md) - [Commits](https://github.com/pvolok/mprocs/compare/v0.7.2...v0.9.2) Updates `prettier` from 3.5.0 to 3.8.3 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.0...3.8.3) Updates `typescript-eslint` from 8.24.0 to 8.59.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@eth-optimism/viem" dependency-version: 0.4.15 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@radix-ui/react-separator" dependency-version: 1.1.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-minor-patch - dependency-name: "@radix-ui/react-slot" dependency-version: 1.2.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/vite" dependency-version: 4.2.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tanstack/react-query" dependency-version: 5.100.8 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: abitype dependency-version: 1.2.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: tailwind-merge dependency-version: 3.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: tailwindcss dependency-version: 4.2.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: viem dependency-version: 2.48.8 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: eslint-plugin-react-refresh dependency-version: 0.5.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: mprocs dependency-version: 0.9.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: prettier dependency-version: 3.8.3 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: typescript-eslint dependency-version: 8.59.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-patch ... Signed-off-by: dependabot[bot] * fix(readiness): run pre-checks before contracts working directory exists * fix(frontend): remove non-component export from button ui --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/contracts-ci.yml | 10 +- .github/workflows/contracts-env-guard.yml | 2 +- .../workflows/contracts-evidence-manifest.yml | 8 +- .../workflows/contracts-mainnet-readiness.yml | 4 +- .../contracts-production-lock-verify.yml | 4 +- .../contracts-promotion-checklist.yml | 4 +- .github/workflows/contracts-slither.yml | 2 +- .../workflows/contracts-staging-rehearsal.yml | 6 +- .github/workflows/frontend-ci.yml | 4 +- .github/workflows/governance-policy-guard.yml | 2 +- .../workflows/release-evidence-validator.yml | 2 +- .github/workflows/secrets-drift-guard.yml | 2 +- package.json | 26 +- pnpm-lock.yaml | 1301 ++++++++++------- src/components/ui/button.tsx | 2 +- 15 files changed, 806 insertions(+), 573 deletions(-) diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index 9523615..f91c72c 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -67,7 +67,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -130,7 +130,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -158,12 +158,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: "20" package-manager-cache: false diff --git a/.github/workflows/contracts-env-guard.yml b/.github/workflows/contracts-env-guard.yml index 75a802e..921a687 100644 --- a/.github/workflows/contracts-env-guard.yml +++ b/.github/workflows/contracts-env-guard.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive diff --git a/.github/workflows/contracts-evidence-manifest.yml b/.github/workflows/contracts-evidence-manifest.yml index ff3cb6a..2c97b58 100644 --- a/.github/workflows/contracts-evidence-manifest.yml +++ b/.github/workflows/contracts-evidence-manifest.yml @@ -59,7 +59,7 @@ jobs: working-directory: contracts steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -109,7 +109,7 @@ jobs: - name: Upload self-test manifest artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: mark-evidence-manifest-self-test path: | @@ -149,7 +149,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -180,7 +180,7 @@ jobs: - name: Upload manifest artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: mark-evidence-manifest path: | diff --git a/.github/workflows/contracts-mainnet-readiness.yml b/.github/workflows/contracts-mainnet-readiness.yml index c8229ab..f345401 100644 --- a/.github/workflows/contracts-mainnet-readiness.yml +++ b/.github/workflows/contracts-mainnet-readiness.yml @@ -43,7 +43,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -71,7 +71,7 @@ jobs: - name: Upload readiness artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: mark-mainnet-readiness-artifact path: contracts/${{ inputs.artifact_path }} diff --git a/.github/workflows/contracts-production-lock-verify.yml b/.github/workflows/contracts-production-lock-verify.yml index 03b5bf5..3237c53 100644 --- a/.github/workflows/contracts-production-lock-verify.yml +++ b/.github/workflows/contracts-production-lock-verify.yml @@ -59,7 +59,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -105,7 +105,7 @@ jobs: - name: Upload verification artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: mark-production-lock-verify path: contracts/broadcast/mark-production-lock-verify.json diff --git a/.github/workflows/contracts-promotion-checklist.yml b/.github/workflows/contracts-promotion-checklist.yml index e644862..92a6b5b 100644 --- a/.github/workflows/contracts-promotion-checklist.yml +++ b/.github/workflows/contracts-promotion-checklist.yml @@ -60,7 +60,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -69,7 +69,7 @@ jobs: - name: Upload checklist artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: mark-promotion-checklist path: | diff --git a/.github/workflows/contracts-slither.yml b/.github/workflows/contracts-slither.yml index e2fe0af..0365b9e 100644 --- a/.github/workflows/contracts-slither.yml +++ b/.github/workflows/contracts-slither.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive diff --git a/.github/workflows/contracts-staging-rehearsal.yml b/.github/workflows/contracts-staging-rehearsal.yml index 92c916e..9bdbe0a 100644 --- a/.github/workflows/contracts-staging-rehearsal.yml +++ b/.github/workflows/contracts-staging-rehearsal.yml @@ -76,7 +76,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive @@ -103,7 +103,7 @@ jobs: - name: Upload release artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: mark-staging-release path: contracts/${{ env.MARK_RELEASE_ARTIFACT_PATH }} @@ -111,7 +111,7 @@ jobs: - name: Upload rehearsal artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: mark-staging-rehearsal path: contracts/${{ env.MARK_REHEARSAL_ARTIFACT_PATH }} diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 1a54291..2acf75e 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -38,12 +38,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} package-manager-cache: false diff --git a/.github/workflows/governance-policy-guard.yml b/.github/workflows/governance-policy-guard.yml index cbd6034..eace5c2 100644 --- a/.github/workflows/governance-policy-guard.yml +++ b/.github/workflows/governance-policy-guard.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Run governance policy validator run: bash scripts/ci/validate-governance-policy.sh diff --git a/.github/workflows/release-evidence-validator.yml b/.github/workflows/release-evidence-validator.yml index 8db2e2f..77df87b 100644 --- a/.github/workflows/release-evidence-validator.yml +++ b/.github/workflows/release-evidence-validator.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Validate release PR fields and evidence - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const pr = context.payload.pull_request; diff --git a/.github/workflows/secrets-drift-guard.yml b/.github/workflows/secrets-drift-guard.yml index 6677ad3..a70365a 100644 --- a/.github/workflows/secrets-drift-guard.yml +++ b/.github/workflows/secrets-drift-guard.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/package.json b/package.json index 875f6a5..e591874 100644 --- a/package.json +++ b/package.json @@ -25,21 +25,21 @@ "shadcn:add": "pnpm dlx shadcn@canary add" }, "dependencies": { - "@eth-optimism/viem": "^0.3.2", - "@radix-ui/react-separator": "^1.1.2", - "@radix-ui/react-slot": "^1.1.2", - "@tailwindcss/vite": "^4.0.6", - "@tanstack/react-query": "^5.66.0", - "abitype": "^1.0.8", + "@eth-optimism/viem": "^0.4.15", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/react-query": "^5.100.8", + "abitype": "^1.2.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.475.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "tailwind-merge": "^3.0.1", - "tailwindcss": "^4.0.6", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.4", "tailwindcss-animate": "^1.0.7", - "viem": "^2.23.1", + "viem": "^2.48.8", "wagmi": "^2.14.11" }, "devDependencies": { @@ -51,13 +51,13 @@ "@vitejs/plugin-react": "^4.3.4", "eslint": "^9.19.0", "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.18", + "eslint-plugin-react-refresh": "^0.5.2", "globals": "^15.14.0", - "mprocs": "^0.7.2", - "prettier": "^3.5.0", + "mprocs": "^0.9.2", + "prettier": "^3.8.3", "supersim": "0.1.0-alpha.45", "typescript": "~5.7.2", - "typescript-eslint": "^8.22.0", + "typescript-eslint": "^8.59.1", "vite": "^6.1.0", "wait-port": "^1.1.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c8d61d..41fbfb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,23 +9,23 @@ importers: .: dependencies: '@eth-optimism/viem': - specifier: ^0.3.2 - version: 0.3.2(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) + specifier: ^0.4.15 + version: 0.4.15(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) '@radix-ui/react-separator': - specifier: ^1.1.2 - version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': - specifier: ^1.1.2 - version: 1.1.2(@types/react@18.3.18)(react@18.3.1) + specifier: ^1.2.4 + version: 1.2.4(@types/react@18.3.18)(react@18.3.1) '@tailwindcss/vite': - specifier: ^4.0.6 - version: 4.0.6(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)) + specifier: ^4.2.4 + version: 4.2.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0)) '@tanstack/react-query': - specifier: ^5.66.0 - version: 5.66.0(react@18.3.1) + specifier: ^5.100.8 + version: 5.100.8(react@18.3.1) abitype: - specifier: ^1.0.8 - version: 1.0.8(typescript@5.7.3)(zod@3.24.1) + specifier: ^1.2.4 + version: 1.2.4(typescript@5.7.3)(zod@3.24.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -42,27 +42,27 @@ importers: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) tailwind-merge: - specifier: ^3.0.1 - version: 3.0.1 + specifier: ^3.5.0 + version: 3.5.0 tailwindcss: - specifier: ^4.0.6 - version: 4.0.6 + specifier: ^4.2.4 + version: 4.2.4 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@4.0.6) + version: 1.0.7(tailwindcss@4.2.4) viem: - specifier: ^2.23.1 - version: 2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + specifier: ^2.48.8 + version: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) wagmi: specifier: ^2.14.11 - version: 2.14.11(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) + version: 2.14.11(@tanstack/query-core@5.100.8)(@tanstack/react-query@5.100.8(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) devDependencies: '@eslint/js': specifier: ^9.19.0 version: 9.20.0 '@eth-optimism/super-cli': specifier: ^0.0.13 - version: 0.0.13(@tanstack/query-core@5.66.0)(@types/react@18.3.18)(bufferutil@4.0.9)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(utf-8-validate@5.0.10)(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)) + version: 0.0.13(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(bufferutil@4.0.9)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(utf-8-validate@5.0.10)(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0)) '@types/node': specifier: ^22.13.1 version: 22.13.1 @@ -74,25 +74,25 @@ importers: version: 18.3.5(@types/react@18.3.18) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)) + version: 4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0)) eslint: specifier: ^9.19.0 - version: 9.20.0(jiti@2.4.2) + version: 9.20.0(jiti@2.6.1) eslint-plugin-react-hooks: specifier: ^5.0.0 - version: 5.1.0(eslint@9.20.0(jiti@2.4.2)) + version: 5.1.0(eslint@9.20.0(jiti@2.6.1)) eslint-plugin-react-refresh: - specifier: ^0.4.18 - version: 0.4.19(eslint@9.20.0(jiti@2.4.2)) + specifier: ^0.5.2 + version: 0.5.2(eslint@9.20.0(jiti@2.6.1)) globals: specifier: ^15.14.0 version: 15.14.0 mprocs: - specifier: ^0.7.2 - version: 0.7.2 + specifier: ^0.9.2 + version: 0.9.2 prettier: - specifier: ^3.5.0 - version: 3.5.0 + specifier: ^3.8.3 + version: 3.8.3 supersim: specifier: 0.1.0-alpha.45 version: 0.1.0-alpha.45 @@ -100,19 +100,19 @@ importers: specifier: ~5.7.2 version: 5.7.3 typescript-eslint: - specifier: ^8.22.0 - version: 8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3) + specifier: ^8.59.1 + version: 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) vite: specifier: ^6.1.0 - version: 6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1) + version: 6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0) wait-port: specifier: ^1.1.0 version: 1.1.0 packages: - '@adraffy/ens-normalize@1.11.0': - resolution: {integrity: sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==} + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} '@alcalzone/ansi-tokenize@0.1.3': resolution: {integrity: sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==} @@ -373,10 +373,20 @@ 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.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.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} + '@eslint/config-array@0.19.2': resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -410,8 +420,13 @@ packages: engines: {node: '>=18.20'} hasBin: true - '@eth-optimism/viem@0.3.2': - resolution: {integrity: sha512-oH8T3uY8oTPMNF1ZHI6k6i226w9KuhMGT/KEeNhdaR9U8ertzITW095nPuQoD8OYtK7o+3kJuPa9wJciwEeP1Q==} + '@eth-optimism/viem@0.3.3': + resolution: {integrity: sha512-WN1rzyJVRLSQvnqu+9cjz+zucVlNXXlwSBuQ8QNXOsB/z8zFkPxosbuLDE+7GZGc6R2KoBs6HKXMxK8O/NgKgQ==} + peerDependencies: + viem: ^2.17.9 + + '@eth-optimism/viem@0.4.15': + resolution: {integrity: sha512-7tExoHdybGUAfQRbpoE1hKnsob7vMRHvorZtuO+W5zGUheo0vTVVWbH7y9lSUfoaWaO8nnbNJCU6HQN+050EgA==} peerDependencies: viem: ^2.17.9 @@ -463,10 +478,16 @@ packages: peerDependencies: ink: '>=5' + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -475,12 +496,15 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@libsql/client@0.14.0': resolution: {integrity: sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==} @@ -578,6 +602,7 @@ packages: '@metamask/sdk-communication-layer@0.32.0': resolution: {integrity: sha512-dmj/KFjMi1fsdZGIOtbhxdg3amxhKL/A5BqSU4uh/SyDKPub/OT+x5pX8bGjpTL1WPWY/Q0OIlvFyX3VWnT06Q==} + deprecated: No longer maintained, superseded by https://docs.metamask.io/metamask-connect peerDependencies: cross-fetch: ^4.0.0 eciesjs: '*' @@ -587,9 +612,11 @@ packages: '@metamask/sdk-install-modal-web@0.32.0': resolution: {integrity: sha512-TFoktj0JgfWnQaL3yFkApqNwcaqJ+dw4xcnrJueMP3aXkSNev2Ido+WVNOg4IIMxnmOrfAC9t0UJ0u/dC9MjOQ==} + deprecated: No longer maintained, superseded by https://docs.metamask.io/metamask-connect '@metamask/sdk@0.32.0': resolution: {integrity: sha512-WmGAlP1oBuD9hk4CsdlG1WJFuPtYJY+dnTHJMeCyohTWD2GgkcLMUUuvu9lO1/NVzuOoSi1OrnjbuY1O/1NZ1g==} + deprecated: No longer maintained, superseded by https://docs.metamask.io/metamask-connect '@metamask/superstruct@3.1.0': resolution: {integrity: sha512-N08M56HdOgBfRKkrgCMZvQppkZGcArEop3kixNEtVbJKm6P9Cfg0YkI6X0s1g78sNrj2fWUwvJADdZuzJgFttA==} @@ -635,42 +662,35 @@ packages: '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} - '@noble/ciphers@1.2.1': - resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} '@noble/curves@1.4.2': resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} - '@noble/curves@1.8.1': - resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} engines: {node: ^14.21.3 || >=16} '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} - '@noble/hashes@1.7.1': - resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - '@paulmillr/qr@0.2.1': resolution: {integrity: sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==} + deprecated: 'The package is now available as "qr": npm install qr' - '@radix-ui/react-compose-refs@1.1.1': - resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -678,8 +698,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-primitive@2.0.2': - resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -691,8 +711,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-separator@1.1.2': - resolution: {integrity: sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==} + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -704,8 +724,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.1.2': - resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -821,20 +841,20 @@ packages: '@scure/base@1.1.9': resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} - '@scure/base@1.2.4': - resolution: {integrity: sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ==} + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} '@scure/bip32@1.4.0': resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} - '@scure/bip32@1.6.2': - resolution: {integrity: sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==} + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} '@scure/bip39@1.3.0': resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} - '@scure/bip39@1.5.4': - resolution: {integrity: sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==} + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -893,89 +913,101 @@ packages: '@stablelib/x25519@1.0.3': resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==} - '@tailwindcss/node@4.0.6': - resolution: {integrity: sha512-jb6E0WeSq7OQbVYcIJ6LxnZTeC4HjMvbzFBMCrQff4R50HBlo/obmYNk6V2GCUXDeqiXtvtrQgcIbT+/boB03Q==} + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} - '@tailwindcss/oxide-android-arm64@4.0.6': - resolution: {integrity: sha512-xDbym6bDPW3D2XqQqX3PjqW3CKGe1KXH7Fdkc60sX5ZLVUbzPkFeunQaoP+BuYlLc2cC1FoClrIRYnRzof9Sow==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.0.6': - resolution: {integrity: sha512-1f71/ju/tvyGl5c2bDkchZHy8p8EK/tDHCxlpYJ1hGNvsYihZNurxVpZ0DefpN7cNc9RTT8DjrRoV8xXZKKRjg==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.0.6': - resolution: {integrity: sha512-s/hg/ZPgxFIrGMb0kqyeaqZt505P891buUkSezmrDY6lxv2ixIELAlOcUVTkVh245SeaeEiUVUPiUN37cwoL2g==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.0.6': - resolution: {integrity: sha512-Z3Wo8FWZnmio8+xlcbb7JUo/hqRMSmhQw8IGIRoRJ7GmLR0C+25Wq+bEX/135xe/yEle2lFkhu9JBHd4wZYiig==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.6': - resolution: {integrity: sha512-SNSwkkim1myAgmnbHs4EjXsPL7rQbVGtjcok5EaIzkHkCAVK9QBQsWeP2Jm2/JJhq4wdx8tZB9Y7psMzHYWCkA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.0.6': - resolution: {integrity: sha512-tJ+mevtSDMQhKlwCCuhsFEFg058kBiSy4TkoeBG921EfrHKmexOaCyFKYhVXy4JtkaeeOcjJnCLasEeqml4i+Q==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.0.6': - resolution: {integrity: sha512-IoArz1vfuTR4rALXMUXI/GWWfx2EaO4gFNtBNkDNOYhlTD4NVEwE45nbBoojYiTulajI4c2XH8UmVEVJTOJKxA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.0.6': - resolution: {integrity: sha512-QtsUfLkEAeWAC3Owx9Kg+7JdzE+k9drPhwTAXbXugYB9RZUnEWWx5x3q/au6TvUYcL+n0RBqDEO2gucZRvRFgQ==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.0.6': - resolution: {integrity: sha512-QthvJqIji2KlGNwLcK/PPYo7w1Wsi/8NK0wAtRGbv4eOPdZHkQ9KUk+oCoP20oPO7i2a6X1aBAFQEL7i08nNMA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-win32-arm64-msvc@4.0.6': - resolution: {integrity: sha512-+oka+dYX8jy9iP00DJ9Y100XsqvbqR5s0yfMZJuPR1H/lDVtDfsZiSix1UFBQ3X1HWxoEEl6iXNJHWd56TocVw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.0.6': - resolution: {integrity: sha512-+o+juAkik4p8Ue/0LiflQXPmVatl6Av3LEZXpBTfg4qkMIbZdhCGWFzHdt2NjoMiLOJCFDddoV6GYaimvK1Olw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.0.6': - resolution: {integrity: sha512-lVyKV2y58UE9CeKVcYykULe9QaE1dtKdxDEdrTPIdbzRgBk6bdxHNAoDqvcqXbIGXubn3VOl1O/CFF77v/EqSA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + engines: {node: '>= 20'} - '@tailwindcss/vite@4.0.6': - resolution: {integrity: sha512-O25vZ/URWbZ2JHdk2o8wH7jOKqEGCsYmX3GwGmYS5DjE4X3mpf93a72Rn7VRnefldNauBzr5z2hfZptmBNtTUQ==} + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} peerDependencies: - vite: ^5.2.0 || ^6 + vite: ^5.2.0 || ^6 || ^7 || ^8 - '@tanstack/query-core@5.66.0': - resolution: {integrity: sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw==} + '@tanstack/query-core@5.100.8': + resolution: {integrity: sha512-ceYwSFOqjPwET5TA6IOYxzxlGc0ekyH/gfOtWkP0PX43rzX9bxW48Iuw8KAduKCToi4rJAQ6nRy2kAe8gszdmg==} - '@tanstack/react-query@5.66.0': - resolution: {integrity: sha512-z3sYixFQJe8hndFnXgWu7C79ctL+pI0KAelYyW+khaNJ1m22lWrhJU2QrsTcRKMuVPtoZvfBYrTStIdKo+x0Xw==} + '@tanstack/react-query@5.100.8': + resolution: {integrity: sha512-iNNEekixXU5vtAGKKZX2lx3jTooG5yNY+kv0wSgEdEYG0Mj0JM5bcuQtC35ZAP3nDopT6jciUK3xeX65U7AnfA==} peerDependencies: react: ^18 || ^19 @@ -994,6 +1026,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -1035,51 +1070,63 @@ packages: '@types/ws@8.5.14': resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} - '@typescript-eslint/eslint-plugin@8.24.0': - resolution: {integrity: sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==} + '@typescript-eslint/eslint-plugin@8.59.1': + resolution: {integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.59.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.59.1': + resolution: {integrity: sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.24.0': - resolution: {integrity: sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==} + '@typescript-eslint/project-service@8.59.1': + resolution: {integrity: sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.24.0': - resolution: {integrity: sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==} + '@typescript-eslint/scope-manager@8.59.1': + resolution: {integrity: sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.24.0': - resolution: {integrity: sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==} + '@typescript-eslint/tsconfig-utils@8.59.1': + resolution: {integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.24.0': - resolution: {integrity: sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==} + '@typescript-eslint/type-utils@8.59.1': + resolution: {integrity: sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==} + 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.1.0' + + '@typescript-eslint/types@8.59.1': + resolution: {integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.24.0': - resolution: {integrity: sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==} + '@typescript-eslint/typescript-estree@8.59.1': + resolution: {integrity: sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.24.0': - resolution: {integrity: sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==} + '@typescript-eslint/utils@8.59.1': + resolution: {integrity: sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.24.0': - resolution: {integrity: sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==} + '@typescript-eslint/visitor-keys@8.59.1': + resolution: {integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-react@4.3.4': @@ -1119,6 +1166,7 @@ packages: '@walletconnect/ethereum-provider@2.17.0': resolution: {integrity: sha512-b+KTAXOb6JjoxkwpgYQQKPUcTwENGmdEdZoIDLeRicUmZTn/IQKfkMoC2frClB4YxkyoVMtj1oMV2JAax+yu9A==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/events@1.0.1': resolution: {integrity: sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==} @@ -1160,6 +1208,7 @@ packages: '@walletconnect/modal@2.7.0': resolution: {integrity: sha512-RQVt58oJ+rwqnPcIvRFeMGKuXb9qkgSmwz4noF8JZGUym3gUAzVs+uW2NQ1Owm9XOJAV+sANrtJ+VoVq1ftElw==} + deprecated: Please follow the migration guide on https://docs.reown.com/appkit/upgrade/wcm '@walletconnect/relay-api@1.0.11': resolution: {integrity: sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==} @@ -1172,6 +1221,7 @@ packages: '@walletconnect/sign-client@2.17.0': resolution: {integrity: sha512-sErYwvSSHQolNXni47L3Bm10ptJc1s1YoJvJd34s5E9h9+d3rj7PrhbiW9X82deN+Dm5oA8X9tC4xty1yIBrVg==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/time@1.0.2': resolution: {integrity: sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==} @@ -1181,6 +1231,7 @@ packages: '@walletconnect/universal-provider@2.17.0': resolution: {integrity: sha512-d3V5Be7AqLrvzcdMZSBS8DmGDRdqnyLk1DWmRKAGgR6ieUWykhhUKlvfeoZtvJrIXrY7rUGYpH1X41UtFkW5Pw==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/utils@2.17.0': resolution: {integrity: sha512-1aeQvjwsXy4Yh9G6g2eGmXrEl+BzkNjHRdCrGdMYqFTFa8ROEJfTGsSH3pLsNDlOY94CoBUvJvM55q/PMoN/FQ==} @@ -1191,11 +1242,22 @@ packages: '@walletconnect/window-metadata@1.0.1': resolution: {integrity: sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==} - abitype@1.0.8: - resolution: {integrity: sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==} + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + abitype@1.2.4: + resolution: {integrity: sha512-dpKH+N27vRjarMVTFFkeY445VTKftzGWpL0FiT7xmVmzQRKazZexzC5uHG0f6XKsVLAuUlndnbGau6lRejClxg==} peerDependencies: typescript: '>=5.0.4' - zod: ^3 >=3.22.0 + zod: ^3.22.0 || ^4.0.0 peerDependenciesMeta: typescript: optional: true @@ -1267,6 +1329,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} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1274,11 +1340,11 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bn.js@4.12.1: - resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} + bn.js@4.12.3: + resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} - bn.js@5.2.1: - resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + bn.js@5.2.3: + resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} @@ -1286,8 +1352,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + 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==} @@ -1312,6 +1379,10 @@ packages: resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.8: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} @@ -1320,6 +1391,10 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1464,6 +1539,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'} @@ -1508,15 +1592,14 @@ packages: detect-browser@5.3.0: resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} - detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -1652,8 +1735,8 @@ packages: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - enhanced-resolve@5.18.1: - resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + enhanced-resolve@5.21.0: + resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} engines: {node: '>=10.13.0'} environment@1.1.0: @@ -1698,10 +1781,10 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react-refresh@0.4.19: - resolution: {integrity: sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==} + eslint-plugin-react-refresh@0.5.2: + resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} peerDependencies: - eslint: '>=8.40' + eslint: ^9 || ^10 eslint-scope@8.2.0: resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} @@ -1715,6 +1798,10 @@ packages: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.20.0: resolution: {integrity: sha512-aL4F8167Hg4IvsW89ejnpTwx+B/UQRzJPGgbIOl+4XqffWsahVVsLEWoZvnrVuwpWmnRd7XeXmQI1zlKcFDteA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1768,6 +1855,9 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -1779,10 +1869,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -1796,8 +1882,14 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fastq@1.19.0: - resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} @@ -1883,6 +1975,10 @@ packages: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1918,9 +2014,6 @@ packages: resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==} engines: {node: '>=10'} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - h3@1.15.0: resolution: {integrity: sha512-OsjX4JW8J4XGgCgEcad20pepFQWnuKH+OwkCJjogF3C+9AZ1iYdtB4hX6vAb5DskBiu5ljEXqApINjR8CqoCMQ==} @@ -1946,6 +2039,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + hey-listen@1.0.8: resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} @@ -1970,6 +2067,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + immer@10.1.1: resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} @@ -2114,16 +2215,19 @@ packages: isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isows@1.0.6: - resolution: {integrity: sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==} + isows@1.0.7: + resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} peerDependencies: ws: '*' - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true js-base64@3.7.7: @@ -2182,70 +2286,77 @@ packages: libsql@0.4.7: resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==} + cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] - lightningcss-darwin-arm64@1.29.1: - resolution: {integrity: sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.29.1: - resolution: {integrity: sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==} + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.29.1: - resolution: {integrity: sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.29.1: - resolution: {integrity: sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==} + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.29.1: - resolution: {integrity: sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==} + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.29.1: - resolution: {integrity: sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==} + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.29.1: - resolution: {integrity: sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==} + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.29.1: - resolution: {integrity: sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==} + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.29.1: - resolution: {integrity: sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==} + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.29.1: - resolution: {integrity: sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.29.1: - resolution: {integrity: sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} lit-element@3.3.3: @@ -2267,6 +2378,7 @@ packages: lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -2286,21 +2398,16 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - micro-ftch@0.3.1: resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -2319,13 +2426,13 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - mipd@0.0.7: resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==} peerDependencies: @@ -2337,8 +2444,8 @@ packages: motion@10.16.2: resolution: {integrity: sha512-p+PurYqfUdcJZvtnmAqu5fJgV2kR0uLFQuBKtLeFVTrYEVllI99tiOTSefVNYuip9ELTEkepIIDftNdze76NAQ==} - mprocs@0.7.2: - resolution: {integrity: sha512-EtHuUzkNMmw0ltswa4vHfulbl34l2e5RLgB59Kd9A1WKYDAZ/zmQbjk1q5lTFWNlsYh9rHTylM4u94Ho+NglIw==} + mprocs@0.9.2: + resolution: {integrity: sha512-FSHarRnBDw1w3deGWkIR1ZuU6HX2XAZdGDPLv7gcvNyTHjQkOaRjqXcJjtwdhyxbUygCQ8CBaZzwLyGzvLw2qw==} engines: {node: '>=0.10.0'} hasBin: true @@ -2362,6 +2469,7 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-fetch-native@1.6.6: resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} @@ -2424,8 +2532,8 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ox@0.6.7: - resolution: {integrity: sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==} + ox@0.14.20: + resolution: {integrity: sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw==} peerDependencies: typescript: '>=5.4.0' peerDependenciesMeta: @@ -2483,10 +2591,14 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} @@ -2528,12 +2640,15 @@ packages: preact@10.25.4: resolution: {integrity: sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==} + preact@10.29.1: + resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.5.0: - resolution: {integrity: sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==} + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} engines: {node: '>=14'} hasBin: true @@ -2568,9 +2683,6 @@ packages: resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} engines: {node: '>=6'} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -2640,18 +2752,11 @@ packages: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.34.6: resolution: {integrity: sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -2673,8 +2778,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -2685,8 +2790,9 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} hasBin: true shebang-command@2.0.0: @@ -2805,19 +2911,19 @@ packages: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} engines: {node: '>=8'} - tailwind-merge@3.0.1: - resolution: {integrity: sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==} + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@4.0.6: - resolution: {integrity: sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==} + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} terminal-link@3.0.0: @@ -2830,9 +2936,17 @@ packages: tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinygradient@1.1.5: resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==} + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2840,8 +2954,8 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - ts-api-utils@2.0.1: - resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -2864,12 +2978,16 @@ packages: resolution: {integrity: sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==} engines: {node: '>=16'} - typescript-eslint@8.24.0: - resolution: {integrity: sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ==} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.59.1: + resolution: {integrity: sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' typescript@5.7.3: resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} @@ -2982,10 +3100,12 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true validate-npm-package-license@3.0.4: @@ -3003,8 +3123,8 @@ packages: react: optional: true - viem@2.23.1: - resolution: {integrity: sha512-c5AyJCTA5LeNI/KCu++vkbqbh7irYjUSHxLIAHPKJ6IEcBNMt8+7sPG7gjMXpqVWnqPMzaW9CA2n+yUsKWttDA==} + viem@2.48.8: + resolution: {integrity: sha512-Xj3Nrt66SKtn06kczU91ELn9Difr84ZM5A62BTlaisT5lpgt058i2mBkfMZCXHGb1ocOLjzC2ztPhD0Lvky7uQ==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: @@ -3140,8 +3260,20 @@ packages: utf-8-validate: optional: true - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3228,7 +3360,7 @@ packages: snapshots: - '@adraffy/ens-normalize@1.11.0': {} + '@adraffy/ens-normalize@1.11.1': {} '@alcalzone/ansi-tokenize@0.1.3': dependencies: @@ -3357,28 +3489,28 @@ snapshots: '@coinbase/wallet-sdk@3.9.3': dependencies: - bn.js: 5.2.1 + bn.js: 5.2.3 buffer: 6.0.3 clsx: 1.2.1 eth-block-tracker: 7.1.0 eth-json-rpc-filters: 6.0.1 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 keccak: 3.0.4 - preact: 10.25.4 - sha.js: 2.4.11 + preact: 10.29.1 + sha.js: 2.4.12 transitivePeerDependencies: - supports-color '@coinbase/wallet-sdk@4.3.0': dependencies: - '@noble/hashes': 1.7.1 + '@noble/hashes': 1.8.0 clsx: 1.2.1 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 preact: 10.25.4 - '@ecies/ciphers@0.2.2(@noble/ciphers@1.2.1)': + '@ecies/ciphers@0.2.2(@noble/ciphers@1.3.0)': dependencies: - '@noble/ciphers': 1.2.1 + '@noble/ciphers': 1.3.0 '@esbuild/aix-ppc64@0.24.2': optional: true @@ -3455,13 +3587,20 @@ snapshots: '@esbuild/win32-x64@0.24.2': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.20.0(jiti@2.4.2))': + '@eslint-community/eslint-utils@4.4.1(eslint@9.20.0(jiti@2.6.1))': dependencies: - eslint: 9.20.0(jiti@2.4.2) + eslint: 9.20.0(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.1(eslint@9.20.0(jiti@2.6.1))': + dependencies: + eslint: 9.20.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} + '@eslint/config-array@0.19.2': dependencies: '@eslint/object-schema': 2.1.6 @@ -3501,16 +3640,16 @@ snapshots: '@eslint/core': 0.10.0 levn: 0.4.1 - '@eth-optimism/super-cli@0.0.13(@tanstack/query-core@5.66.0)(@types/react@18.3.18)(bufferutil@4.0.9)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(utf-8-validate@5.0.10)(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1))': + '@eth-optimism/super-cli@0.0.13(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(bufferutil@4.0.9)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(utf-8-validate@5.0.10)(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0))': dependencies: - '@eth-optimism/viem': 0.3.2(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) + '@eth-optimism/viem': 0.3.3(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) '@hono/node-server': 1.13.8(hono@4.7.0) '@inkjs/ui': 2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10)) '@libsql/client': 0.14.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@tanstack/react-query': 5.66.0(react@18.3.1) - '@vitejs/plugin-react': 4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)) - '@wagmi/core': 2.16.4(@tanstack/query-core@5.66.0)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) - abitype: 1.0.8(typescript@5.7.3)(zod@3.24.1) + '@tanstack/react-query': 5.100.8(react@18.3.1) + '@vitejs/plugin-react': 4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0)) + '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) + abitype: 1.2.4(typescript@5.7.3)(zod@3.24.1) chalk: 5.4.1 dependency-graph: 1.0.0 dotenv: 16.4.7 @@ -3527,8 +3666,8 @@ snapshots: pastel: 3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(zod@3.24.1) react: 18.3.1 smol-toml: 1.3.1 - viem: 2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) - wagmi: 2.14.11(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) + viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + wagmi: 2.14.11(@tanstack/query-core@5.100.8)(@tanstack/react-query@5.100.8(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) zod: 3.24.1 zod-validation-error: 3.4.0(zod@3.24.1) zustand: 5.0.3(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) @@ -3587,9 +3726,13 @@ snapshots: - utf-8-validate - vite - '@eth-optimism/viem@0.3.2(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))': + '@eth-optimism/viem@0.3.3(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))': dependencies: - viem: 2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + + '@eth-optimism/viem@0.4.15(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))': + dependencies: + viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) '@ethereumjs/common@3.2.0': dependencies: @@ -3636,22 +3779,37 @@ snapshots: figures: 6.1.0 ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 '@libsql/client@0.14.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: @@ -3689,7 +3847,7 @@ snapshots: '@libsql/isomorphic-ws@0.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@types/ws': 8.5.14 - ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -3790,7 +3948,7 @@ snapshots: bufferutil: 4.0.9 cross-fetch: 4.1.0 date-fns: 2.30.0 - debug: 4.4.0 + debug: 4.4.3 eciesjs: 0.4.13 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -3814,7 +3972,7 @@ snapshots: '@paulmillr/qr': 0.2.1 bowser: 2.11.0 cross-fetch: 4.1.0 - debug: 4.4.0 + debug: 4.4.3 eciesjs: 0.4.13 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -3836,9 +3994,9 @@ snapshots: '@metamask/utils@5.0.2': dependencies: '@ethereumjs/tx': 4.2.0 - '@types/debug': 4.1.12 - debug: 4.4.0 - semver: 7.7.1 + '@types/debug': 4.1.13 + debug: 4.4.3 + semver: 7.7.4 superstruct: 1.0.4 transitivePeerDependencies: - supports-color @@ -3847,12 +4005,12 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@metamask/superstruct': 3.1.0 - '@noble/hashes': 1.7.1 - '@scure/base': 1.2.4 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.0 + debug: 4.4.3 pony-cause: 2.1.11 - semver: 7.7.1 + semver: 7.7.4 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -3861,12 +4019,12 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@metamask/superstruct': 3.1.0 - '@noble/hashes': 1.7.1 - '@scure/base': 1.2.4 - '@types/debug': 4.1.12 - debug: 4.4.0 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@types/debug': 4.1.13 + debug: 4.4.3 pony-cause: 2.1.11 - semver: 7.7.1 + semver: 7.7.4 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -3918,61 +4076,53 @@ snapshots: '@neon-rs/load@0.0.4': {} - '@noble/ciphers@1.2.1': {} + '@noble/ciphers@1.3.0': {} '@noble/curves@1.4.2': dependencies: '@noble/hashes': 1.4.0 - '@noble/curves@1.8.1': + '@noble/curves@1.9.1': dependencies: - '@noble/hashes': 1.7.1 - - '@noble/hashes@1.4.0': {} - - '@noble/hashes@1.7.1': {} + '@noble/hashes': 1.8.0 - '@nodelib/fs.scandir@2.1.5': + '@noble/curves@1.9.7': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@noble/hashes': 1.8.0 - '@nodelib/fs.stat@2.0.5': {} + '@noble/hashes@1.4.0': {} - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.0 + '@noble/hashes@1.8.0': {} '@paulmillr/qr@0.2.1': {} - '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.18)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.18 - '@radix-ui/react-primitive@2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.18)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-separator@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-separator@1.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-slot@1.1.2(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-slot@1.2.4(@types/react@18.3.18)(react@18.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1) react: 18.3.1 optionalDependencies: '@types/react': 18.3.18 @@ -4047,7 +4197,7 @@ snapshots: '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)': dependencies: '@safe-global/safe-gateway-typescript-sdk': 3.22.9 - viem: 2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) transitivePeerDependencies: - bufferutil - typescript @@ -4058,7 +4208,7 @@ snapshots: '@scure/base@1.1.9': {} - '@scure/base@1.2.4': {} + '@scure/base@1.2.6': {} '@scure/bip32@1.4.0': dependencies: @@ -4066,21 +4216,21 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.9 - '@scure/bip32@1.6.2': + '@scure/bip32@1.7.0': dependencies: - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 - '@scure/base': 1.2.4 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 '@scure/bip39@1.3.0': dependencies: '@noble/hashes': 1.4.0 '@scure/base': 1.1.9 - '@scure/bip39@1.5.4': + '@scure/bip39@1.6.0': dependencies: - '@noble/hashes': 1.7.1 - '@scure/base': 1.2.4 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 '@socket.io/component-emitter@3.1.2': {} @@ -4164,72 +4314,79 @@ snapshots: '@stablelib/random': 1.0.2 '@stablelib/wipe': 1.0.1 - '@tailwindcss/node@4.0.6': + '@tailwindcss/node@4.2.4': dependencies: - enhanced-resolve: 5.18.1 - jiti: 2.4.2 - tailwindcss: 4.0.6 + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.21.0 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.4 - '@tailwindcss/oxide-android-arm64@4.0.6': + '@tailwindcss/oxide-android-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-arm64@4.0.6': + '@tailwindcss/oxide-darwin-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-x64@4.0.6': + '@tailwindcss/oxide-darwin-x64@4.2.4': optional: true - '@tailwindcss/oxide-freebsd-x64@4.0.6': + '@tailwindcss/oxide-freebsd-x64@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.6': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.0.6': + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.0.6': + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.0.6': + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.0.6': + '@tailwindcss/oxide-linux-x64-musl@4.2.4': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.0.6': + '@tailwindcss/oxide-wasm32-wasi@4.2.4': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.0.6': + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': optional: true - '@tailwindcss/oxide@4.0.6': + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide@4.2.4': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.0.6 - '@tailwindcss/oxide-darwin-arm64': 4.0.6 - '@tailwindcss/oxide-darwin-x64': 4.0.6 - '@tailwindcss/oxide-freebsd-x64': 4.0.6 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.6 - '@tailwindcss/oxide-linux-arm64-gnu': 4.0.6 - '@tailwindcss/oxide-linux-arm64-musl': 4.0.6 - '@tailwindcss/oxide-linux-x64-gnu': 4.0.6 - '@tailwindcss/oxide-linux-x64-musl': 4.0.6 - '@tailwindcss/oxide-win32-arm64-msvc': 4.0.6 - '@tailwindcss/oxide-win32-x64-msvc': 4.0.6 - - '@tailwindcss/vite@4.0.6(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1))': - dependencies: - '@tailwindcss/node': 4.0.6 - '@tailwindcss/oxide': 4.0.6 - lightningcss: 1.29.1 - tailwindcss: 4.0.6 - vite: 6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1) - - '@tanstack/query-core@5.66.0': {} - - '@tanstack/react-query@5.66.0(react@18.3.1)': - dependencies: - '@tanstack/query-core': 5.66.0 + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + + '@tailwindcss/vite@4.2.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0))': + dependencies: + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0) + + '@tanstack/query-core@5.100.8': {} + + '@tanstack/react-query@5.100.8(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.100.8 react: 18.3.1 '@types/babel__core@7.20.5': @@ -4257,6 +4414,10 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/estree@1.0.6': {} '@types/gensync@1.0.4': {} @@ -4294,104 +4455,118 @@ snapshots: dependencies: '@types/node': 22.13.1 - '@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3))(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/type-utils': 8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.24.0 - eslint: 9.20.0(jiti@2.4.2) - graphemer: 1.4.0 - ignore: 5.3.2 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.59.1 + '@typescript-eslint/type-utils': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/utils': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.59.1 + eslint: 9.20.0(jiti@2.6.1) + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.0.1(typescript@5.7.3) + ts-api-utils: 2.5.0(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/parser@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3)': dependencies: - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.24.0 - debug: 4.4.0 - eslint: 9.20.0(jiti@2.4.2) + '@typescript-eslint/scope-manager': 8.59.1 + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.59.1 + debug: 4.4.3 + eslint: 9.20.0(jiti@2.6.1) typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.24.0': + '@typescript-eslint/project-service@8.59.1(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/visitor-keys': 8.24.0 + '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.7.3) + '@typescript-eslint/types': 8.59.1 + debug: 4.4.3 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color - '@typescript-eslint/type-utils@8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/scope-manager@8.59.1': + dependencies: + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/visitor-keys': 8.59.1 + + '@typescript-eslint/tsconfig-utils@8.59.1(typescript@5.7.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3) - debug: 4.4.0 - eslint: 9.20.0(jiti@2.4.2) - ts-api-utils: 2.0.1(typescript@5.7.3) + typescript: 5.7.3 + + '@typescript-eslint/type-utils@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.7.3) + '@typescript-eslint/utils': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + debug: 4.4.3 + eslint: 9.20.0(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.24.0': {} + '@typescript-eslint/types@8.59.1': {} - '@typescript-eslint/typescript-estree@8.24.0(typescript@5.7.3)': + '@typescript-eslint/typescript-estree@8.59.1(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/visitor-keys': 8.24.0 - debug: 4.4.0 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.0.1(typescript@5.7.3) + '@typescript-eslint/project-service': 8.59.1(typescript@5.7.3) + '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.7.3) + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/visitor-keys': 8.59.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/utils@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - eslint: 9.20.0(jiti@2.4.2) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.20.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.59.1 + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.7.3) + eslint: 9.20.0(jiti@2.6.1) typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.24.0': + '@typescript-eslint/visitor-keys@8.59.1': dependencies: - '@typescript-eslint/types': 8.24.0 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.59.1 + eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1))': + '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0))': dependencies: '@babel/core': 7.26.8 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.8) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.8) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1) + vite: 6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0) transitivePeerDependencies: - supports-color - '@wagmi/connectors@5.7.7(@types/react@18.3.18)(@wagmi/core@2.16.4(@tanstack/query-core@5.66.0)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)))(bufferutil@4.0.9)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1)': + '@wagmi/connectors@5.7.7(@types/react@18.3.18)(@wagmi/core@2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)))(bufferutil@4.0.9)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1)': dependencies: '@coinbase/wallet-sdk': 4.3.0 '@metamask/sdk': 0.32.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@safe-global/safe-apps-provider': 0.18.5(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) - '@wagmi/core': 2.16.4(@tanstack/query-core@5.66.0)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) + '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) '@walletconnect/ethereum-provider': 2.17.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) cbw-sdk: '@coinbase/wallet-sdk@3.9.3' - viem: 2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) optionalDependencies: typescript: 5.7.3 transitivePeerDependencies: @@ -4421,14 +4596,14 @@ snapshots: - utf-8-validate - zod - '@wagmi/core@2.16.4(@tanstack/query-core@5.66.0)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))': + '@wagmi/core@2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))': dependencies: eventemitter3: 5.0.1 mipd: 0.0.7(typescript@5.7.3) - viem: 2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) zustand: 5.0.0(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) optionalDependencies: - '@tanstack/query-core': 5.66.0 + '@tanstack/query-core': 5.100.8 typescript: 5.7.3 transitivePeerDependencies: - '@types/react' @@ -4781,7 +4956,12 @@ snapshots: '@walletconnect/window-getters': 1.0.1 tslib: 1.14.1 - abitype@1.0.8(typescript@5.7.3)(zod@3.24.1): + abitype@1.2.3(typescript@5.7.3)(zod@3.24.1): + optionalDependencies: + typescript: 5.7.3 + zod: 3.24.1 + + abitype@1.2.4(typescript@5.7.3)(zod@3.24.1): optionalDependencies: typescript: 5.7.3 zod: 3.24.1 @@ -4820,7 +5000,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 argparse@2.0.1: {} @@ -4840,13 +5020,15 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} binary-extensions@2.3.0: {} - bn.js@4.12.1: {} + bn.js@4.12.3: {} - bn.js@5.2.1: {} + bn.js@5.2.3: {} bowser@2.11.0: {} @@ -4855,9 +5037,9 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@5.0.5: dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.4 braces@3.0.3: dependencies: @@ -4886,6 +5068,11 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.1 @@ -4898,6 +5085,11 @@ snapshots: call-bind-apply-helpers: 1.0.1 get-intrinsic: 1.2.7 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camelcase@5.3.1: {} @@ -5023,6 +5215,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decamelize@6.0.0: {} @@ -5053,10 +5249,10 @@ snapshots: detect-browser@5.3.0: {} - detect-libc@1.0.3: {} - detect-libc@2.0.2: {} + detect-libc@2.1.2: {} + dijkstrajs@1.0.3: {} dotenv@16.4.7: {} @@ -5082,16 +5278,16 @@ snapshots: eciesjs@0.4.13: dependencies: - '@ecies/ciphers': 0.2.2(@noble/ciphers@1.2.1) - '@noble/ciphers': 1.2.1 - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 + '@ecies/ciphers': 0.2.2(@noble/ciphers@1.3.0) + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 electron-to-chromium@1.5.97: {} elliptic@6.6.1: dependencies: - bn.js: 4.12.1 + bn.js: 4.12.3 brorand: 1.1.0 hash.js: 1.1.7 hmac-drbg: 1.0.1 @@ -5123,10 +5319,10 @@ snapshots: engine.io-parser@5.2.3: {} - enhanced-resolve@5.18.1: + enhanced-resolve@5.21.0: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.1 + tapable: 2.3.3 environment@1.1.0: {} @@ -5174,13 +5370,13 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.1.0(eslint@9.20.0(jiti@2.4.2)): + eslint-plugin-react-hooks@5.1.0(eslint@9.20.0(jiti@2.6.1)): dependencies: - eslint: 9.20.0(jiti@2.4.2) + eslint: 9.20.0(jiti@2.6.1) - eslint-plugin-react-refresh@0.4.19(eslint@9.20.0(jiti@2.4.2)): + eslint-plugin-react-refresh@0.5.2(eslint@9.20.0(jiti@2.6.1)): dependencies: - eslint: 9.20.0(jiti@2.4.2) + eslint: 9.20.0(jiti@2.6.1) eslint-scope@8.2.0: dependencies: @@ -5191,9 +5387,11 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.20.0(jiti@2.4.2): + eslint-visitor-keys@5.0.1: {} + + eslint@9.20.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.19.2 '@eslint/core': 0.11.0 @@ -5228,7 +5426,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.4.2 + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -5288,6 +5486,8 @@ snapshots: eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} + events@3.3.0: {} extension-port-stream@3.0.0: @@ -5297,14 +5497,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -5313,9 +5505,9 @@ snapshots: fast-safe-stringify@2.1.1: {} - fastq@1.19.0: - dependencies: - reusify: 1.0.4 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 fetch-blob@3.2.0: dependencies: @@ -5395,6 +5587,19 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -5423,8 +5628,6 @@ snapshots: chalk: 4.1.2 tinygradient: 1.1.5 - graphemer@1.4.0: {} - h3@1.15.0: dependencies: cookie-es: 1.2.2 @@ -5459,6 +5662,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + hey-listen@1.0.8: {} hmac-drbg@1.0.1: @@ -5479,6 +5686,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + immer@10.1.1: {} import-fresh@3.3.1: @@ -5540,7 +5749,7 @@ snapshots: type-fest: 4.34.1 widest-line: 5.0.0 wrap-ansi: 9.0.0 - ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) yoga-wasm-web: 0.3.3 optionalDependencies: '@types/react': 18.3.18 @@ -5624,13 +5833,15 @@ snapshots: isarray@1.0.0: {} + isarray@2.0.5: {} + isexe@2.0.0: {} - isows@1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + isows@1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: - ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - jiti@2.4.2: {} + jiti@2.6.1: {} js-base64@3.7.7: {} @@ -5691,50 +5902,54 @@ snapshots: '@libsql/linux-x64-musl': 0.4.7 '@libsql/win32-x64-msvc': 0.4.7 - lightningcss-darwin-arm64@1.29.1: + lightningcss-android-arm64@1.32.0: optional: true - lightningcss-darwin-x64@1.29.1: + lightningcss-darwin-arm64@1.32.0: optional: true - lightningcss-freebsd-x64@1.29.1: + lightningcss-darwin-x64@1.32.0: optional: true - lightningcss-linux-arm-gnueabihf@1.29.1: + lightningcss-freebsd-x64@1.32.0: optional: true - lightningcss-linux-arm64-gnu@1.29.1: + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true - lightningcss-linux-arm64-musl@1.29.1: + lightningcss-linux-arm64-gnu@1.32.0: optional: true - lightningcss-linux-x64-gnu@1.29.1: + lightningcss-linux-arm64-musl@1.32.0: optional: true - lightningcss-linux-x64-musl@1.29.1: + lightningcss-linux-x64-gnu@1.32.0: optional: true - lightningcss-win32-arm64-msvc@1.29.1: + lightningcss-linux-x64-musl@1.32.0: optional: true - lightningcss-win32-x64-msvc@1.29.1: + lightningcss-win32-arm64-msvc@1.32.0: optional: true - lightningcss@1.29.1: + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: dependencies: - detect-libc: 1.0.3 + detect-libc: 2.1.2 optionalDependencies: - lightningcss-darwin-arm64: 1.29.1 - lightningcss-darwin-x64: 1.29.1 - lightningcss-freebsd-x64: 1.29.1 - lightningcss-linux-arm-gnueabihf: 1.29.1 - lightningcss-linux-arm64-gnu: 1.29.1 - lightningcss-linux-arm64-musl: 1.29.1 - lightningcss-linux-x64-gnu: 1.29.1 - lightningcss-linux-x64-musl: 1.29.1 - lightningcss-win32-arm64-msvc: 1.29.1 - lightningcss-win32-x64-msvc: 1.29.1 + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 lit-element@3.3.3: dependencies: @@ -5778,17 +5993,14 @@ snapshots: dependencies: react: 18.3.1 - math-intrinsics@1.1.0: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 - merge2@1.4.1: {} + math-intrinsics@1.1.0: {} micro-ftch@0.3.1: {} - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - mime-db@1.52.0: {} mime-types@2.1.35: @@ -5801,13 +6013,13 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} - minimatch@3.1.2: + minimatch@10.2.5: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 5.0.5 - minimatch@9.0.5: + minimatch@3.1.2: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 1.1.11 mipd@0.0.7(typescript@5.7.3): optionalDependencies: @@ -5822,7 +6034,7 @@ snapshots: '@motionone/utils': 10.18.0 '@motionone/vue': 10.16.4 - mprocs@0.7.2: {} + mprocs@0.9.2: {} ms@2.1.3: {} @@ -5857,7 +6069,7 @@ snapshots: normalize-package-data@6.0.2: dependencies: hosted-git-info: 7.0.2 - semver: 7.7.1 + semver: 7.7.4 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -5897,14 +6109,15 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ox@0.6.7(typescript@5.7.3)(zod@3.24.1): + ox@0.14.20(typescript@5.7.3)(zod@3.24.1): dependencies: - '@adraffy/ens-normalize': 1.11.0 - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 - '@scure/bip32': 1.6.2 - '@scure/bip39': 1.5.4 - abitype: 1.0.8(typescript@5.7.3)(zod@3.24.1) + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.4(typescript@5.7.3)(zod@3.24.1) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.7.3 @@ -5959,7 +6172,9 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} + + picomatch@4.0.4: {} pify@3.0.0: {} @@ -6004,9 +6219,11 @@ snapshots: preact@10.25.4: {} + preact@10.29.1: {} + prelude-ls@1.2.1: {} - prettier@3.5.0: {} + prettier@3.8.3: {} process-nextick-args@2.0.1: {} @@ -6043,8 +6260,6 @@ snapshots: split-on-first: 1.1.0 strict-uri-encode: 2.0.0 - queue-microtask@1.2.3: {} - quick-format-unescaped@4.0.4: {} radix3@1.1.2: {} @@ -6101,7 +6316,7 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.1 + picomatch: 2.3.2 real-require@0.1.0: {} @@ -6118,8 +6333,6 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - reusify@1.0.4: {} - rollup@4.34.6: dependencies: '@types/estree': 1.0.6 @@ -6145,10 +6358,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.34.6 fsevents: 2.3.3 - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -6167,7 +6376,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.1: {} + semver@7.7.4: {} set-blocking@2.0.0: {} @@ -6180,10 +6389,11 @@ snapshots: gopd: 1.2.0 has-property-descriptors: 1.0.2 - sha.js@2.4.11: + sha.js@2.4.12: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + to-buffer: 1.2.2 shebang-command@2.0.0: dependencies: @@ -6306,15 +6516,15 @@ snapshots: has-flag: 4.0.0 supports-color: 7.2.0 - tailwind-merge@3.0.1: {} + tailwind-merge@3.5.0: {} - tailwindcss-animate@1.0.7(tailwindcss@4.0.6): + tailwindcss-animate@1.0.7(tailwindcss@4.2.4): dependencies: - tailwindcss: 4.0.6 + tailwindcss: 4.2.4 - tailwindcss@4.0.6: {} + tailwindcss@4.2.4: {} - tapable@2.2.1: {} + tapable@2.3.3: {} terminal-link@3.0.0: dependencies: @@ -6327,18 +6537,29 @@ snapshots: tinycolor2@1.6.0: {} + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinygradient@1.1.5: dependencies: '@types/tinycolor2': 1.4.6 tinycolor2: 1.6.0 + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 tr46@0.0.3: {} - ts-api-utils@2.0.1(typescript@5.7.3): + ts-api-utils@2.5.0(typescript@5.7.3): dependencies: typescript: 5.7.3 @@ -6354,12 +6575,19 @@ snapshots: type-fest@4.34.1: {} - typescript-eslint@8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3): + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typescript-eslint@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/parser': 8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3) - eslint: 9.20.0(jiti@2.4.2) + '@typescript-eslint/eslint-plugin': 8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3))(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/parser': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.7.3) + '@typescript-eslint/utils': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + eslint: 9.20.0(jiti@2.6.1) typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -6440,16 +6668,16 @@ snapshots: '@types/react': 18.3.18 react: 18.3.1 - viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1): + viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1): dependencies: - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 - '@scure/bip32': 1.6.2 - '@scure/bip39': 1.5.4 - abitype: 1.0.8(typescript@5.7.3)(zod@3.24.1) - isows: 1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.6.7(typescript@5.7.3)(zod@3.24.1) - ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@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@5.7.3)(zod@3.24.1) + isows: 1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + ox: 0.14.20(typescript@5.7.3)(zod@3.24.1) + ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: typescript: 5.7.3 transitivePeerDependencies: @@ -6457,7 +6685,7 @@ snapshots: - utf-8-validate - zod - vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1): + vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0): dependencies: esbuild: 0.24.2 postcss: 8.5.1 @@ -6465,17 +6693,17 @@ snapshots: optionalDependencies: '@types/node': 22.13.1 fsevents: 2.3.3 - jiti: 2.4.2 - lightningcss: 1.29.1 + jiti: 2.6.1 + lightningcss: 1.32.0 - wagmi@2.14.11(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1): + wagmi@2.14.11(@tanstack/query-core@5.100.8)(@tanstack/react-query@5.100.8(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1): dependencies: - '@tanstack/react-query': 5.66.0(react@18.3.1) - '@wagmi/connectors': 5.7.7(@types/react@18.3.18)(@wagmi/core@2.16.4(@tanstack/query-core@5.66.0)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)))(bufferutil@4.0.9)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) - '@wagmi/core': 2.16.4(@tanstack/query-core@5.66.0)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) + '@tanstack/react-query': 5.100.8(react@18.3.1) + '@wagmi/connectors': 5.7.7(@types/react@18.3.18)(@wagmi/core@2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)))(bufferutil@4.0.9)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) + '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) react: 18.3.1 use-sync-external-store: 1.4.0(react@18.3.1) - viem: 2.23.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) optionalDependencies: typescript: 5.7.3 transitivePeerDependencies: @@ -6575,7 +6803,12 @@ snapshots: bufferutil: 4.0.9 utf-8-validate: 5.0.10 - ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10): + ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.0.9 + utf-8-validate: 5.0.10 + + ws@8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10): optionalDependencies: bufferutil: 4.0.9 utf-8-validate: 5.0.10 diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 1d23e35..8d7f5ab 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -52,4 +52,4 @@ function Button({ ); } -export { Button, buttonVariants }; +export { Button }; From 18ca92b144cc428c9f6dcb7352e087fd47b72fd4 Mon Sep 17 00:00:00 2001 From: Iko Date: Mon, 4 May 2026 01:47:40 +0700 Subject: [PATCH 27/38] ci(security): add codeql and dependency review gates --- .github/dependabot.yml | 10 ++++-- .github/workflows/codeql.yml | 47 +++++++++++++++++++++++++ .github/workflows/dependency-review.yml | 29 +++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/dependency-review.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ba50c1b..8bf9291 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,12 +2,13 @@ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" + target-branch: "dev" schedule: interval: "weekly" day: "monday" time: "03:00" timezone: "UTC" - open-pull-requests-limit: 10 + open-pull-requests-limit: 5 labels: - "dependencies" - "ci" @@ -16,15 +17,20 @@ updates: - package-ecosystem: "npm" directory: "/" + target-branch: "dev" schedule: interval: "weekly" day: "monday" time: "03:30" timezone: "UTC" - open-pull-requests-limit: 10 + open-pull-requests-limit: 8 labels: - "dependencies" - "frontend" + - "security" + allow: + - dependency-type: "direct" + - dependency-type: "indirect" groups: frontend-minor-patch: update-types: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..41af7c2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,47 @@ +name: CodeQL + +on: + push: + branches: + - dev + - canary + - main + pull_request: + branches: + - dev + - canary + - main + schedule: + - cron: '22 3 * * 1' + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: Analyze (JavaScript/TypeScript) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: + - javascript-typescript + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: security-extended + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..4455621 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,29 @@ +name: Dependency Review + +on: + pull_request: + branches: + - dev + - canary + - main + +permissions: + contents: read + +jobs: + dependency-review: + name: Dependency Review + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Review dependency changes + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: high + warn-only: false + comment-summary-in-pr: always From c0b74f64865488e7bea2787b3890f2f7ae6ff448 Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Sat, 9 May 2026 07:59:08 +0700 Subject: [PATCH 28/38] chore: promote dev to canary 56 commits: EIP-712 verifier, bridge tests, CI fixes, governance cleanup, trust model doc, RYLA Credits rename. --- .coderabbit.yaml | 4 +- .github/CODEOWNERS | 53 +- .github/PRODUCTION_GOVERNANCE_CHECKLIST.md | 62 +- .github/PULL_REQUEST_TEMPLATE/release.md | 16 + .github/actions/setup-foundry/action.yml | 16 + .github/actions/setup-node-pnpm/action.yml | 28 + .github/dependabot.yml | 9 + .../workflows/_reusable-contracts-slither.yml | 51 + .../workflows/_reusable-frontend-checks.yml | 48 + .github/workflows/_reusable-secrets-scan.yml | 35 + .github/workflows/codeql.yml | 48 +- .github/workflows/contracts-ci.yml | 21 +- .../workflows/contracts-evidence-manifest.yml | 1 + .../workflows/contracts-mainnet-readiness.yml | 2 +- .../contracts-release-gate-container.yml | 47 + .github/workflows/contracts-slither.yml | 45 +- .github/workflows/frontend-ci.yml | 55 +- .github/workflows/governance-verify.yml | 25 + .../workflows/release-evidence-validator.yml | 2 + .github/workflows/release-pr-checklist.yml | 2 + .github/workflows/scripts-ci.yml | 31 + .github/workflows/secrets-drift-guard.yml | 3 +- .github/workflows/secrets-scan.yml | 17 + .gitignore | 8 + BRANCHING.md | 70 +- CONTRIBUTING.md | 461 ++++ DEPLOYMENT.md | 761 ++++++ SECURITY.md | 31 + TROUBLESHOOTING.md | 980 ++++++++ contracts/.gitignore | 3 + contracts/Makefile | 28 +- contracts/README.md | 15 +- contracts/RUNBOOK.md | 95 + contracts/STAGING_GO_NO_GO_CHECKLIST.md | 66 + contracts/docker/release-gate.Dockerfile | 19 + contracts/foundry.toml | 5 +- .../script/ci/run-release-gate-container.sh | 41 + .../ops/settlement/VerifyMARKDeployment.s.sol | 2 +- contracts/script/ops/smoke-production-mode.sh | 2 +- contracts/src/bridge/MARKBridgeAdapter.sol | 9 +- contracts/src/errors/BridgeErrors.sol | 1 + contracts/src/interfaces/IRYLA.sol | 11 + .../src/settlement/MARKSettlementModule.sol | 11 +- .../verifier/AttestedSettlementVerifier.sol | 60 +- contracts/src/token/RYLA.sol | 2 +- .../e2e/settlement/MARKSettlementE2E.t.sol | 32 +- .../bridge/MARKBridgeIntegration.t.sol | 93 + .../bridge/MARKBridgeInvariants.t.sol | 124 + contracts/test/unit/RYLA.t.sol | 35 +- .../test/unit/bridge/MARKBridgeAdapter.t.sol | 43 + .../AttestedSettlementVerifier.t.sol | 59 +- .../settlement/MARKSettlementModule.t.sol | 25 + package.json | 23 +- pnpm-lock.yaml | 2180 ++++++++--------- scripts/github/apply-governance.sh | 33 +- scripts/github/posttransfer-bootstrap.sh | 27 + scripts/github/pretransfer-readiness.sh | 69 + scripts/github/verify-governance.sh | 128 + tsconfig.json | 1 + 59 files changed, 4774 insertions(+), 1400 deletions(-) create mode 100644 .github/actions/setup-foundry/action.yml create mode 100644 .github/actions/setup-node-pnpm/action.yml create mode 100644 .github/workflows/_reusable-contracts-slither.yml create mode 100644 .github/workflows/_reusable-frontend-checks.yml create mode 100644 .github/workflows/_reusable-secrets-scan.yml create mode 100644 .github/workflows/contracts-release-gate-container.yml create mode 100644 .github/workflows/governance-verify.yml create mode 100644 .github/workflows/scripts-ci.yml create mode 100644 .github/workflows/secrets-scan.yml create mode 100644 CONTRIBUTING.md create mode 100644 DEPLOYMENT.md create mode 100644 SECURITY.md create mode 100644 TROUBLESHOOTING.md create mode 100644 contracts/STAGING_GO_NO_GO_CHECKLIST.md create mode 100644 contracts/docker/release-gate.Dockerfile create mode 100755 contracts/script/ci/run-release-gate-container.sh create mode 100644 contracts/src/interfaces/IRYLA.sol create mode 100644 contracts/test/integration/bridge/MARKBridgeIntegration.t.sol create mode 100644 contracts/test/invariant/bridge/MARKBridgeInvariants.t.sol create mode 100755 scripts/github/posttransfer-bootstrap.sh create mode 100755 scripts/github/pretransfer-readiness.sh create mode 100755 scripts/github/verify-governance.sh diff --git a/.coderabbit.yaml b/.coderabbit.yaml index ca31c7a..4476a62 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -3,8 +3,8 @@ language: "en-US" early_access: false reviews: - profile: "chill" - request_changes_workflow: false + profile: "assertive" + request_changes_workflow: true high_level_summary: true review_status: true collapse_walkthrough: false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 234ec4e..dd447ac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,14 +1,45 @@ -# Global default owner -* @iap +# Format: @owner +# Paths are matched top-to-bottom; first match wins. -# Protocol-critical scope -/contracts/src/** @iap -/contracts/script/** @iap -/contracts/test/** @iap -/contracts/RUNBOOK.md @iap -/contracts/README.md @iap +* @trade/maintainers + +# Contracts + +/contracts/src/** @trade/maintainers +/contracts/test/** @trade/maintainers +/contracts/script/** @trade/maintainers +/contracts/foundry.toml @trade/maintainers +/contracts/Makefile @trade/maintainers +/contracts/RUNBOOK.md @trade/maintainers +/contracts/README.md @trade/maintainers # CI and governance -/.github/workflows/** @iap -/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md @iap -/BRANCHING.md @iap + +/.github/workflows/** @trade/maintainers +/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md @trade/maintainers +/.github/CODEOWNERS @trade/maintainers +/BRANCHING.md @trade/maintainers +/CONTRIBUTING.md @trade/maintainers + +# Frontend + +/src/** @trade/maintainers +/index.html @trade/maintainers + +# Configuration and build + +/package.json @trade/maintainers +/pnpm-lock.yaml @trade/maintainers +/tsconfig*.json @trade/maintainers +/vite.config.ts @trade/maintainers +/eslint.config.js @trade/maintainers +/.prettierrc.json @trade/maintainers +/mprocs.yaml @trade/maintainers +/remappings.txt @trade/maintainers + +# Documentation + +/README.md @trade/maintainers +/DEPLOYMENT.md @trade/maintainers +/TROUBLESHOOTING.md @trade/maintainers +/LICENSE @trade/maintainers diff --git a/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md b/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md index fbbd5c3..d0a0820 100644 --- a/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md +++ b/.github/PRODUCTION_GOVERNANCE_CHECKLIST.md @@ -12,12 +12,24 @@ GitHub path: `Settings -> Branches -> Add branch protection rule` - Enable `Dismiss stale pull request approvals when new commits are pushed` - Enable `Require status checks to pass before merging` - Add required checks: + - `Analyze (javascript-typescript)` + - `gitleaks / Gitleaks Scan` + - `Detect Secrets Drift` + - `Release Gate Container` + - `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` - - `Slither Core Contracts` - - `Secrets Drift Guard` + - `Contracts Production Mode Smoke` + - `slither-core / Slither Core Contracts` + - `frontend-checks / Frontend Checks (Node 20)` + - `frontend-checks / Frontend Checks (Node 22)` - `Validate Release PR Checklist` - `Validate Release Evidence` +- Optional additional checks (recommended but not globally required): + - `Contracts Unit + Invariant` + - `Contracts Env Guard` + - `Contracts Evidence Manifest` + - `Governance Policy Guard` - Governance policy PR rule: - If PR changes `scripts/github/apply-governance.sh`, `BRANCHING.md`, or this checklist, ensure `Validate Governance Policy Consistency` passes before merge. - Enable `Require branches to be up to date before merging` @@ -34,10 +46,21 @@ GitHub path: `Settings -> Branches -> Add branch protection rule` - Enable `Dismiss stale pull request approvals when new commits are pushed` - Enable `Require status checks to pass before merging` - Add required checks: + - `Analyze (javascript-typescript)` + - `gitleaks / Gitleaks Scan` + - `Detect Secrets Drift` + - `Release Gate Container` + - `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` - - `Slither Core Contracts` - - `Secrets Drift Guard` + - `Contracts Production Mode Smoke` + - `slither-core / Slither Core Contracts` + - `frontend-checks / Frontend Checks (Node 20)` + - `frontend-checks / Frontend Checks (Node 22)` +- Optional additional checks (recommended but not globally required): + - `Contracts Unit + Invariant` + - `Contracts Env Guard` + - `Governance Policy Guard` - Governance policy PR rule: - If PR changes `scripts/github/apply-governance.sh`, `BRANCHING.md`, or this checklist, ensure `Validate Governance Policy Consistency` passes before merge. - Enable `Require branches to be up to date before merging` @@ -50,10 +73,21 @@ GitHub path: `Settings -> Branches -> Add branch protection rule` - Enable `Require a pull request before merging` - Enable `Require status checks to pass before merging` - Add required checks: + - `Analyze (javascript-typescript)` + - `gitleaks / Gitleaks Scan` + - `Detect Secrets Drift` + - `Release Gate Container` + - `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` - - `Slither Core Contracts` - - `Secrets Drift Guard` + - `Contracts Production Mode Smoke` + - `slither-core / Slither Core Contracts` + - `frontend-checks / Frontend Checks (Node 20)` + - `frontend-checks / Frontend Checks (Node 22)` +- Optional additional checks (recommended but not globally required): + - `Contracts Unit + Invariant` + - `Contracts Env Guard` + - `Governance Policy Guard` - Governance policy PR rule: - If PR changes `scripts/github/apply-governance.sh`, `BRANCHING.md`, or this checklist, ensure `Validate Governance Policy Consistency` passes before merge. - Choose one model: @@ -111,7 +145,7 @@ You can apply most settings via script: cd /path/to/mark export GH_PAT= # optional: -# export GH_REPO=iap/mark +# export GH_REPO=trade/mark # export MAIN_REVIEW_COUNT=2 # export DEV_REVIEW_COUNT=1 # export MAIN_PUSH_ALLOW_USERS=iap @@ -129,3 +163,17 @@ What this script applies: - `production` environment creation - optional production required reviewers by user ID - optional direct-push restrictions via `*_PUSH_ALLOW_*` allowlists + + +## 9) Verify active protections after transfer + +Run the verification script with a repo-admin token: + +```bash +cd /path/to/mark +export GH_PAT= +# optional: export GH_REPO=your-org/mark +./scripts/github/verify-governance.sh +``` + +Expected output: all three branches (`dev`, `canary`, `main`) report `PASS` and required checks include CodeQL (`Analyze (javascript-typescript)`), `gitleaks / Gitleaks Scan`, and `Dependency Review`. diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md index d5b343c..5f73e15 100644 --- a/.github/PULL_REQUEST_TEMPLATE/release.md +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -12,7 +12,13 @@ Use this template only for production candidate merges. - [ ] `Contracts Unit + Invariant` CI passed - [ ] `Contracts Release Check (Dry-Run + Execute Smoke)` CI passed +- [ ] `Contracts Production Mode Smoke` CI passed - [ ] `Slither Core Contracts` CI passed +- [ ] `Analyze (javascript-typescript)` CI passed +- [ ] `Gitleaks Scan` CI passed +- [ ] `Dependency Review` CI passed +- [ ] `Frontend Checks (Node 20)` CI passed +- [ ] `Frontend Checks (Node 22)` CI passed - [ ] `Contracts Mainnet Readiness` run from `main` branch - [ ] Readiness artifact uploaded and reviewed - [ ] Verify output reviewed (role/config expectations) @@ -27,6 +33,16 @@ Evidence links/values: - [ ] Security reviewer approval - [ ] Deployment operator approval +## Staging Go/No-Go (Pre-Mainnet) + +Reference: `contracts/STAGING_GO_NO_GO_CHECKLIST.md` + +- [ ] Staging rehearsal workflow succeeded (`contracts-staging-rehearsal.yml`) +- [ ] Production-lock verify succeeded (`contracts-production-lock-verify.yml`) +- [ ] Staging evidence artifacts reviewed (`mark-staging-release`, `mark-staging-rehearsal`, `mark-production-lock-verify`) +- [ ] Freshness and lineage policy passed (`contracts-promotion-checklist.yml`) +- [ ] Final Go/No-Go decision documented with links + ## Deployment Inputs - RPC target: diff --git a/.github/actions/setup-foundry/action.yml b/.github/actions/setup-foundry/action.yml new file mode 100644 index 0000000..06a5531 --- /dev/null +++ b/.github/actions/setup-foundry/action.yml @@ -0,0 +1,16 @@ +name: Setup Foundry + +description: Install pinned Foundry toolchain used by repository workflows. + +inputs: + foundry-version: + description: Foundry version (for example 1.5.0 or nightly-) + required: true + +runs: + using: composite + steps: + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: ${{ inputs.foundry-version }} diff --git a/.github/actions/setup-node-pnpm/action.yml b/.github/actions/setup-node-pnpm/action.yml new file mode 100644 index 0000000..48a041f --- /dev/null +++ b/.github/actions/setup-node-pnpm/action.yml @@ -0,0 +1,28 @@ +name: Setup Node + pnpm + +description: Setup Node.js and activate a pinned pnpm version via corepack. + +inputs: + node-version: + description: Node.js version + required: true + pnpm-version: + description: pnpm version to activate via corepack + required: false + default: "9.0.2" + +runs: + using: composite + steps: + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: ${{ inputs.node-version }} + package-manager-cache: false + + - name: Setup pnpm (corepack) + shell: bash + run: | + corepack enable + corepack prepare pnpm@${{ inputs.pnpm-version }} --activate + pnpm --version diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8bf9291..ae334ac 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -38,3 +38,12 @@ updates: - "patch" commit-message: prefix: "chore(deps)" + ignore: + # Transitive deps from @eth-optimism/super-cli (dev/deploy tool, not runtime). + # No upstream fix available — re-evaluate when super-cli bumps these packages. + - dependency-name: "@hono/node-server" + versions: ["<= 1.13.8"] + - dependency-name: "drizzle-orm" + versions: ["<= 0.38.1"] + - dependency-name: "@stablelib/ed25519" + versions: ["<= 1.0.3"] diff --git a/.github/workflows/_reusable-contracts-slither.yml b/.github/workflows/_reusable-contracts-slither.yml new file mode 100644 index 0000000..618f863 --- /dev/null +++ b/.github/workflows/_reusable-contracts-slither.yml @@ -0,0 +1,51 @@ +name: Reusable Contracts Slither + +on: + workflow_call: + inputs: + foundry_version: + description: Foundry version used for Slither compile path + required: false + default: "1.5.0" + type: string + +jobs: + slither-core: + name: Slither Core Contracts + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Install Slither + run: pip install slither-analyzer==0.11.5 + + - name: Setup Foundry + uses: ./.github/actions/setup-foundry + with: + foundry-version: ${{ inputs.foundry_version }} + + - name: Run Slither on MARK core contracts + working-directory: contracts + run: | + for target in \ + src/token/RYLA.sol \ + src/bridge/MARKBridgeAdapter.sol \ + src/settlement/MARKSettlementModule.sol \ + src/settlement/verifier/AttestedSettlementVerifier.sol + do + slither "$target" \ + --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ + --exclude-dependencies \ + --exclude "naming-convention,timestamp,arbitrary-send-erc20,reentrancy-balance,reentrancy-benign" \ + --filter-paths "lib|test|script|out|cache" \ + --fail-medium + done diff --git a/.github/workflows/_reusable-frontend-checks.yml b/.github/workflows/_reusable-frontend-checks.yml new file mode 100644 index 0000000..7ef6b4c --- /dev/null +++ b/.github/workflows/_reusable-frontend-checks.yml @@ -0,0 +1,48 @@ +name: Reusable Frontend Checks + +on: + workflow_call: + inputs: + node_versions: + description: JSON array of Node versions + required: false + default: '["20","22"]' + type: string + pnpm_version: + description: pnpm version used by corepack + required: false + default: "9.0.2" + type: string + +jobs: + frontend-checks: + name: Frontend Checks (Node ${{ matrix.node }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: ${{ fromJSON(inputs.node_versions) }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Setup Node + pnpm + uses: ./.github/actions/setup-node-pnpm + with: + node-version: ${{ matrix.node }} + pnpm-version: ${{ inputs.pnpm_version }} + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm -s typecheck + + - name: Lint + run: pnpm -s lint + + - name: Build frontend + run: pnpm -s build:frontend diff --git a/.github/workflows/_reusable-secrets-scan.yml b/.github/workflows/_reusable-secrets-scan.yml new file mode 100644 index 0000000..5c3cfc0 --- /dev/null +++ b/.github/workflows/_reusable-secrets-scan.yml @@ -0,0 +1,35 @@ +name: Reusable Secrets Scan + +on: + workflow_call: + +jobs: + gitleaks: + name: Gitleaks Scan + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Run gitleaks + run: | + docker run --rm \ + -v "$PWD:/repo" \ + -w /repo \ + zricethezav/gitleaks:v8.24.2@sha256:b5918eb91b8d2473cec722f066abb4352e4ffdc4ec9f4283ec143aba9ec9ebc4 \ + dir /repo \ + --redact \ + --report-format sarif \ + --report-path gitleaks.sarif + + - name: Upload SARIF + if: always() + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: gitleaks.sarif diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 41af7c2..7092e60 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,18 +1,25 @@ name: CodeQL on: - push: - branches: - - dev - - canary - - main pull_request: - branches: - - dev - - canary - - main + branches: [main, canary, dev] + paths: + - "src/**" + - "contracts/**" + - "package.json" + - "pnpm-lock.yaml" + - ".github/workflows/codeql.yml" + push: + branches: [main, canary, dev] + paths: + - "src/**" + - "contracts/**" + - "package.json" + - "pnpm-lock.yaml" + - ".github/workflows/codeql.yml" schedule: - - cron: '22 3 * * 1' + - cron: "30 3 * * 1" + workflow_dispatch: permissions: actions: read @@ -21,7 +28,7 @@ permissions: jobs: analyze: - name: Analyze (JavaScript/TypeScript) + name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest strategy: @@ -34,14 +41,27 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "22" + + - name: Setup pnpm (corepack) + run: | + corepack enable + corepack prepare pnpm@9.0.2 --activate + + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} queries: security-extended - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index f91c72c..5d5ff5b 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -2,17 +2,11 @@ name: Contracts CI on: pull_request: - paths: - - "contracts/**" - - ".github/workflows/contracts-ci.yml" push: branches: - main - canary - dev - paths: - - "contracts/**" - - ".github/workflows/contracts-ci.yml" workflow_dispatch: inputs: run_integration: @@ -54,11 +48,11 @@ jobs: name: Contracts Release Check (Dry-Run + Execute Smoke) runs-on: ubuntu-latest needs: contracts-unit-invariant + timeout-minutes: 15 defaults: run: working-directory: contracts env: - PRIVATE_KEY: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" RPC_URL: http://127.0.0.1:8545 MARK_RELEASE_EXECUTE: "false" MARK_RELEASE_RUN_POSTDEPLOY: "false" @@ -89,6 +83,16 @@ jobs: tail -n 200 /tmp/anvil.log || true exit 1 + - name: Export anvil deployer private key + run: | + key="$(grep -Eo '\(0\)[[:space:]]*0x[0-9a-fA-F]{64}' /tmp/anvil.log | head -n 1 | sed -E 's/^\(0\)[[:space:]]*//')" + if [ -z "$key" ]; then + echo "failed to extract anvil deployer key" >&2 + tail -n 120 /tmp/anvil.log || true + exit 1 + fi + echo "PRIVATE_KEY=$key" >> "$GITHUB_ENV" + - name: Run release orchestrator dry-run run: forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url $RPC_URL -vv @@ -124,6 +128,7 @@ jobs: name: Contracts Production Mode Smoke runs-on: ubuntu-latest needs: contracts-unit-invariant + timeout-minutes: 15 defaults: run: working-directory: contracts @@ -186,7 +191,7 @@ jobs: working-directory: . - name: Wait for supersim readiness - run: pnpm wait-port http://127.0.0.1:8420/ready + run: pnpm wait-port 8420 working-directory: . - name: Run integration suite diff --git a/.github/workflows/contracts-evidence-manifest.yml b/.github/workflows/contracts-evidence-manifest.yml index 2c97b58..b89f211 100644 --- a/.github/workflows/contracts-evidence-manifest.yml +++ b/.github/workflows/contracts-evidence-manifest.yml @@ -8,6 +8,7 @@ on: push: branches: - main + - canary - dev paths: - "contracts/**" diff --git a/.github/workflows/contracts-mainnet-readiness.yml b/.github/workflows/contracts-mainnet-readiness.yml index f345401..745a7b0 100644 --- a/.github/workflows/contracts-mainnet-readiness.yml +++ b/.github/workflows/contracts-mainnet-readiness.yml @@ -60,7 +60,7 @@ jobs: python-version: "3.11" - name: Install Slither - run: pip install slither-analyzer + run: pip install slither-analyzer==0.11.5 - name: Setup Foundry uses: foundry-rs/foundry-toolchain@v1 diff --git a/.github/workflows/contracts-release-gate-container.yml b/.github/workflows/contracts-release-gate-container.yml new file mode 100644 index 0000000..eadc1ac --- /dev/null +++ b/.github/workflows/contracts-release-gate-container.yml @@ -0,0 +1,47 @@ +name: Contracts Release Gate (Containerized) + +on: + pull_request: + paths: + - "contracts/**" + - ".github/workflows/contracts-release-gate-container.yml" + workflow_dispatch: + inputs: + gate_mode: + description: "MARK_RELEASE_GATE_MODE: local or remote" + required: false + default: "local" + type: choice + options: + - local + - remote + push: + branches: + - main + - canary + - dev + paths: + - "contracts/**" + - ".github/workflows/contracts-release-gate-container.yml" + +jobs: + release-gate-container: + name: Release Gate Container + runs-on: ubuntu-latest + defaults: + run: + working-directory: contracts + env: + MARK_RELEASE_GATE_MODE: ${{ inputs.gate_mode || 'local' }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Run release gate in container + run: make release-gate-container diff --git a/.github/workflows/contracts-slither.yml b/.github/workflows/contracts-slither.yml index 0365b9e..01d293c 100644 --- a/.github/workflows/contracts-slither.yml +++ b/.github/workflows/contracts-slither.yml @@ -2,55 +2,12 @@ name: Contracts Slither on: pull_request: - paths: - - "contracts/src/**" - - "contracts/foundry.toml" - - ".github/workflows/contracts-slither.yml" push: branches: - main - canary - dev - paths: - - "contracts/src/**" - - "contracts/foundry.toml" - - ".github/workflows/contracts-slither.yml" jobs: slither-core: - name: Slither Core Contracts - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.11" - - - name: Install Slither - run: pip install slither-analyzer - - - name: Setup Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Run Slither on MARK core contracts - working-directory: contracts - run: | - for target in \ - src/token/RYLA.sol \ - src/bridge/MARKBridgeAdapter.sol \ - src/settlement/MARKSettlementModule.sol \ - src/settlement/verifier/AttestedSettlementVerifier.sol - do - slither "$target" \ - --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ - --exclude-dependencies \ - --exclude "naming-convention,timestamp,arbitrary-send-erc20,reentrancy-balance,reentrancy-benign" \ - --filter-paths "lib|test|script|out|cache" \ - --fail-medium - done + uses: ./.github/workflows/_reusable-contracts-slither.yml diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 2acf75e..8fe4dd0 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -2,66 +2,13 @@ name: Frontend CI on: pull_request: - paths: - - "src/**" - - "public/**" - - "package.json" - - "pnpm-lock.yaml" - - "tsconfig*.json" - - "vite.config.*" - - "eslint.config.*" - - ".github/workflows/frontend-ci.yml" push: branches: - main - canary - dev - paths: - - "src/**" - - "public/**" - - "package.json" - - "pnpm-lock.yaml" - - "tsconfig*.json" - - "vite.config.*" - - "eslint.config.*" - - ".github/workflows/frontend-ci.yml" workflow_dispatch: jobs: frontend-checks: - name: Frontend Checks (Node ${{ matrix.node }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - node: ["20", "22"] - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: ${{ matrix.node }} - package-manager-cache: false - - - name: Setup pnpm (corepack) - run: | - corepack enable - corepack prepare pnpm@9.0.2 --activate - pnpm --version - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Typecheck - run: pnpm -s typecheck - - - name: Lint - run: pnpm -s lint - - - name: Build frontend - run: pnpm -s build:frontend + uses: ./.github/workflows/_reusable-frontend-checks.yml diff --git a/.github/workflows/governance-verify.yml b/.github/workflows/governance-verify.yml new file mode 100644 index 0000000..827c5bd --- /dev/null +++ b/.github/workflows/governance-verify.yml @@ -0,0 +1,25 @@ +name: Governance Verify + +on: + workflow_dispatch: + schedule: + - cron: "15 4 * * 1" + +jobs: + verify-governance: + name: Verify Governance Baseline + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Verify governance protections + env: + GH_PAT: ${{ secrets.GOVERNANCE_VERIFY_PAT }} + GH_REPO: ${{ github.repository }} + run: | + if [ -z "${GH_PAT}" ]; then + echo "Missing required secret: GOVERNANCE_VERIFY_PAT" >&2 + exit 1 + fi + ./scripts/github/verify-governance.sh diff --git a/.github/workflows/release-evidence-validator.yml b/.github/workflows/release-evidence-validator.yml index 77df87b..07f21a9 100644 --- a/.github/workflows/release-evidence-validator.yml +++ b/.github/workflows/release-evidence-validator.yml @@ -1,6 +1,8 @@ name: Release Evidence Validator on: + # pull_request_target runs with base-branch permissions so the GITHUB_TOKEN + # can read PR metadata from forks. No untrusted code is checked out here. pull_request_target: branches: - main diff --git a/.github/workflows/release-pr-checklist.yml b/.github/workflows/release-pr-checklist.yml index 7d500ce..49c0ba7 100644 --- a/.github/workflows/release-pr-checklist.yml +++ b/.github/workflows/release-pr-checklist.yml @@ -1,6 +1,8 @@ name: Release PR Checklist on: + # pull_request_target runs with base-branch permissions so the GITHUB_TOKEN + # can read PR metadata from forks. No untrusted code is checked out here. pull_request_target: branches: - main diff --git a/.github/workflows/scripts-ci.yml b/.github/workflows/scripts-ci.yml new file mode 100644 index 0000000..51a65a0 --- /dev/null +++ b/.github/workflows/scripts-ci.yml @@ -0,0 +1,31 @@ +name: Scripts CI + +on: + pull_request: + paths: + - "scripts/**/*.sh" + - "contracts/script/**/*.sh" + - ".github/workflows/scripts-ci.yml" + push: + branches: + - main + - canary + - dev + paths: + - "scripts/**/*.sh" + - "contracts/script/**/*.sh" + - ".github/workflows/scripts-ci.yml" + workflow_dispatch: + +jobs: + shellcheck: + name: Shellcheck Scripts + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Run shellcheck + uses: ludeeus/action-shellcheck@2.0.0 + with: + scandir: "scripts contracts/script" diff --git a/.github/workflows/secrets-drift-guard.yml b/.github/workflows/secrets-drift-guard.yml index a70365a..5b10e54 100644 --- a/.github/workflows/secrets-drift-guard.yml +++ b/.github/workflows/secrets-drift-guard.yml @@ -42,8 +42,7 @@ jobs: -e 'github_pat_[A-Za-z0-9_]{20,}' \ -e 'AKIA[0-9A-Z]{16}' \ -e '-----BEGIN (RSA|EC|OPENSSH|DSA|PRIVATE) KEY-----' \ - -e '(^|\s)(MNEMONIC|SEED_PHRASE|PRIVATE_KEY)\s*[:=]\s*[^[:space:]]{12,}' \ - -e '0x[a-fA-F0-9]{64}' \ + -e '(MNEMONIC|SEED_PHRASE|PRIVATE_KEY)[[:space:]]*[:=][[:space:]]*[^[:space:]]{12,}' \ /tmp/pr.added.filtered > /tmp/secret.hits; then echo "Potential secret material detected in added lines:" cat /tmp/secret.hits diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml new file mode 100644 index 0000000..c212862 --- /dev/null +++ b/.github/workflows/secrets-scan.yml @@ -0,0 +1,17 @@ +name: Secrets Scan + +on: + pull_request: + push: + branches: + - main + - canary + - dev + workflow_dispatch: + +jobs: + gitleaks: + permissions: + contents: read + security-events: write + uses: ./.github/workflows/_reusable-secrets-scan.yml diff --git a/.gitignore b/.gitignore index a547bf3..a7b5dcf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,14 @@ dist dist-ssr *.local +# Environment files +.env +.env.* +*.env + +# Local dev runtime output +supersim-logs/ + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/BRANCHING.md b/BRANCHING.md index f26db6f..e6ee2b1 100644 --- a/BRANCHING.md +++ b/BRANCHING.md @@ -32,11 +32,15 @@ This repository uses a three-track branch model: ## CI and Deployment Policy -- `contracts-ci` runs on pushes to `dev`, `canary`, and `main`, and on PRs touching contracts. -- `contracts-slither` runs on pushes to `dev`, `canary`, and `main`, and on PRs touching core contracts. +- `contracts-ci` runs on pushes to `dev`, `canary`, and `main`, and on all PRs into protected branches. +- `contracts-slither` runs on pushes to `dev`, `canary`, and `main`, and on all PRs into protected branches. - `contracts-env-guard` runs on pushes to `dev`, `canary`, and `main`, and on PRs touching contracts. - `secrets-drift-guard` runs on all PRs into `dev`, `canary`, and `main`. +- `secrets-scan` (gitleaks) runs on PRs/pushes to detect accidental secret commits early. +- `scripts-ci` runs shellcheck on repository automation scripts to reduce operational breakage risk. +- `frontend-ci` runs on pushes to `dev`, `canary`, and `main`, and on all PRs into protected branches. - `contracts-staging-rehearsal` is automatically triggered on push to `canary`. +- `contracts-release-gate-container` runs release gate in a pinned container on pushes to `dev`/`canary`/`main` and manual dispatch. - `contracts-mainnet-readiness` is production-gated: - manual only (`workflow_dispatch`) - enforced to run from `main` branch @@ -58,26 +62,47 @@ Use this matrix as the merge baseline. ### PRs into `dev` +- `Analyze (javascript-typescript)` +- `gitleaks / Gitleaks Scan` +- `Detect Secrets Drift` +- `Release Gate Container` +- `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` -- `Slither Core Contracts` -- `Secrets Drift Guard` +- `Contracts Production Mode Smoke` +- `slither-core / Slither Core Contracts` +- `frontend-checks / Frontend Checks (Node 20)` +- `frontend-checks / Frontend Checks (Node 22)` - If PR touches governance policy files (`apply-governance.sh`, `BRANCHING.md`, governance checklist): `Validate Governance Policy Consistency` ### PRs into `canary` +- `Analyze (javascript-typescript)` +- `gitleaks / Gitleaks Scan` +- `Detect Secrets Drift` +- `Release Gate Container` +- `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` -- `Slither Core Contracts` -- `Secrets Drift Guard` +- `Contracts Production Mode Smoke` +- `slither-core / Slither Core Contracts` +- `frontend-checks / Frontend Checks (Node 20)` +- `frontend-checks / Frontend Checks (Node 22)` - If PR touches governance policy files (`apply-governance.sh`, `BRANCHING.md`, governance checklist): `Validate Governance Policy Consistency` ### PRs into `main` (release candidate) +- `Analyze (javascript-typescript)` +- `gitleaks / Gitleaks Scan` +- `Detect Secrets Drift` +- `Release Gate Container` +- `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` -- `Slither Core Contracts` -- `Secrets Drift Guard` +- `Contracts Production Mode Smoke` +- `slither-core / Slither Core Contracts` +- `frontend-checks / Frontend Checks (Node 20)` +- `frontend-checks / Frontend Checks (Node 22)` - `Validate Release PR Checklist` - `Validate Release Evidence` - If PR touches governance policy files (`apply-governance.sh`, `BRANCHING.md`, governance checklist): `Validate Governance Policy Consistency` @@ -94,10 +119,15 @@ Apply these repository settings: 1. Protect `main` - Require pull request before merge. - Require status checks: + - `Analyze (javascript-typescript)` + - `gitleaks / Gitleaks Scan` + - `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` - - `Slither Core Contracts` - - `Secrets Drift Guard` + - `Contracts Production Mode Smoke` + - `slither-core / Slither Core Contracts` + - `frontend-checks / Frontend Checks (Node 20)` + - `frontend-checks / Frontend Checks (Node 22)` - `Validate Release PR Checklist` - `Validate Release Evidence` - Require at least 1-2 approvals. @@ -107,26 +137,36 @@ Apply these repository settings: 2. Protect `canary` - Require pull request before merge. - Require status checks: + - `Analyze (javascript-typescript)` + - `gitleaks / Gitleaks Scan` + - `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` - - `Slither Core Contracts` - - `Secrets Drift Guard` + - `Contracts Production Mode Smoke` + - `slither-core / Slither Core Contracts` + - `frontend-checks / Frontend Checks (Node 20)` + - `frontend-checks / Frontend Checks (Node 22)` - Require at least 1 approval. - Dismiss stale approvals on new commits. 3. Protect `dev` - Require pull request before merge (or allow maintainers direct push if desired). - Require status checks: + - `Analyze (javascript-typescript)` + - `gitleaks / Gitleaks Scan` + - `Dependency Review` - `Contracts Unit + Invariant` - `Contracts Release Check (Dry-Run + Execute Smoke)` - - `Slither Core Contracts` - - `Secrets Drift Guard` + - `Contracts Production Mode Smoke` + - `slither-core / Slither Core Contracts` + - `frontend-checks / Frontend Checks (Node 20)` + - `frontend-checks / Frontend Checks (Node 22)` Notes: - Do not add `Validate Governance Policy Consistency` as a global required branch-protection check because it is intentionally path-filtered; require it only on governance-touching PRs. 4. Protect tags -- Reserve release tags (for example `v*`) to maintainers only. +- Release tags (`v*`) are protected by the `tag-protection` ruleset: creation is restricted to maintainers, deletion and force-update are blocked for all actors. ## Merge Flow diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6702ec0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,461 @@ +# Contributing to MARK Protocol + +Thank you for your interest in contributing to MARK Protocol! This guide will walk you through the development workflow, code standards, and release procedures. + +## Table of Contents + +- [Getting Started](#getting-started) +- [Development Workflow](#development-workflow) +- [Code Standards](#code-standards) +- [Testing](#testing) +- [Submitting a PR](#submitting-a-pr) +- [Release Process](#release-process) +- [Troubleshooting](#troubleshooting) + +--- + +## Getting Started + +### Prerequisites + +- **Node.js**: 20 or 22 (check `.nvmrc` for pinned version) +- **pnpm**: 9.0.2+ (managed via corepack) +- **Foundry**: Latest version ([install](https://book.getfoundry.sh/getting-started/installation)) +- **super-cli**: Latest version ([install](https://github.com/ethereum-optimism/super-cli)) + +### Local Setup + +```bash +# Clone the repository +git clone https://github.com/trade/mark.git +cd mark + +# Install dependencies (pnpm is auto-managed via corepack) +pnpm i + +# Verify setup +pnpm typecheck # TypeScript check +pnpm lint # Linting +cd contracts && make ci-fast # Contract tests +``` + +### Start Development + +```bash +# Start the full multi-process dev environment +pnpm dev + +# This will: +# - Start local Superchain (supersim) with L1 + 2 L2 chains +# - Launch Vite dev server at http://localhost:5173 +# - Deploy contracts to local network +# - Watch for file changes + +# In separate terminals, you can also run individually: +pnpm dev:frontend # Frontend only (port 5173) +pnpm dev:supersim # Local Superchain only +pnpm build:contracts # Compile contracts +``` + +--- + +## Development Workflow + +### Branching Strategy + +MARK uses a **three-track branch model**: + +- **`dev`** — Active integration branch (default for features) +- **`canary`** — Staging/stabilization branch +- **`main`** — Production-ready (release branch) + +### Feature Development + +1. **Create a feature branch from `dev`**: + ```bash + git checkout dev + git pull origin dev + git checkout -b feature/your-feature-name + ``` + +2. **Use conventional commit naming**: + - `feature/add-settlement-ui` ✅ + - `feature/fix-bridge-relay` ✅ + - `feature/docs-update` ✅ + - `feature/123` ❌ (use descriptive names) + +3. **Make changes and commit**: + ```bash + git add . + git commit -m "feat(settlement): add transaction confirmation ui" + ``` + + **Commit format**: `(): ` + + - **Types**: `feat`, `fix`, `chore`, `ci`, `refactor`, `docs`, `test` + - **Scope**: Component area (`settlement`, `bridge`, `token`, `frontend`, `workflows`, etc.) + - **Subject**: Clear, imperative tone + +### Before Submitting PR + +1. **Run all checks locally**: + ```bash + # Type checking + pnpm typecheck + + # Linting + pnpm lint + + # Format code + pnpm format + + # Contract compilation and tests + cd contracts && make ci-fast + ``` + +2. **Verify no secrets in code**: + ```bash + # Check for common patterns (key, password, token, secret) + git diff HEAD^ HEAD | grep -i "password\|api_key\|private_key" + # Should return nothing + ``` + +3. **Update documentation** if your changes affect: + - Contract APIs + - Environment setup + - Release procedures + - Architecture + +--- + +## Code Standards + +### TypeScript/React + +- **Use TypeScript** for all `.ts`/`.tsx` files (no `any` types without justification) +- **ESLint** enforces rules automatically +- **Prettier** formats code consistently +- Prefer **functional components** with hooks +- **Component files** should be small and focused + +Example component: +```typescript +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +interface SettlementCardProps { + id: string; + status: 'pending' | 'confirmed' | 'failed'; + onExecute: () => void; +} + +export function SettlementCard({ id, status, onExecute }: SettlementCardProps) { + return ( + + + Settlement {id} + + +
Status: {status}
+ +
+
+ ); +} +``` + +### Solidity + +- **Use PascalCase** for contract names (`MARKSettlementModule`, not `mark_settlement_module`) +- **Keep modules focused**: one contract per file (with related errors/interfaces ok) +- **Document with NatSpec comments** for all public functions +- **Follow existing patterns** in `src/token`, `src/bridge`, `src/settlement` +- **No cross-domain imports** (enforced by `make architecture-guard`) + +Example contract: +```solidity +/// @title MARKSettlementModule +/// @notice Handles cross-chain settlement execution +contract MARKSettlementModule is ISettlement { + /// @notice Execute a settlement transaction + /// @param id The settlement ID + /// @param proof The zero-knowledge proof + function settle(bytes32 id, bytes calldata proof) external { + // Implementation + } +} +``` + +### Testing + +- **Test names** should describe behavior: `testSettlementSucceeds()`, not `test1()` +- **Arrange-Act-Assert** pattern for clarity +- **One assertion per test** (or group related assertions with comments) + +Example test: +```solidity +function testSettlementExecutesWithValidProof() public { + // Arrange: Set up settlement state + bytes32 settlementId = keccak256("test"); + bytes memory proof = _generateValidProof(); + + // Act: Execute settlement + vm.prank(user); + settlement.execute(settlementId, proof); + + // Assert: Verify result + assertEq(settlement.status(settlementId), Status.EXECUTED); +} +``` + +--- + +## Testing + +### Running Tests + +```bash +cd contracts + +# Fast tests (guards + unit/e2e, ~30 seconds) +make ci-fast + +# Full test suite (includes invariants, ~2 minutes) +make ci-full + +# Specific test file +forge test --match-path "test/unit/RYLA.t.sol" -v + +# Specific test function +forge test --match-test "testMintSucceeds" -v + +# With gas reporting +forge test --gas-report +``` + +### Test Structure + +``` +contracts/test/ +├── unit/ # Unit tests (isolated contract behavior) +│ ├── settlement/ +│ ├── bridge/ +│ ├── token/ +│ └── ... +├── e2e/ # End-to-end tests (integration scenarios) +│ └── settlement/ +└── invariant/ # Invariant-based fuzzing tests + └── settlement/ +``` + +### Writing Tests + +1. **Unit tests** should test single functions in isolation +2. **E2E tests** should test complete flows (e.g., bridge → settlement) +3. **Invariants** should test protocol properties that always hold + +See `contracts/test/unit/RYLA.t.sol` for examples. + +--- + +## Submitting a PR + +### PR Checklist + +Before opening a PR, verify: + +- [ ] Feature branch created from `dev` (or `main` for hotfixes) +- [ ] All local tests pass: `pnpm typecheck && pnpm lint && cd contracts && make ci-fast` +- [ ] No secrets/private keys added +- [ ] Commits follow conventional format +- [ ] Documentation updated (if applicable) +- [ ] Branch name is descriptive (e.g., `feature/add-settlement-ui`) + +### Creating the PR + +1. **Push your branch**: + ```bash + git push origin feature/your-feature-name + ``` + +2. **Open PR on GitHub**: + - Target: `dev` (unless hotfix → `main`) + - Title: Use conventional format (e.g., "feat(settlement): add confirmation ui") + - Description: Use the PR template (auto-populated) + +3. **Fill in the PR template**: + - **Summary**: What does this change do? + - **Scope**: Which areas are affected? + - **Verification**: Show commands you ran locally + - **Risk**: Any potential issues? + - **Linked Context**: Related issues/PRs? + +### PR Review Process + +1. **Automated checks run**: + - TypeScript compilation + - ESLint linting + - Contracts CI (unit + invariant tests) + - Slither security scan (if contracts touched) + - CodeQL security scanning + - Secrets drift detection + +2. **Manual code review**: + - CODEOWNERS (see `.github/CODEOWNERS`) required + - May request changes or ask questions + - Approval required before merge + +3. **Merge**: + - Squash commits into single commit (auto on GitHub) + - Branch auto-deleted after merge + - PR closed + +--- + +## Release Process + +**For full details**, see `DEPLOYMENT.md` (detailed runbook) or `BRANCHING.md` (policy). + +### Quick Summary + +``` +1. Create PR: dev → canary + ↓ (Staging rehearsal auto-triggers) + +2. After staging passes, create PR: canary → main + ↓ (All production gates triggered) + +3. After approval, manual dispatch: + - Run mainnet readiness workflow + +4. Tag release: v0.1.0 +``` + +### For Solo Maintainers + +```bash +cd contracts + +# Validate everything works +make ci-full + +# Generate release evidence +make generate-evidence-manifest + +# Sign evidence +make sign-evidence-manifest + +# Verify before deployment +make verify-evidence-manifest +``` + +See `DEPLOYMENT.md` for step-by-step walkthrough. + +--- + +## Troubleshooting + +### Common Issues + +#### "pnpm command not found" + +```bash +# pnpm is managed by corepack. Enable it: +corepack enable +corepack prepare + +# Then verify: +pnpm --version +``` + +#### "Foundry not installed" + +```bash +# Install Foundry +curl -L https://foundry.paradigm.xyz | bash +source $HOME/.bashrc # or .zshrc +foundryup +``` + +#### Tests fail with "address already in use" + +```bash +# Kill leftover anvil processes +pkill -f anvil +# Or restart dev server +pnpm dev +``` + +#### "architecture-guard failed: forbidden import" + +This means a contract imported from wrong domain. Check: +- bridge contracts shouldn't import from `src/settlement/` +- settlement contracts shouldn't import from `src/bridge/` +- token/errors/interfaces are allowed everywhere + +Fix: Remove the forbidden import, use interfaces instead. + +#### "Slither findings not in report" + +Some findings are intentionally excluded. See `contracts/Makefile`: +```makefile +--exclude "naming-convention,timestamp,arbitrary-send-erc20,reentrancy-balance,reentrancy-benign" +``` + +Each exclusion is documented in the codebase. If you disagree with an exclusion, discuss in PR. + +### Getting Help + +1. **Check existing issues**: GitHub Issues tab +2. **Read existing PRs**: Similar changes may have docs/discussions +3. **Review TROUBLESHOOTING.md**: Common solutions +4. **Ask in PR/issue**: Context helps maintainers help you + +--- + +## Best Practices + +### Commits +- ✅ Atomic commits (one logical change per commit) +- ✅ Descriptive messages (what + why, not just what) +- ✅ Conventional format (feat/fix/chore/etc.) +- ❌ "wip", "asdf", "fix stuff" commits + +### Code Review +- ✅ Ask questions if unclear +- ✅ Suggest improvements, don't demand +- ✅ Acknowledge good solutions +- ❌ Personal criticism + +### Testing +- ✅ Test new code before submitting +- ✅ Add tests for bug fixes +- ✅ Update tests when changing behavior +- ❌ "I'll add tests later" (won't happen) + +### Documentation +- ✅ Update docs for API changes +- ✅ Add comments for complex logic +- ✅ Include examples +- ❌ "Code is self-documenting" (it's not always) + +--- + +## Additional Resources + +- [BRANCHING.md](./BRANCHING.md) — Release strategy & branch protection +- [DEPLOYMENT.md](./DEPLOYMENT.md) — Step-by-step release runbook +- [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) — Common issues & solutions +- [contracts/ARCHITECTURE.md](./contracts/ARCHITECTURE.md) — Module architecture +- [README.md](./README.md) — Project overview + +--- + +## Questions? + +Feel free to open an issue or ask in a PR. We're here to help! + +--- + +**Happy contributing!** 🚀 + +*Last updated: 2026-05-06* diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..e907029 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,761 @@ +# MARK Protocol Deployment Runbook + +**Purpose**: Step-by-step procedures for deploying MARK Protocol to staging (OP Sepolia) and mainnet. + +**Audience**: Release maintainer, DevOps, protocol stewards. + +**Last Updated**: 2026-05-06 + +--- + +## Table of Contents + +1. [Pre-Deployment Checklist](#pre-deployment-checklist) +2. [Staging Deployment (OP Sepolia)](#staging-deployment-op-sepolia) +3. [Mainnet Deployment](#mainnet-deployment) +4. [Verification & Monitoring](#verification--monitoring) +5. [Rollback Procedures](#rollback-procedures) +6. [Troubleshooting](#troubleshooting) +7. [Post-Deployment](#post-deployment) + +--- + +## Pre-Deployment Checklist + +### Environment Setup + +- [ ] All environments sourced: `staging.env` and/or `mainnet.env` +- [ ] Private keys loaded (via GitHub Secrets, not committed) +- [ ] RPC endpoints accessible (test: `curl `) +- [ ] Sufficient balance in deployer account (≥ 0.5 ETH for gas) +- [ ] super-cli installed: `super-cli --version` + +### Code Readiness + +- [ ] All tests passing: `cd contracts && make ci-full` +- [ ] Slither scan clean: `cd contracts && make slither-core` +- [ ] No uncommitted changes: `git status` (clean) +- [ ] On correct branch: + - Staging: `canary` branch + - Mainnet: `main` branch +- [ ] Latest contracts built: `pnpm build:contracts` + +### Governance & Evidence + +For **mainnet only**: +- [ ] Release evidence manifest generated +- [ ] Manifest verified & signatures valid +- [ ] Release checklist completed +- [ ] CODEOWNERS review approved + +--- + +## Staging Deployment (OP Sepolia) + +### Phase 1: Pre-Release Validation + +**Target Branch**: `canary` + +**Estimated Time**: 15 minutes + +#### Step 1: Trigger Staging Rehearsal (Automated) + +When you push to `canary`, the `contracts-staging-rehearsal` workflow auto-triggers. + +```bash +# On canary branch +git status # Should show "On branch canary" +git log --oneline -1 # Verify latest commit + +# Wait for GitHub workflow to start +# Monitor: https://github.com/trade/mark/actions +``` + +**What this does**: +- Deploys contracts to OP Sepolia L2A +- Deploys contracts to OP Sepolia L2B +- Runs post-deployment validation tests +- Generates evidence artifacts + +**Expected output**: +``` +✅ Deployment successful +✅ All chains have contracts deployed +✅ Evidence manifest generated +✅ Artifacts uploaded +``` + +#### Step 2: Verify Staging Deployment + +Monitor the workflow in GitHub Actions: + +``` +Contracts Staging Rehearsal Workflow +├─ Deployment (OP Sepolia L2A) ✅ +├─ Deployment (OP Sepolia L2B) ✅ +├─ Post-deployment validation ✅ +└─ Evidence artifact generation ✅ +``` + +**Check deployment on OP Sepolia**: +```bash +# Verify contract on OP Sepolia Sepolia explorer +# https://sepolia-optimism.etherscan.io + +# Expected contracts: +# - RYLA (token) +# - MARKBridgeAdapter +# - MARKSettlementModule +# - AttestedSettlementVerifier +``` + +#### Step 3: Run Manual Validation (Optional) + +```bash +# Source staging config +set -a +source contracts/config/profiles/staging.env +set +a + +# Run validation tests +cd contracts +MARK_RELEASE_EXECUTE=true MARK_SETTLEMENT_PROOF_ENABLED=true \ + ./script/ops/validate-prod-env.sh +``` + +**Expected output**: +``` +Staging environment validation +├─ RPC endpoints reachable ✅ +├─ Contracts deployed ✅ +├─ Settlement module functional ✅ +└─ Bridge adapter operational ✅ +``` + +### Phase 2: Evidence Generation & Verification + +#### Step 4: Generate Evidence Manifest + +```bash +cd contracts + +# Generate official release evidence +make generate-evidence-manifest + +# Output: contracts/broadcast/evidence-manifest.json +``` + +**What's in manifest**: +- Deployment timestamps +- Contract addresses (all chains) +- Transaction hashes +- Test results +- Slither findings (if any) + +#### Step 5: Verify Evidence + +```bash +cd contracts + +# Verify manifest integrity +make verify-evidence-manifest + +# Expected output: +# ✅ Manifest is valid +# ✅ All contracts verified +# ✅ No tampering detected +``` + +#### Step 6: Sign Evidence (For Production) + +```bash +cd contracts + +# If signing releases (recommended for mainnet): +make sign-evidence-manifest + +# Prompts for signing key, outputs: evidence-manifest.sig +``` + +### Phase 3: Promote to Production (Canary → Main) + +#### Step 7: Create PR: Canary → Main + +```bash +# Create PR via GitHub UI or CLI: +gh pr create \ + --title "Release: MARK Protocol v0.1.0" \ + --base main \ + --head canary \ + --body "See DEPLOYMENT.md for release procedures" \ + --label "release" +``` + +**PR description should include**: +```markdown +## Release Summary +- Version: v0.1.0 +- Settlement module: Production-ready +- Bridge adapter: Tested on OP Sepolia +- Evidence: Available in broadcast/evidence-manifest.json + +## Verification +- [x] All tests passing +- [x] Staging rehearsal passed +- [x] Evidence manifest generated +- [x] Slither findings reviewed + +## Deployment Impact +- [ ] Breaking API changes: None +- [ ] Database migrations: None +- [ ] Environment variables: None +``` + +#### Step 8: Wait for CI Checks + +GitHub Actions will automatically run: +- ✅ Contracts Unit + Invariant tests +- ✅ Slither core contracts scan +- ✅ Secrets drift guard +- ✅ Release evidence validator +- ✅ Release PR checklist + +**Expected time**: 3-5 minutes + +#### Step 9: Code Review & Approval + +- [ ] CODEOWNERS review requested (auto-required) +- [ ] Wait for review comments (if any) +- [ ] Address feedback +- [ ] Request re-review if changes made + +#### Step 10: Merge to Main + +```bash +# After approval, merge PR +# Use GitHub UI: Click "Merge pull request" + +# Or via CLI: +gh pr merge --squash --delete-branch +``` + +--- + +## Mainnet Deployment + +### Pre-Mainnet Gate + +**Branch**: `main` + +**After successful PR merge to `main`, follow steps below.** + +### Phase 1: Production Readiness Gate + +#### Step 11: Dispatch Mainnet Readiness Workflow + +This is a **manual workflow dispatch** (not automatic). + +```bash +# Via GitHub CLI: +gh workflow run contracts-mainnet-readiness.yml \ + --ref main \ + -f environment=production + +# Or via GitHub UI: +# 1. Go to Actions tab +# 2. Select "Contracts Mainnet Readiness" workflow +# 3. Click "Run workflow" +# 4. Select branch: main +# 5. Click "Run workflow" button +``` + +**What this does**: +- Validates production environment variables +- Runs full contract test suite against mainnet RPCs +- Verifies deployment readiness +- Generates mainnet evidence + +**Expected time**: 10-15 minutes + +#### Step 12: Monitor Mainnet Readiness + +``` +Contracts Mainnet Readiness Workflow +├─ Environment validation ✅ +├─ Mainnet RPC connectivity ✅ +├─ Full test suite (mainnet) ✅ +├─ Production lock verification ✅ +└─ Readiness report generated ✅ +``` + +**Check workflow output**: +```bash +# View workflow details +gh run view --repo trade/mark +``` + +**If successful**: +``` +✅ Production is ready for deployment +✅ All safety checks passed +✅ Evidence artifacts prepared +``` + +**If failed**: +- See [Troubleshooting](#troubleshooting) section +- DO NOT proceed to deployment +- Fix issues, create new PR, restart from Step 7 + +### Phase 2: Deployment Execution + +#### Step 13: Approve Deployment Authorization + +Production deployments require manual approval from authorized personnel. + +```bash +# Check production environment status +set -a +source contracts/config/profiles/mainnet.env +set +a + +# Verify deployment keys are loaded securely +# (Should NOT print private keys) +echo "Deployer ready: $DEPLOYMENT_ADDRESS" +``` + +#### Step 14: Execute Mainnet Deployment + +```bash +# Navigate to contracts directory +cd contracts + +# List what will be deployed +make smoke-production-mode # Dry-run, no actual deployment + +# If dry-run looks good, execute actual deployment: +MARK_RELEASE_EXECUTE=true \ + ./script/ops/dispatch-release-evidence-sequence.sh +``` + +**What happens**: +1. Deploys RYLA token +2. Deploys MARKBridgeAdapter +3. Deploys MARKSettlementModule +4. Deploys AttestedSettlementVerifier +5. Configures cross-chain bridges +6. Locks production state + +**Expected output**: +``` +Deploying to mainnet... +✅ RYLA deployed: 0x1234... +✅ Bridge deployed: 0x5678... +✅ Settlement deployed: 0x9abc... +✅ Verifier deployed: 0xdef0... +✅ Cross-chain relay configured +✅ Production lock verified +``` + +**Expected time**: 5-10 minutes (includes block confirmations) + +### Phase 3: Post-Deployment Verification + +#### Step 15: Verify Production State + +```bash +cd contracts + +# Verify production lock is in effect +make verify-production-lock + +# Expected output: +# ✅ Production mode locked +# ✅ All safety invariants in place +# ✅ Emergency pause enabled (optional) +``` + +#### Step 16: Create Release Tag + +After successful mainnet deployment: + +```bash +# Tag release (local) +git tag -a v0.1.0 -m "Release: MARK Protocol v0.1.0 + +Contracts deployed to mainnet. +Settlement live on OP Mainnet. +Evidence: " + +# Push tag +git push origin v0.1.0 +``` + +**GitHub auto-creates release**: Check https://github.com/trade/mark/releases + +#### Step 17: Generate Mainnet Evidence + +```bash +cd contracts + +# Generate final evidence report +make generate-evidence-manifest + +# Verify evidence +make verify-evidence-manifest + +# Sign evidence (if keys available) +make sign-evidence-manifest +``` + +--- + +## Verification & Monitoring + +### During Deployment + +Monitor these metrics in real-time: + +```bash +# Check deployment transactions +# https://etherscan.io/tx/ + +# Watch contract state updates +cast call 0x "name()" --rpc-url $MAINNET_RPC + +# Monitor gas prices +ethgas-tracker # or: https://ethgasstation.info +``` + +### Post-Deployment Health Checks + +#### Health Check Script + +```bash +#!/bin/bash +# contracts/script/ops/health-check.sh + +set -a +source config/profiles/mainnet.env +set +a + +echo "🏥 MARK Protocol Mainnet Health Check" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +# Check RYLA token +cast call $RYLA_ADDRESS "totalSupply()" --rpc-url $MAINNET_RPC +echo "✅ RYLA token operational" + +# Check settlement module +cast call $SETTLEMENT_ADDRESS "isProofEnabled()" --rpc-url $MAINNET_RPC +echo "✅ Settlement module functional" + +# Check bridge adapter +cast call $BRIDGE_ADDRESS "isBridgeActive()" --rpc-url $MAINNET_RPC +echo "✅ Bridge adapter connected" + +# Check verifier +cast call $VERIFIER_ADDRESS "name()" --rpc-url $MAINNET_RPC +echo "✅ Verifier contract live" + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "✨ All systems operational!" +``` + +Run health checks: +```bash +cd contracts +./script/ops/health-check.sh +``` + +--- + +## Rollback Procedures + +### Scenario 1: Deployment Failed Mid-Way + +**If deployment halted before completion**: + +```bash +# Check what deployed successfully +cd contracts && git log --oneline broadcast/ | head -5 + +# Redeploy missing contracts +make smoke-production-mode # Verify first + +MARK_RELEASE_EXECUTE=true \ + ./script/ops/dispatch-release-evidence-sequence.sh +``` + +### Scenario 2: Post-Deployment Issue (Within 24h) + +**If issue found shortly after deployment**: + +1. **Pause operations** (if applicable): + ```bash + cast send $SETTLEMENT_ADDRESS "pause()" \ + --rpc-url $MAINNET_RPC \ + --private-key $DEPLOYER_KEY + ``` + +2. **Document issue**: Create GitHub issue with: + - What went wrong + - When detected + - User impact + - Initial mitigation + +3. **Prepare hotfix PR**: + ```bash + git checkout dev + git pull origin dev + git checkout -b hotfix/critical-issue-name + # Make fixes + # Push and create PR into main + ``` + +4. **Deploy hotfix**: Follow full deployment procedure again + +### Scenario 3: Contract Bug Found (Long-term) + +**If critical bug discovered after deployment**: + +```bash +# Create hotfix branch from main +git checkout main +git pull origin main +git checkout -b hotfix/contract-bug-fix + +# Fix the bug +# Commit and push +git push origin hotfix/contract-bug-fix + +# Create PR: hotfix → main +# After approval, deploy as new release +``` + +--- + +## Troubleshooting + +### "Deployment Failed: Insufficient Balance" + +**Cause**: Deployer account has < 0.5 ETH for gas fees + +**Fix**: +```bash +# Check balance +cast balance $DEPLOYER_ADDRESS --rpc-url $MAINNET_RPC + +# Fund deployer (from treasury or team account) +cast send $DEPLOYER_ADDRESS --value 1ether \ + --rpc-url $MAINNET_RPC \ + --private-key $FUNDING_KEY + +# Retry deployment +MARK_RELEASE_EXECUTE=true \ + ./script/ops/dispatch-release-evidence-sequence.sh +``` + +### "Workflow Timeout: Tests Took Too Long" + +**Cause**: Tests exceeded 1 hour limit on GitHub Actions + +**Fix**: +```bash +# Run tests locally to find slow test +cd contracts && make ci-full + +# Profile test execution +forge test --gas-report + +# Look for tests marked [SLOW] +# Consider splitting into separate PR or optimizing +``` + +### "Slither Findings: High Severity Alert" + +**Cause**: Security analysis found a medium/high issue + +**Fix**: +```bash +cd contracts && make slither-core + +# Review findings in Slither output +# If legitimate issue: +# 1. Fix in code +# 2. Create new PR +# 3. Restart deployment process +# +# If false positive: +# 1. Document why it's safe +# 2. Add to slither exclusions in Makefile +# 3. Document reasoning in comments +``` + +### "Evidence Manifest Verification Failed" + +**Cause**: Manifest was modified or corrupted + +**Fix**: +```bash +cd contracts + +# Regenerate manifest +make generate-evidence-manifest + +# Verify new manifest +make verify-evidence-manifest + +# If still failing, check git history +git log --oneline broadcast/evidence-manifest.json | head -3 +``` + +### "Staging Rehearsal Passed but Mainnet Failed" + +**Cause**: Environment differences (OP Sepolia vs OP Mainnet) + +**Fix**: +```bash +# Check environment differences +diff contracts/config/profiles/staging.env \ + contracts/config/profiles/mainnet.env + +# Verify mainnet RPC is healthy +curl -X POST $MAINNET_RPC \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' + +# Re-run mainnet readiness with verbose logging +VERBOSE=true gh workflow run contracts-mainnet-readiness.yml --ref main +``` + +### "Production Lock Test Failed" + +**Cause**: Deployed state doesn't match expected production configuration + +**Fix**: +```bash +cd contracts + +# Run production lock test with verbose output +make test-production-lock + +# Check what's different +cast call $SETTLEMENT_ADDRESS "productionMode()" --rpc-url $MAINNET_RPC + +# If misconfigured, deploy fix +git checkout -b fix/production-lock-config +# Update deployment script or initialization +git push origin fix/production-lock-config +# Create PR into main +``` + +--- + +## Post-Deployment + +### Monitoring & Observability + +1. **Set up alerts** for contract events: + ```bash + # Watch for SettlementExecuted events + cast logs "SettlementExecuted(bytes32)" --rpc-url $MAINNET_RPC + ``` + +2. **Weekly health checks**: + ```bash + cd contracts && ./script/ops/health-check.sh + ``` + +3. **Monitor gas prices** for future deployments: + - https://ethgasstation.info + - https://gasnow.org + +### Communication + +After successful mainnet deployment, communicate: + +1. **Announce release**: + - Twitter/Discord: "MARK Protocol v0.1.0 live on mainnet!" + - Blog post: Architecture, features, security audit results + +2. **Share evidence**: + - Link to deployment evidence manifest + - Contract addresses on Etherscan + - Verification instructions for community + +3. **Create incident response playbook**: + - Who to contact if issues arise + - Escalation procedures + - Monitoring dashboards + +### Documentation Updates + +- [ ] Update README.md with mainnet addresses +- [ ] Create DEPLOYMENT_EVIDENCE.md with manifest link +- [ ] Update BRANCHING.md with lessons learned +- [ ] Add release notes to GitHub Releases + +### Team Debriefing + +- [ ] Schedule post-deployment review +- [ ] Document what went well +- [ ] Document what could improve +- [ ] Update this runbook with learnings + +--- + +## Quick Reference + +### Command Cheat Sheet + +```bash +# Staging deployment (automated on push to canary) +# No manual steps needed during staging rehearsal + +# Mainnet readiness check (manual dispatch) +gh workflow run contracts-mainnet-readiness.yml --ref main + +# Generate evidence +cd contracts && make generate-evidence-manifest + +# Verify evidence +cd contracts && make verify-evidence-manifest + +# Sign evidence +cd contracts && make sign-evidence-manifest + +# Mainnet deployment (manual) +cd contracts && MARK_RELEASE_EXECUTE=true \ + ./script/ops/dispatch-release-evidence-sequence.sh + +# Health check +cd contracts && ./script/ops/health-check.sh + +# Rollback (pause operations) +cast send $SETTLEMENT_ADDRESS "pause()" \ + --rpc-url $MAINNET_RPC --private-key $DEPLOYER_KEY +``` + +### Timeline Estimates + +- **Staging deployment**: 20 minutes (auto, mostly waiting) +- **Staging validation**: 10 minutes (manual checks) +- **Mainnet readiness**: 15 minutes (automated) +- **Mainnet deployment**: 10 minutes (includes block confirmations) +- **Post-deployment verification**: 5 minutes (health checks) + +**Total**: ~60 minutes (mostly waiting for automation) + +--- + +## Support & Escalation + +- **Questions**: Open GitHub issue or discussion +- **Urgent issue**: Contact @trade/maintainers +- **Security concern**: Email security@mark.protocol + +--- + +**Version**: 1.0 +**Last Updated**: 2026-05-06 +**Maintained By**: @trade/maintainers diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..cdded4e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,31 @@ +# Security Policy + +## Reporting a Vulnerability + +Use [GitHub private vulnerability reporting](https://github.com/trade/mark/security/advisories/new) to report security issues. This keeps the disclosure private until a fix is ready. + +Do not open a public issue for security vulnerabilities. + +## Scope + +In scope: + +- Smart contracts (`contracts/src/`) +- Deployment and operational scripts (`contracts/script/`) +- CI/CD workflows (`.github/workflows/`) + +Out of scope: + +- The frontend (`src/`) — it is a read-only dev dashboard with no wallet interaction or user funds +- Local development tooling (`supersim`, `super-cli`, `mprocs`) +- Known transitive dependency alerts from `@eth-optimism/super-cli` with no upstream fix available + +## Response + +- Acknowledgement within 3 business days +- Status update within 7 business days +- Coordinated disclosure after a fix is available or a risk decision is made + +## Supported Versions + +Only the latest commit on `main` is supported. Pre-production branches (`dev`, `canary`) are not considered production. diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..b08e20f --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,980 @@ +# MARK Protocol Troubleshooting Guide + +A comprehensive guide to diagnosing and resolving common issues in MARK Protocol development, testing, and deployment. + +--- + +## Table of Contents + +- [Development Setup Issues](#development-setup-issues) +- [Smart Contract Issues](#smart-contract-issues) +- [Frontend Development Issues](#frontend-development-issues) +- [Testing Issues](#testing-issues) +- [Deployment Issues](#deployment-issues) +- [CI/CD Workflow Issues](#cicd-workflow-issues) +- [Network & RPC Issues](#network--rpc-issues) + +--- + +## Development Setup Issues + +### "pnpm: command not found" + +**Symptoms**: +``` +bash: pnpm: command not found +``` + +**Causes**: +- pnpm not installed +- corepack not enabled +- PATH not updated + +**Solutions**: + +1. **Enable corepack** (recommended): + ```bash + # Enable corepack globally + corepack enable + + # Prepare pnpm version from package.json + corepack prepare + + # Verify + pnpm --version # Should show 9.0.2+ + ``` + +2. **Install pnpm manually** (fallback): + ```bash + npm install -g pnpm@9.0.2 + + # Verify + which pnpm + pnpm --version + ``` + +3. **Update PATH**: + ```bash + # Add to ~/.bashrc, ~/.zshrc, or ~/.profile + export PATH="$HOME/.npm/_npx:$PATH" + + # Reload shell + source ~/.bashrc # or source ~/.zshrc + ``` + +--- + +### "Node.js version mismatch" + +**Symptoms**: +``` +⚠️ This project requires Node.js v20 or v22 +You are using v18.x.x +``` + +**Causes**: +- Node.js version doesn't match `.nvmrc` +- Node version manager not installed + +**Solutions**: + +1. **Use nvm** (recommended): + ```bash + # Install nvm if not present + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + + # Install correct version + nvm install 20 + nvm use 20 + + # Verify + node --version # Should show v20.x.x + ``` + +2. **Use fnm** (faster alternative): + ```bash + brew install fnm # macOS + fnm install 20 + fnm use 20 + ``` + +3. **Manual installation**: + - Download from https://nodejs.org + - Install v20 LTS or v22 + +--- + +### "Foundry not installed" + +**Symptoms**: +``` +forge: command not found +``` + +**Solutions**: + +```bash +# Install Foundry (macOS/Linux/WSL) +curl -L https://foundry.paradigm.xyz | bash + +# Update PATH +source $HOME/.bashrc # or ~/.zshrc + +# Install Foundry components +foundryup + +# Verify +forge --version +cast --version +``` + +**Windows users**: +```powershell +# Use WSL2 or install from https://github.com/foundry-rs/foundry/releases +``` + +--- + +### "super-cli not installed" + +**Symptoms**: +``` +sup: command not found +``` + +**Solutions**: + +```bash +# Install super-cli (via npm) +npm install -g @eth-optimism/super-cli + +# Or via pnpm +pnpm add -g @eth-optimism/super-cli + +# Verify +sup --version +``` + +--- + +### "Git hooks not running" + +**Symptoms**: +- Linting not running before commit +- Tests not running before push + +**Causes**: +- Git hooks not installed +- `.git/hooks` directory corrupted + +**Solutions**: + +```bash +# Reinstall dependencies (usually sets up hooks) +pnpm i + +# If still not working, manually set up hooks +# (Hook setup depends on your husky/lint-staged config) + +# Force rebuild +rm -rf node_modules pnpm-lock.yaml +pnpm i +``` + +--- + +## Smart Contract Issues + +### "architecture-guard failed: forbidden import" + +**Symptoms**: +``` +CI Error: architecture-guard +ERROR: Forbidden import found in src/bridge/MARKBridgeAdapter.sol + Importing from: src/settlement/ISettlement.sol + Settlement should not be imported by bridge domain +``` + +**Causes**: +- Bridge contract imported settlement module +- Settlement contract imported bridge module +- Violates strict domain boundaries + +**Solutions**: + +1. **Check the forbidden import**: + ```bash + grep -n "import.*settlement\|import.*bridge" src/bridge/MARKBridgeAdapter.sol + grep -n "import.*settlement\|import.*bridge" src/settlement/MARKSettlementModule.sol + ``` + +2. **Remove the forbidden import**: + ```solidity + // ❌ DON'T DO THIS (bridge importing settlement) + import { ISettlement } from "../settlement/ISettlement.sol"; + + // ✅ DO THIS INSTEAD (use shared interface) + import { ISharedSettlement } from "../interfaces/ISharedSettlement.sol"; + ``` + +3. **Create shared interface if needed**: + ```solidity + // src/interfaces/ISharedSettlement.sol + pragma solidity ^0.8.0; + + interface ISharedSettlement { + function execute(bytes32 id, bytes calldata proof) external; + } + ``` + +4. **Run guard to verify**: + ```bash + cd contracts && make architecture-guard + # Should pass: ✅ Architecture guard passed + ``` + +--- + +### "layering-guard failed: test imports src/script" + +**Symptoms**: +``` +CI Error: layering-guard +ERROR: Test file importing script in src/script/Deploy.sol + Tests should mock or import only from src/ +``` + +**Causes**: +- Test file imports from deployment scripts +- Script file imports from tests +- Violates layering boundaries + +**Solutions**: + +1. **Identify the problematic import**: + ```bash + grep -n "import.*script" contracts/test/**/*.sol + grep -n "import.*test" contracts/script/**/*.sol + ``` + +2. **Refactor to remove cross-layer dependency**: + ```solidity + // ❌ DON'T DO THIS (test importing script) + import { Deploy } from "../../script/Deploy.s.sol"; + + // ✅ DO THIS (test imports contract directly) + import { RYLA } from "../../src/token/RYLA.sol"; + ``` + +3. **Extract shared code to `src/` if needed**: + ```bash + # Move common utilities to src/ + mv contracts/script/Helpers.sol contracts/src/lib/Helpers.sol + + # Update imports in both test and script files + ``` + +4. **Run guard to verify**: + ```bash + cd contracts && make layering-guard + # Should pass: ✅ Layering guard passed + ``` + +--- + +### "slither finds high-severity issue" + +**Symptoms**: +``` +Slither output: +HIGH: Arbitrary Send ERC20 in MARKBridgeAdapter.transfer() + Risk: Caller can transfer arbitrary tokens +``` + +**Causes**: +- Actual security issue (fix it!) +- False positive (document exclusion) + +**Solutions**: + +1. **If legitimate issue** (do this first): + ```solidity + // ❌ Before: Vulnerable + function bridgeTransfer(address token, uint amount) external { + IERC20(token).transfer(recipient, amount); // Arbitrary token! + } + + // ✅ After: Safe + function bridgeTransfer(uint amount) external { + require(msg.sender == owner); // Only owner can bridge + RYLA.transfer(recipient, amount); // Only RYLA token + } + ``` + +2. **If false positive**, document exclusion: + ```makefile + # contracts/Makefile + slither-core: + slither "$$target" \ + --exclude-dependencies \ + --exclude "naming-convention,arbitrary-send-erc20" \ # ← Add here + ``` + + **Document why** in your code: + ```solidity + /// @notice Transfer is safe because: + /// - Only whitelisted tokens allowed (see tokenWhitelist) + /// - Transfer is guarded by onlyOwner modifier + /// - Slither exclusion: arbitrary-send-erc20 (documented safe) + function transfer(address token, uint amount) external onlyOwner { + IERC20(token).transfer(recipient, amount); + } + ``` + +3. **Update Slither exclusions in Makefile**: + ```bash + cd contracts && make slither-core + # Review findings + # Update Makefile if excluding + make slither-core # Re-verify + ``` + +--- + +### "forge test: compilation failed" + +**Symptoms**: +``` +Compiler error: contracts/src/token/RYLA.sol:5:1: DeclarationError + Import "openzeppelin-contracts/token/ERC20/ERC20.sol" not found +``` + +**Causes**: +- Missing dependencies +- Remapping misconfigured +- Submodule not initialized + +**Solutions**: + +1. **Initialize submodules**: + ```bash + git submodule init + git submodule update --recursive + ``` + +2. **Check remappings**: + ```bash + cat contracts/foundry.toml | grep -A5 "remappings" + # Should show: + # @openzeppelin/ => lib/createx/lib/openzeppelin-contracts/ + # @interop-lib/ => lib/interop-lib/src/ + ``` + +3. **Verify dependencies exist**: + ```bash + ls -la contracts/lib/ + # Should show: forge-std, interop-lib, createx + ``` + +4. **Reinstall dependencies**: + ```bash + cd contracts + rm -rf lib cache + forge install + ``` + +5. **Try building again**: + ```bash + cd contracts && forge build + ``` + +--- + +### "test execution out of gas" + +**Symptoms**: +``` +forge test execution reverted: OutOfGas + Function: testSettlementWithLargeProof +``` + +**Causes**: +- Test uses too much gas +- Fuzzing with many iterations +- Complex contract interaction + +**Solutions**: + +1. **Reduce fuzzing runs** (in test file): + ```solidity + // Add at top of test file: + /// forge-config: default.fuzz.runs = 10 (instead of default 256) + + function testFuzzSettlement(bytes32 id) public { + // Test logic + } + ``` + +2. **Optimize test setup**: + ```solidity + // ❌ Before: Redundant setup + function testManySettlements() public { + for (uint i = 0; i < 1000; i++) { + settlement.execute(id, proof); // 1000 executions = high gas + } + } + + // ✅ After: Test key scenario + function testSettlement() public { + settlement.execute(id, proof); + } + + function testMultipleSettlements() public { + settlement.execute(id1, proof1); + settlement.execute(id2, proof2); + // Test only 2-3 scenarios, not 1000 + } + ``` + +3. **Check gas limits in Makefile**: + ```bash + grep -n "gas\|FOUNDRY" contracts/Makefile + ``` + +4. **Run with gas reporting**: + ```bash + cd contracts && forge test --gas-report + # See which functions consume most gas + ``` + +--- + +## Frontend Development Issues + +### "pnpm dev fails: port already in use" + +**Symptoms**: +``` +Error: listen EADDRINUSE: address already in use :::5173 +``` + +**Causes**: +- Another process using port 5173 +- Previous dev server still running +- Port configured differently + +**Solutions**: + +1. **Kill process on port 5173**: + ```bash + # macOS/Linux + lsof -i :5173 # Find process + kill -9 # Kill it + + # Or use direct command + pkill -f "vite\|dev:frontend" + ``` + +2. **Use different port**: + ```bash + pnpm dev:frontend -- --port 5174 + ``` + +3. **Restart dev server**: + ```bash + # Stop all processes + pkill -f "vite\|anvil\|supersim" + + # Start fresh + pnpm dev + ``` + +--- + +### "TypeScript errors in IDE but tests pass" + +**Symptoms**: +- VS Code shows red squiggles +- `pnpm typecheck` passes locally +- CI passes but IDE unhappy + +**Causes**: +- IDE cache stale +- TypeScript version mismatch +- tsconfig.json not reloaded + +**Solutions**: + +1. **Reload TypeScript server in VS Code**: + - Press: `Cmd/Ctrl + Shift + P` + - Type: "TypeScript: Restart TS Server" + - Press Enter + +2. **Clear IDE cache**: + ```bash + # Close VS Code + rm -rf .vscode/ + rm -rf node_modules/.vite + pnpm i + # Reopen VS Code + ``` + +3. **Verify TypeScript version**: + ```bash + pnpm ls typescript + # Should show 5.7.2+ + ``` + +4. **Force typecheck**: + ```bash + pnpm typecheck --strict + ``` + +--- + +### "Module not found error in browser" + +**Symptoms**: +``` +Browser console: Uncaught ReferenceError: MARK is not defined +or +Failed to resolve module: @eth-optimism/viem +``` + +**Causes**: +- Dependency not installed +- Import path wrong +- Build cache stale + +**Solutions**: + +1. **Reinstall dependencies**: + ```bash + rm -rf node_modules pnpm-lock.yaml + pnpm i + ``` + +2. **Check import path**: + ```typescript + // ❌ Wrong + import { useAccount } from 'wagmi' // Missing path + + // ✅ Correct + import { useAccount } from 'wagmi' + import { supersimL2A } from '@eth-optimism/viem/chains' + ``` + +3. **Clear browser cache**: + ```bash + # Hard refresh in browser + Cmd/Ctrl + Shift + R (or Cmd/Ctrl + Shift + Delete, then clear cache) + ``` + +4. **Restart dev server**: + ```bash + pkill -f "vite" + pnpm dev:frontend + ``` + +--- + +## Testing Issues + +### "forge test hangs or times out" + +**Symptoms**: +``` +forge test +# ... waiting forever ... +Test suite hangs without completing +``` + +**Causes**: +- Infinite loop in test +- Test stuck waiting for transaction +- Network call timeout +- Foundry offline mode issue + +**Solutions**: + +1. **Kill hanging process**: + ```bash + # In another terminal + pkill -f forge + ``` + +2. **Run single test to isolate**: + ```bash + cd contracts + forge test --match-test "testSpecificFunction" -vv + ``` + +3. **Check for infinite loops** in problematic test: + ```solidity + // ❌ Bad: Infinite loop + function testLoop() public { + while(true) { // INFINITE! + settlement.execute(...); + } + } + + // ✅ Good: Bounded loop + function testLoop() public { + for (uint i = 0; i < 10; i++) { // 10 iterations max + settlement.execute(...); + } + } + ``` + +4. **Try offline mode**: + ```bash + cd contracts + FOUNDRY_OFFLINE=true forge test + ``` + +5. **Increase timeout**: + ```bash + timeout 300 forge test # 5 minute timeout + ``` + +--- + +### "test fails with 'insufficient balance'" + +**Symptoms**: +``` +forge test error: Assertion failed + Message: Insufficient balance +``` + +**Causes**: +- Test didn't fund account properly +- Token transfer didn't work +- Balance calculation wrong + +**Solutions**: + +1. **Add explicit balance setup**: + ```solidity + function setUp() public { + // Mint tokens to test accounts + vm.prank(owner); + token.mint(user, 1000e18); // 1000 tokens + + // Verify balance + assertEq(token.balanceOf(user), 1000e18); + } + ``` + +2. **Use deal()** (simpler): + ```solidity + // Set ETH balance + vm.deal(user, 10 ether); + assertEq(user.balance, 10 ether); + + // Set ERC20 balance (requires contract) + deal(address(token), user, 1000e18); + assertEq(token.balanceOf(user), 1000e18); + ``` + +3. **Debug balance in test**: + ```solidity + function testDebugBalance() public { + emit log_uint(token.balanceOf(user)); // Print to console + assertEq(token.balanceOf(user), expectedAmount); + } + + // Run with: forge test -vv + ``` + +--- + +## Deployment Issues + +### "Insufficient balance for gas fees" + +**Symptoms**: +``` +Error: Insufficient balance. Required: 0.5 ETH, Have: 0.01 ETH +``` + +**Solutions**: + +```bash +# Check balance +cast balance $DEPLOYER_ADDRESS --rpc-url $RPC + +# Fund account (from another address) +cast send $DEPLOYER_ADDRESS --value 1ether \ + --rpc-url $RPC \ + --private-key $FUNDING_KEY + +# Verify funded +cast balance $DEPLOYER_ADDRESS --rpc-url $RPC +``` + +--- + +### "Deployment timeout: transaction not mined" + +**Symptoms**: +``` +Error: Transaction not mined after 300 seconds +TX Hash: 0x1234... +``` + +**Causes**: +- Network congestion +- Gas price too low +- RPC node issues + +**Solutions**: + +1. **Check transaction status**: + ```bash + cast receipt 0x1234... --rpc-url $RPC + # If output is empty, transaction dropped + ``` + +2. **Increase gas price**: + ```bash + # Check current gas + cast gas-price --rpc-url $RPC + + # Redeploy with higher gas + GASPRICE=50gwei pnpm sup deploy create2 ... + ``` + +3. **Verify RPC is healthy**: + ```bash + # Test RPC + curl -X POST $RPC \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' + ``` + +4. **Wait longer**: + ```bash + # Testnet congestion? Wait 5-10 minutes and retry + sleep 300 + pnpm sup deploy ... + ``` + +--- + +## CI/CD Workflow Issues + +### "GitHub Actions workflow stuck" + +**Symptoms**: +- Workflow shows "In progress" for >1 hour +- No error message + +**Solutions**: + +1. **Cancel workflow**: + - Go to: https://github.com/iap/mark/actions + - Find workflow + - Click: "Cancel workflow" + +2. **Check logs**: + - Click workflow name + - View "Logs" section + - Look for last message before hang + +3. **Restart workflow**: + - Go to workflow run + - Click: "Re-run failed jobs" + +4. **Check GitHub status**: + - https://www.githubstatus.com/ + - If GitHub is down, wait for recovery + +--- + +### "Secrets not available in workflow" + +**Symptoms**: +``` +Error: Environment variable DEPLOYER_KEY not set +``` + +**Causes**: +- Secret not added to repository +- Secret name misspelled +- Secret scoped to wrong environment + +**Solutions**: + +1. **Add secret to repository**: + - Go to: Settings → Secrets and variables → Actions + - Click: "New repository secret" + - Name: `DEPLOYER_KEY` + - Value: (paste actual private key) + - Click: "Add secret" + +2. **Verify secret name in workflow**: + ```yaml + # In .github/workflows/deploy.yml + - name: Deploy + env: + DEPLOYER_KEY: ${{ secrets.DEPLOYER_KEY }} # Must match exact name + run: pnpm sup deploy create2 + ``` + +3. **For environment-specific secrets**: + ```yaml + environment: production # Or staging + # Secrets from production environment used + ``` + +4. **Re-run workflow** after adding secret: + - Go to workflow run + - Click: "Re-run failed jobs" + +--- + +### "Slither workflow fails but locally passes" + +**Symptoms**: +``` +GitHub Actions: Slither Core Contracts → FAILED +Local: cd contracts && make slither-core → PASSED +``` + +**Causes**: +- Slither version different +- Environment differences +- Cache issues + +**Solutions**: + +1. **Update Slither locally**: + ```bash + pip install --upgrade slither-analyzer + slither --version + ``` + +2. **Run Slither like CI does**: + ```bash + cd contracts && make slither-core + # Should match GitHub output + ``` + +3. **Check Slither remappings**: + ```bash + # Verify remappings in workflow + slither src/token/RYLA.sol \ + --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" + ``` + +--- + +## Network & RPC Issues + +### "RPC endpoint timeout or unreachable" + +**Symptoms**: +``` +Error: connect ECONNREFUSED 127.0.0.1:9545 +or +Error: Gateway timeout +``` + +**Causes**: +- Local network (anvil/supersim) not running +- RPC URL wrong +- Network congestion + +**Solutions**: + +1. **For local network**: + ```bash + # Check if supersim is running + lsof -i :9545 # Should show anvil process + + # If not, start it + pnpm dev:supersim + ``` + +2. **For public RPC**: + ```bash + # Verify RPC URL + echo $MAINNET_RPC + # Example: https://mainnet.infura.io/v3/YOUR_API_KEY + + # Test RPC + curl -X POST $MAINNET_RPC \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' + ``` + +3. **Retry with timeout**: + ```bash + # Use cast with timeout + cast call 0x123... "balanceOf(address)" $ACCOUNT \ + --rpc-url $RPC \ + --timeout 30s # 30 second timeout + ``` + +--- + +### "Contract address not found / zero address" + +**Symptoms**: +``` +Error: Contract address is 0x0000000000000000000000000000000000000000 +or +Error: Contract not found at 0x1234... +``` + +**Causes**: +- Deployment failed silently +- Contract address misconfigured +- Wrong network selected + +**Solutions**: + +1. **Check deployment status**: + ```bash + # Look at broadcast files + ls -la contracts/broadcast/ + cat contracts/broadcast/Deploy.s.sol/*/run-latest.json | jq '.transactions' + ``` + +2. **Verify contract deployed**: + ```bash + # Check if code exists at address + cast code 0x1234... --rpc-url $RPC + # Should show bytecode (not 0x) + ``` + +3. **Check network**: + ```bash + # Verify you're on correct network + cast chain-id --rpc-url $RPC + # Mainnet: 1 + # OP Sepolia: 11155420 + # Sepolia: 11155111 + ``` + +4. **Redeploy if needed**: + ```bash + # Get fresh deployment + cd contracts && make ci-full # Verify locally first + pnpm sup deploy create2 ... # Deploy + ``` + +--- + +## Getting More Help + +- **Check existing issues**: https://github.com/iap/mark/issues +- **Review pull requests**: https://github.com/iap/mark/pulls +- **Read documentation**: `CONTRIBUTING.md`, `DEPLOYMENT.md`, `BRANCHING.md` +- **Ask in GitHub Discussions** (if enabled) + +--- + +**Version**: 1.0 +**Last Updated**: 2026-05-06 diff --git a/contracts/.gitignore b/contracts/.gitignore index 58af24b..b15aa4c 100644 --- a/contracts/.gitignore +++ b/contracts/.gitignore @@ -5,6 +5,9 @@ out/ # Ignores all broadcast logs /broadcast/ +# Coverage output +coverage/ + # Docs docs/ diff --git a/contracts/Makefile b/contracts/Makefile index 37f168a..94c0aa9 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -1,4 +1,4 @@ -.PHONY: ci-local ci-fast ci-full test-core test-invariants release-gate architecture-guard layering-guard smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature +.PHONY: ci-local ci-fast ci-full test-core test-invariants release-gate release-gate-container architecture-guard layering-guard smoke-production-mode test-production-lock verify-production-lock dispatch-production-lock-verify dispatch-release-evidence-sequence bootstrap-release-secrets rehearse-production-lock generate-promotion-checklist validate-prod-env validate-prod-env-all generate-evidence-manifest verify-evidence-manifest sign-evidence-manifest verify-evidence-signature slither-install slither-core # Fast local loop: guards + unit/e2e tests (excludes invariants and integration profile tests). ci-fast: architecture-guard layering-guard test-core @@ -19,6 +19,9 @@ ci-local: ci-fast release-gate: @./script/ci/release-gate.sh +release-gate-container: + @./script/ci/run-release-gate-container.sh + architecture-guard: @./script/ci/architecture-guard.sh @@ -73,3 +76,26 @@ sign-evidence-manifest: verify-evidence-signature: @./script/ops/verify-evidence-signature.sh + +slither-install: + @python3 -m pip install --user slither-analyzer==0.11.5 + +slither-core: + @command -v slither >/dev/null 2>&1 || { \ + echo "slither not found in PATH. Try: export PATH=\"$$HOME/Library/Python/3.9/bin:$$PATH\""; \ + echo "or run: make slither-install"; \ + exit 1; \ + } + @for target in \ + src/token/RYLA.sol \ + src/bridge/MARKBridgeAdapter.sol \ + src/settlement/MARKSettlementModule.sol \ + src/settlement/verifier/AttestedSettlementVerifier.sol; \ + do \ + slither "$$target" \ + --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ + --exclude-dependencies \ + --exclude "naming-convention,timestamp,arbitrary-send-erc20,reentrancy-balance,reentrancy-benign" \ + --filter-paths "lib|test|script|out|cache" \ + --fail-medium; \ + done diff --git a/contracts/README.md b/contracts/README.md index 12c4cc1..0b0e926 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -3,6 +3,7 @@ Smart contracts for Superchain interoperability and `RYLA` standard credit primitives. Operational procedures (deployment, incident, rollback) are documented in [RUNBOOK.md](./RUNBOOK.md). +Pre-mainnet promotion criteria are documented in [STAGING_GO_NO_GO_CHECKLIST.md](./STAGING_GO_NO_GO_CHECKLIST.md). ## Contracts @@ -404,16 +405,10 @@ forge test Run Slither locally on MARK core contracts: ```bash -cd /Users/iap/mark/contracts -slither \ - src/token/RYLA.sol \ - src/bridge/MARKBridgeAdapter.sol \ - src/settlement/MARKSettlementModule.sol \ - src/settlement/verifier/AttestedSettlementVerifier.sol \ - --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ - --exclude-dependencies \ - --filter-paths "lib|test|script|out|cache" \ - --fail-medium +cd contracts +make slither-install +export PATH="$HOME/Library/Python/3.9/bin:$PATH" +make slither-core ``` CI workflow: diff --git a/contracts/RUNBOOK.md b/contracts/RUNBOOK.md index bae30fc..d9c5f84 100644 --- a/contracts/RUNBOOK.md +++ b/contracts/RUNBOOK.md @@ -2,6 +2,9 @@ This runbook is the operational source of truth for MARK (`RYLA`) deployments. +For operator sign-off before production promotion, use: +- [`STAGING_GO_NO_GO_CHECKLIST.md`](./STAGING_GO_NO_GO_CHECKLIST.md) + ## 0) Branch Policy - Production deployment and mainnet readiness checks are executed from `main` branch only. @@ -280,3 +283,95 @@ When rollback is triggered: 2. Revoke unsafe operators/attesters. 3. Redeploy from clean, verified env snapshot. 4. Re-run full readiness gate. + +## 8) Trust Model + +This section documents the trust assumptions, key custody model, and break-glass procedures for the MARK protocol. Auditors and operators should read this before reviewing or operating the contracts. + +### Key Roles and Trust Assumptions + +**Default Admin** (`DEFAULT_ADMIN_ROLE`) +- Held by the protocol deployer address on all three contracts: `RYLA`, `MARKBridgeAdapter`, `MARKSettlementModule`. +- Controls role grants/revokes, verifier configuration, bridge limits, and production mode activation. +- Protected by a 1-day delay (`AccessControlDefaultAdminRules`): admin transfers cannot take effect for at least 24 hours after initiation. +- Trust assumption: the admin key is held by the protocol owner in a hardware wallet or equivalent secure storage. Compromise of this key is the highest-severity incident. + +**Operator** (`OPERATOR_ROLE` on bridge and settlement) +- Submits bridge transactions and settlement intents. +- Can move tokens cross-chain (bridge) and trigger mint/burn (settlement). +- Trust assumption: operator keys are hot keys used by the protocol's backend. They are scoped to operational actions only — they cannot change configuration, rotate roles, or disable proof validation. +- Rotation: use section 5 (Operator Key Compromise Playbook) if compromised. + +**Attester** (`ATTESTER_ROLE` on `AttestedSettlementVerifier`) +- Signs settlement attestations that authorize mint/burn operations when proof validation is enabled. +- Trust assumption: the attester key is a hot signing key. Its compromise allows fraudulent settlement attestations until revoked. +- Rotation policy: see Attester Key Rotation below. + +**Minter / Burner** (`MINTER_ROLE`, `BURNER_ROLE` on `RYLA`) +- Held exclusively by `MARKSettlementModule`. Not held by any EOA in production. +- Trust assumption: the settlement module is the only authorized issuer. Direct mint/burn by any EOA is not permitted. + +### Attester Key Rotation + +Rotate the attester key if: +- Key material may have been exposed. +- Signing infrastructure is being migrated. +- Scheduled rotation policy requires it. + +Steps: +1. Generate new attester key in secure environment. +2. Grant new attester role before revoking old: +```bash +cast send "setAttester(address,bool)" true --private-key $ADMIN_PK +``` +3. Verify new attester is active. +4. Revoke old attester: +```bash +cast send "setAttester(address,bool)" false --private-key $ADMIN_PK +``` +5. Re-run verify script to confirm clean attester state. + +Note: there is a 1-day admin delay on the admin key itself, but `setAttester` is callable immediately by the current admin. Rotation is instant once the admin key is available. + +### Break-Glass Procedure + +Use this procedure when normal operational controls are insufficient — for example, if a critical vulnerability is discovered post-deployment. + +**Step 1: Contain** +- Revoke all operator roles on bridge and settlement immediately (section 5). +- Revoke all attester roles on the verifier. +- This stops new settlements and bridge operations without requiring admin key rotation. + +**Step 2: Assess** +- Determine whether the vulnerability is in contract logic or in key material. +- If key material: proceed to admin rotation (section 6). +- If contract logic: assess whether existing deployed state is safe to leave in place while a fix is prepared. + +**Step 3: Communicate** +- Use GitHub private vulnerability reporting (see `SECURITY.md`) to coordinate disclosure. +- Do not deploy fixes to production without re-running the full mainnet readiness gate. + +**Step 4: Recover** +- If redeployment is required: follow section 7 (Rollback Decision Rule). +- If configuration fix is sufficient: use `PostDeployMARKSetup.s.sol` and re-run verify. + +### Production Mode Implications + +Once `activateProductionMode()` is called on `MARKSettlementModule`: +- Proof validation cannot be disabled. +- The verifier address cannot be set to zero. +- This is irreversible. + +Before activating production mode, confirm: +- The attester key is in secure, long-term storage. +- The verifier contract has been audited. +- The admin key is in a hardware wallet or equivalent. + +### Key Storage Recommendations + +| Key | Recommended storage | Rotation frequency | +|-----|--------------------|--------------------| +| Default admin | Hardware wallet (Ledger/Trezor) | On compromise or scheduled annually | +| Operator | HSM or secure server key | On compromise or quarterly | +| Attester | HSM or secure server key | On compromise or quarterly | +| Deployer (one-time) | Hardware wallet | N/A after deployment | diff --git a/contracts/STAGING_GO_NO_GO_CHECKLIST.md b/contracts/STAGING_GO_NO_GO_CHECKLIST.md new file mode 100644 index 0000000..e9f6c7e --- /dev/null +++ b/contracts/STAGING_GO_NO_GO_CHECKLIST.md @@ -0,0 +1,66 @@ +# Staging Go/No-Go Checklist + +Use this checklist before any `canary -> main` promotion. Local Anvil checks are required, but final confidence must come from a real staging network rehearsal. + +## Scope + +- Target: Optimism staging/testnet (for example OP Sepolia). +- Branch source: `canary` commit intended for promotion. +- Governance model: use the same Safe / role model planned for mainnet. + +## Required Inputs + +- `STAGING_RPC_URL` +- `MARK_STAGING_DEPLOYER_PRIVATE_KEY` (GitHub secret) +- `VALIDATE_MODE` (set to `rehearsal` for this staging checklist) +- staging operator address (`STAGING_SETTLEMENT_OPERATOR`) +- expected admin/owner/verifier/attester addresses + +## Execution Steps + +1. Validate environment and config schema: +```bash +cd contracts +VALIDATE_MODE=rehearsal make validate-prod-env +``` + +2. Run local baseline gates (must be green): +```bash +make ci-full +make slither-core +``` + +3. Run staging rehearsal workflow: +- GitHub Actions: `.github/workflows/contracts-staging-rehearsal.yml` +- Confirm artifacts: + - `mark-staging-release` + - `mark-staging-rehearsal` + +4. Run post-deploy production-lock verify: +- GitHub Actions: `.github/workflows/contracts-production-lock-verify.yml` +- Confirm artifact: `mark-production-lock-verify` + +5. Run functional rehearsal on staging: +- settlement mint (valid proof path) +- settlement burn (escrow + burn invariants) +- bridge flow with destination allowlist and limit checks +- operator/attester rotation and re-verify (see RUNBOOK.md sections 5 and 6) + +6. Generate promotion checklist evidence: +- GitHub Actions: `.github/workflows/contracts-promotion-checklist.yml` +- Confirm freshness + lineage policy passes. + +## Go Criteria + +- All required CI checks pass on promotion commit. +- Staging rehearsal + production-lock verify pass. +- Evidence artifacts are present, reviewed, and hashed. +- Role/config verify outputs match expected addresses. +- Security reviewer + operator + admin signer approvals recorded. + +## No-Go Triggers + +- Any failed verify/preflight/security gate. +- Evidence missing or stale. +- Address/role mismatch vs planned production config. +- Unresolved incident drill or key-rotation concern. diff --git a/contracts/docker/release-gate.Dockerfile b/contracts/docker/release-gate.Dockerfile new file mode 100644 index 0000000..459a88f --- /dev/null +++ b/contracts/docker/release-gate.Dockerfile @@ -0,0 +1,19 @@ +FROM ghcr.io/foundry-rs/foundry:latest + +USER root + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl git jq python3 python3-pip ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Pin Node + pnpm for deterministic JS tooling in CI steps. +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get update \ + && apt-get install -y --no-install-recommends nodejs \ + && corepack enable \ + && corepack prepare pnpm@9.0.2 --activate + +# Slither analyzer is required by mainnet-readiness/release hardening checks. +RUN python3 -m pip install --no-cache-dir slither-analyzer==0.11.5 + +WORKDIR /repo/contracts diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 74574b3..2c64fc4 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -8,12 +8,14 @@ broadcast = "broadcast" libs = ["lib"] no_match_path = "test/integration/**" fs_permissions = [{ access = "read-write", path = "./broadcast" }] - remappings = [ "@interop-lib/=lib/interop-lib/src/", "@openzeppelin/=lib/createx/lib/openzeppelin-contracts/" ] +[profile.default.invariant] +runs = 256 + [profile.integration] src = "src" out = "out" @@ -25,3 +27,4 @@ libs = ["lib"] no_match_path = "test/never/**" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + diff --git a/contracts/script/ci/run-release-gate-container.sh b/contracts/script/ci/run-release-gate-container.sh new file mode 100755 index 0000000..d96f5e9 --- /dev/null +++ b/contracts/script/ci/run-release-gate-container.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +REPO_DIR="$(cd "${ROOT_DIR}/.." && pwd)" +IMAGE_TAG="${MARK_RELEASE_GATE_IMAGE_TAG:-mark-release-gate:local}" +DOCKERFILE_PATH="${ROOT_DIR}/docker/release-gate.Dockerfile" + +if ! command -v docker >/dev/null 2>&1; then + echo "docker is required for release-gate-container" >&2 + exit 1 +fi + +echo "[release-gate-container] building image: ${IMAGE_TAG}" +docker build \ + --cache-from "type=gha" \ + --cache-to "type=gha,mode=max" \ + -t "${IMAGE_TAG}" \ + -f "${DOCKERFILE_PATH}" \ + "${REPO_DIR}" + +echo "[release-gate-container] running release gate inside container" +# Pass through environment variables commonly used by release-gate and remote mode. +docker run --rm \ + -v "${REPO_DIR}:/repo" \ + -w /repo/contracts \ + -e MARK_RELEASE_GATE_MODE \ + -e MARK_RELEASE_VERIFY_REQUIRE_SIGNED_MANIFEST \ + -e MARK_RELEASE_VERIFY_ARTIFACT_PATH \ + -e MARK_RELEASE_ARTIFACT_PATH \ + -e MARK_RELEASE_VERIFY_MANIFEST_PATH \ + -e MARK_RELEASE_VERIFY_SIGNATURE_PATH \ + -e MARK_RELEASE_VERIFY_SIGNATURE_META_PATH \ + -e VERIFY_PUBLIC_KEY_FILE \ + -e VERIFY_PUBLIC_KEY_PEM \ + -e MARK_MAINNET_GATE_MODE \ + -e MARK_MAINNET_GATE_ARTIFACT_PATH \ + -e RPC_URL \ + -e PRIVATE_KEY \ + "${IMAGE_TAG}" \ + bash -lc 'corepack enable >/dev/null 2>&1 || true; pnpm --version >/dev/null 2>&1 || true; make release-gate' diff --git a/contracts/script/ops/settlement/VerifyMARKDeployment.s.sol b/contracts/script/ops/settlement/VerifyMARKDeployment.s.sol index c557c95..9ba5295 100644 --- a/contracts/script/ops/settlement/VerifyMARKDeployment.s.sol +++ b/contracts/script/ops/settlement/VerifyMARKDeployment.s.sol @@ -42,7 +42,7 @@ contract VerifyMARKDeployment is Script { function _runWithConfig(ExpectedConfig memory cfg) internal view { RYLA token = RYLA(cfg.tokenAddress); - _assertEq(token.name(), "RYLA", "Token name mismatch"); + _assertEq(token.name(), "RYLA Credits", "Token name mismatch"); _assertEq(token.symbol(), "RYLA", "Token symbol mismatch"); _assertEq(uint256(token.defaultAdminDelay()), uint256(EXPECTED_ADMIN_DELAY), "Token admin delay mismatch"); diff --git a/contracts/script/ops/smoke-production-mode.sh b/contracts/script/ops/smoke-production-mode.sh index bf124b4..8d37e8f 100755 --- a/contracts/script/ops/smoke-production-mode.sh +++ b/contracts/script/ops/smoke-production-mode.sh @@ -63,7 +63,7 @@ SEND_JSON="$(cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" --creat VERIFIER_TX="$(echo "$SEND_JSON" | jq -r '.transactionHash' 2>/dev/null || true)" if [[ -z "$VERIFIER_TX" || "$VERIFIER_TX" == "null" ]]; then SEND_OUT="$(cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" --create "$DATA")" - VERIFIER_TX="$(echo "$SEND_OUT" | rg -o '0x[0-9a-fA-F]{64}' | head -n1 || true)" + VERIFIER_TX="$(echo "$SEND_OUT" | grep -Eo '0x[0-9a-fA-F]{64}' | head -n1 || true)" fi VERIFIER_ADDRESS="$(cast receipt "$VERIFIER_TX" --rpc-url "$RPC_URL" --json | jq -r '.contractAddress')" diff --git a/contracts/src/bridge/MARKBridgeAdapter.sol b/contracts/src/bridge/MARKBridgeAdapter.sol index 1b3c7fa..ce53f3f 100644 --- a/contracts/src/bridge/MARKBridgeAdapter.sol +++ b/contracts/src/bridge/MARKBridgeAdapter.sol @@ -92,7 +92,14 @@ contract MARKBridgeAdapter is ReentrancyGuard, AccessControlDefaultAdminRules, B TOKEN.safeTransferFrom(msg.sender, address(this), amount); TOKEN.forceApprove(address(SUPERCHAIN_TOKEN_BRIDGE), amount); - messageHash = SUPERCHAIN_TOKEN_BRIDGE.sendERC20(address(TOKEN), recipient, amount, destinationChainId); + try SUPERCHAIN_TOKEN_BRIDGE.sendERC20(address(TOKEN), recipient, amount, destinationChainId) returns ( + bytes32 hash + ) { + messageHash = hash; + } catch { + TOKEN.forceApprove(address(SUPERCHAIN_TOKEN_BRIDGE), 0); + revert BridgeFailed(); + } emit BridgedOut(msg.sender, recipient, destinationChainId, amount, messageHash); } diff --git a/contracts/src/errors/BridgeErrors.sol b/contracts/src/errors/BridgeErrors.sol index a0ea410..6eef4fa 100644 --- a/contracts/src/errors/BridgeErrors.sol +++ b/contracts/src/errors/BridgeErrors.sol @@ -8,4 +8,5 @@ abstract contract BridgeErrors { error DestinationDisabled(); error MaxPerTxExceeded(); error DailyCapExceeded(); + error BridgeFailed(); } diff --git a/contracts/src/interfaces/IRYLA.sol b/contracts/src/interfaces/IRYLA.sol new file mode 100644 index 0000000..eaa68e8 --- /dev/null +++ b/contracts/src/interfaces/IRYLA.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title IRYLA +/// @notice Minimal interface for settlement modules interacting with the RYLA token. +interface IRYLA is IERC20 { + function mint(address to, uint256 amount) external; + function burn(uint256 amount) external; +} diff --git a/contracts/src/settlement/MARKSettlementModule.sol b/contracts/src/settlement/MARKSettlementModule.sol index 02a0fea..699507e 100644 --- a/contracts/src/settlement/MARKSettlementModule.sol +++ b/contracts/src/settlement/MARKSettlementModule.sol @@ -4,10 +4,9 @@ pragma solidity ^0.8.25; import { AccessControlDefaultAdminRules } from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {RYLA} from "../token/RYLA.sol"; +import {IRYLA} from "../interfaces/IRYLA.sol"; import {IUTXOSettlementVerifier} from "./interfaces/IUTXOSettlementVerifier.sol"; import {SettlementErrors} from "../errors/SettlementErrors.sol"; import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; @@ -16,7 +15,7 @@ import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; /// @notice Boundary module for integrating external UTXO/zk accounting with RYLA mint/burn. /// @dev Holds RYLA minter and burner roles. Replay protection is enforced via `intentId`. contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules, SettlementErrors { - using SafeERC20 for IERC20; + using SafeERC20 for IRYLA; event OperatorUpdated(address indexed operator, bool enabled); event VerifierUpdated(address indexed verifier, bool validationEnabled); event ProductionModeActivated(address indexed admin); @@ -26,7 +25,7 @@ contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); - RYLA public immutable TOKEN; + IRYLA public immutable TOKEN; mapping(bytes32 => bool) public consumedIntents; uint256 public totalSettledMint; @@ -41,7 +40,7 @@ contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules { if (initialAdmin == address(0)) revert ZeroAddress(); if (tokenAddress == address(0)) revert ZeroAddress(); - TOKEN = RYLA(tokenAddress); + TOKEN = IRYLA(tokenAddress); } function setOperator(address operator, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { @@ -97,7 +96,7 @@ contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules _consumeAndValidate(intentId, account, amount, false, proof); uint256 moduleBalanceBefore = TOKEN.balanceOf(address(this)); - IERC20(address(TOKEN)).safeTransferFrom(account, address(this), amount); + TOKEN.safeTransferFrom(account, address(this), amount); uint256 moduleBalanceAfterTransfer = TOKEN.balanceOf(address(this)); if (moduleBalanceAfterTransfer != moduleBalanceBefore + amount) revert BurnEscrowInvariantFailed(); diff --git a/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol b/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol index d7dc557..1d2b4fb 100644 --- a/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol +++ b/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol @@ -5,20 +5,28 @@ import { AccessControlDefaultAdminRules } from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {IUTXOSettlementVerifier} from "../interfaces/IUTXOSettlementVerifier.sol"; import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; /// @title AttestedSettlementVerifier -/// @notice Signature-based verifier for settlement intents. +/// @notice Signature-based verifier for settlement intents using EIP-712 typed data signing. /// @dev Intended as a production-safe bridge step before zk verifier integration. -/// Proof encoding: +/// Proof encoding (160 bytes — 5 × 32-byte ABI words): /// `abi.encode(uint256 deadline, bytes32 contextHash, uint8 v, bytes32 r, bytes32 s)`. -/// Settlement digest binds to the calling settlement module address. -contract AttestedSettlementVerifier is IUTXOSettlementVerifier, AccessControlDefaultAdminRules { - using MessageHashUtils for bytes32; +/// The digest binds to the verifier address, chain id, and settlement module address, +/// preventing cross-verifier and cross-module replay. +/// `contextHash` is an opaque attester-controlled binding value (e.g. off-chain UTXO +/// state root or batch id) included in the signed digest to tie the attestation to +/// external state without exposing that state on-chain. +contract AttestedSettlementVerifier is IUTXOSettlementVerifier, EIP712, AccessControlDefaultAdminRules { + using ECDSA for bytes32; + + /// @dev EIP-712 type hash for the SettlementAttestation struct. + bytes32 public constant SETTLEMENT_ATTESTATION_TYPEHASH = keccak256( + "SettlementAttestation(bytes32 intentId,address verifier,address settlementModule,address account,uint256 amount,bool isMint,bytes32 contextHash,uint256 deadline)" + ); - bytes32 public constant SETTLEMENT_ATTESTATION_DOMAIN = keccak256("AttestedSettlementVerifier.v1"); uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; bytes32 public constant ATTESTER_ROLE = keccak256("ATTESTER_ROLE"); @@ -32,7 +40,10 @@ contract AttestedSettlementVerifier is IUTXOSettlementVerifier, AccessControlDef bytes32 s; } - constructor(address initialAdmin) AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) { + constructor(address initialAdmin) + EIP712("AttestedSettlementVerifier", "1") + AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) + { if (initialAdmin == address(0)) revert ZeroAddress(); } @@ -59,14 +70,17 @@ contract AttestedSettlementVerifier is IUTXOSettlementVerifier, AccessControlDef override returns (bool) { - if (intentId == bytes32(0) || settlementModule == address(0) || account == address(0) || amount == 0) return false; + if (intentId == bytes32(0) || settlementModule == address(0) || account == address(0) || amount == 0) { + return false; + } + // 160 bytes = 5 × 32-byte ABI words from abi.encode of the Attestation struct + // (uint8 v is padded to 32 bytes by ABI encoding). if (proof.length != 160) return false; Attestation memory att = abi.decode(proof, (Attestation)); if (att.deadline < block.timestamp) return false; - bytes32 digest = - _settlementDigest(intentId, settlementModule, account, amount, isMint, att.contextHash, att.deadline); + bytes32 digest = _settlementDigest(intentId, settlementModule, account, amount, isMint, att.contextHash, att.deadline); bytes memory signature = abi.encodePacked(att.r, att.s, att.v); (address signer, ECDSA.RecoverError err,) = ECDSA.tryRecover(digest, signature); if (err != ECDSA.RecoverError.NoError || signer == address(0)) return false; @@ -74,6 +88,21 @@ contract AttestedSettlementVerifier is IUTXOSettlementVerifier, AccessControlDef return hasRole(ATTESTER_ROLE, signer); } + /// @dev Returns the EIP-712 digest for a settlement attestation. + /// Exposed so off-chain signers can compute the exact bytes to sign without + /// reading internal implementation details. + function settlementDigest( + bytes32 intentId, + address settlementModule, + address account, + uint256 amount, + bool isMint, + bytes32 contextHash, + uint256 deadline + ) external view returns (bytes32) { + return _settlementDigest(intentId, settlementModule, account, amount, isMint, contextHash, deadline); + } + function _settlementDigest( bytes32 intentId, address settlementModule, @@ -83,12 +112,11 @@ contract AttestedSettlementVerifier is IUTXOSettlementVerifier, AccessControlDef bytes32 contextHash, uint256 deadline ) internal view returns (bytes32) { - bytes32 settlementHash = keccak256( + bytes32 structHash = keccak256( abi.encode( - SETTLEMENT_ATTESTATION_DOMAIN, - address(this), - block.chainid, + SETTLEMENT_ATTESTATION_TYPEHASH, intentId, + address(this), settlementModule, account, amount, @@ -97,6 +125,6 @@ contract AttestedSettlementVerifier is IUTXOSettlementVerifier, AccessControlDef deadline ) ); - return settlementHash.toEthSignedMessageHash(); + return _hashTypedDataV4(structHash); } } diff --git a/contracts/src/token/RYLA.sol b/contracts/src/token/RYLA.sol index 895a3b0..d96c32f 100644 --- a/contracts/src/token/RYLA.sol +++ b/contracts/src/token/RYLA.sol @@ -25,7 +25,7 @@ contract RYLA is SuperchainERC20, AccessControlDefaultAdminRules, TokenErrors { } function name() public pure override returns (string memory) { - return "RYLA"; + return "RYLA Credits"; } function symbol() public pure override returns (string memory) { diff --git a/contracts/test/e2e/settlement/MARKSettlementE2E.t.sol b/contracts/test/e2e/settlement/MARKSettlementE2E.t.sol index ba09220..f32deea 100644 --- a/contracts/test/e2e/settlement/MARKSettlementE2E.t.sol +++ b/contracts/test/e2e/settlement/MARKSettlementE2E.t.sol @@ -90,21 +90,7 @@ contract MARKSettlementE2ETest is Test { function testInvalidAttestationReverts() public { uint256 deadline = block.timestamp + 1 hours; - bytes32 settlementHash = keccak256( - abi.encode( - verifier.SETTLEMENT_ATTESTATION_DOMAIN(), - address(verifier), - block.chainid, - INTENT_MINT, - address(module), - user, - 1 ether, - true, - CONTEXT_MINT, - deadline - ) - ); - bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); + bytes32 digest = verifier.settlementDigest(INTENT_MINT, address(module), user, 1 ether, true, CONTEXT_MINT, deadline); (uint8 v, bytes32 r, bytes32 s) = vm.sign(0xDEAD, digest); bytes memory wrongProof = abi.encode(deadline, CONTEXT_MINT, v, r, s); @@ -133,21 +119,7 @@ contract MARKSettlementE2ETest is Test { bytes32 contextHash, uint256 deadline ) internal view returns (bytes memory) { - bytes32 settlementHash = keccak256( - abi.encode( - verifier.SETTLEMENT_ATTESTATION_DOMAIN(), - address(verifier), - block.chainid, - intentId, - moduleAddress, - account, - amount, - isMint, - contextHash, - deadline - ) - ); - bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); + bytes32 digest = verifier.settlementDigest(intentId, moduleAddress, account, amount, isMint, contextHash, deadline); (uint8 v, bytes32 r, bytes32 s) = vm.sign(attesterPk, digest); return abi.encode(deadline, contextHash, v, r, s); } diff --git a/contracts/test/integration/bridge/MARKBridgeIntegration.t.sol b/contracts/test/integration/bridge/MARKBridgeIntegration.t.sol new file mode 100644 index 0000000..0bb8b64 --- /dev/null +++ b/contracts/test/integration/bridge/MARKBridgeIntegration.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../../src/bridge/MARKBridgeAdapter.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; + +/// @notice Integration test for MARKBridgeAdapter against live supersim chains. +/// @dev Requires two running supersim L2 forks: +/// CHAIN_A_RPC_URL (default: http://127.0.0.1:9545) — source chain +/// CHAIN_B_RPC_URL (default: http://127.0.0.1:9546) — destination chain +/// +/// Run with: +/// CHAIN_A_RPC_URL=http://127.0.0.1:9545 \ +/// CHAIN_B_RPC_URL=http://127.0.0.1:9546 \ +/// FOUNDRY_PROFILE=integration forge test --match-path 'test/integration/**/*.t.sol' -vv +contract MARKBridgeIntegrationTest is Test { + string internal chainAUrl; + string internal chainBUrl; + + uint256 internal forkA; + uint256 internal forkB; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + address internal recipient = makeAddr("recipient"); + + RYLA internal token; + MARKBridgeAdapter internal adapter; + + function setUp() public { + chainAUrl = vm.envOr("CHAIN_A_RPC_URL", string("http://127.0.0.1:9545")); + chainBUrl = vm.envOr("CHAIN_B_RPC_URL", string("http://127.0.0.1:9546")); + + forkA = vm.createFork(chainAUrl); + forkB = vm.createFork(chainBUrl); + + // Deploy on chain A. + vm.selectFork(forkA); + uint256 destChainId = vm.envOr("CHAIN_B_CHAIN_ID", uint256(902)); + + vm.startPrank(owner); + token = new RYLA(owner); + token.setMinter(owner, true); + token.mint(operator, 100 ether); + + adapter = new MARKBridgeAdapter(owner, address(token)); + adapter.setOperator(operator, true); + adapter.setDestination(destChainId, true); + vm.stopPrank(); + + vm.prank(operator); + token.approve(address(adapter), type(uint256).max); + } + + /// @notice Verifies that bridgeTo burns tokens on chain A and the recipient + /// receives them on chain B via SuperchainTokenBridge auto-relay. + function testBridgeToTransfersTokensCrossChain() public { + vm.selectFork(forkA); + uint256 destChainId = vm.envOr("CHAIN_B_CHAIN_ID", uint256(902)); + uint256 amount = 10 ether; + + uint256 operatorBalanceBefore = token.balanceOf(operator); + uint256 supplyBefore = token.totalSupply(); + + vm.prank(operator); + adapter.bridgeTo(recipient, amount, destChainId); + + // Tokens are burned on chain A by SuperchainTokenBridge. + assertEq(token.balanceOf(operator), operatorBalanceBefore - amount, "operator balance not reduced"); + assertEq(token.totalSupply(), supplyBefore - amount, "supply not reduced on chain A"); + + // Supersim auto-relays the message; verify recipient balance on chain B. + vm.selectFork(forkB); + // RYLA is a SuperchainERC20 — same address on both chains via CREATE2. + address tokenOnB = address(token); + assertEq(RYLA(tokenOnB).balanceOf(recipient), amount, "recipient did not receive tokens on chain B"); + } + + /// @notice Verifies that rate limits are enforced even against the live bridge. + function testBridgeToEnforcesMaxPerTxOnLiveFork() public { + vm.selectFork(forkA); + uint256 destChainId = vm.envOr("CHAIN_B_CHAIN_ID", uint256(902)); + + vm.prank(owner); + adapter.setBridgeLimits(5 ether, 0); + + vm.prank(operator); + vm.expectRevert(abi.encodeWithSignature("MaxPerTxExceeded()")); + adapter.bridgeTo(recipient, 6 ether, destChainId); + } +} diff --git a/contracts/test/invariant/bridge/MARKBridgeInvariants.t.sol b/contracts/test/invariant/bridge/MARKBridgeInvariants.t.sol new file mode 100644 index 0000000..538ae1a --- /dev/null +++ b/contracts/test/invariant/bridge/MARKBridgeInvariants.t.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {StdInvariant} from "forge-std/StdInvariant.sol"; +import {Test} from "forge-std/Test.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKBridgeAdapter} from "../../../src/bridge/MARKBridgeAdapter.sol"; +import {ISuperchainTokenBridge} from "@interop-lib/interfaces/ISuperchainTokenBridge.sol"; +import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; + +contract MARKBridgeHandler is Test { + RYLA public immutable token; + MARKBridgeAdapter public immutable adapter; + address public immutable owner; + address public immutable operator; + + address internal constant SUPERCHAIN_BRIDGE = PredeployAddresses.SUPERCHAIN_TOKEN_BRIDGE; + uint256 internal constant DST_CHAIN_ID = 902; + + uint256 internal nonce; + + constructor(RYLA _token, MARKBridgeAdapter _adapter, address _owner, address _operator) { + token = _token; + adapter = _adapter; + owner = _owner; + operator = _operator; + } + + function bridge(uint96 rawAmount) external { + uint256 maxPerTx_ = adapter.maxPerTx(); + uint256 dailyCap_ = adapter.dailyCap(); + + uint256 ceiling = maxPerTx_ > 0 ? maxPerTx_ : 1_000_000 ether; + uint256 amount = bound(uint256(rawAmount), 1, ceiling); + + // Skip if daily cap would be exceeded — lets fuzzer explore more paths. + if (dailyCap_ > 0) { + uint64 epoch = uint64(block.timestamp / 1 days); + uint256 accumulated = epoch == adapter.dailyCapEpoch() ? adapter.bridgedInDailyCapEpoch() : 0; + if (accumulated + amount > dailyCap_) return; + } + + vm.prank(owner); + token.mint(operator, amount); + + bytes memory callData = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), operator, amount, DST_CHAIN_ID + ); + vm.mockCall(SUPERCHAIN_BRIDGE, callData, abi.encode(keccak256(abi.encodePacked(nonce++)))); + + vm.prank(operator); + adapter.bridgeTo(operator, amount, DST_CHAIN_ID); + } + + function advanceTime(uint32 seconds_) external { + vm.warp(block.timestamp + bound(uint256(seconds_), 1, 3 days)); + } + + function updateLimits(uint96 rawMaxPerTx, uint96 rawDailyCap) external { + uint256 maxPerTx_ = bound(uint256(rawMaxPerTx), 0, 1_000_000 ether); + uint256 dailyCap_ = bound(uint256(rawDailyCap), 0, 10_000_000 ether); + vm.prank(owner); + adapter.setBridgeLimits(maxPerTx_, dailyCap_); + } +} + +contract MARKBridgeInvariants is StdInvariant, Test { + RYLA internal token; + MARKBridgeAdapter internal adapter; + MARKBridgeHandler internal handler; + + address internal owner = makeAddr("owner"); + address internal operator = makeAddr("operator"); + + uint256 internal constant DST_CHAIN_ID = 902; + + function setUp() public { + vm.startPrank(owner); + token = new RYLA(owner); + token.setMinter(owner, true); + + adapter = new MARKBridgeAdapter(owner, address(token)); + adapter.setOperator(operator, true); + adapter.setDestination(DST_CHAIN_ID, true); + adapter.setBridgeLimits(100 ether, 500 ether); + vm.stopPrank(); + + vm.prank(operator); + IERC20(address(token)).approve(address(adapter), type(uint256).max); + + handler = new MARKBridgeHandler(token, adapter, owner, operator); + + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = MARKBridgeHandler.bridge.selector; + selectors[1] = MARKBridgeHandler.advanceTime.selector; + selectors[2] = MARKBridgeHandler.updateLimits.selector; + + targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); + targetContract(address(handler)); + } + + /// @notice bridgedInDailyCapEpoch never exceeds dailyCap within the current epoch. + function invariant_dailyCapNeverExceeded() public view { + uint256 dailyCap_ = adapter.dailyCap(); + if (dailyCap_ == 0) return; + uint64 currentEpoch = uint64(block.timestamp / 1 days); + if (adapter.dailyCapEpoch() == currentEpoch) { + assertLe(adapter.bridgedInDailyCapEpoch(), dailyCap_, "daily cap exceeded"); + } + } + + /// @notice Accumulator is always consistent: never exceeds cap for its stored epoch. + function invariant_accumulatorConsistentWithCap() public view { + uint256 dailyCap_ = adapter.dailyCap(); + if (dailyCap_ == 0) return; + assertLe(adapter.bridgedInDailyCapEpoch(), dailyCap_, "accumulator exceeds cap"); + } + + /// @notice Operator role is never granted to zero address. + function invariant_operatorRoleNeverZeroAddress() public view { + assertFalse(adapter.hasRole(adapter.OPERATOR_ROLE(), address(0)), "zero address has operator role"); + } +} diff --git a/contracts/test/unit/RYLA.t.sol b/contracts/test/unit/RYLA.t.sol index d378875..419af3f 100644 --- a/contracts/test/unit/RYLA.t.sol +++ b/contracts/test/unit/RYLA.t.sol @@ -6,6 +6,8 @@ import {Test} from "forge-std/Test.sol"; import {RYLA} from "../../src/token/RYLA.sol"; import {PredeployAddresses} from "@interop-lib/libraries/PredeployAddresses.sol"; import {Unauthorized} from "@interop-lib/libraries/errors/CommonErrors.sol"; +import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; +import {TokenErrors} from "../../src/errors/TokenErrors.sol"; contract RYLATest is Test { RYLA internal token; @@ -21,7 +23,7 @@ contract RYLATest is Test { } function testMetadata() public view { - assertEq(token.name(), "RYLA"); + assertEq(token.name(), "RYLA Credits"); assertEq(token.symbol(), "RYLA"); assertEq(token.decimals(), 18); } @@ -76,4 +78,35 @@ contract RYLATest is Test { token.crosschainBurn(user, 2 ether); assertEq(token.balanceOf(user), 3 ether); } + + function testSupportsInterface() public view { + // ERC165 + assertTrue(token.supportsInterface(0x01ffc9a7)); + // IAccessControl + assertTrue(token.supportsInterface(0x7965db0b)); + // random — should return false + assertFalse(token.supportsInterface(0xdeadbeef)); + } + + function testSetMinterRevertsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert(ZeroAddress.selector); + token.setMinter(address(0), true); + } + + function testSetBurnerRevertsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert(ZeroAddress.selector); + token.setBurner(address(0), true); + } + + function testBurnRevertsForZeroAmount() public { + vm.startPrank(owner); + token.setBurner(burner, true); + vm.stopPrank(); + + vm.prank(burner); + vm.expectRevert(TokenErrors.InvalidAmount.selector); + token.burn(0); + } } diff --git a/contracts/test/unit/bridge/MARKBridgeAdapter.t.sol b/contracts/test/unit/bridge/MARKBridgeAdapter.t.sol index 3ba2e96..6aaf440 100644 --- a/contracts/test/unit/bridge/MARKBridgeAdapter.t.sol +++ b/contracts/test/unit/bridge/MARKBridgeAdapter.t.sol @@ -110,6 +110,49 @@ contract MARKBridgeAdapterTest is Test { assertEq(adapter.bridgedInDailyCapEpoch(), 60 ether); } + function testSetBridgeLimitsResetsEpochAccumulatorMidEpoch() public { + vm.prank(owner); + adapter.setBridgeLimits(0, 100 ether); + + bytes memory callData = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, 60 ether, DST_CHAIN_ID + ); + vm.mockCall(SUPERCHAIN_BRIDGE, callData, abi.encode(keccak256("h1"))); + vm.prank(operator); + adapter.bridgeTo(recipient, 60 ether, DST_CHAIN_ID); + assertEq(adapter.bridgedInDailyCapEpoch(), 60 ether); + + // reset limits mid-epoch — accumulator and epoch should clear + vm.prank(owner); + adapter.setBridgeLimits(0, 200 ether); + assertEq(adapter.bridgedInDailyCapEpoch(), 0); + assertEq(adapter.dailyCapEpoch(), 0); + + // should now allow bridging up to new cap from zero + bytes memory callData2 = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, 150 ether, DST_CHAIN_ID + ); + vm.mockCall(SUPERCHAIN_BRIDGE, callData2, abi.encode(keccak256("h2"))); + vm.prank(operator); + adapter.bridgeTo(recipient, 150 ether, DST_CHAIN_ID); + assertEq(adapter.bridgedInDailyCapEpoch(), 150 ether); + } + + function testBridgeToRevertsWithBridgeFailedAndClearsApprovalOnSendERC20Revert() public { + uint256 amount = 25 ether; + + bytes memory callData = abi.encodeWithSelector( + ISuperchainTokenBridge.sendERC20.selector, address(token), recipient, amount, DST_CHAIN_ID + ); + vm.mockCallRevert(SUPERCHAIN_BRIDGE, callData, "bridge down"); + + vm.prank(operator); + vm.expectRevert(BridgeErrors.BridgeFailed.selector); + adapter.bridgeTo(recipient, amount, DST_CHAIN_ID); + + assertEq(token.allowance(address(adapter), SUPERCHAIN_BRIDGE), 0); + } + function testFuzz_BridgeLimitsRespectCapsAndDayReset( uint96 firstRaw, uint96 secondRaw, diff --git a/contracts/test/unit/settlement/AttestedSettlementVerifier.t.sol b/contracts/test/unit/settlement/AttestedSettlementVerifier.t.sol index fc1495b..8426826 100644 --- a/contracts/test/unit/settlement/AttestedSettlementVerifier.t.sol +++ b/contracts/test/unit/settlement/AttestedSettlementVerifier.t.sol @@ -61,6 +61,37 @@ contract AttestedSettlementVerifierTest is Test { assertFalse(ok); } + function testVerifySettlementReturnsFalseForZeroIntentId() public view { + uint256 deadline = block.timestamp + 1 hours; + bytes memory proof = _buildProof(bytes32(0), settlementModule, user, 1 ether, true, CONTEXT, deadline, attesterPk); + assertFalse(verifier.verifySettlement(bytes32(0), settlementModule, user, 1 ether, true, proof)); + } + + function testVerifySettlementReturnsFalseForZeroSettlementModule() public view { + uint256 deadline = block.timestamp + 1 hours; + bytes memory proof = _buildProof(INTENT, address(0), user, 1 ether, true, CONTEXT, deadline, attesterPk); + assertFalse(verifier.verifySettlement(INTENT, address(0), user, 1 ether, true, proof)); + } + + function testVerifySettlementReturnsFalseForZeroAccount() public view { + uint256 deadline = block.timestamp + 1 hours; + bytes memory proof = _buildProof(INTENT, settlementModule, address(0), 1 ether, true, CONTEXT, deadline, attesterPk); + assertFalse(verifier.verifySettlement(INTENT, settlementModule, address(0), 1 ether, true, proof)); + } + + function testVerifySettlementReturnsFalseForZeroAmount() public view { + uint256 deadline = block.timestamp + 1 hours; + bytes memory proof = _buildProof(INTENT, settlementModule, user, 0, true, CONTEXT, deadline, attesterPk); + assertFalse(verifier.verifySettlement(INTENT, settlementModule, user, 0, true, proof)); + } + + function testVerifySettlementReturnsFalseForWrongIsMintFlag() public view { + uint256 deadline = block.timestamp + 1 hours; + // proof signed for isMint=true, but called with isMint=false + bytes memory proof = _buildProof(INTENT, settlementModule, user, 1 ether, true, CONTEXT, deadline, attesterPk); + assertFalse(verifier.verifySettlement(INTENT, settlementModule, user, 1 ether, false, proof)); + } + function _buildProof( bytes32 intentId, address moduleAddress, @@ -71,34 +102,8 @@ contract AttestedSettlementVerifierTest is Test { uint256 deadline, uint256 signerPk ) internal view returns (bytes memory proof) { - bytes32 digest = _settlementDigest(intentId, moduleAddress, account, amount, isMint, contextHash, deadline); + bytes32 digest = verifier.settlementDigest(intentId, moduleAddress, account, amount, isMint, contextHash, deadline); (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, digest); proof = abi.encode(deadline, contextHash, v, r, s); } - - function _settlementDigest( - bytes32 intentId, - address moduleAddress, - address account, - uint256 amount, - bool isMint, - bytes32 contextHash, - uint256 deadline - ) internal view returns (bytes32) { - bytes32 settlementHash = keccak256( - abi.encode( - verifier.SETTLEMENT_ATTESTATION_DOMAIN(), - address(verifier), - block.chainid, - intentId, - moduleAddress, - account, - amount, - isMint, - contextHash, - deadline - ) - ); - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", settlementHash)); - } } diff --git a/contracts/test/unit/settlement/MARKSettlementModule.t.sol b/contracts/test/unit/settlement/MARKSettlementModule.t.sol index 03390de..84a7c7b 100644 --- a/contracts/test/unit/settlement/MARKSettlementModule.t.sol +++ b/contracts/test/unit/settlement/MARKSettlementModule.t.sol @@ -150,6 +150,31 @@ contract MARKSettlementModuleTest is Test { module.setVerifier(address(verifier), false); } + function testTotalSettledAccumulatesAcrossMultipleIntents() public { + vm.startPrank(operator); + module.settleMint(user, 3 ether, keccak256("acc-m1"), bytes("")); + module.settleMint(user, 7 ether, keccak256("acc-m2"), bytes("")); + vm.stopPrank(); + assertEq(module.totalSettledMint(), 10 ether); + + vm.prank(user); + token.approve(address(module), 10 ether); + + vm.startPrank(operator); + module.settleBurn(user, 4 ether, keccak256("acc-b1"), bytes("")); + module.settleBurn(user, 2 ether, keccak256("acc-b2"), bytes("")); + vm.stopPrank(); + assertEq(module.totalSettledBurn(), 6 ether); + assertEq(token.balanceOf(user), 4 ether); + } + + function testSetVerifierRejectsEOAAddress() public { + address eoa = makeAddr("eoa"); + vm.prank(owner); + vm.expectRevert(SettlementErrors.VerifierRequired.selector); + module.setVerifier(eoa, true); + } + function testProductionModeStillEnforcesBurnProofValidation() public { vm.startPrank(owner); module.setVerifier(address(verifier), true); diff --git a/package.json b/package.json index e591874..9356345 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@tailwindcss/vite": "^4.2.4", - "@tanstack/react-query": "^5.100.8", + "@tanstack/react-query": "^5.100.9", "abitype": "^1.2.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -39,13 +39,13 @@ "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.4", "tailwindcss-animate": "^1.0.7", - "viem": "^2.48.8", + "viem": "^2.48.11", "wagmi": "^2.14.11" }, "devDependencies": { "@eslint/js": "^9.19.0", "@eth-optimism/super-cli": "^0.0.13", - "@types/node": "^22.13.1", + "@types/node": "^25.6.2", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", "@vitejs/plugin-react": "^4.3.4", @@ -56,9 +56,18 @@ "mprocs": "^0.9.2", "prettier": "^3.8.3", "supersim": "0.1.0-alpha.45", - "typescript": "~5.7.2", - "typescript-eslint": "^8.59.1", - "vite": "^6.1.0", + "typescript": "~6.0.3", + "typescript-eslint": "^8.59.2", + "vite": "6.4.2", "wait-port": "^1.1.0" } -} \ No newline at end of file +} + + + + + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41fbfb2..caea721 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@eth-optimism/viem': specifier: ^0.4.15 - version: 0.4.15(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) + version: 0.4.15(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)) '@radix-ui/react-separator': specifier: ^1.1.8 version: 1.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -19,13 +19,13 @@ importers: version: 1.2.4(@types/react@18.3.18)(react@18.3.1) '@tailwindcss/vite': specifier: ^4.2.4 - version: 4.2.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0)) + version: 4.2.4(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)) '@tanstack/react-query': - specifier: ^5.100.8 - version: 5.100.8(react@18.3.1) + specifier: ^5.100.9 + version: 5.100.9(react@18.3.1) abitype: specifier: ^1.2.4 - version: 1.2.4(typescript@5.7.3)(zod@3.24.1) + version: 1.2.4(typescript@6.0.3)(zod@3.24.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -51,21 +51,21 @@ importers: specifier: ^1.0.7 version: 1.0.7(tailwindcss@4.2.4) viem: - specifier: ^2.48.8 - version: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + specifier: ^2.48.11 + version: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) wagmi: specifier: ^2.14.11 - version: 2.14.11(@tanstack/query-core@5.100.8)(@tanstack/react-query@5.100.8(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) + version: 2.14.11(@tanstack/query-core@5.100.9)(@tanstack/react-query@5.100.9(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.1.0)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(utf-8-validate@6.0.6)(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1))(zod@3.24.1) devDependencies: '@eslint/js': specifier: ^9.19.0 version: 9.20.0 '@eth-optimism/super-cli': specifier: ^0.0.13 - version: 0.0.13(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(bufferutil@4.0.9)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(utf-8-validate@5.0.10)(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0)) + version: 0.0.13(@tanstack/query-core@5.100.9)(@types/react@18.3.18)(bufferutil@4.1.0)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@18.3.1))(utf-8-validate@6.0.6)(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)) '@types/node': - specifier: ^22.13.1 - version: 22.13.1 + specifier: ^25.6.2 + version: 25.6.2 '@types/react': specifier: ^18.2.55 version: 18.3.18 @@ -74,16 +74,16 @@ importers: version: 18.3.5(@types/react@18.3.18) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0)) + version: 4.3.4(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)) eslint: specifier: ^9.19.0 - version: 9.20.0(jiti@2.6.1) + version: 9.20.0(jiti@2.7.0) eslint-plugin-react-hooks: specifier: ^5.0.0 - version: 5.1.0(eslint@9.20.0(jiti@2.6.1)) + version: 5.1.0(eslint@9.20.0(jiti@2.7.0)) eslint-plugin-react-refresh: specifier: ^0.5.2 - version: 0.5.2(eslint@9.20.0(jiti@2.6.1)) + version: 0.5.2(eslint@9.20.0(jiti@2.7.0)) globals: specifier: ^15.14.0 version: 15.14.0 @@ -97,14 +97,14 @@ importers: specifier: 0.1.0-alpha.45 version: 0.1.0-alpha.45 typescript: - specifier: ~5.7.2 - version: 5.7.3 + specifier: ~6.0.3 + version: 6.0.3 typescript-eslint: - specifier: ^8.59.1 - version: 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + specifier: ^8.59.2 + version: 8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3) vite: - specifier: ^6.1.0 - version: 6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0) + specifier: 6.4.2 + version: 6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0) wait-port: specifier: ^1.1.0 version: 1.1.0 @@ -118,91 +118,91 @@ packages: resolution: {integrity: sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==} engines: {node: '>=14.13.1'} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.8': - resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.8': - resolution: {integrity: sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.8': - resolution: {integrity: sha512-ef383X5++iZHWAXX0SXQR6ZyQhw/0KtTkrTz61WXRhFM6dhpHulO/RJz79L8S6ugZHJkOOkUrUdxgdF2YiPFnA==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.26.5': - resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.26.5': - resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.7': - resolution: {integrity: sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==} + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.26.8': - resolution: {integrity: sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==} + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.26.7': - resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} - '@babel/template@7.26.8': - resolution: {integrity: sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.8': - resolution: {integrity: sha512-nic9tRkjYH0oB2dzr/JoGIm+4Q6SuYeLEiIiZDwBscRMYFJ+tMAz98fuel9ZnbXViA2I0HVSSRRK8DW5fjXStA==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.8': - resolution: {integrity: sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} '@coinbase/wallet-sdk@3.9.3': @@ -211,178 +211,174 @@ packages: '@coinbase/wallet-sdk@4.3.0': resolution: {integrity: sha512-T3+SNmiCw4HzDm4we9wCHCxlP0pqCiwKe4sOwPH3YAK2KSKjxPRydKu6UQJrdONFVLG7ujXvbd/6ZqmvJb8rkw==} - '@ecies/ciphers@0.2.2': - resolution: {integrity: sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==} - engines: {bun: '>=1', deno: '>=2', node: '>=16'} + '@ecies/ciphers@0.2.6': + resolution: {integrity: sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==} + engines: {bun: '>=1', deno: '>=2.7.10', node: '>=16'} peerDependencies: '@noble/ciphers': ^1.0.0 - '@esbuild/aix-ppc64@0.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.24.2': - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.24.2': - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.24.2': - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.24.2': - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.24.2': - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.24.2': - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.2': - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.24.2': - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.24.2': - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.24.2': - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.24.2': - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.24.2': - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.24.2': - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.24.2': - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.24.2': - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.24.2': - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.24.2': - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.2': - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.2': - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.2': - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.24.2': - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.24.2': - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.24.2': - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.24.2': - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - 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.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.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} @@ -391,16 +387,16 @@ packages: resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.10.0': - resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.11.0': resolution: {integrity: sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.20.0': @@ -411,8 +407,8 @@ packages: resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.5': - resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eth-optimism/super-cli@0.0.13': @@ -452,24 +448,24 @@ packages: peerDependencies: hono: ^4 - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - - '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} '@inkjs/ui@2.0.0': @@ -481,10 +477,6 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - '@jridgewell/remapping@2.3.5': resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} @@ -492,16 +484,9 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -556,8 +541,8 @@ packages: cpu: [x64] os: [win32] - '@lit-labs/ssr-dom-shim@1.3.0': - resolution: {integrity: sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==} + '@lit-labs/ssr-dom-shim@1.5.1': + resolution: {integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==} '@lit/reactive-element@1.6.3': resolution: {integrity: sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==} @@ -618,8 +603,8 @@ packages: resolution: {integrity: sha512-WmGAlP1oBuD9hk4CsdlG1WJFuPtYJY+dnTHJMeCyohTWD2GgkcLMUUuvu9lO1/NVzuOoSi1OrnjbuY1O/1NZ1g==} deprecated: No longer maintained, superseded by https://docs.metamask.io/metamask-connect - '@metamask/superstruct@3.1.0': - resolution: {integrity: sha512-N08M56HdOgBfRKkrgCMZvQppkZGcArEop3kixNEtVbJKm6P9Cfg0YkI6X0s1g78sNrj2fWUwvJADdZuzJgFttA==} + '@metamask/superstruct@3.2.1': + resolution: {integrity: sha512-fLgJnDOXFmuVlB38rUN5SmU7hAFQcCjrg3Vrxz67KTY7YHFnSNEKvX4avmEBdOI0yTCxZjwMCFEqsC8k2+Wd3g==} engines: {node: '>=16.0.0'} '@metamask/utils@5.0.2': @@ -733,98 +718,128 @@ packages: '@types/react': optional: true - '@rollup/rollup-android-arm-eabi@4.34.6': - resolution: {integrity: sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==} + '@rollup/rollup-android-arm-eabi@4.60.3': + resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.34.6': - resolution: {integrity: sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==} + '@rollup/rollup-android-arm64@4.60.3': + resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.34.6': - resolution: {integrity: sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==} + '@rollup/rollup-darwin-arm64@4.60.3': + resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.34.6': - resolution: {integrity: sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==} + '@rollup/rollup-darwin-x64@4.60.3': + resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.34.6': - resolution: {integrity: sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==} + '@rollup/rollup-freebsd-arm64@4.60.3': + resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.34.6': - resolution: {integrity: sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==} + '@rollup/rollup-freebsd-x64@4.60.3': + resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.34.6': - resolution: {integrity: sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.3': + resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.34.6': - resolution: {integrity: sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==} + '@rollup/rollup-linux-arm-musleabihf@4.60.3': + resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.34.6': - resolution: {integrity: sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==} + '@rollup/rollup-linux-arm64-gnu@4.60.3': + resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.34.6': - resolution: {integrity: sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==} + '@rollup/rollup-linux-arm64-musl@4.60.3': + resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.34.6': - resolution: {integrity: sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==} + '@rollup/rollup-linux-loong64-gnu@4.60.3': + resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.3': + resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.34.6': - resolution: {integrity: sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==} + '@rollup/rollup-linux-ppc64-gnu@4.60.3': + resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.34.6': - resolution: {integrity: sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==} + '@rollup/rollup-linux-ppc64-musl@4.60.3': + resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.3': + resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.3': + resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.34.6': - resolution: {integrity: sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==} + '@rollup/rollup-linux-s390x-gnu@4.60.3': + resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.34.6': - resolution: {integrity: sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==} + '@rollup/rollup-linux-x64-gnu@4.60.3': + resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.34.6': - resolution: {integrity: sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==} + '@rollup/rollup-linux-x64-musl@4.60.3': + resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.34.6': - resolution: {integrity: sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==} + '@rollup/rollup-openbsd-x64@4.60.3': + resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.3': + resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.3': + resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.34.6': - resolution: {integrity: sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==} + '@rollup/rollup-win32-ia32-msvc@4.60.3': + resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.34.6': - resolution: {integrity: sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==} + '@rollup/rollup-win32-x64-gnu@4.60.3': + resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.3': + resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} cpu: [x64] os: [win32] @@ -834,8 +849,8 @@ packages: '@safe-global/safe-apps-sdk@9.1.0': resolution: {integrity: sha512-N5p/ulfnnA2Pi2M3YeWjULeWbjo7ei22JwU/IXnhoHzKq3pYCN6ynL9mJBOlvDVv892EgLPCWCOwQk/uBT2v0Q==} - '@safe-global/safe-gateway-typescript-sdk@3.22.9': - resolution: {integrity: sha512-7ojVK/crhOaGowEO8uYWaopZzcr5rR76emgllGIfjCLR70aY4PbASpi9Pbs+7jIRzPDBBkM0RBo+zYx5UduX8Q==} + '@safe-global/safe-gateway-typescript-sdk@3.23.1': + resolution: {integrity: sha512-6ORQfwtEJYpalCeVO21L4XXGSdbEMfyp2hEv6cP82afKXSwvse6d3sdelgaPWUxHIsFRkWvHDdzh8IyyKHZKxw==} engines: {node: '>=16'} '@scure/base@1.1.9': @@ -1003,37 +1018,31 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 - '@tanstack/query-core@5.100.8': - resolution: {integrity: sha512-ceYwSFOqjPwET5TA6IOYxzxlGc0ekyH/gfOtWkP0PX43rzX9bxW48Iuw8KAduKCToi4rJAQ6nRy2kAe8gszdmg==} + '@tanstack/query-core@5.100.9': + resolution: {integrity: sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ==} - '@tanstack/react-query@5.100.8': - resolution: {integrity: sha512-iNNEekixXU5vtAGKKZX2lx3jTooG5yNY+kv0wSgEdEYG0Mj0JM5bcuQtC35ZAP3nDopT6jciUK3xeX65U7AnfA==} + '@tanstack/react-query@5.100.9': + resolution: {integrity: sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A==} peerDependencies: react: ^18 || ^19 '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} '@types/babel__template@7.4.4': resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - - '@types/gensync@1.0.4': - resolution: {integrity: sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/gradient-string@1.1.6': resolution: {integrity: sha512-LkaYxluY4G5wR1M4AKQUal2q61Di1yVVCw42ImFTuaIoQVgmV0WP1xUaLB8zwb47mp82vWTpePI9JmrjEnJ7nQ==} @@ -1044,14 +1053,14 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@22.13.1': - resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==} + '@types/node@25.6.2': + resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} '@types/react-dom@18.3.5': resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} @@ -1067,66 +1076,66 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@types/ws@8.5.14': - resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript-eslint/eslint-plugin@8.59.1': - resolution: {integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==} + '@typescript-eslint/eslint-plugin@8.59.2': + resolution: {integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.59.1 + '@typescript-eslint/parser': ^8.59.2 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.1': - resolution: {integrity: sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==} + '@typescript-eslint/parser@8.59.2': + resolution: {integrity: sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==} 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.1.0' - '@typescript-eslint/project-service@8.59.1': - resolution: {integrity: sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==} + '@typescript-eslint/project-service@8.59.2': + resolution: {integrity: sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.59.1': - resolution: {integrity: sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==} + '@typescript-eslint/scope-manager@8.59.2': + resolution: {integrity: sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.59.1': - resolution: {integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==} + '@typescript-eslint/tsconfig-utils@8.59.2': + resolution: {integrity: sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.1': - resolution: {integrity: sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==} + '@typescript-eslint/type-utils@8.59.2': + resolution: {integrity: sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==} 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.1.0' - '@typescript-eslint/types@8.59.1': - resolution: {integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==} + '@typescript-eslint/types@8.59.2': + resolution: {integrity: sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.59.1': - resolution: {integrity: sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==} + '@typescript-eslint/typescript-estree@8.59.2': + resolution: {integrity: sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.1': - resolution: {integrity: sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==} + '@typescript-eslint/utils@8.59.2': + resolution: {integrity: sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==} 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.1.0' - '@typescript-eslint/visitor-keys@8.59.1': - resolution: {integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==} + '@typescript-eslint/visitor-keys@8.59.2': + resolution: {integrity: sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-react@4.3.4': @@ -1269,13 +1278,13 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ansi-escapes@5.0.0: resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} @@ -1336,9 +1345,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} + baseline-browser-mapping@2.10.27: + resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} + engines: {node: '>=6.0.0'} + hasBin: true bn.js@4.12.3: resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} @@ -1346,49 +1356,37 @@ packages: bn.js@5.2.3: resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} 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'} - brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bufferutil@4.0.9: - resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} + bufferutil@4.1.0: + resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} engines: {node: '>=6.14.2'} - call-bind-apply-helpers@1.0.1: - resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} - engines: {node: '>= 0.4'} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - - call-bound@1.0.3: - resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -1403,11 +1401,11 @@ packages: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - caniuse-lite@1.0.30001699: - resolution: {integrity: sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==} + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} - cfonts@3.3.0: - resolution: {integrity: sha512-RlVxeEw2FXWI5Bs9LD0/Ef3bsQIc9m6lK/DINN20HIW0Y0YHUO2jjy88cot9YKZITiRTCdWzTfLmTyx47HeSLA==} + cfonts@3.3.1: + resolution: {integrity: sha512-ZGEmN3W9mViWEDjsuPo4nK4h39sfh6YtoneFYp9WLPI/rw8BaSSrfQC6jkrGW3JMvV3ZnExJB/AEqXc/nHYxkw==} engines: {node: '>=10'} hasBin: true @@ -1419,9 +1417,9 @@ packages: resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1434,8 +1432,8 @@ packages: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-spinners@3.2.0: - resolution: {integrity: sha512-pXftdQloMZzjCr3pCTIRniDcys6dDzgpgVhAHHk6TKBDbRuP1MkuetTF5KSv4YUutbOPa7+7ZrAJ2kVtbMqyXA==} + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} engines: {node: '>=18.20'} cli-truncate@4.0.0: @@ -1486,8 +1484,8 @@ packages: resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cookie-es@1.2.2: - resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie-es@1.2.3: + resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -1507,11 +1505,11 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crossws@0.3.4: - resolution: {integrity: sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw==} + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} @@ -1521,24 +1519,6 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1575,8 +1555,8 @@ packages: resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} engines: {node: '>=0.10.0'} - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -1586,8 +1566,8 @@ packages: resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==} engines: {node: '>=4'} - destr@2.0.3: - resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} detect-browser@5.3.0: resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} @@ -1706,12 +1686,12 @@ packages: duplexify@4.1.3: resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} - eciesjs@0.4.13: - resolution: {integrity: sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==} + eciesjs@0.4.18: + resolution: {integrity: sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} - electron-to-chromium@1.5.97: - resolution: {integrity: sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==} + electron-to-chromium@1.5.352: + resolution: {integrity: sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg==} elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} @@ -1725,18 +1705,18 @@ packages: encode-utf8@1.0.3: resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - engine.io-client@6.6.3: - resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==} + engine.io-client@6.6.4: + resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==} engine.io-parser@5.2.3: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - enhanced-resolve@5.21.0: - resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} + enhanced-resolve@5.21.1: + resolution: {integrity: sha512-8p7DUVq6XJnZEz9W4oSwiwycxBIjHjRzYb3Je3zVN+geKTRQKzAkR/K4PBExlS0090d9nshak6phMUxr3PDjmQ==} engines: {node: '>=10.13.0'} environment@1.1.0: @@ -1755,11 +1735,15 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - es-toolkit@1.32.0: - resolution: {integrity: sha512-ZfSfHP1l6ubgW/B/FRtqb9bYdMvI6jizbOSfbwwJNcOQ1QE6TFsC3jpQkZ900uUPSR3t3SU5Ds7UWKnYz+uP8Q==} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-toolkit@1.46.1: + resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} - esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} hasBin: true @@ -1816,8 +1800,8 @@ packages: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -1903,16 +1887,12 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - filter-obj@1.1.0: resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} engines: {node: '>=0.10.0'} - find-up-simple@1.0.0: - resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} find-up@4.1.0: @@ -1927,11 +1907,11 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -1939,12 +1919,12 @@ packages: debug: optional: true - for-each@0.3.4: - resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} formdata-polyfill@4.0.10: @@ -1959,6 +1939,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1967,14 +1951,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.3.0: - resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} - get-intrinsic@1.2.7: - resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} - engines: {node: '>= 0.4'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1983,18 +1963,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -2014,8 +1986,8 @@ packages: resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==} engines: {node: '>=10'} - h3@1.15.0: - resolution: {integrity: sha512-OsjX4JW8J4XGgCgEcad20pepFQWnuKH+OwkCJjogF3C+9AZ1iYdtB4hX6vAb5DskBiu5ljEXqApINjR8CqoCMQ==} + h3@1.15.11: + resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -2035,10 +2007,6 @@ packages: hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - hasown@2.0.3: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} @@ -2049,16 +2017,16 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hono@4.7.0: - resolution: {integrity: sha512-hV97aIR4WYbG30k234sD9B3VNr1ZWdQRmrVF76LKFlmI7O9Yo70mG9+mFwyQ6Sjrz4wH71GfnBxv6CPjcx3QNw==} + hono@4.12.18: + resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==} engines: {node: '>=16.9.0'} hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} - idb-keyval@6.2.1: - resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} + idb-keyval@6.2.2: + resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2086,8 +2054,8 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} - index-to-position@0.1.2: - resolution: {integrity: sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} engines: {node: '>=18'} inherits@2.0.4: @@ -2140,10 +2108,6 @@ packages: resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} @@ -2175,8 +2139,8 @@ packages: resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} engines: {node: '>=18'} - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} is-glob@4.0.3: @@ -2192,10 +2156,6 @@ packages: resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} engines: {node: '>=0.10.0'} - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -2226,18 +2186,18 @@ packages: peerDependencies: ws: '*' - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsesc@3.1.0: @@ -2390,6 +2350,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2430,8 +2394,8 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} 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==} mipd@0.0.7: resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==} @@ -2455,8 +2419,8 @@ packages: multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2471,8 +2435,8 @@ packages: engines: {node: '>=10.5.0'} deprecated: Use your platform's native DOMException instead - node-fetch-native@1.6.6: - resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} @@ -2491,11 +2455,11 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-mock-http@1.0.0: - resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==} + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} normalize-package-data@6.0.2: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} @@ -2512,11 +2476,8 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - ofetch@1.4.1: - resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} - - ohash@1.1.4: - resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} on-exit-leak-free@0.2.0: resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==} @@ -2564,8 +2525,8 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-json@8.1.0: - resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} pastel@3.0.0: @@ -2633,13 +2594,10 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.5.1: - resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} - preact@10.25.4: - resolution: {integrity: sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==} - preact@10.29.1: resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} @@ -2667,8 +2625,8 @@ packages: proxy-compare@2.5.1: resolution: {integrity: sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==} - pump@3.0.2: - resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} @@ -2726,17 +2684,14 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} real-require@0.1.0: resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} engines: {node: '>= 12.13.0'} - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -2752,8 +2707,8 @@ packages: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - rollup@4.34.6: - resolution: {integrity: sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==} + rollup@4.60.3: + resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2814,16 +2769,16 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} - smol-toml@1.3.1: - resolution: {integrity: sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==} + smol-toml@1.6.1: + resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} - socket.io-client@4.8.1: - resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} + socket.io-client@4.8.3: + resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} engines: {node: '>=10.0.0'} - socket.io-parser@4.2.4: - resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + socket.io-parser@4.2.6: + resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} engines: {node: '>=10.0.0'} sonic-boom@2.8.0: @@ -2842,8 +2797,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.21: - resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} split-on-first@1.1.0: resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} @@ -2947,10 +2902,6 @@ packages: resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} engines: {node: '>= 0.4'} - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -2978,24 +2929,28 @@ packages: resolution: {integrity: sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==} engines: {node: '>=16'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} - typescript-eslint@8.59.1: - resolution: {integrity: sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==} + typescript-eslint@8.59.2: + resolution: {integrity: sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==} 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.1.0' - typescript@5.7.3: - resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true - ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} uint8arrays@3.1.0: resolution: {integrity: sha512-ei5rfKtoRO8OyOIor2Rz5fhzjThwIHJZ3uyDPnDHTXbP0aMQ1RN/6AI5B5d9dBxJOU+BvOAk7ZQ1xphsX8Lrog==} @@ -3003,34 +2958,35 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} - unstorage@1.14.4: - resolution: {integrity: sha512-1SYeamwuYeQJtJ/USE1x4l17LkmQBzg7deBJ+U9qOBoHo15d1cDxG4jM31zKRgF7pG0kirZy4wVMX6WL6Zoscg==} + unstorage@1.17.5: + resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} peerDependencies: '@azure/app-configuration': ^1.8.0 '@azure/cosmos': ^4.2.0 '@azure/data-tables': ^13.3.0 - '@azure/identity': ^4.5.0 + '@azure/identity': ^4.6.0 '@azure/keyvault-secrets': ^4.9.0 '@azure/storage-blob': ^12.26.0 - '@capacitor/preferences': ^6.0.3 - '@deno/kv': '>=0.8.4' - '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 '@planetscale/database': ^1.19.0 '@upstash/redis': ^1.34.3 - '@vercel/blob': '>=0.27.0' - '@vercel/kv': ^1.0.1 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 aws4fetch: ^1.0.20 db0: '>=0.2.1' idb-keyval: ^6.2.1 ioredis: ^5.4.2 - uploadthing: ^7.4.1 + uploadthing: ^7.4.4 peerDependenciesMeta: '@azure/app-configuration': optional: true @@ -3056,6 +3012,8 @@ packages: optional: true '@vercel/blob': optional: true + '@vercel/functions': + optional: true '@vercel/kv': optional: true aws4fetch: @@ -3069,8 +3027,8 @@ packages: uploadthing: optional: true - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -3088,10 +3046,19 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + utf-8-validate@5.0.10: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} + utf-8-validate@6.0.6: + resolution: {integrity: sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==} + engines: {node: '>=6.14.2'} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -3123,16 +3090,16 @@ packages: react: optional: true - viem@2.48.8: - resolution: {integrity: sha512-Xj3Nrt66SKtn06kczU91ELn9Difr84ZM5A62BTlaisT5lpgt058i2mBkfMZCXHGb1ocOLjzC2ztPhD0Lvky7uQ==} + viem@2.48.11: + resolution: {integrity: sha512-+WZ5E0dBS6GtKb+1wEk5DeYRRRW42+pFnXCo67Ydodf42sBwO+hu3wnQy66lc4MKmHz+llPVdbyehYr9oTE2iw==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: typescript: optional: true - vite@6.1.0: - resolution: {integrity: sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==} + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -3203,8 +3170,8 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-typed-array@1.1.18: - resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} which@2.0.2: @@ -3248,18 +3215,6 @@ packages: utf-8-validate: optional: true - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -3367,125 +3322,119 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 4.0.0 - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - - '@babel/code-frame@7.26.2': + '@babel/code-frame@7.29.0': dependencies: - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.26.8': {} - - '@babel/core@7.26.8': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.8 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.8) - '@babel/helpers': 7.26.7 - '@babel/parser': 7.26.8 - '@babel/template': 7.26.8 - '@babel/traverse': 7.26.8 - '@babel/types': 7.26.8 - '@types/gensync': 1.0.4 + '@babel/compat-data@7.29.3': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.26.8': + '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.26.8 - '@babel/types': 7.26.8 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.26.5': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.26.8 - '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.4 + '@babel/compat-data': 7.29.3 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-module-imports@7.25.9': + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.26.8 - '@babel/types': 7.26.8 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.8)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.26.8 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.8 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.26.5': {} + '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-option@7.25.9': {} + '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.26.7': + '@babel/helpers@7.29.2': dependencies: - '@babel/template': 7.26.8 - '@babel/types': 7.26.8 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 - '@babel/parser@7.26.8': + '@babel/parser@7.29.3': dependencies: - '@babel/types': 7.26.8 + '@babel/types': 7.29.0 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.8)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.26.8 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.8)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.26.8 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/runtime@7.26.7': - dependencies: - regenerator-runtime: 0.14.1 + '@babel/runtime@7.29.2': {} - '@babel/template@7.26.8': + '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.8 - '@babel/types': 7.26.8 + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 - '@babel/traverse@7.26.8': + '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.8 - '@babel/parser': 7.26.8 - '@babel/template': 7.26.8 - '@babel/types': 7.26.8 - debug: 4.4.0 - globals: 11.12.0 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.26.8': + '@babel/types@7.29.0': dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@coinbase/wallet-sdk@3.9.3': dependencies: @@ -3506,127 +3455,123 @@ snapshots: '@noble/hashes': 1.8.0 clsx: 1.2.1 eventemitter3: 5.0.4 - preact: 10.25.4 + preact: 10.29.1 - '@ecies/ciphers@0.2.2(@noble/ciphers@1.3.0)': + '@ecies/ciphers@0.2.6(@noble/ciphers@1.3.0)': dependencies: '@noble/ciphers': 1.3.0 - '@esbuild/aix-ppc64@0.24.2': + '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/android-arm64@0.24.2': + '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm@0.24.2': + '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-x64@0.24.2': + '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.24.2': + '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.24.2': + '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.24.2': + '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.24.2': + '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/linux-arm64@0.24.2': + '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm@0.24.2': + '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-ia32@0.24.2': + '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-loong64@0.24.2': + '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-mips64el@0.24.2': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.24.2': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.24.2': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-s390x@0.24.2': + '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-x64@0.24.2': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.24.2': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.24.2': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.24.2': + '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.24.2': + '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/sunos-x64@0.24.2': + '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.24.2': + '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-ia32@0.24.2': + '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-x64@0.24.2': + '@esbuild/win32-ia32@0.25.12': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.20.0(jiti@2.6.1))': - dependencies: - eslint: 9.20.0(jiti@2.6.1) - eslint-visitor-keys: 3.4.3 + '@esbuild/win32-x64@0.25.12': + optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.20.0(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.20.0(jiti@2.7.0))': dependencies: - eslint: 9.20.0(jiti@2.6.1) + eslint: 9.20.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} - '@eslint-community/regexpp@4.12.2': {} '@eslint/config-array@0.19.2': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.0 - minimatch: 3.1.2 + debug: 4.4.3 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color - '@eslint/core@0.10.0': + '@eslint/core@0.11.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/core@0.11.0': + '@eslint/core@0.13.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.2.0': + '@eslint/eslintrc@3.3.5': dependencies: - ajv: 6.12.6 - debug: 4.4.0 + ajv: 6.15.0 + debug: 4.4.3 espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 + js-yaml: 4.1.1 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color @@ -3635,42 +3580,42 @@ snapshots: '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.5': + '@eslint/plugin-kit@0.2.8': dependencies: - '@eslint/core': 0.10.0 + '@eslint/core': 0.13.0 levn: 0.4.1 - '@eth-optimism/super-cli@0.0.13(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(bufferutil@4.0.9)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(utf-8-validate@5.0.10)(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0))': + '@eth-optimism/super-cli@0.0.13(@tanstack/query-core@5.100.9)(@types/react@18.3.18)(bufferutil@4.1.0)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@18.3.1))(utf-8-validate@6.0.6)(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0))': dependencies: - '@eth-optimism/viem': 0.3.3(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) - '@hono/node-server': 1.13.8(hono@4.7.0) - '@inkjs/ui': 2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10)) - '@libsql/client': 0.14.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@tanstack/react-query': 5.100.8(react@18.3.1) - '@vitejs/plugin-react': 4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0)) - '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) - abitype: 1.2.4(typescript@5.7.3)(zod@3.24.1) + '@eth-optimism/viem': 0.3.3(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)) + '@hono/node-server': 1.13.8(hono@4.12.18) + '@inkjs/ui': 2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6)) + '@libsql/client': 0.14.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + '@tanstack/react-query': 5.100.9(react@18.3.1) + '@vitejs/plugin-react': 4.3.4(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)) + '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.9)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@18.3.1))(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)) + abitype: 1.2.4(typescript@6.0.3)(zod@3.24.1) chalk: 5.4.1 dependency-graph: 1.0.0 dotenv: 16.4.7 - drizzle-orm: 0.38.4(@libsql/client@0.14.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@types/react@18.3.18)(react@18.3.1) + drizzle-orm: 0.38.4(@libsql/client@0.14.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@18.3.18)(react@18.3.1) fast-json-stable-stringify: 2.1.0 figures: 6.1.0 - form-data: 4.0.1 - hono: 4.7.0 + form-data: 4.0.5 + hono: 4.12.18 immer: 10.1.1 - ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) - ink-big-text: 2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) - ink-gradient: 3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10)) - ink-link: 4.1.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10)) - pastel: 3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(zod@3.24.1) + ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) + ink-big-text: 2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))(react@18.3.1) + ink-gradient: 3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6)) + ink-link: 4.1.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6)) + pastel: 3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))(react@18.3.1)(zod@3.24.1) react: 18.3.1 - smol-toml: 1.3.1 - viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) - wagmi: 2.14.11(@tanstack/query-core@5.100.8)(@tanstack/react-query@5.100.8(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) + smol-toml: 1.6.1 + viem: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) + wagmi: 2.14.11(@tanstack/query-core@5.100.9)(@tanstack/react-query@5.100.9(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.1.0)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(utf-8-validate@6.0.6)(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1))(zod@3.24.1) zod: 3.24.1 zod-validation-error: 3.4.0(zod@3.24.1) - zustand: 5.0.3(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) + zustand: 5.0.3(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) transitivePeerDependencies: - '@aws-sdk/client-rds-data' - '@azure/app-configuration' @@ -3699,6 +3644,7 @@ snapshots: - '@types/sql.js' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - '@vercel/postgres' - '@xata.io/client' @@ -3726,13 +3672,13 @@ snapshots: - utf-8-validate - vite - '@eth-optimism/viem@0.3.3(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))': + '@eth-optimism/viem@0.3.3(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1))': dependencies: - viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) - '@eth-optimism/viem@0.4.15(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))': + '@eth-optimism/viem@0.4.15(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1))': dependencies: - viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) '@ethereumjs/common@3.2.0': dependencies: @@ -3754,42 +3700,39 @@ snapshots: ethereum-cryptography: 2.2.1 micro-ftch: 0.3.1 - '@hono/node-server@1.13.8(hono@4.7.0)': + '@hono/node-server@1.13.8(hono@4.12.18)': dependencies: - hono: 4.7.0 + hono: 4.12.18 - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 - '@humanfs/node@0.16.6': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 - '@humanwhocodes/module-importer@1.0.1': {} + '@humanfs/types@0.15.0': {} - '@humanwhocodes/retry@0.3.1': {} + '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.4.1': {} + '@humanwhocodes/retry@0.4.3': {} - '@inkjs/ui@2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10))': + '@inkjs/ui@2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))': dependencies: chalk: 5.4.1 - cli-spinners: 3.2.0 + cli-spinners: 3.4.0 deepmerge: 4.3.1 figures: 6.1.0 - ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) + ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.25 - '@jridgewell/remapping@2.3.5': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -3797,25 +3740,18 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} - '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@libsql/client@0.14.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@libsql/client@0.14.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: '@libsql/core': 0.14.0 - '@libsql/hrana-client': 0.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - js-base64: 3.7.7 + '@libsql/hrana-client': 0.7.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + js-base64: 3.7.8 libsql: 0.4.7 promise-limit: 2.7.0 transitivePeerDependencies: @@ -3824,7 +3760,7 @@ snapshots: '@libsql/core@0.14.0': dependencies: - js-base64: 3.7.7 + js-base64: 3.7.8 '@libsql/darwin-arm64@0.4.7': optional: true @@ -3832,11 +3768,11 @@ snapshots: '@libsql/darwin-x64@0.4.7': optional: true - '@libsql/hrana-client@0.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@libsql/hrana-client@0.7.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: '@libsql/isomorphic-fetch': 0.3.1 - '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) - js-base64: 3.7.7 + '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6) + js-base64: 3.7.8 node-fetch: 3.3.2 transitivePeerDependencies: - bufferutil @@ -3844,10 +3780,10 @@ snapshots: '@libsql/isomorphic-fetch@0.3.1': {} - '@libsql/isomorphic-ws@0.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@libsql/isomorphic-ws@0.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: - '@types/ws': 8.5.14 - ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@types/ws': 8.18.1 + ws: 8.20.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -3867,11 +3803,11 @@ snapshots: '@libsql/win32-x64-msvc@0.4.7': optional: true - '@lit-labs/ssr-dom-shim@1.3.0': {} + '@lit-labs/ssr-dom-shim@1.5.1': {} '@lit/reactive-element@1.6.3': dependencies: - '@lit-labs/ssr-dom-shim': 1.3.0 + '@lit-labs/ssr-dom-shim': 1.5.1 '@metamask/eth-json-rpc-provider@1.0.1': dependencies: @@ -3913,7 +3849,7 @@ snapshots: '@metamask/onboarding@1.0.1': dependencies: - bowser: 2.11.0 + bowser: 2.14.1 '@metamask/providers@16.1.0': dependencies: @@ -3943,16 +3879,16 @@ snapshots: '@metamask/safe-event-emitter@3.1.2': {} - '@metamask/sdk-communication-layer@0.32.0(cross-fetch@4.1.0)(eciesjs@0.4.13)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@metamask/sdk-communication-layer@0.32.0(cross-fetch@4.1.0)(eciesjs@0.4.18)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))': dependencies: - bufferutil: 4.0.9 + bufferutil: 4.1.0 cross-fetch: 4.1.0 date-fns: 2.30.0 debug: 4.4.3 - eciesjs: 0.4.13 + eciesjs: 0.4.18 eventemitter2: 6.4.9 readable-stream: 3.6.2 - socket.io-client: 4.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + socket.io-client: 4.8.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) utf-8-validate: 5.0.10 uuid: 8.3.2 transitivePeerDependencies: @@ -3962,24 +3898,24 @@ snapshots: dependencies: '@paulmillr/qr': 0.2.1 - '@metamask/sdk@0.32.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@metamask/sdk@0.32.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.29.2 '@metamask/onboarding': 1.0.1 '@metamask/providers': 16.1.0 - '@metamask/sdk-communication-layer': 0.32.0(cross-fetch@4.1.0)(eciesjs@0.4.13)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@metamask/sdk-communication-layer': 0.32.0(cross-fetch@4.1.0)(eciesjs@0.4.18)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) '@metamask/sdk-install-modal-web': 0.32.0 '@paulmillr/qr': 0.2.1 - bowser: 2.11.0 + bowser: 2.14.1 cross-fetch: 4.1.0 debug: 4.4.3 - eciesjs: 0.4.13 + eciesjs: 0.4.18 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 obj-multiplex: 1.0.0 - pump: 3.0.2 + pump: 3.0.4 readable-stream: 3.6.2 - socket.io-client: 4.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + socket.io-client: 4.8.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) tslib: 2.8.1 util: 0.12.5 uuid: 8.3.2 @@ -3989,7 +3925,7 @@ snapshots: - supports-color - utf-8-validate - '@metamask/superstruct@3.1.0': {} + '@metamask/superstruct@3.2.1': {} '@metamask/utils@5.0.2': dependencies: @@ -4004,10 +3940,10 @@ snapshots: '@metamask/utils@8.5.0': dependencies: '@ethereumjs/tx': 4.2.0 - '@metamask/superstruct': 3.1.0 + '@metamask/superstruct': 3.2.1 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 - '@types/debug': 4.1.12 + '@types/debug': 4.1.13 debug: 4.4.3 pony-cause: 2.1.11 semver: 7.7.4 @@ -4018,7 +3954,7 @@ snapshots: '@metamask/utils@9.3.0': dependencies: '@ethereumjs/tx': 4.2.0 - '@metamask/superstruct': 3.1.0 + '@metamask/superstruct': 3.2.1 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.13 @@ -4127,66 +4063,84 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 - '@rollup/rollup-android-arm-eabi@4.34.6': + '@rollup/rollup-android-arm-eabi@4.60.3': + optional: true + + '@rollup/rollup-android-arm64@4.60.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.3': optional: true - '@rollup/rollup-android-arm64@4.34.6': + '@rollup/rollup-darwin-x64@4.60.3': optional: true - '@rollup/rollup-darwin-arm64@4.34.6': + '@rollup/rollup-freebsd-arm64@4.60.3': optional: true - '@rollup/rollup-darwin-x64@4.34.6': + '@rollup/rollup-freebsd-x64@4.60.3': optional: true - '@rollup/rollup-freebsd-arm64@4.34.6': + '@rollup/rollup-linux-arm-gnueabihf@4.60.3': optional: true - '@rollup/rollup-freebsd-x64@4.34.6': + '@rollup/rollup-linux-arm-musleabihf@4.60.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.6': + '@rollup/rollup-linux-arm64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.6': + '@rollup/rollup-linux-arm64-musl@4.60.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.6': + '@rollup/rollup-linux-loong64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.34.6': + '@rollup/rollup-linux-loong64-musl@4.60.3': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.6': + '@rollup/rollup-linux-ppc64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.34.6': + '@rollup/rollup-linux-ppc64-musl@4.60.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.34.6': + '@rollup/rollup-linux-riscv64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.34.6': + '@rollup/rollup-linux-riscv64-musl@4.60.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.34.6': + '@rollup/rollup-linux-s390x-gnu@4.60.3': optional: true - '@rollup/rollup-linux-x64-musl@4.34.6': + '@rollup/rollup-linux-x64-gnu@4.60.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.34.6': + '@rollup/rollup-linux-x64-musl@4.60.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.34.6': + '@rollup/rollup-openbsd-x64@4.60.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.34.6': + '@rollup/rollup-openharmony-arm64@4.60.3': optional: true - '@safe-global/safe-apps-provider@0.18.5(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)': + '@rollup/rollup-win32-arm64-msvc@4.60.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.3': + optional: true + + '@safe-global/safe-apps-provider@0.18.5(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)': dependencies: - '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) events: 3.3.0 transitivePeerDependencies: - bufferutil @@ -4194,17 +4148,17 @@ snapshots: - utf-8-validate - zod - '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)': + '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)': dependencies: - '@safe-global/safe-gateway-typescript-sdk': 3.22.9 - viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + '@safe-global/safe-gateway-typescript-sdk': 3.23.1 + viem: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod - '@safe-global/safe-gateway-typescript-sdk@3.22.9': {} + '@safe-global/safe-gateway-typescript-sdk@3.23.1': {} '@scure/base@1.1.9': {} @@ -4317,8 +4271,8 @@ snapshots: '@tailwindcss/node@4.2.4': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.0 - jiti: 2.6.1 + enhanced-resolve: 5.21.1 + jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 @@ -4375,52 +4329,46 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 - '@tailwindcss/vite@4.2.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0))': + '@tailwindcss/vite@4.2.4(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0))': dependencies: '@tailwindcss/node': 4.2.4 '@tailwindcss/oxide': 4.2.4 tailwindcss: 4.2.4 - vite: 6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0) + vite: 6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0) - '@tanstack/query-core@5.100.8': {} + '@tanstack/query-core@5.100.9': {} - '@tanstack/react-query@5.100.8(react@18.3.1)': + '@tanstack/react-query@5.100.9(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.100.8 + '@tanstack/query-core': 5.100.9 react: 18.3.1 '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.8 - '@babel/types': 7.26.8 - '@types/babel__generator': 7.6.8 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 + '@types/babel__traverse': 7.28.0 - '@types/babel__generator@7.6.8': + '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.26.8 + '@babel/types': 7.29.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.8 - '@babel/types': 7.26.8 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 - '@types/babel__traverse@7.20.6': + '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.26.8 - - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 + '@babel/types': 7.29.0 '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 - '@types/estree@1.0.6': {} - - '@types/gensync@1.0.4': {} + '@types/estree@1.0.8': {} '@types/gradient-string@1.1.6': dependencies: @@ -4430,13 +4378,13 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@22.13.1': + '@types/node@25.6.2': dependencies: - undici-types: 6.20.0 + undici-types: 7.19.2 '@types/normalize-package-data@2.4.4': {} - '@types/prop-types@15.7.14': {} + '@types/prop-types@15.7.15': {} '@types/react-dom@18.3.5(@types/react@18.3.18)': dependencies: @@ -4444,131 +4392,131 @@ snapshots: '@types/react@18.3.18': dependencies: - '@types/prop-types': 15.7.14 - csstype: 3.1.3 + '@types/prop-types': 15.7.15 + csstype: 3.2.3 '@types/tinycolor2@1.4.6': {} '@types/trusted-types@2.0.7': {} - '@types/ws@8.5.14': + '@types/ws@8.18.1': dependencies: - '@types/node': 22.13.1 + '@types/node': 25.6.2 - '@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3))(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3)': + '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/type-utils': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) - '@typescript-eslint/utils': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.59.1 - eslint: 9.20.0(jiti@2.6.1) + '@typescript-eslint/parser': 8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/type-utils': 8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.2 + eslint: 9.20.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.7.3) - typescript: 5.7.3 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3)': + '@typescript-eslint/parser@8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.59.1 + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.2 debug: 4.4.3 - eslint: 9.20.0(jiti@2.6.1) - typescript: 5.7.3 + eslint: 9.20.0(jiti@2.7.0) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.1(typescript@5.7.3)': + '@typescript-eslint/project-service@8.59.2(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.7.3) - '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@6.0.3) + '@typescript-eslint/types': 8.59.2 debug: 4.4.3 - typescript: 5.7.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.1': + '@typescript-eslint/scope-manager@8.59.2': dependencies: - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/visitor-keys': 8.59.1 + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/visitor-keys': 8.59.2 - '@typescript-eslint/tsconfig-utils@8.59.1(typescript@5.7.3)': + '@typescript-eslint/tsconfig-utils@8.59.2(typescript@6.0.3)': dependencies: - typescript: 5.7.3 + typescript: 6.0.3 - '@typescript-eslint/type-utils@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3)': + '@typescript-eslint/type-utils@8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.7.3) - '@typescript-eslint/utils': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 9.20.0(jiti@2.6.1) - ts-api-utils: 2.5.0(typescript@5.7.3) - typescript: 5.7.3 + eslint: 9.20.0(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.1': {} + '@typescript-eslint/types@8.59.2': {} - '@typescript-eslint/typescript-estree@8.59.1(typescript@5.7.3)': + '@typescript-eslint/typescript-estree@8.59.2(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.59.1(typescript@5.7.3) - '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.7.3) - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/visitor-keys': 8.59.1 + '@typescript-eslint/project-service': 8.59.2(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@6.0.3) + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/visitor-keys': 8.59.2 debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 tinyglobby: 0.2.16 - ts-api-utils: 2.5.0(typescript@5.7.3) - typescript: 5.7.3 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3)': + '@typescript-eslint/utils@8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.20.0(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.7.3) - eslint: 9.20.0(jiti@2.6.1) - typescript: 5.7.3 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.20.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3) + eslint: 9.20.0(jiti@2.7.0) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.1': + '@typescript-eslint/visitor-keys@8.59.2': dependencies: - '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/types': 8.59.2 eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0))': + '@vitejs/plugin-react@4.3.4(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0))': dependencies: - '@babel/core': 7.26.8 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.8) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.8) + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0) + vite: 6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0) transitivePeerDependencies: - supports-color - '@wagmi/connectors@5.7.7(@types/react@18.3.18)(@wagmi/core@2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)))(bufferutil@4.0.9)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1)': + '@wagmi/connectors@5.7.7(@types/react@18.3.18)(@wagmi/core@2.16.4(@tanstack/query-core@5.100.9)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)))(bufferutil@4.1.0)(react@18.3.1)(typescript@6.0.3)(utf-8-validate@6.0.6)(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1))(zod@3.24.1)': dependencies: '@coinbase/wallet-sdk': 4.3.0 - '@metamask/sdk': 0.32.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@safe-global/safe-apps-provider': 0.18.5(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) - '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) - '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) - '@walletconnect/ethereum-provider': 2.17.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) + '@metamask/sdk': 0.32.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + '@safe-global/safe-apps-provider': 0.18.5(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) + '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.9)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@18.3.1))(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)) + '@walletconnect/ethereum-provider': 2.17.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) cbw-sdk: '@coinbase/wallet-sdk@3.9.3' - viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) optionalDependencies: - typescript: 5.7.3 + typescript: 6.0.3 transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -4584,6 +4532,7 @@ snapshots: - '@types/react' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - bufferutil @@ -4596,28 +4545,28 @@ snapshots: - utf-8-validate - zod - '@wagmi/core@2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))': + '@wagmi/core@2.16.4(@tanstack/query-core@5.100.9)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@18.3.1))(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1))': dependencies: eventemitter3: 5.0.1 - mipd: 0.0.7(typescript@5.7.3) - viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) - zustand: 5.0.0(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) + mipd: 0.0.7(typescript@6.0.3) + viem: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) + zustand: 5.0.0(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) optionalDependencies: - '@tanstack/query-core': 5.100.8 - typescript: 5.7.3 + '@tanstack/query-core': 5.100.9 + typescript: 6.0.3 transitivePeerDependencies: - '@types/react' - immer - react - use-sync-external-store - '@walletconnect/core@2.17.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@walletconnect/core@2.17.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/jsonrpc-ws-connection': 1.0.14(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@walletconnect/jsonrpc-ws-connection': 1.0.14(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@walletconnect/keyvaluestorage': 1.1.1 '@walletconnect/logger': 2.1.2 '@walletconnect/relay-api': 1.0.11 @@ -4643,6 +4592,7 @@ snapshots: - '@react-native-async-storage/async-storage' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - bufferutil @@ -4655,16 +4605,16 @@ snapshots: dependencies: tslib: 1.14.1 - '@walletconnect/ethereum-provider@2.17.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10)': + '@walletconnect/ethereum-provider@2.17.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6)': dependencies: '@walletconnect/jsonrpc-http-connection': 1.0.8 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/modal': 2.7.0(@types/react@18.3.18)(react@18.3.1) - '@walletconnect/sign-client': 2.17.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@walletconnect/sign-client': 2.17.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@walletconnect/types': 2.17.0 - '@walletconnect/universal-provider': 2.17.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@walletconnect/universal-provider': 2.17.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@walletconnect/utils': 2.17.0 events: 3.3.0 transitivePeerDependencies: @@ -4682,6 +4632,7 @@ snapshots: - '@types/react' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - bufferutil @@ -4729,12 +4680,12 @@ snapshots: '@walletconnect/jsonrpc-types': 1.0.4 tslib: 1.14.1 - '@walletconnect/jsonrpc-ws-connection@1.0.14(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@walletconnect/jsonrpc-ws-connection@1.0.14(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/safe-json': 1.0.2 events: 3.3.0 - ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -4742,8 +4693,8 @@ snapshots: '@walletconnect/keyvaluestorage@1.1.1': dependencies: '@walletconnect/safe-json': 1.0.2 - idb-keyval: 6.2.1 - unstorage: 1.14.4(idb-keyval@6.2.1) + idb-keyval: 6.2.2 + unstorage: 1.17.5(idb-keyval@6.2.2) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -4757,6 +4708,7 @@ snapshots: - '@planetscale/database' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - db0 @@ -4810,9 +4762,9 @@ snapshots: dependencies: tslib: 1.14.1 - '@walletconnect/sign-client@2.17.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@walletconnect/sign-client@2.17.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: - '@walletconnect/core': 2.17.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@walletconnect/core': 2.17.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-utils': 1.0.8 @@ -4835,6 +4787,7 @@ snapshots: - '@react-native-async-storage/async-storage' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - bufferutil @@ -4869,20 +4822,21 @@ snapshots: - '@react-native-async-storage/async-storage' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - db0 - ioredis - uploadthing - '@walletconnect/universal-provider@2.17.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@walletconnect/universal-provider@2.17.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: '@walletconnect/jsonrpc-http-connection': 1.0.8 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.17.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@walletconnect/sign-client': 2.17.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@walletconnect/types': 2.17.0 '@walletconnect/utils': 2.17.0 events: 3.3.0 @@ -4900,6 +4854,7 @@ snapshots: - '@react-native-async-storage/async-storage' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - bufferutil @@ -4941,6 +4896,7 @@ snapshots: - '@react-native-async-storage/async-storage' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - db0 @@ -4956,23 +4912,23 @@ snapshots: '@walletconnect/window-getters': 1.0.1 tslib: 1.14.1 - abitype@1.2.3(typescript@5.7.3)(zod@3.24.1): + abitype@1.2.3(typescript@6.0.3)(zod@3.24.1): optionalDependencies: - typescript: 5.7.3 + typescript: 6.0.3 zod: 3.24.1 - abitype@1.2.4(typescript@5.7.3)(zod@3.24.1): + abitype@1.2.4(typescript@6.0.3)(zod@3.24.1): optionalDependencies: - typescript: 5.7.3 + typescript: 6.0.3 zod: 3.24.1 - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: - acorn: 8.14.0 + acorn: 8.16.0 - acorn@8.14.0: {} + acorn@8.16.0: {} - ajv@6.12.6: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -5024,15 +4980,15 @@ snapshots: base64-js@1.5.1: {} - binary-extensions@2.3.0: {} + baseline-browser-mapping@2.10.27: {} bn.js@4.12.3: {} bn.js@5.2.3: {} - bowser@2.11.0: {} + bowser@2.14.1: {} - brace-expansion@1.1.11: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -5041,50 +4997,37 @@ snapshots: dependencies: balanced-match: 4.0.4 - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - brorand@1.1.0: {} - browserslist@4.24.4: + browserslist@4.28.2: dependencies: - caniuse-lite: 1.0.30001699 - electron-to-chromium: 1.5.97 - node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) + baseline-browser-mapping: 2.10.27 + caniuse-lite: 1.0.30001792 + electron-to-chromium: 1.5.352 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer@6.0.3: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - bufferutil@4.0.9: + bufferutil@4.1.0: dependencies: node-gyp-build: 4.8.4 - call-bind-apply-helpers@1.0.1: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 set-function-length: 1.2.2 - call-bound@1.0.3: - dependencies: - call-bind-apply-helpers: 1.0.1 - get-intrinsic: 1.2.7 - call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5094,9 +5037,9 @@ snapshots: camelcase@5.3.1: {} - caniuse-lite@1.0.30001699: {} + caniuse-lite@1.0.30001792: {} - cfonts@3.3.0: + cfonts@3.3.1: dependencies: supports-color: 8.1.1 window-size: 1.1.1 @@ -5108,17 +5051,9 @@ snapshots: chalk@5.4.1: {} - chokidar@3.6.0: + chokidar@5.0.0: dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 + readdirp: 5.0.0 class-variance-authority@0.7.1: dependencies: @@ -5130,7 +5065,7 @@ snapshots: dependencies: restore-cursor: 4.0.0 - cli-spinners@3.2.0: {} + cli-spinners@3.4.0: {} cli-truncate@4.0.0: dependencies: @@ -5171,7 +5106,7 @@ snapshots: convert-to-spaces@2.0.1: {} - cookie-es@1.2.2: {} + cookie-es@1.2.3: {} core-util-is@1.0.3: {} @@ -5195,25 +5130,17 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crossws@0.3.4: + crossws@0.3.5: dependencies: uncrypto: 0.1.3 - csstype@3.1.3: {} + csstype@3.2.3: {} data-uri-to-buffer@4.0.1: {} date-fns@2.30.0: dependencies: - '@babel/runtime': 7.26.7 - - debug@4.3.7: - dependencies: - ms: 2.1.3 - - debug@4.4.0: - dependencies: - ms: 2.1.3 + '@babel/runtime': 7.29.2 debug@4.4.3: dependencies: @@ -5239,13 +5166,13 @@ snapshots: dependencies: is-descriptor: 1.0.3 - defu@6.1.4: {} + defu@6.1.7: {} delayed-stream@1.0.0: {} dependency-graph@1.0.0: {} - destr@2.0.3: {} + destr@2.0.5: {} detect-browser@5.3.0: {} @@ -5257,33 +5184,33 @@ snapshots: dotenv@16.4.7: {} - drizzle-orm@0.38.4(@libsql/client@0.14.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@types/react@18.3.18)(react@18.3.1): + drizzle-orm@0.38.4(@libsql/client@0.14.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@18.3.18)(react@18.3.1): optionalDependencies: - '@libsql/client': 0.14.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@libsql/client': 0.14.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@types/react': 18.3.18 react: 18.3.1 dunder-proto@1.0.1: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 duplexify@4.1.3: dependencies: - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 inherits: 2.0.4 readable-stream: 3.6.2 stream-shift: 1.0.3 - eciesjs@0.4.13: + eciesjs@0.4.18: dependencies: - '@ecies/ciphers': 0.2.2(@noble/ciphers@1.3.0) + '@ecies/ciphers': 0.2.6(@noble/ciphers@1.3.0) '@noble/ciphers': 1.3.0 '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 - electron-to-chromium@1.5.97: {} + electron-to-chromium@1.5.352: {} elliptic@6.6.1: dependencies: @@ -5301,16 +5228,16 @@ snapshots: encode-utf8@1.0.3: {} - end-of-stream@1.4.4: + end-of-stream@1.4.5: dependencies: once: 1.4.0 - engine.io-client@6.6.3(bufferutil@4.0.9)(utf-8-validate@5.0.10): + engine.io-client@6.6.4(bufferutil@4.1.0)(utf-8-validate@6.0.6): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 + debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) xmlhttprequest-ssl: 2.1.2 transitivePeerDependencies: - bufferutil @@ -5319,7 +5246,7 @@ snapshots: engine.io-parser@5.2.3: {} - enhanced-resolve@5.21.0: + enhanced-resolve@5.21.1: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -5334,35 +5261,43 @@ snapshots: dependencies: es-errors: 1.3.0 - es-toolkit@1.32.0: {} + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 - esbuild@0.24.2: + es-toolkit@1.46.1: {} + + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.2.0: {} @@ -5370,13 +5305,13 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.1.0(eslint@9.20.0(jiti@2.6.1)): + eslint-plugin-react-hooks@5.1.0(eslint@9.20.0(jiti@2.7.0)): dependencies: - eslint: 9.20.0(jiti@2.6.1) + eslint: 9.20.0(jiti@2.7.0) - eslint-plugin-react-refresh@0.5.2(eslint@9.20.0(jiti@2.6.1)): + eslint-plugin-react-refresh@0.5.2(eslint@9.20.0(jiti@2.7.0)): dependencies: - eslint: 9.20.0(jiti@2.6.1) + eslint: 9.20.0(jiti@2.7.0) eslint-scope@8.2.0: dependencies: @@ -5389,29 +5324,29 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.20.0(jiti@2.6.1): + eslint@9.20.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.0(jiti@2.6.1)) - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.20.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.19.2 '@eslint/core': 0.11.0 - '@eslint/eslintrc': 3.2.0 + '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.20.0 - '@eslint/plugin-kit': 0.2.5 - '@humanfs/node': 0.16.6 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 - '@types/estree': 1.0.6 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 - ajv: 6.12.6 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 eslint-visitor-keys: 4.2.0 espree: 10.3.0 - esquery: 1.6.0 + esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 @@ -5422,21 +5357,21 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color espree@10.3.0: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 4.2.0 - esquery@1.6.0: + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -5522,13 +5457,9 @@ snapshots: dependencies: flat-cache: 4.0.1 - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - filter-obj@1.1.0: {} - find-up-simple@1.0.0: {} + find-up-simple@1.0.1: {} find-up@4.1.0: dependencies: @@ -5542,21 +5473,23 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.2 + flatted: 3.4.2 keyv: 4.5.4 - flatted@3.3.2: {} + flatted@3.4.2: {} - follow-redirects@1.15.9: {} + follow-redirects@1.16.0: {} - for-each@0.3.4: + for-each@0.3.5: dependencies: is-callable: 1.2.7 - form-data@4.0.1: + form-data@4.0.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 mime-types: 2.1.35 formdata-polyfill@4.0.10: @@ -5568,24 +5501,13 @@ snapshots: function-bind@1.1.2: {} + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} - get-east-asian-width@1.3.0: {} - - get-intrinsic@1.2.7: - dependencies: - call-bind-apply-helpers: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 + get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: dependencies: @@ -5605,16 +5527,10 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - globals@11.12.0: {} - globals@14.0.0: {} globals@15.14.0: {} @@ -5628,17 +5544,16 @@ snapshots: chalk: 4.1.2 tinygradient: 1.1.5 - h3@1.15.0: + h3@1.15.11: dependencies: - cookie-es: 1.2.2 - crossws: 0.3.4 - defu: 6.1.4 - destr: 2.0.3 + cookie-es: 1.2.3 + crossws: 0.3.5 + defu: 6.1.7 + destr: 2.0.5 iron-webcrypto: 1.2.1 - node-mock-http: 1.0.0 - ohash: 1.1.4 + node-mock-http: 1.0.4 radix3: 1.1.2 - ufo: 1.5.4 + ufo: 1.6.4 uncrypto: 0.1.3 has-flag@4.0.0: {} @@ -5658,10 +5573,6 @@ snapshots: inherits: 2.0.4 minimalistic-assert: 1.0.1 - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -5674,13 +5585,13 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - hono@4.7.0: {} + hono@4.12.18: {} hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 - idb-keyval@6.2.1: {} + idb-keyval@6.2.2: {} ieee754@1.2.1: {} @@ -5699,32 +5610,32 @@ snapshots: indent-string@5.0.0: {} - index-to-position@0.1.2: {} + index-to-position@1.2.0: {} inherits@2.0.4: {} - ink-big-text@2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1): + ink-big-text@2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))(react@18.3.1): dependencies: - cfonts: 3.3.0 - ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) + cfonts: 3.3.1 + ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) prop-types: 15.8.1 react: 18.3.1 - ink-gradient@3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10)): + ink-gradient@3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6)): dependencies: '@types/gradient-string': 1.1.6 gradient-string: 2.0.2 - ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) + ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) prop-types: 15.8.1 strip-ansi: 7.1.0 - ink-link@4.1.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10)): + ink-link@4.1.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6)): dependencies: - ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) + ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) prop-types: 15.8.1 terminal-link: 3.0.0 - ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10): + ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6): dependencies: '@alcalzone/ansi-tokenize': 0.1.3 ansi-escapes: 7.0.0 @@ -5735,7 +5646,7 @@ snapshots: cli-cursor: 4.0.0 cli-truncate: 4.0.0 code-excerpt: 4.0.0 - es-toolkit: 1.32.0 + es-toolkit: 1.46.1 indent-string: 5.0.0 is-in-ci: 1.0.0 patch-console: 2.0.0 @@ -5749,7 +5660,7 @@ snapshots: type-fest: 4.34.1 widest-line: 5.0.0 wrap-ansi: 9.0.0 - ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.20.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) yoga-wasm-web: 0.3.3 optionalDependencies: '@types/react': 18.3.18 @@ -5763,24 +5674,20 @@ snapshots: is-accessor-descriptor@1.0.1: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 is-arguments@1.2.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - is-buffer@1.1.6: {} is-callable@1.2.7: {} is-data-descriptor@1.0.1: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 is-descriptor@1.0.3: dependencies: @@ -5795,11 +5702,12 @@ snapshots: is-fullwidth-code-point@5.0.0: dependencies: - get-east-asian-width: 1.3.0 + get-east-asian-width: 1.5.0 - is-generator-function@1.1.0: + is-generator-function@1.1.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 + generator-function: 2.0.1 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 @@ -5814,20 +5722,18 @@ snapshots: dependencies: kind-of: 3.2.2 - is-number@7.0.0: {} - is-regex@1.2.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 is-stream@2.0.1: {} is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.18 + which-typed-array: 1.1.20 is-unicode-supported@2.1.0: {} @@ -5837,17 +5743,17 @@ snapshots: isexe@2.0.0: {} - isows@1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + isows@1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)): dependencies: - ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) - jiti@2.6.1: {} + jiti@2.7.0: {} - js-base64@3.7.7: {} + js-base64@3.7.8: {} js-tokens@4.0.0: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -5953,7 +5859,7 @@ snapshots: lit-element@3.3.3: dependencies: - '@lit-labs/ssr-dom-shim': 1.3.0 + '@lit-labs/ssr-dom-shim': 1.5.1 '@lit/reactive-element': 1.6.3 lit-html: 2.8.0 @@ -5985,6 +5891,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.3.6: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -6017,13 +5925,13 @@ snapshots: dependencies: brace-expansion: 5.0.5 - minimatch@3.1.2: + minimatch@3.1.5: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.14 - mipd@0.0.7(typescript@5.7.3): + mipd@0.0.7(typescript@6.0.3): optionalDependencies: - typescript: 5.7.3 + typescript: 6.0.3 motion@10.16.2: dependencies: @@ -6040,7 +5948,7 @@ snapshots: multiformats@9.9.0: {} - nanoid@3.3.8: {} + nanoid@3.3.12: {} natural-compare@1.4.0: {} @@ -6048,7 +5956,7 @@ snapshots: node-domexception@1.0.0: {} - node-fetch-native@1.6.6: {} + node-fetch-native@1.6.7: {} node-fetch@2.7.0: dependencies: @@ -6062,9 +5970,9 @@ snapshots: node-gyp-build@4.8.4: {} - node-mock-http@1.0.0: {} + node-mock-http@1.0.4: {} - node-releases@2.0.19: {} + node-releases@2.0.38: {} normalize-package-data@6.0.2: dependencies: @@ -6076,19 +5984,17 @@ snapshots: obj-multiplex@1.0.0: dependencies: - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 once: 1.4.0 readable-stream: 2.3.8 object-assign@4.1.1: {} - ofetch@1.4.1: + ofetch@1.5.1: dependencies: - destr: 2.0.3 - node-fetch-native: 1.6.6 - ufo: 1.5.4 - - ohash@1.1.4: {} + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.4 on-exit-leak-free@0.2.0: {} @@ -6109,7 +6015,7 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ox@0.14.20(typescript@5.7.3)(zod@3.24.1): + ox@0.14.20(typescript@6.0.3)(zod@3.24.1): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -6117,10 +6023,10 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.4(typescript@5.7.3)(zod@3.24.1) + abitype: 1.2.4(typescript@6.0.3)(zod@3.24.1) eventemitter3: 5.0.1 optionalDependencies: - typescript: 5.7.3 + typescript: 6.0.3 transitivePeerDependencies: - zod @@ -6146,18 +6052,18 @@ snapshots: dependencies: callsites: 3.1.0 - parse-json@8.1.0: + parse-json@8.3.0: dependencies: - '@babel/code-frame': 7.26.2 - index-to-position: 0.1.2 - type-fest: 4.34.1 + '@babel/code-frame': 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 - pastel@3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(zod@3.24.1): + pastel@3.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))(react@18.3.1)(zod@3.24.1): dependencies: - '@inkjs/ui': 2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10)) + '@inkjs/ui': 2.0.0(ink@5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6)) commander: 12.1.0 decamelize: 6.0.0 - ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10) + ink: 5.1.0(@types/react@18.3.18)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) plur: 5.1.0 react: 18.3.1 read-package-up: 11.0.0 @@ -6211,14 +6117,12 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.5.1: + postcss@8.5.14: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 - preact@10.25.4: {} - preact@10.29.1: {} prelude-ls@1.2.1: {} @@ -6239,9 +6143,9 @@ snapshots: proxy-compare@2.5.1: {} - pump@3.0.2: + pump@3.0.4: dependencies: - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 once: 1.4.0 punycode@2.3.1: {} @@ -6286,7 +6190,7 @@ snapshots: read-package-up@11.0.0: dependencies: - find-up-simple: 1.0.0 + find-up-simple: 1.0.1 read-pkg: 9.0.1 type-fest: 4.34.1 @@ -6294,7 +6198,7 @@ snapshots: dependencies: '@types/normalize-package-data': 2.4.4 normalize-package-data: 6.0.2 - parse-json: 8.1.0 + parse-json: 8.3.0 type-fest: 4.34.1 unicorn-magic: 0.1.0 @@ -6314,14 +6218,10 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.2 + readdirp@5.0.0: {} real-require@0.1.0: {} - regenerator-runtime@0.14.1: {} - require-directory@2.1.1: {} require-main-filename@2.0.0: {} @@ -6333,29 +6233,35 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - rollup@4.34.6: + rollup@4.60.3: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.6 - '@rollup/rollup-android-arm64': 4.34.6 - '@rollup/rollup-darwin-arm64': 4.34.6 - '@rollup/rollup-darwin-x64': 4.34.6 - '@rollup/rollup-freebsd-arm64': 4.34.6 - '@rollup/rollup-freebsd-x64': 4.34.6 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.6 - '@rollup/rollup-linux-arm-musleabihf': 4.34.6 - '@rollup/rollup-linux-arm64-gnu': 4.34.6 - '@rollup/rollup-linux-arm64-musl': 4.34.6 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.6 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.6 - '@rollup/rollup-linux-riscv64-gnu': 4.34.6 - '@rollup/rollup-linux-s390x-gnu': 4.34.6 - '@rollup/rollup-linux-x64-gnu': 4.34.6 - '@rollup/rollup-linux-x64-musl': 4.34.6 - '@rollup/rollup-win32-arm64-msvc': 4.34.6 - '@rollup/rollup-win32-ia32-msvc': 4.34.6 - '@rollup/rollup-win32-x64-msvc': 4.34.6 + '@rollup/rollup-android-arm-eabi': 4.60.3 + '@rollup/rollup-android-arm64': 4.60.3 + '@rollup/rollup-darwin-arm64': 4.60.3 + '@rollup/rollup-darwin-x64': 4.60.3 + '@rollup/rollup-freebsd-arm64': 4.60.3 + '@rollup/rollup-freebsd-x64': 4.60.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.3 + '@rollup/rollup-linux-arm-musleabihf': 4.60.3 + '@rollup/rollup-linux-arm64-gnu': 4.60.3 + '@rollup/rollup-linux-arm64-musl': 4.60.3 + '@rollup/rollup-linux-loong64-gnu': 4.60.3 + '@rollup/rollup-linux-loong64-musl': 4.60.3 + '@rollup/rollup-linux-ppc64-gnu': 4.60.3 + '@rollup/rollup-linux-ppc64-musl': 4.60.3 + '@rollup/rollup-linux-riscv64-gnu': 4.60.3 + '@rollup/rollup-linux-riscv64-musl': 4.60.3 + '@rollup/rollup-linux-s390x-gnu': 4.60.3 + '@rollup/rollup-linux-x64-gnu': 4.60.3 + '@rollup/rollup-linux-x64-musl': 4.60.3 + '@rollup/rollup-openbsd-x64': 4.60.3 + '@rollup/rollup-openharmony-arm64': 4.60.3 + '@rollup/rollup-win32-arm64-msvc': 4.60.3 + '@rollup/rollup-win32-ia32-msvc': 4.60.3 + '@rollup/rollup-win32-x64-gnu': 4.60.3 + '@rollup/rollup-win32-x64-msvc': 4.60.3 fsevents: 2.3.3 safe-buffer@5.1.2: {} @@ -6364,7 +6270,7 @@ snapshots: safe-regex-test@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-regex: 1.2.1 @@ -6385,7 +6291,7 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -6413,23 +6319,23 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 - smol-toml@1.3.1: {} + smol-toml@1.6.1: {} - socket.io-client@4.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10): + socket.io-client@4.8.3(bufferutil@4.1.0)(utf-8-validate@6.0.6): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 - engine.io-client: 6.6.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - socket.io-parser: 4.2.4 + debug: 4.4.3 + engine.io-client: 6.6.4(bufferutil@4.1.0)(utf-8-validate@6.0.6) + socket.io-parser: 4.2.6 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@4.2.4: + socket.io-parser@4.2.6: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -6442,16 +6348,16 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.21 + spdx-license-ids: 3.0.23 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 + spdx-license-ids: 3.0.23 - spdx-license-ids@3.0.21: {} + spdx-license-ids@3.0.23: {} split-on-first@1.1.0: {} @@ -6474,7 +6380,7 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.4.0 - get-east-asian-width: 1.3.0 + get-east-asian-width: 1.5.0 strip-ansi: 7.1.0 string_decoder@1.1.1: @@ -6497,7 +6403,7 @@ snapshots: supersim@0.1.0-alpha.45: dependencies: - follow-redirects: 1.15.9 + follow-redirects: 1.16.0 transitivePeerDependencies: - debug @@ -6553,15 +6459,11 @@ snapshots: safe-buffer: 5.2.1 typed-array-buffer: 1.0.3 - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - tr46@0.0.3: {} - ts-api-utils@2.5.0(typescript@5.7.3): + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: - typescript: 5.7.3 + typescript: 6.0.3 tslib@1.14.1: {} @@ -6575,26 +6477,28 @@ snapshots: type-fest@4.34.1: {} + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-typed-array: 1.1.15 - typescript-eslint@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3): + typescript-eslint@8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3))(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) - '@typescript-eslint/parser': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.7.3) - '@typescript-eslint/utils': 8.59.1(eslint@9.20.0(jiti@2.6.1))(typescript@5.7.3) - eslint: 9.20.0(jiti@2.6.1) - typescript: 5.7.3 + '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.2(eslint@9.20.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 9.20.0(jiti@2.7.0) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - typescript@5.7.3: {} + typescript@6.0.3: {} - ufo@1.5.4: {} + ufo@1.6.4: {} uint8arrays@3.1.0: dependencies: @@ -6602,26 +6506,26 @@ snapshots: uncrypto@0.1.3: {} - undici-types@6.20.0: {} + undici-types@7.19.2: {} unicorn-magic@0.1.0: {} - unstorage@1.14.4(idb-keyval@6.2.1): + unstorage@1.17.5(idb-keyval@6.2.2): dependencies: anymatch: 3.1.3 - chokidar: 3.6.0 - destr: 2.0.3 - h3: 1.15.0 - lru-cache: 10.4.3 - node-fetch-native: 1.6.6 - ofetch: 1.4.1 - ufo: 1.5.4 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.11 + lru-cache: 11.3.6 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.4 optionalDependencies: - idb-keyval: 6.2.1 + idb-keyval: 6.2.2 - update-browserslist-db@1.1.2(browserslist@4.24.4): + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: - browserslist: 4.24.4 + browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -6637,19 +6541,29 @@ snapshots: dependencies: react: 18.3.1 + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + optional: true + utf-8-validate@5.0.10: dependencies: node-gyp-build: 4.8.4 + utf-8-validate@6.0.6: + dependencies: + node-gyp-build: 4.8.4 + optional: true + util-deprecate@1.0.2: {} util@0.12.5: dependencies: inherits: 2.0.4 is-arguments: 1.2.0 - is-generator-function: 1.1.0 + is-generator-function: 1.1.2 is-typed-array: 1.1.15 - which-typed-array: 1.1.18 + which-typed-array: 1.1.20 uuid@8.3.2: {} @@ -6668,44 +6582,47 @@ snapshots: '@types/react': 18.3.18 react: 18.3.1 - viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1): + viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1): 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@5.7.3)(zod@3.24.1) - isows: 1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.14.20(typescript@5.7.3)(zod@3.24.1) - ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) + abitype: 1.2.3(typescript@6.0.3)(zod@3.24.1) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + ox: 0.14.20(typescript@6.0.3)(zod@3.24.1) + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: - typescript: 5.7.3 + typescript: 6.0.3 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - vite@6.1.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.32.0): + vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0): dependencies: - esbuild: 0.24.2 - postcss: 8.5.1 - rollup: 4.34.6 + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.14 + rollup: 4.60.3 + tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 22.13.1 + '@types/node': 25.6.2 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 lightningcss: 1.32.0 - wagmi@2.14.11(@tanstack/query-core@5.100.8)(@tanstack/react-query@5.100.8(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.0.9)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1): + wagmi@2.14.11(@tanstack/query-core@5.100.9)(@tanstack/react-query@5.100.9(react@18.3.1))(@types/react@18.3.18)(bufferutil@4.1.0)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(utf-8-validate@6.0.6)(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1))(zod@3.24.1): dependencies: - '@tanstack/react-query': 5.100.8(react@18.3.1) - '@wagmi/connectors': 5.7.7(@types/react@18.3.18)(@wagmi/core@2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)))(bufferutil@4.0.9)(react@18.3.1)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1))(zod@3.24.1) - '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.8)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@5.7.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1)) + '@tanstack/react-query': 5.100.9(react@18.3.1) + '@wagmi/connectors': 5.7.7(@types/react@18.3.18)(@wagmi/core@2.16.4(@tanstack/query-core@5.100.9)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)))(bufferutil@4.1.0)(react@18.3.1)(typescript@6.0.3)(utf-8-validate@6.0.6)(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1))(zod@3.24.1) + '@wagmi/core': 2.16.4(@tanstack/query-core@5.100.9)(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@18.3.1))(viem@2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1)) react: 18.3.1 use-sync-external-store: 1.4.0(react@18.3.1) - viem: 2.48.8(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.24.1) + viem: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) optionalDependencies: - typescript: 5.7.3 + typescript: 6.0.3 transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -6722,6 +6639,7 @@ snapshots: - '@types/react' - '@upstash/redis' - '@vercel/blob' + - '@vercel/functions' - '@vercel/kv' - aws4fetch - bufferutil @@ -6738,7 +6656,7 @@ snapshots: dependencies: chalk: 4.1.2 commander: 9.5.0 - debug: 4.4.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -6755,12 +6673,13 @@ snapshots: which-module@2.0.1: {} - which-typed-array@1.1.18: + which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.3 - for-each: 0.3.4 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 @@ -6793,25 +6712,20 @@ snapshots: wrappy@1.0.2: {} - ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10): + ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6): optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 + bufferutil: 4.1.0 + utf-8-validate: 6.0.6 - ws@8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10): + ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6): optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 + bufferutil: 4.1.0 + utf-8-validate: 6.0.6 - ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10): + ws@8.20.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 - - ws@8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 + bufferutil: 4.1.0 + utf-8-validate: 6.0.6 xmlhttprequest-ssl@2.1.2: {} @@ -6850,16 +6764,16 @@ snapshots: zod@3.24.1: {} - zustand@5.0.0(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): + zustand@5.0.0(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): optionalDependencies: '@types/react': 18.3.18 immer: 10.1.1 react: 18.3.1 - use-sync-external-store: 1.4.0(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) - zustand@5.0.3(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): + zustand@5.0.3(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): optionalDependencies: '@types/react': 18.3.18 immer: 10.1.1 react: 18.3.1 - use-sync-external-store: 1.4.0(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) diff --git a/scripts/github/apply-governance.sh b/scripts/github/apply-governance.sh index 713389c..b1084f3 100755 --- a/scripts/github/apply-governance.sh +++ b/scripts/github/apply-governance.sh @@ -209,22 +209,43 @@ ensure_environment() { # Baseline checks for dev, canary, and main. DEV_CHECKS_JSON='[ + "Analyze (javascript-typescript)", + "Dependency Review", "Contracts Unit + Invariant", "Contracts Release Check (Dry-Run + Execute Smoke)", - "Slither Core Contracts", - "Secrets Drift Guard" + "Contracts Production Mode Smoke", + "gitleaks / Gitleaks Scan", + "slither-core / Slither Core Contracts", + "frontend-checks / Frontend Checks (Node 20)", + "frontend-checks / Frontend Checks (Node 22)", + "Detect Secrets Drift", + "Release Gate Container" ]' CANARY_CHECKS_JSON='[ + "Analyze (javascript-typescript)", + "Dependency Review", "Contracts Unit + Invariant", "Contracts Release Check (Dry-Run + Execute Smoke)", - "Slither Core Contracts", - "Secrets Drift Guard" + "Contracts Production Mode Smoke", + "gitleaks / Gitleaks Scan", + "slither-core / Slither Core Contracts", + "frontend-checks / Frontend Checks (Node 20)", + "frontend-checks / Frontend Checks (Node 22)", + "Detect Secrets Drift", + "Release Gate Container" ]' MAIN_CHECKS_JSON='[ + "Analyze (javascript-typescript)", + "Dependency Review", "Contracts Unit + Invariant", "Contracts Release Check (Dry-Run + Execute Smoke)", - "Slither Core Contracts", - "Secrets Drift Guard", + "Contracts Production Mode Smoke", + "gitleaks / Gitleaks Scan", + "slither-core / Slither Core Contracts", + "frontend-checks / Frontend Checks (Node 20)", + "frontend-checks / Frontend Checks (Node 22)", + "Detect Secrets Drift", + "Release Gate Container", "Validate Release PR Checklist", "Validate Release Evidence" ]' diff --git a/scripts/github/posttransfer-bootstrap.sh b/scripts/github/posttransfer-bootstrap.sh new file mode 100755 index 0000000..cc11b4d --- /dev/null +++ b/scripts/github/posttransfer-bootstrap.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Post-transfer bootstrap: +# 1) apply governance protections +# 2) verify protections are active +# +# Required env: +# GH_PAT= +# Optional: +# GH_REPO=owner/repo + +if [[ -z "${GH_PAT:-}" ]]; then + echo "GH_PAT is required" >&2 + exit 1 +fi + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +echo "[posttransfer] applying governance protections" +./scripts/github/apply-governance.sh + +echo "[posttransfer] verifying governance protections" +./scripts/github/verify-governance.sh + +echo "[posttransfer] SUCCESS: governance baseline applied and verified" diff --git a/scripts/github/pretransfer-readiness.sh b/scripts/github/pretransfer-readiness.sh new file mode 100755 index 0000000..07e0a93 --- /dev/null +++ b/scripts/github/pretransfer-readiness.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Pre-transfer readiness checks for org migration. +# Required env: +# GH_PAT= +# Optional: +# GH_REPO=owner/repo (inferred from origin) + +require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "$1 is required" >&2; exit 1; }; } +require_cmd curl +require_cmd jq +require_cmd git + +if [[ -z "${GH_PAT:-}" ]]; then + echo "GH_PAT is required" >&2 + exit 1 +fi + +infer_repo_from_remote() { + local remote + remote="$(git remote get-url origin)" + if [[ "$remote" =~ ^git@github.com:([^/]+/[^/]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}"; return + fi + if [[ "$remote" =~ ^https://github.com/([^/]+/[^/]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}"; return + fi + echo "Could not infer GH_REPO from origin: $remote" >&2 + exit 1 +} + +GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" +owner="${GH_REPO%%/*}" +repo="${GH_REPO##*/}" +api="https://api.github.com/repos/${owner}/${repo}" +auth_headers=(-H "Authorization: Bearer ${GH_PAT}" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28") + +echo "[pretransfer] repo=${GH_REPO}" + +# Basic repo access check +curl -sS "${auth_headers[@]}" "${api}" | jq -e '.full_name != null' >/dev/null + +echo "[pretransfer] checking required workflow files" +required_workflows=( + ".github/workflows/codeql.yml" + ".github/workflows/secrets-scan.yml" + ".github/workflows/governance-verify.yml" + ".github/workflows/contracts-release-gate-container.yml" +) +for wf in "${required_workflows[@]}"; do + if [[ ! -f "$wf" ]]; then + echo " FAIL: missing workflow file: $wf" >&2 + exit 1 + fi + echo " PASS: $wf" +done + +echo "[pretransfer] checking required repo secret names for post-transfer workflows" +secrets_json="$(curl -sS "${auth_headers[@]}" "${api}/actions/secrets")" +for s in GOVERNANCE_VERIFY_PAT; do + if jq -e --arg n "$s" '.secrets[]?.name | select(. == $n)' <<<"$secrets_json" >/dev/null; then + echo " PASS: secret exists: $s" + else + echo " WARN: secret missing (add after transfer if needed): $s" + fi +done + +echo "[pretransfer] done" diff --git a/scripts/github/verify-governance.sh b/scripts/github/verify-governance.sh new file mode 100755 index 0000000..77e3c9e --- /dev/null +++ b/scripts/github/verify-governance.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Verify governance baseline is active on GitHub repository. +# Required env: +# GH_PAT= +# Optional: +# GH_REPO=owner/repo (inferred from origin if omitted) + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || { echo "$1 is required" >&2; exit 1; } +} + +require_cmd curl +require_cmd jq +require_cmd git + +if [[ -z "${GH_PAT:-}" ]]; then + echo "GH_PAT is required" >&2 + exit 1 +fi + +infer_repo_from_remote() { + local remote + remote="$(git remote get-url origin)" + if [[ "$remote" =~ ^git@github.com:([^/]+/[^/]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}"; return + fi + if [[ "$remote" =~ ^https://github.com/([^/]+/[^/]+)(\.git)?$ ]]; then + echo "${BASH_REMATCH[1]}"; return + fi + echo "Could not infer GH_REPO from origin: $remote" >&2 + exit 1 +} + +GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" +owner="${GH_REPO%%/*}" +repo="${GH_REPO##*/}" +api="https://api.github.com/repos/${owner}/${repo}" + +auth_headers=( + -H "Authorization: Bearer ${GH_PAT}" + -H "Accept: application/vnd.github+json" + -H "X-GitHub-Api-Version: 2022-11-28" +) + +require_checks_dev=( + "Analyze (javascript-typescript)" + "Dependency Review" + "Contracts Unit + Invariant" + "Contracts Release Check (Dry-Run + Execute Smoke)" + "Contracts Production Mode Smoke" + "gitleaks / Gitleaks Scan" + "slither-core / Slither Core Contracts" + "frontend-checks / Frontend Checks (Node 20)" + "frontend-checks / Frontend Checks (Node 22)" + "Detect Secrets Drift" + "Release Gate Container" +) + +require_checks_main=( + "Analyze (javascript-typescript)" + "Dependency Review" + "Contracts Unit + Invariant" + "Contracts Release Check (Dry-Run + Execute Smoke)" + "Contracts Production Mode Smoke" + "gitleaks / Gitleaks Scan" + "slither-core / Slither Core Contracts" + "frontend-checks / Frontend Checks (Node 20)" + "frontend-checks / Frontend Checks (Node 22)" + "Detect Secrets Drift" + "Release Gate Container" + "Validate Release PR Checklist" + "Validate Release Evidence" +) + +get_protection() { + local branch="$1" + curl -sS "${auth_headers[@]}" "${api}/branches/${branch}/protection" +} + +check_branch() { + local branch="$1" + local require_stale="$2" + shift 2 + local -a expected=("$@") + + echo "[verify] branch=${branch}" + local json + json="$(get_protection "$branch")" + + local enabled + enabled="$(jq -r '.required_status_checks != null' <<<"$json")" + if [[ "$enabled" != "true" ]]; then + echo " FAIL: required_status_checks not enabled for ${branch}" >&2 + return 1 + fi + + if [[ "$require_stale" == "true" ]]; then + local stale + stale="$(jq -r '.required_pull_request_reviews.dismiss_stale_reviews // false' <<<"$json")" + if [[ "$stale" != "true" ]]; then + echo " FAIL: dismiss_stale_reviews is not enabled for ${branch}" >&2 + return 1 + fi + fi + + local missing=0 + for check in "${expected[@]}"; do + if ! jq -e --arg c "$check" '.required_status_checks.checks[]?.context | select(. == $c)' <<<"$json" >/dev/null; then + echo " FAIL: missing required check on ${branch}: ${check}" >&2 + missing=1 + fi + done + + if [[ $missing -ne 0 ]]; then + return 1 + fi + + echo " PASS" +} + +# dev has 0 required approvals so dismiss_stale_reviews is not applicable. +check_branch dev false "${require_checks_dev[@]}" +check_branch canary true "${require_checks_dev[@]}" +check_branch main true "${require_checks_main[@]}" + +echo "[verify] governance baseline active for ${GH_REPO}" diff --git a/tsconfig.json b/tsconfig.json index 2b78387..0f1bf1b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "files": [], "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }], "compilerOptions": { + "ignoreDeprecations": "6.0", "baseUrl": ".", "paths": { "@/*": ["./src/*"] From f9edcd8195865f39831399a31a9c83016f36f3e2 Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Sun, 10 May 2026 15:18:44 +0700 Subject: [PATCH 29/38] chore: promote dev to canary 65 commits: staging rehearsal fixes, frontend info page, NatSpec improvements, README philosophy. --- .github/workflows/contracts-env-guard.yml | 1 + .../workflows/contracts-evidence-manifest.yml | 1 + .../workflows/contracts-mainnet-readiness.yml | 1 + .../contracts-production-lock-verify.yml | 1 + .../contracts-promotion-checklist.yml | 1 + .../workflows/contracts-staging-rehearsal.yml | 1 + .github/workflows/secrets-drift-guard.yml | 2 +- CONTRIBUTING.md | 6 - README.md | 20 ++- .../networks/optimism-sepolia.env.example | 29 +++- contracts/config/profiles/staging.env | 31 +++- contracts/foundry.toml | 4 + .../script/ops/rehearse-production-lock.sh | 2 +- .../script/ops/settlement/ReleaseMARK.s.sol | 7 + contracts/src/bridge/MARKBridgeAdapter.sol | 3 + .../src/settlement/MARKSettlementModule.sol | 2 + .../verifier/AttestedSettlementVerifier.sol | 2 + index.html | 4 +- package.json | 1 - src/App.tsx | 152 +++++++++--------- src/Providers.tsx | 20 +-- 21 files changed, 176 insertions(+), 115 deletions(-) diff --git a/.github/workflows/contracts-env-guard.yml b/.github/workflows/contracts-env-guard.yml index 921a687..cd626dd 100644 --- a/.github/workflows/contracts-env-guard.yml +++ b/.github/workflows/contracts-env-guard.yml @@ -42,6 +42,7 @@ jobs: MARK_SETTLEMENT_PROOF_ENABLED=true \ MARK_SETTLEMENT_PRODUCTION_MODE=true \ MARK_DEPLOY_ATTESTED_VERIFIER=true \ + PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000001 \ VALIDATE_MODE=rehearsal ./script/ops/validate-prod-env.sh - name: Validate dispatch env profile diff --git a/.github/workflows/contracts-evidence-manifest.yml b/.github/workflows/contracts-evidence-manifest.yml index b89f211..583dee6 100644 --- a/.github/workflows/contracts-evidence-manifest.yml +++ b/.github/workflows/contracts-evidence-manifest.yml @@ -143,6 +143,7 @@ jobs: steps: - name: Enforce main branch for production manifest run + working-directory: . run: | if [ "${GITHUB_REF_NAME}" != "main" ]; then echo "Manual production manifest verification must run from main branch. Current: ${GITHUB_REF_NAME}" diff --git a/.github/workflows/contracts-mainnet-readiness.yml b/.github/workflows/contracts-mainnet-readiness.yml index 745a7b0..364e9d1 100644 --- a/.github/workflows/contracts-mainnet-readiness.yml +++ b/.github/workflows/contracts-mainnet-readiness.yml @@ -36,6 +36,7 @@ jobs: steps: - name: Enforce main branch for production readiness + working-directory: . run: | if [ "${GITHUB_REF_NAME}" != "main" ]; then echo "Production readiness workflow must run from main branch. Current: ${GITHUB_REF_NAME}" diff --git a/.github/workflows/contracts-production-lock-verify.yml b/.github/workflows/contracts-production-lock-verify.yml index 3237c53..2f65bc0 100644 --- a/.github/workflows/contracts-production-lock-verify.yml +++ b/.github/workflows/contracts-production-lock-verify.yml @@ -52,6 +52,7 @@ jobs: steps: - name: Enforce main branch for production verification + working-directory: . run: | if [ "${GITHUB_REF_NAME}" != "main" ]; then echo "Production lock verification workflow must run from main branch. Current: ${GITHUB_REF_NAME}" diff --git a/.github/workflows/contracts-promotion-checklist.yml b/.github/workflows/contracts-promotion-checklist.yml index 92a6b5b..50f400a 100644 --- a/.github/workflows/contracts-promotion-checklist.yml +++ b/.github/workflows/contracts-promotion-checklist.yml @@ -53,6 +53,7 @@ jobs: steps: - name: Enforce main branch for promotion checklist + working-directory: . run: | if [ "${GITHUB_REF_NAME}" != "main" ]; then echo "Promotion checklist workflow must run from main branch. Current: ${GITHUB_REF_NAME}" diff --git a/.github/workflows/contracts-staging-rehearsal.yml b/.github/workflows/contracts-staging-rehearsal.yml index 9bdbe0a..3432a0f 100644 --- a/.github/workflows/contracts-staging-rehearsal.yml +++ b/.github/workflows/contracts-staging-rehearsal.yml @@ -69,6 +69,7 @@ jobs: steps: - name: Enforce canary or dev or main branch for rehearsal + working-directory: . run: | if [ "${GITHUB_REF_NAME}" != "canary" ] && [ "${GITHUB_REF_NAME}" != "dev" ] && [ "${GITHUB_REF_NAME}" != "main" ]; then echo "Staging rehearsal must run from canary, dev, or main branch. Current: ${GITHUB_REF_NAME}" diff --git a/.github/workflows/secrets-drift-guard.yml b/.github/workflows/secrets-drift-guard.yml index 5b10e54..e787798 100644 --- a/.github/workflows/secrets-drift-guard.yml +++ b/.github/workflows/secrets-drift-guard.yml @@ -34,7 +34,7 @@ jobs: grep -E '^\+[^+]' /tmp/pr.diff > /tmp/pr.added || true # Remove obvious placeholders and known non-secret examples. - grep -Ev 'YOUR_PRIVATE_KEY|0x0000000000000000000000000000000000000000|gho_\*+|github\.com/cli/cli/releases' /tmp/pr.added > /tmp/pr.added.filtered || true + grep -Ev 'YOUR_PRIVATE_KEY|0x0000000000000000000000000000000000000000|gho_\*+|github\.com/cli/cli/releases|PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000001' /tmp/pr.added > /tmp/pr.added.filtered || true # High-signal patterns. if grep -Ein \ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6702ec0..501074d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -453,9 +453,3 @@ Each exclusion is documented in the codebase. If you disagree with an exclusion, ## Questions? Feel free to open an issue or ask in a PR. We're here to help! - ---- - -**Happy contributing!** 🚀 - -*Last updated: 2026-05-06* diff --git a/README.md b/README.md index eb68b31..49776b5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -# Mark Protocol +# MARK Protocol Decentralised and privacy-first by design. Leveraging zero-knowledge proofs for secure, scalable settlement on the Superchain. +Code is a rule. No DAO, no drama. Don't Trust, Verify. + +The protocol enforces settlement rules on-chain. Whether operators run it as a centralised service or a decentralised network is their choice — the contracts don't care. + ## Getting Started ### Prerequisites @@ -12,8 +16,8 @@ Decentralised and privacy-first by design. Leveraging zero-knowledge proofs for ### 1. Clone the repository ```bash -git clone -cd +git clone https://github.com/trade/mark.git +cd mark ``` ### 2. Install dependencies @@ -46,7 +50,7 @@ Full policy is documented in [BRANCHING.md](./BRANCHING.md). ## Deploying Contracts -Mark uses `super-cli` (`sup`) for contract deployment across the Superchain. +MARK uses `super-cli` (`sup`) for contract deployment across the Superchain. ### Interactive mode @@ -74,6 +78,13 @@ pnpm build:contracts ## Overview +### Contracts + +- **RYLA Credits** (`RYLA`) — Superchain-compatible credit token. Mintable and burnable only by the settlement module. +- **MARKSettlementModule** — Operator-gated settlement boundary with replay protection and optional ZK proof verification. +- **MARKBridgeAdapter** — Operator-gated bridge adapter routing RYLA cross-chain via SuperchainTokenBridge with rate limits. +- **AttestedSettlementVerifier** — EIP-712 signature-based verifier for settlement intents. + ### Tools - **[supersim](https://github.com/ethereum-optimism/supersim)** — local Superchain test environment with pre-deployed contracts @@ -88,7 +99,6 @@ pnpm build:contracts mark/ ├── contracts/ # Smart contract code (Foundry) ├── src/ # Frontend code (vite, tailwind, shadcn, wagmi, viem) -│ └── App.tsx # Main application component ├── public/ # Static assets ├── supersim-logs/ # Local supersim logs ├── package.json # Project dependencies and scripts diff --git a/contracts/config/networks/optimism-sepolia.env.example b/contracts/config/networks/optimism-sepolia.env.example index 1c1f1c2..9d141da 100644 --- a/contracts/config/networks/optimism-sepolia.env.example +++ b/contracts/config/networks/optimism-sepolia.env.example @@ -1,11 +1,30 @@ -# Optimism Sepolia example config -RPC_URL=https://sepolia.optimism.io +# OP Sepolia deployment config example +# Copy to .env and fill in real values. Never commit .env files. +# +# GitHub environment setup (Settings -> Environments -> staging): +# Secret: MARK_STAGING_DEPLOYER_PRIVATE_KEY = +# Variable: MARK_STAGING_RPC_URL = +# Variable: MARK_STAGING_OWNER_ADDRESS = +# Variable: MARK_STAGING_SETTLEMENT_OPERATOR = +# Variable: MARK_STAGING_BRIDGE_OPERATOR = +# Variable: MARK_STAGING_ATTESTER_ADDRESS = +# Variable: MARK_STAGING_DESTINATION_CHAIN_ID = 11155420 -# Governance / role holders +# Use a private RPC endpoint to avoid public rate limits. +RPC_URL=https://opt-sepolia.g.alchemy.com/v2/ + +# Deployer key is injected from the MARK_STAGING_DEPLOYER_PRIVATE_KEY CI secret. +# For local runs, export PRIVATE_KEY before sourcing this file. + +# Owner: holds DEFAULT_ADMIN_ROLE. Use a hardware wallet address. MARK_RYLA_OWNER=0x0000000000000000000000000000000000000000 + +# Operators: hot keys for bridge and settlement operations. MARK_BRIDGE_OPERATOR=0x0000000000000000000000000000000000000000 MARK_SETTLEMENT_OPERATOR=0x0000000000000000000000000000000000000000 + +# Attester: signs settlement attestations. Set to 0x0 to skip during initial staging. MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 -# Optional initial bridge destination chain id -MARK_BRIDGE_DESTINATION_CHAIN_ID=0 +# Bridge destination: OP Sepolia (not OP mainnet). +MARK_BRIDGE_DESTINATION_CHAIN_ID=11155420 diff --git a/contracts/config/profiles/staging.env b/contracts/config/profiles/staging.env index dae0e1d..b2ef87c 100644 --- a/contracts/config/profiles/staging.env +++ b/contracts/config/profiles/staging.env @@ -1,15 +1,34 @@ # MARK staging profile (OP Sepolia) # Safe by default: execution is disabled unless explicitly overridden. +# +# Key separation model: +# DEPLOYER — hot key that pays gas and deploys contracts (CI secret: MARK_STAGING_DEPLOYER_PRIVATE_KEY) +# After deployment, deployer has no privileged access to deployed contracts. +# OWNER — address that holds DEFAULT_ADMIN_ROLE on all contracts (hardware wallet, address only) +# OPERATOR — hot key for settlement and bridge operations (separate from deployer) +# ATTESTER — hot key for signing settlement attestations (separate from operator) +# +# The deployer key is not stored here. In CI it is injected from the +# MARK_STAGING_DEPLOYER_PRIVATE_KEY GitHub secret. For local rehearsal +# runs, export PRIVATE_KEY before sourcing this file. +# Use a private RPC endpoint (Alchemy/Infura/QuickNode) to avoid public rate limits. RPC_URL=https://sepolia.optimism.io -PRIVATE_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 -MARK_RYLA_OWNER=0x1111111111111111111111111111111111111111 -MARK_MODULE_OWNER=0x1111111111111111111111111111111111111111 -MARK_BRIDGE_OPERATOR=0x2222222222222222222222222222222222222222 -MARK_SETTLEMENT_OPERATOR=0x3333333333333333333333333333333333333333 +# Owner address (hardware wallet — address only, never the key). +MARK_RYLA_OWNER=0x0000000000000000000000000000000000000000 +MARK_MODULE_OWNER=0x0000000000000000000000000000000000000000 + +# Operator addresses (hot keys — addresses only here, keys stay in secure storage). +MARK_BRIDGE_OPERATOR=0x0000000000000000000000000000000000000000 +MARK_SETTLEMENT_OPERATOR=0x0000000000000000000000000000000000000000 + +# Attester address (set to 0x0 to skip attester verification during rehearsal). MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 -MARK_BRIDGE_DESTINATION_CHAIN_ID=10 + +# Bridge destination: OP Sepolia (11155420), not OP mainnet. +# Never use mainnet (10) as a staging bridge destination. +MARK_BRIDGE_DESTINATION_CHAIN_ID=11155420 MARK_SETTLEMENT_VERIFIER=0x0000000000000000000000000000000000000000 MARK_SETTLEMENT_PROOF_ENABLED=true diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 2c64fc4..87a32e7 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -28,3 +28,7 @@ no_match_path = "test/never/**" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + + + + diff --git a/contracts/script/ops/rehearse-production-lock.sh b/contracts/script/ops/rehearse-production-lock.sh index 4651c80..38062ee 100755 --- a/contracts/script/ops/rehearse-production-lock.sh +++ b/contracts/script/ops/rehearse-production-lock.sh @@ -47,7 +47,7 @@ export MARK_SETTLEMENT_OPERATOR export MARK_SETTLEMENT_ATTESTER export MARK_RELEASE_EXECUTE=true -export MARK_RELEASE_RUN_POSTDEPLOY=false +export MARK_RELEASE_RUN_POSTDEPLOY=true export MARK_RELEASE_WRITE_ARTIFACT=true export MARK_RELEASE_ARTIFACT_PATH="$RELEASE_ARTIFACT_PATH" export MARK_RELEASE_STRICT_VERIFY=false diff --git a/contracts/script/ops/settlement/ReleaseMARK.s.sol b/contracts/script/ops/settlement/ReleaseMARK.s.sol index dbb395f..5a4d5e6 100644 --- a/contracts/script/ops/settlement/ReleaseMARK.s.sol +++ b/contracts/script/ops/settlement/ReleaseMARK.s.sol @@ -98,6 +98,13 @@ contract ReleaseMARK is Script { if (runPostDeploy) { vm.setEnv("MARK_BRIDGE_ADAPTER", vm.toString(address(adapter))); vm.setEnv("MARK_SETTLEMENT_MODULE", vm.toString(address(module))); + // Export the deployed verifier address so PostDeployMARKSetup can read it. + // DeployMARKSettlementModule deploys the verifier when MARK_DEPLOY_ATTESTED_VERIFIER=true + // but does not export the address; read it from the module state instead. + address deployedVerifier = address(module.verifier()); + if (deployedVerifier != address(0)) { + vm.setEnv("MARK_SETTLEMENT_VERIFIER", vm.toString(deployedVerifier)); + } _runPostDeployPreflight(preflight, deployerKey, deployer, address(token), address(adapter), address(module)); diff --git a/contracts/src/bridge/MARKBridgeAdapter.sol b/contracts/src/bridge/MARKBridgeAdapter.sol index ce53f3f..81ffebc 100644 --- a/contracts/src/bridge/MARKBridgeAdapter.sol +++ b/contracts/src/bridge/MARKBridgeAdapter.sol @@ -15,6 +15,9 @@ import {BridgeErrors} from "../errors/BridgeErrors.sol"; /// @title MARKBridgeAdapter /// @notice Operator-gated bridge-out adapter for RYLA using SuperchainTokenBridge. /// @dev Uses destination allowlist and optional per-tx / daily caps. +/// No pause mechanism is provided by design: emergency containment is achieved by +/// revoking all OPERATOR_ROLE holders (see RUNBOOK.md section 5), which stops all +/// bridge operations without introducing pause-admin key risk. contract MARKBridgeAdapter is ReentrancyGuard, AccessControlDefaultAdminRules, BridgeErrors { using SafeERC20 for IERC20; diff --git a/contracts/src/settlement/MARKSettlementModule.sol b/contracts/src/settlement/MARKSettlementModule.sol index 699507e..7e32c61 100644 --- a/contracts/src/settlement/MARKSettlementModule.sol +++ b/contracts/src/settlement/MARKSettlementModule.sol @@ -58,6 +58,8 @@ contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules revert ProductionModeRequiresProofValidation(); } if (enableValidation && verifierAddress == address(0)) revert VerifierRequired(); + // Rejects EOAs and undeployed addresses. Does not verify IUTXOSettlementVerifier + // compliance — a non-conforming contract would revert at settlement call time. if (verifierAddress != address(0) && verifierAddress.code.length == 0) revert VerifierRequired(); verifier = IUTXOSettlementVerifier(verifierAddress); diff --git a/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol b/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol index 1d2b4fb..f942bba 100644 --- a/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol +++ b/contracts/src/settlement/verifier/AttestedSettlementVerifier.sol @@ -19,6 +19,8 @@ import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; /// `contextHash` is an opaque attester-controlled binding value (e.g. off-chain UTXO /// state root or batch id) included in the signed digest to tie the attestation to /// external state without exposing that state on-chain. +/// EIP-5267: `eip712Domain()` is inherited from OZ EIP712 and available for wallets +/// to discover domain parameters without reading source code. contract AttestedSettlementVerifier is IUTXOSettlementVerifier, EIP712, AccessControlDefaultAdminRules { using ECDSA for bytes32; diff --git a/index.html b/index.html index e4b78ea..5438f29 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - - Vite + React + TS + + MARK Protocol
diff --git a/package.json b/package.json index 9356345..e5e440c 100644 --- a/package.json +++ b/package.json @@ -70,4 +70,3 @@ - diff --git a/src/App.tsx b/src/App.tsx index de2b02d..389550d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,91 +1,99 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; -import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'; -type ChainInfo = { - name: string; - id: number; - role: string; -}; -const MARK_FLOW = [ - 'Preflight deployment checks', - 'Release orchestration and artifact generation', - 'Staging rehearsal on canary', - 'Mainnet readiness gate on main', - 'Evidence manifest and signature verification', +const CONTRACTS = [ + { + name: 'RYLA Credits', + symbol: 'RYLA', + description: 'Superchain-compatible credit token. Mintable and burnable only by the settlement module.', + }, + { + name: 'MARKSettlementModule', + description: + 'Operator-gated settlement boundary. Validates proofs and executes RYLA mint/burn with replay protection.', + }, + { + name: 'MARKBridgeAdapter', + description: + 'Operator-gated bridge adapter routing RYLA cross-chain via SuperchainTokenBridge with rate limits.', + }, + { + name: 'AttestedSettlementVerifier', + description: + 'EIP-712 signature-based verifier for settlement intents. Production bridge step before ZK verifier integration.', + }, ]; -const CHAINS: ChainInfo[] = [ - { name: supersimL2A.name, id: supersimL2A.id, role: 'source lane' }, - { name: supersimL2B.name, id: supersimL2B.id, role: 'destination lane' }, +const LINKS = [ + { label: 'GitHub', href: 'https://github.com/trade/mark' }, + { label: 'Security Policy', href: 'https://github.com/trade/mark/security/policy' }, + { label: 'Report a Vulnerability', href: 'https://github.com/trade/mark/security/advisories/new' }, ]; -const QuickCommand = ({ label, cmd }: { label: string; cmd: string }) => ( -
-
{label}
-
{cmd}
-
-); - function App() { return ( -
+
+
+

MARK Protocol

+

by Trade

+
+ - - MARK Protocol Workspace - - This app now tracks protocol operations and release flow. - - + +
+ + Pre-production + + + Staging on OP Sepolia. Not yet deployed to mainnet. + +
+
-
- - - Superchain Lanes - Local development network topology. - - - {CHAINS.map(chain => ( -
-
- {chain.name} ({chain.id}) -
-
{chain.role}
-
- ))} -
-
+ - - - Release Flow - Canonical MARK release checkpoints. - - -
    - {MARK_FLOW.map(step => ( -
  1. {step}
  2. - ))} -
-
-
+
+

Contracts

+
+ {CONTRACTS.map(contract => ( + + + + {contract.name} + {contract.symbol && ( + + ({contract.symbol}) + + )} + + + +

{contract.description}

+
+
+ ))} +
- - - Quick Commands - Use contract Make targets for protocol operations. - - - - - - - - +
+

Resources

+
+ {LINKS.map(link => ( + + {link.label} + + ))} +
+
); } diff --git a/src/Providers.tsx b/src/Providers.tsx index 2431135..efd76ec 100644 --- a/src/Providers.tsx +++ b/src/Providers.tsx @@ -1,29 +1,17 @@ import { createConfig, http, WagmiProvider } from 'wagmi'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; -import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'; +import { optimism, optimismSepolia } from 'viem/chains'; const queryClient = new QueryClient(); const config = createConfig({ - chains: [supersimL2A, supersimL2B], + chains: [optimism, optimismSepolia], transports: { - [supersimL2A.id]: http(), - [supersimL2B.id]: http(), + [optimism.id]: http(), + [optimismSepolia.id]: http(), }, }); -// When using using the interop-alpha chains, use the following config: - -// import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'; - -// const config = createConfig({ -// chains: [interopAlpha0, interopAlpha1], -// transports: { -// [interopAlpha0.id]: http(), -// [interopAlpha1.id]: http(), -// }, -// }); - export const Providers = ({ children }: { children: React.ReactNode }) => { return ( From 83bc888b9564768ea9175e8de36fe90d6f3134b2 Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Sun, 10 May 2026 23:10:09 +0700 Subject: [PATCH 30/38] chore: promote dev to canary (v0.1.1 prep) 69 commits: CEI fix, audit docs, staging pipeline fixes, frontend info page. --- .github/workflows/secrets-drift-guard.yml | 2 +- contracts/KNOWN_ISSUES.md | 73 ++++++++++++ contracts/README.md | 5 + contracts/THREAT_MODEL.md | 109 ++++++++++++++++++ contracts/foundry.toml | 1 + .../src/settlement/MARKSettlementModule.sol | 6 +- 6 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 contracts/KNOWN_ISSUES.md create mode 100644 contracts/THREAT_MODEL.md diff --git a/.github/workflows/secrets-drift-guard.yml b/.github/workflows/secrets-drift-guard.yml index e787798..64ad493 100644 --- a/.github/workflows/secrets-drift-guard.yml +++ b/.github/workflows/secrets-drift-guard.yml @@ -34,7 +34,7 @@ jobs: grep -E '^\+[^+]' /tmp/pr.diff > /tmp/pr.added || true # Remove obvious placeholders and known non-secret examples. - grep -Ev 'YOUR_PRIVATE_KEY|0x0000000000000000000000000000000000000000|gho_\*+|github\.com/cli/cli/releases|PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000001' /tmp/pr.added > /tmp/pr.added.filtered || true + grep -Ev 'YOUR_PRIVATE_KEY|0x0000000000000000000000000000000000000000|gho_\*+|github\.com/cli/cli/releases|PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000001|0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' /tmp/pr.added > /tmp/pr.added.filtered || true # High-signal patterns. if grep -Ein \ diff --git a/contracts/KNOWN_ISSUES.md b/contracts/KNOWN_ISSUES.md new file mode 100644 index 0000000..3ae439b --- /dev/null +++ b/contracts/KNOWN_ISSUES.md @@ -0,0 +1,73 @@ +# MARK Protocol — Known Issues and Design Decisions + +This document lists known limitations and intentional design decisions that auditors should be aware of before reviewing the contracts. These are not bugs — they are accepted tradeoffs with documented rationale. + +## KI-1: AttestedSettlementVerifier is a placeholder for ZK + +**Contract:** `AttestedSettlementVerifier` + +**Description:** The verifier uses ECDSA signatures (EIP-712) rather than zero-knowledge proofs. It is explicitly designed as a production-safe bridge step before ZK verifier integration. The ZK proof system has not been designed yet. + +**Impact:** Settlement validity depends on attester key security rather than cryptographic proof. A compromised attester key allows fraudulent attestations (though still requires operator cooperation to execute). + +**Accepted because:** The attested verifier provides meaningful security (role-gated, replay-protected, deadline-bound, module-bound) while the ZK system is being designed. Production mode lock ensures the verifier cannot be removed once activated. + +--- + +## KI-2: No pause mechanism on MARKBridgeAdapter + +**Contract:** `MARKBridgeAdapter` + +**Description:** There is no `pause()` function. Emergency containment is achieved by revoking all `OPERATOR_ROLE` holders, which stops all bridge operations. + +**Impact:** Emergency response requires an admin transaction to revoke operators. There is no single-transaction pause. + +**Accepted because:** A pause function introduces pause-admin key risk. Operator revocation achieves the same containment without adding a new privileged role. The 1-day admin delay does not apply to role revocation — it is immediate. + +--- + +## KI-3: setVerifier does not verify IUTXOSettlementVerifier compliance + +**Contract:** `MARKSettlementModule.setVerifier` + +**Description:** The `code.length > 0` check rejects EOAs and undeployed addresses, but does not verify that the contract at `verifierAddress` implements `IUTXOSettlementVerifier`. A non-conforming contract would revert at settlement call time rather than at configuration time. + +**Impact:** A misconfigured verifier address would cause all settlements to revert until corrected by admin. + +**Accepted because:** Adding an ERC-165 check would require the verifier to implement `supportsInterface`, which is not part of the `IUTXOSettlementVerifier` interface. The admin controls the verifier address and is expected to verify correctness before setting it. + +--- + +## KI-4: totalSettledMint and totalSettledBurn are informational only + +**Contract:** `MARKSettlementModule` + +**Description:** `totalSettledMint` and `totalSettledBurn` are cumulative counters with no overflow protection beyond Solidity 0.8's default checked arithmetic (which reverts on overflow). + +**Impact:** If either counter overflows `type(uint256).max`, all future settlements would revert. This requires minting or burning more than 2^256 - 1 tokens, which is practically impossible given token supply constraints. + +**Accepted because:** The overflow condition is unreachable in practice. The counters are informational — they do not affect settlement logic. + +--- + +## KI-5: Bridge daily cap uses block.timestamp epoch + +**Contract:** `MARKBridgeAdapter._consumeLimits` + +**Description:** The daily cap epoch is computed as `block.timestamp / 1 days`. Miners/validators can manipulate `block.timestamp` by a small amount (~15 seconds on OP Stack). This could allow a small amount of cap boundary manipulation. + +**Impact:** An operator with validator cooperation could bridge slightly more than the daily cap at epoch boundaries. The manipulation window is bounded by the timestamp tolerance (~15 seconds). + +**Accepted because:** The daily cap is a soft risk control, not a hard security boundary. The manipulation window is negligible relative to the cap amounts expected in production. + +--- + +## KI-6: Transitive dependency alerts from @eth-optimism/super-cli + +**Scope:** Development tooling only + +**Description:** `@hono/node-server` (high), `drizzle-orm` (high), and `@stablelib/ed25519` (medium) have open Dependabot alerts. All are transitive dependencies of `@eth-optimism/super-cli`, a dev/deploy tool that never runs in production. + +**Impact:** None — these packages are not part of the deployed protocol. + +**Accepted because:** No upstream fix is available. The packages are scoped to development tooling only. diff --git a/contracts/README.md b/contracts/README.md index 0b0e926..200a4a0 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -5,6 +5,11 @@ Smart contracts for Superchain interoperability and `RYLA` standard credit primi Operational procedures (deployment, incident, rollback) are documented in [RUNBOOK.md](./RUNBOOK.md). Pre-mainnet promotion criteria are documented in [STAGING_GO_NO_GO_CHECKLIST.md](./STAGING_GO_NO_GO_CHECKLIST.md). +## Audit Preparation + +- [THREAT_MODEL.md](./THREAT_MODEL.md) — trust assumptions, role compromise impact, external dependencies +- [KNOWN_ISSUES.md](./KNOWN_ISSUES.md) — accepted design decisions and known limitations + ## Contracts ### [RYLA.sol](./src/token/RYLA.sol) diff --git a/contracts/THREAT_MODEL.md b/contracts/THREAT_MODEL.md new file mode 100644 index 0000000..272a593 --- /dev/null +++ b/contracts/THREAT_MODEL.md @@ -0,0 +1,109 @@ +# MARK Protocol — Threat Model + +This document is intended for security auditors. It describes the trust assumptions, threat boundaries, and worst-case impact of each privileged role being compromised. + +## System Overview + +MARK is a settlement and bridging protocol on the Optimism Superchain. It consists of four contracts: + +- **RYLA** — ERC-20 credit token with role-gated mint/burn +- **MARKSettlementModule** — operator-gated settlement boundary; holds MINTER_ROLE and BURNER_ROLE on RYLA +- **MARKBridgeAdapter** — operator-gated bridge adapter; routes RYLA cross-chain via SuperchainTokenBridge +- **AttestedSettlementVerifier** — EIP-712 signature verifier; validates settlement attestations before mint/burn + +## Trust Boundaries + +``` +External actors + └── Operator (hot key) + ├── calls settleMint / settleBurn on MARKSettlementModule + └── calls bridgeTo on MARKBridgeAdapter + + └── Attester (hot key) + └── signs EIP-712 attestations consumed by AttestedSettlementVerifier + + └── Default Admin (hardware wallet) + ├── grants/revokes OPERATOR_ROLE on bridge and settlement + ├── grants/revokes ATTESTER_ROLE on verifier + ├── grants/revokes MINTER_ROLE and BURNER_ROLE on RYLA + ├── configures verifier, bridge limits, destination allowlist + └── activates production mode (irreversible) + +External contracts + └── SuperchainTokenBridge (predeploy 0x4200...0028) + └── called by MARKBridgeAdapter.bridgeTo; trusted as a system predeploy +``` + +## Role Compromise Impact + +### Default Admin key compromised + +Worst case: complete protocol takeover. + +- Attacker can grant OPERATOR_ROLE to any address and submit arbitrary settlements +- Attacker can grant MINTER_ROLE directly to any address and mint unbounded RYLA +- Attacker can disable proof validation (if not in production mode) +- Attacker can change the verifier to a malicious contract + +Mitigations: +- 1-day delay on admin transfers (`AccessControlDefaultAdminRules`) — admin key rotation takes at least 24 hours +- Production mode lock prevents disabling proof validation once activated +- Operator role revocation is immediate — existing operators can be revoked before attacker acts + +### Operator key compromised + +Worst case: unauthorized settlements and bridge transactions within configured limits. + +- Attacker can call `settleMint` to mint RYLA to arbitrary addresses (if proof validation is disabled) +- Attacker can call `settleBurn` to burn RYLA from accounts that have approved the module +- Attacker can call `bridgeTo` to bridge RYLA cross-chain within daily cap and per-tx limits +- Attacker cannot change configuration, rotate roles, or disable proof validation + +Mitigations: +- Proof validation (when enabled) requires a valid attester signature — operator alone cannot mint without attester cooperation +- Bridge rate limits (maxPerTx, dailyCap) bound the damage window +- Operator role can be revoked immediately by admin + +### Attester key compromised + +Worst case: fraudulent settlement attestations. + +- Attacker can sign attestations authorizing arbitrary mint/burn operations +- Attacker cannot submit settlements directly (requires OPERATOR_ROLE) +- Attacker cannot change configuration + +Mitigations: +- Requires operator cooperation to execute — attester alone cannot settle +- Attester role can be revoked immediately by admin +- Attestations are bound to a specific verifier address, settlement module, and deadline — cannot be replayed across contracts or after expiry + +### SuperchainTokenBridge compromised + +Worst case: cross-chain token accounting failure. + +- A compromised bridge predeploy could fail to burn tokens on the source chain or fail to mint on the destination +- `MARKBridgeAdapter` handles bridge failure via try/catch — clears approval and reverts `BridgeFailed()` if `sendERC20` fails +- The bridge is a system predeploy — its security is outside the scope of this protocol + +## External Dependencies + +| Dependency | Version | Trust level | Notes | +|---|---|---|---| +| OpenZeppelin Contracts | via createx lib | High | AccessControl, SafeERC20, EIP712, ECDSA, ReentrancyGuard | +| interop-lib | submodule | High | SuperchainERC20, ISuperchainTokenBridge, PredeployAddresses | +| SuperchainTokenBridge | predeploy 0x4200...0028 | System | Trusted as OP Stack system contract | + +## What Is Explicitly Out of Scope + +- **AttestedSettlementVerifier is a placeholder** — it is a production-safe bridge step before ZK verifier integration. The ZK proof system has not been designed yet. Auditors should evaluate the attested verifier as a standalone ECDSA-based verifier, not as a ZK system. +- **Off-chain operator infrastructure** — the protocol does not specify how operators construct or submit settlement intents. That is provider-layer responsibility. +- **Frontend** — the frontend is a read-only info page with no wallet interaction or user funds. +- **Deployment scripts** — `contracts/script/` contains operational tooling, not protocol logic. + +## Invariants the Protocol Relies On + +1. Only `MARKSettlementModule` holds `MINTER_ROLE` and `BURNER_ROLE` on RYLA in production. +2. `consumedIntents[intentId]` is set to `true` before the external call to `verifier_.verifySettlement()`, following the CEI pattern. This prevents replay even if a future non-view verifier makes a reentrant call. +3. `bridgedInDailyCapEpoch` never exceeds `dailyCap` within a single epoch. +4. `productionMode` is irreversible once set — proof validation cannot be disabled. +5. The module's RYLA balance returns to its pre-settlement value after each `settleBurn` operation. The module does not accumulate tokens across settlements. Note: tokens sent directly to the module address outside of settlement flows are not covered by this invariant. diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 87a32e7..98cc45f 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -32,3 +32,4 @@ no_match_path = "test/never/**" + diff --git a/contracts/src/settlement/MARKSettlementModule.sol b/contracts/src/settlement/MARKSettlementModule.sol index 7e32c61..ae2cb83 100644 --- a/contracts/src/settlement/MARKSettlementModule.sol +++ b/contracts/src/settlement/MARKSettlementModule.sol @@ -116,13 +116,15 @@ contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules if (amount == 0) revert InvalidAmount(); if (consumedIntents[intentId]) revert IntentAlreadyConsumed(); + // Mark consumed before external call to follow CEI pattern. + // Prevents replay even if a future non-view verifier makes a reentrant call. + consumedIntents[intentId] = true; + if (proofValidationEnabled) { IUTXOSettlementVerifier verifier_ = verifier; if (address(verifier_) == address(0)) revert VerifierRequired(); bool ok = verifier_.verifySettlement(intentId, address(this), account, amount, isMint, proof); if (!ok) revert VerificationFailed(); } - - consumedIntents[intentId] = true; } } From 3429cf5c4bc886356a7e2c592b0dfe99fa811eaf Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Mon, 11 May 2026 03:02:35 +0700 Subject: [PATCH 31/38] chore: promote dev to canary Promotes dev to canary. Staging rehearsal will trigger on merge. --- BRANCHING.md | 4 ++-- contracts/foundry.toml | 1 + contracts/src/settlement/MARKSettlementModule.sol | 5 +++++ scripts/github/apply-governance.sh | 2 +- scripts/github/verify-governance.sh | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/BRANCHING.md b/BRANCHING.md index e6ee2b1..d0f3178 100644 --- a/BRANCHING.md +++ b/BRANCHING.md @@ -146,8 +146,8 @@ Apply these repository settings: - `slither-core / Slither Core Contracts` - `frontend-checks / Frontend Checks (Node 20)` - `frontend-checks / Frontend Checks (Node 22)` -- Require at least 1 approval. -- Dismiss stale approvals on new commits. +- 0 required approvals (solo maintainer — CI checks are the gate). + Restore to 1 when a second team member joins. 3. Protect `dev` - Require pull request before merge (or allow maintainers direct push if desired). diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 98cc45f..e75ff1a 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -33,3 +33,4 @@ no_match_path = "test/never/**" + diff --git a/contracts/src/settlement/MARKSettlementModule.sol b/contracts/src/settlement/MARKSettlementModule.sol index ae2cb83..f2a5d25 100644 --- a/contracts/src/settlement/MARKSettlementModule.sol +++ b/contracts/src/settlement/MARKSettlementModule.sol @@ -77,6 +77,7 @@ contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules emit ProductionModeActivated(msg.sender); } + /// @notice Mints RYLA to `recipient` after validating the settlement intent. function settleMint(address recipient, uint256 amount, bytes32 intentId, bytes calldata proof) external onlyRole(OPERATOR_ROLE) @@ -89,6 +90,10 @@ contract MARKSettlementModule is ReentrancyGuard, AccessControlDefaultAdminRules emit MintSettled(intentId, msg.sender, recipient, amount); } + /// @notice Burns RYLA from `account` by transferring to this module then burning. + /// @dev The `account` must have approved this module for at least `amount` tokens + /// before the operator calls this function. Insufficient allowance will revert + /// with a SafeERC20 error. function settleBurn(address account, uint256 amount, bytes32 intentId, bytes calldata proof) external onlyRole(OPERATOR_ROLE) diff --git a/scripts/github/apply-governance.sh b/scripts/github/apply-governance.sh index b1084f3..4bce9b1 100755 --- a/scripts/github/apply-governance.sh +++ b/scripts/github/apply-governance.sh @@ -262,7 +262,7 @@ fi apply_branch_protection "main" "${MAIN_REVIEW_COUNT}" "$MAIN_CHECKS_JSON" "$MAIN_RESTRICTIONS_JSON" # canary: PR + checks; stabilisation track -apply_branch_protection "canary" "1" "$CANARY_CHECKS_JSON" "$CANARY_RESTRICTIONS_JSON" +apply_branch_protection "canary" "0" "$CANARY_CHECKS_JSON" "$CANARY_RESTRICTIONS_JSON" # dev: PR + checks; direct pushes configurable by changing restrict_pushes here apply_branch_protection "dev" "${DEV_REVIEW_COUNT}" "$DEV_CHECKS_JSON" "$DEV_RESTRICTIONS_JSON" diff --git a/scripts/github/verify-governance.sh b/scripts/github/verify-governance.sh index 77e3c9e..0ecf938 100755 --- a/scripts/github/verify-governance.sh +++ b/scripts/github/verify-governance.sh @@ -122,7 +122,7 @@ check_branch() { # dev has 0 required approvals so dismiss_stale_reviews is not applicable. check_branch dev false "${require_checks_dev[@]}" -check_branch canary true "${require_checks_dev[@]}" +check_branch canary false "${require_checks_dev[@]}" check_branch main true "${require_checks_main[@]}" echo "[verify] governance baseline active for ${GH_REPO}" From 1eaaeb65a56cc9a4366951d596d775c5e2e02259 Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Mon, 11 May 2026 03:12:15 +0700 Subject: [PATCH 32/38] chore: promote dev to canary Promotes dev to canary. Staging rehearsal will trigger on merge. --- contracts/foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/foundry.toml b/contracts/foundry.toml index e75ff1a..0bd298c 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -34,3 +34,4 @@ no_match_path = "test/never/**" + From 88389dff6386b40e2e113de25c6fe6aaffac83f5 Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Mon, 11 May 2026 03:24:51 +0700 Subject: [PATCH 33/38] chore: promote dev to canary Promotes dev to canary. Includes --slow fix for staging rehearsal. --- contracts/script/ops/rehearse-production-lock.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/script/ops/rehearse-production-lock.sh b/contracts/script/ops/rehearse-production-lock.sh index 38062ee..e0584ef 100755 --- a/contracts/script/ops/rehearse-production-lock.sh +++ b/contracts/script/ops/rehearse-production-lock.sh @@ -61,7 +61,7 @@ export MARK_SETTLEMENT_VERIFIER="${MARK_SETTLEMENT_VERIFIER:-0x00000000000000000 VALIDATE_MODE=rehearsal ./script/ops/validate-prod-env.sh echo "Running staging rehearsal release..." -forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url "$RPC_URL" --broadcast -q +forge script script/ops/settlement/ReleaseMARK.s.sol --rpc-url "$RPC_URL" --broadcast --slow -q if [[ ! -f "$RELEASE_ARTIFACT_PATH" ]]; then echo "Missing release artifact: $RELEASE_ARTIFACT_PATH" >&2 From 55cd585f3b334edb035b1d8107aed8bb2b19b8b6 Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Mon, 11 May 2026 05:59:12 +0700 Subject: [PATCH 34/38] chore: promote dev to canary Promotes dev to canary. Governance fixes: 0 approvals, push restrictions to trade/maintainers, verify-governance.sh checks. --- contracts/foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 0bd298c..8e3b0a0 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -35,3 +35,4 @@ no_match_path = "test/never/**" + From 21dbf9bd7dc3a5626dbcb174b956aa083d41626d Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Mon, 11 May 2026 13:19:46 +0700 Subject: [PATCH 35/38] chore: promote dev to canary Promotes dev to canary. Includes Groth16SettlementVerifier, IGroth16Verifier, dependabot fix, governance push restrictions. --- .../interfaces/IGroth16Verifier.sol | 26 ++++ .../verifier/Groth16SettlementVerifier.sol | 98 ++++++++++++++ .../Groth16SettlementVerifier.t.sol | 121 ++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 contracts/src/settlement/interfaces/IGroth16Verifier.sol create mode 100644 contracts/src/settlement/verifier/Groth16SettlementVerifier.sol create mode 100644 contracts/test/unit/settlement/Groth16SettlementVerifier.t.sol diff --git a/contracts/src/settlement/interfaces/IGroth16Verifier.sol b/contracts/src/settlement/interfaces/IGroth16Verifier.sol new file mode 100644 index 0000000..07aec7b --- /dev/null +++ b/contracts/src/settlement/interfaces/IGroth16Verifier.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Interface for a Groth16 proof verifier contract. +/// @dev Matches the output of snarkjs `exportSolidityVerifier`. The verifier contract +/// is generated from the compiled circuit and is specific to that circuit's +/// verification key. Swap the implementation by deploying a new verifier contract +/// and calling `Groth16SettlementVerifier.setVerifierContract`. +/// +/// Proof encoding (256 bytes): +/// uint256[2] a — G1 point (proof.pi_a) +/// uint256[2][2] b — G2 point (proof.pi_b) +/// uint256[2] c — G1 point (proof.pi_c) +/// +/// Public signals (4 × uint256, 128 bytes): +/// [0] nullifierHash — keccak256(note_secret, nullifier_nonce), prevents double-spend +/// [1] commitmentHash — keccak256(recipient, amount, blinding_factor), binds output note +/// [2] amount — token amount in base units (must match settlement call) +/// [3] isMint — 1 for mint, 0 for burn +interface IGroth16Verifier { + /// @notice Verifies a Groth16 proof against the circuit's verification key. + /// @param proof ABI-encoded (uint256[2], uint256[2][2], uint256[2]) — 256 bytes. + /// @param pubSignals ABI-encoded uint256[4] public signals — 128 bytes. + /// @return True if the proof is valid. + function verifyProof(bytes calldata proof, bytes calldata pubSignals) external view returns (bool); +} diff --git a/contracts/src/settlement/verifier/Groth16SettlementVerifier.sol b/contracts/src/settlement/verifier/Groth16SettlementVerifier.sol new file mode 100644 index 0000000..665d4f4 --- /dev/null +++ b/contracts/src/settlement/verifier/Groth16SettlementVerifier.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +import {IUTXOSettlementVerifier} from "../interfaces/IUTXOSettlementVerifier.sol"; +import {IGroth16Verifier} from "../interfaces/IGroth16Verifier.sol"; +import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; + +/// @title Groth16SettlementVerifier +/// @notice Groth16 proof verifier for UTXO settlement intents. +/// @dev Implements IUTXOSettlementVerifier by delegating to a circuit-generated +/// IGroth16Verifier contract. The verifier contract is produced by snarkjs +/// `exportSolidityVerifier` from the compiled UTXO circuit and encodes the +/// circuit's verification key. Swap circuits by deploying a new verifier +/// contract and calling `setVerifierContract`. +/// +/// Proof layout (384 bytes total passed as `proof` to verifySettlement): +/// bytes [0:256] — Groth16 proof: abi.encode(uint256[2] a, uint256[2][2] b, uint256[2] c) +/// bytes [256:384] — Public signals: abi.encode(uint256[4]) +/// pubSignals[0] nullifierHash — prevents double-spend of the input UTXO note +/// pubSignals[1] commitmentHash — binds the output note (recipient + amount + blinding) +/// pubSignals[2] amount — must equal the amount passed to verifySettlement +/// pubSignals[3] isMint — must equal 1 (mint) or 0 (burn) +/// +/// The circuit proves knowledge of a secret note such that: +/// - The note's nullifier has not been seen before (enforced off-chain by operator) +/// - The note's value equals `amount` +/// - The note's direction (mint/burn) matches `isMint` +/// On-chain, this contract verifies the proof and checks that the public signals +/// are consistent with the settlement parameters. +contract Groth16SettlementVerifier is IUTXOSettlementVerifier, AccessControlDefaultAdminRules { + uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; + + /// @dev Length of the Groth16 proof component (a, b, c encoded). + uint256 private constant PROOF_BYTES = 256; + /// @dev Length of the public signals component (4 × uint256). + uint256 private constant PUB_SIGNALS_BYTES = 128; + /// @dev Total expected proof length passed to verifySettlement. + uint256 private constant TOTAL_PROOF_BYTES = PROOF_BYTES + PUB_SIGNALS_BYTES; + + event VerifierContractUpdated(address indexed verifierContract); + + /// @notice The circuit-generated Groth16 verifier contract. + /// @dev address(0) until a real circuit verifier is deployed and set. + IGroth16Verifier public verifierContract; + + constructor(address initialAdmin) + AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) + { + if (initialAdmin == address(0)) revert ZeroAddress(); + } + + /// @notice Sets the circuit-generated Groth16 verifier contract. + /// @dev Deploy the verifier produced by `snarkjs exportSolidityVerifier` and pass + /// its address here. Rejects EOAs and undeployed addresses. + function setVerifierContract(address verifierContract_) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (verifierContract_ == address(0)) revert ZeroAddress(); + if (verifierContract_.code.length == 0) revert ZeroAddress(); + verifierContract = IGroth16Verifier(verifierContract_); + emit VerifierContractUpdated(verifierContract_); + } + + /// @inheritdoc IUTXOSettlementVerifier + function verifySettlement( + bytes32 intentId, + address settlementModule, + address account, + uint256 amount, + bool isMint, + bytes calldata proof + ) + external + view + override + returns (bool) + { + if (intentId == bytes32(0) || settlementModule == address(0) || account == address(0) || amount == 0) { + return false; + } + if (proof.length != TOTAL_PROOF_BYTES) return false; + + IGroth16Verifier verifier_ = verifierContract; + if (address(verifier_) == address(0)) return false; + + bytes calldata groth16Proof = proof[0:PROOF_BYTES]; + bytes calldata pubSignalsBytes = proof[PROOF_BYTES:TOTAL_PROOF_BYTES]; + + uint256[4] memory pubSignals = abi.decode(pubSignalsBytes, (uint256[4])); + + // Public signal consistency checks — the circuit must commit to these values. + if (pubSignals[2] != amount) return false; + if (pubSignals[3] != (isMint ? 1 : 0)) return false; + + return verifier_.verifyProof(groth16Proof, pubSignalsBytes); + } +} diff --git a/contracts/test/unit/settlement/Groth16SettlementVerifier.t.sol b/contracts/test/unit/settlement/Groth16SettlementVerifier.t.sol new file mode 100644 index 0000000..fef788c --- /dev/null +++ b/contracts/test/unit/settlement/Groth16SettlementVerifier.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {Groth16SettlementVerifier} from "../../../src/settlement/verifier/Groth16SettlementVerifier.sol"; +import {IGroth16Verifier} from "../../../src/settlement/interfaces/IGroth16Verifier.sol"; + +/// @dev Minimal mock that returns a configurable result for any proof. +contract MockGroth16Verifier is IGroth16Verifier { + bool private _result; + + constructor(bool result) { + _result = result; + } + + function verifyProof(bytes calldata, bytes calldata) external view returns (bool) { + return _result; + } +} + +contract Groth16SettlementVerifierTest is Test { + Groth16SettlementVerifier internal verifier; + MockGroth16Verifier internal mockVerifierOk; + MockGroth16Verifier internal mockVerifierFail; + + address internal owner = makeAddr("owner"); + address internal module = makeAddr("module"); + address internal user = makeAddr("user"); + + bytes32 internal constant INTENT = keccak256("intent-1"); + uint256 internal constant AMOUNT = 10 ether; + + function setUp() public { + vm.prank(owner); + verifier = new Groth16SettlementVerifier(owner); + mockVerifierOk = new MockGroth16Verifier(true); + mockVerifierFail = new MockGroth16Verifier(false); + + vm.prank(owner); + verifier.setVerifierContract(address(mockVerifierOk)); + } + + function _buildProof(uint256 amount, bool isMint) internal pure returns (bytes memory) { + // Groth16 proof component: 256 bytes (a, b, c — all zeroed for mock) + bytes memory proof = new bytes(256); + // Public signals: nullifierHash, commitmentHash, amount, isMint + uint256[4] memory sigs; + sigs[0] = uint256(keccak256("nullifier")); + sigs[1] = uint256(keccak256("commitment")); + sigs[2] = amount; + sigs[3] = isMint ? 1 : 0; + return abi.encodePacked(proof, abi.encode(sigs)); + } + + function testVerifySettlementReturnsTrueForValidProof() public view { + bytes memory proof = _buildProof(AMOUNT, true); + assertTrue(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); + } + + function testVerifySettlementReturnsFalseWhenCircuitVerifierFails() public { + vm.prank(owner); + verifier.setVerifierContract(address(mockVerifierFail)); + bytes memory proof = _buildProof(AMOUNT, true); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); + } + + function testVerifySettlementReturnsFalseWhenNoVerifierSet() public { + vm.prank(owner); + Groth16SettlementVerifier fresh = new Groth16SettlementVerifier(owner); + bytes memory proof = _buildProof(AMOUNT, true); + assertFalse(fresh.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); + } + + function testVerifySettlementReturnsFalseForAmountMismatch() public view { + // proof commits to AMOUNT but call passes AMOUNT+1 + bytes memory proof = _buildProof(AMOUNT, true); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT + 1, true, proof)); + } + + function testVerifySettlementReturnsFalseForIsMintMismatch() public view { + // proof commits to isMint=true but call passes false + bytes memory proof = _buildProof(AMOUNT, true); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, false, proof)); + } + + function testVerifySettlementReturnsFalseForMalformedProof() public view { + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, hex"1234")); + } + + function testVerifySettlementReturnsFalseForZeroIntentId() public view { + bytes memory proof = _buildProof(AMOUNT, true); + assertFalse(verifier.verifySettlement(bytes32(0), module, user, AMOUNT, true, proof)); + } + + function testVerifySettlementReturnsFalseForZeroModule() public view { + bytes memory proof = _buildProof(AMOUNT, true); + assertFalse(verifier.verifySettlement(INTENT, address(0), user, AMOUNT, true, proof)); + } + + function testVerifySettlementReturnsFalseForZeroAccount() public view { + bytes memory proof = _buildProof(AMOUNT, true); + assertFalse(verifier.verifySettlement(INTENT, module, address(0), AMOUNT, true, proof)); + } + + function testVerifySettlementReturnsFalseForZeroAmount() public view { + bytes memory proof = _buildProof(0, true); + assertFalse(verifier.verifySettlement(INTENT, module, user, 0, true, proof)); + } + + function testSetVerifierContractRevertsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert(); + verifier.setVerifierContract(address(0)); + } + + function testSetVerifierContractRevertsForEOA() public { + vm.prank(owner); + vm.expectRevert(); + verifier.setVerifierContract(makeAddr("eoa")); + } +} From 42acd8a6c038a935462034e473fc50e1d0f67d64 Mon Sep 17 00:00:00 2001 From: Iko <6572003+iap@users.noreply.github.com> Date: Sun, 17 May 2026 09:37:27 +0700 Subject: [PATCH 36/38] chore: promote dev to canary for OP Sepolia staging (#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): bump actions/setup-node from 5 to 6 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/upload-artifact from 4 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/github-script from 7 to 9 Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 9. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v9) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump the frontend-minor-patch group with 13 updates Bumps the frontend-minor-patch group with 13 updates: | Package | From | To | | --- | --- | --- | | [@eth-optimism/viem](https://github.com/ethereum-optimism/ecosystem/tree/HEAD/packages/viem) | `0.3.2` | `0.4.15` | | [@radix-ui/react-separator](https://github.com/radix-ui/primitives) | `1.1.2` | `1.1.8` | | [@radix-ui/react-slot](https://github.com/radix-ui/primitives) | `1.1.2` | `1.2.4` | | [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.0.6` | `4.2.4` | | [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.66.0` | `5.100.8` | | [abitype](https://github.com/wevm/abitype) | `1.0.8` | `1.2.4` | | [tailwind-merge](https://github.com/dcastil/tailwind-merge) | `3.0.1` | `3.5.0` | | [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.0.6` | `4.2.4` | | [viem](https://github.com/wevm/viem) | `2.23.1` | `2.48.8` | | [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.4.19` | `0.5.2` | | [mprocs](https://github.com/pvolok/mprocs) | `0.7.2` | `0.9.2` | | [prettier](https://github.com/prettier/prettier) | `3.5.0` | `3.8.3` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.24.0` | `8.59.1` | Updates `@eth-optimism/viem` from 0.3.2 to 0.4.15 - [Changelog](https://github.com/ethereum-optimism/ecosystem/blob/main/packages/viem/CHANGELOG.md) - [Commits](https://github.com/ethereum-optimism/ecosystem/commits/HEAD/packages/viem) Updates `@radix-ui/react-separator` from 1.1.2 to 1.1.8 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@radix-ui/react-slot` from 1.1.2 to 1.2.4 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@tailwindcss/vite` from 4.0.6 to 4.2.4 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.4/packages/@tailwindcss-vite) Updates `@tanstack/react-query` from 5.66.0 to 5.100.8 - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.100.8/packages/react-query) Updates `abitype` from 1.0.8 to 1.2.4 - [Release notes](https://github.com/wevm/abitype/releases) - [Commits](https://github.com/wevm/abitype/compare/abitype@1.0.8...abitype@1.2.4) Updates `tailwind-merge` from 3.0.1 to 3.5.0 - [Release notes](https://github.com/dcastil/tailwind-merge/releases) - [Commits](https://github.com/dcastil/tailwind-merge/compare/v3.0.1...v3.5.0) Updates `tailwindcss` from 4.0.6 to 4.2.4 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.4/packages/tailwindcss) Updates `viem` from 2.23.1 to 2.48.8 - [Release notes](https://github.com/wevm/viem/releases) - [Commits](https://github.com/wevm/viem/compare/viem@2.23.1...viem@2.48.8) Updates `eslint-plugin-react-refresh` from 0.4.19 to 0.5.2 - [Release notes](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/releases) - [Changelog](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/main/CHANGELOG.md) - [Commits](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/compare/v0.4.19...v0.5.2) Updates `mprocs` from 0.7.2 to 0.9.2 - [Release notes](https://github.com/pvolok/mprocs/releases) - [Changelog](https://github.com/pvolok/mprocs/blob/master/CHANGELOG.md) - [Commits](https://github.com/pvolok/mprocs/compare/v0.7.2...v0.9.2) Updates `prettier` from 3.5.0 to 3.8.3 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.0...3.8.3) Updates `typescript-eslint` from 8.24.0 to 8.59.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@eth-optimism/viem" dependency-version: 0.4.15 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@radix-ui/react-separator" dependency-version: 1.1.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-minor-patch - dependency-name: "@radix-ui/react-slot" dependency-version: 1.2.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/vite" dependency-version: 4.2.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tanstack/react-query" dependency-version: 5.100.8 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: abitype dependency-version: 1.2.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: tailwind-merge dependency-version: 3.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: tailwindcss dependency-version: 4.2.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: viem dependency-version: 2.48.8 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: eslint-plugin-react-refresh dependency-version: 0.5.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: mprocs dependency-version: 0.9.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: prettier dependency-version: 3.8.3 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: typescript-eslint dependency-version: 8.59.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-patch ... Signed-off-by: dependabot[bot] * fix(readiness): run pre-checks before contracts working directory exists * fix(frontend): remove non-component export from button ui * ci(security): add codeql and dependency review gates * chore(security): add local slither install and core scan targets * docs(phase1): add comprehensive contributor & deployment runbooks Add Phase 1 foundation documentation for team scaling and professional maintenance: CONTRIBUTING.md: - Local development setup instructions (Node, Foundry, super-cli) - Feature branch workflow with conventional commits - Code standards (TypeScript, Solidity, Testing) - PR submission checklist and review process - Testing guidelines and test structure - Troubleshooting for common dev issues DEPLOYMENT.md: - Step-by-step staging deployment runbook (OP Sepolia) - Mainnet deployment procedures with gates - Pre/post-deployment checklists - Evidence generation and verification - Monitoring and health checks - Rollback procedures for emergency scenarios - Comprehensive troubleshooting guide - Command cheat sheet and timeline estimates TROUBLESHOOTING.md: - Development setup issues (pnpm, Node, Foundry, super-cli, git hooks) - Smart contract issues (architecture guard, layering guard, Slither findings) - Frontend development issues (port conflicts, TypeScript errors, module resolution) - Testing issues (hanging tests, gas, balance) - Deployment issues (insufficient funds, timeouts, RPC problems) - CI/CD workflow issues (stuck workflows, secrets, version mismatches) - Network & RPC issues (timeouts, contract not found, chain ID) .github/CODEOWNERS: - Enhanced documentation with clear sections - Added review requirements annotations - Better organization for team scaling - Maintains strict single-owner model (ready for multi-owner when scaling) Impact: - Enables solo maintainer to self-document workflows - Provides clear onboarding path for new contributors - Establishes professional deployment procedures - Reduces support burden with comprehensive troubleshooting - Foundation for team collaboration (docs ready for team addition) - Production-ready documentation for auditors and stakeholders This commit fulfills Phase 1 foundation requirements: ✅ CONTRIBUTING.md created ✅ DEPLOYMENT.md runbook created ✅ TROUBLESHOOTING.md created ✅ CODEOWNERS enhanced and documented Ready for: Phase 2 (interactive UI) and Phase 3 (security audit planning) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#16) Bump github/codeql-action from v3 to v4 to resolve Node.js 20 deprecation warnings on CI. * chore(ci): bump dependency-review-action from v4 to v5 * chore(ci): disable CodeQL triggers until repo transferred to org with GHAS * Enable org-transfer governance: CodeQL, Gitleaks, release-gate container, and verification scripts (#19) * docs: replace roadmap with lean security next-steps guide * fix(docs): remove duplicate required-check entries in BRANCHING.md * fix(ci): add USER root in release-gate Dockerfile for apt-get permissions * ci(security): fix dependency review tag and use OSS gitleaks CLI * ci(security): fix gitleaks PATH on github runner * ci(security): run gitleaks scan via docker image * ci(security): remove hardcoded key and scope gitleaks to workspace * ci(contracts): fix anvil key extraction for release check * ci(contracts): require 64-byte anvil private key extraction * ci: always run contracts/frontend checks on protected branches (#21) * ci: phase-1 reusable workflows for frontend, slither, and secrets scan (#23) * ci: extract reusable frontend/slither/secrets workflows * ci(security): apply codereview pinning and permissions fixes * fix(contracts): bridge approval safety + IRYLA interface decoupling - Wrap sendERC20 in try/catch; clear approval and revert with BridgeFailed() on failure - Extract IRYLA interface (inherits IERC20); MARKSettlementModule decoupled from concrete RYLA type - Add unit test for BridgeFailed catch branch * docs: sync governance and CI docs with current protections - Add missing required checks (Secrets Drift Guard, Release Gate Container) to all branch matrices - Fix Analyze (JavaScript/TypeScript) casing to match canonical check names - Fixes Validate Governance Policy Consistency CI check * chore(deps): bump frontend minor/patch dependencies 105 minor and patch updates including: - @tanstack/react-query 5.100.8 → 5.100.9 - typescript-eslint 8.59.1 → 8.59.2 - bufferutil 4.0.9 → 4.1.0 - jiti 2.6.1 → 2.7.0 - lockfile resolutions updated accordingly All CI checks pass on Node 20 and 22. * fix(deps): bump vite 6.1.0 → 6.4.2 (security) Fixes high-severity arbitrary file read CVE and medium-severity path traversal in vite dev server. * test(contracts): add missing unit test coverage 71 tests (was 59). Covers zero-input guards, exact error selectors, accumulator resets, supportsInterface, and isMint flag binding. * chore(governance): migrate CODEOWNERS to @trade/maintainers team Replaces @iap with @trade/maintainers across all CODEOWNERS entries. Team created with maintain permission on repo. * chore(ci): switch CodeRabbit to assertive profile profile: chill → assertive, request_changes_workflow: false → true * fix(docs): add VALIDATE_MODE to staging checklist prerequisites Adds missing VALIDATE_MODE env var to staging checklist. Clarifies operator/attester rotation step with RUNBOOK.md reference. Removes trailing newline from package.json. * chore(docs): remove stale pre-transfer planning documents Removes TRANSFER_NOW_CHECKLIST.md, ORG_TRANSFER_SECURITY_CHECKLIST.md, SECURITY_NEXT_STEPS.md, PROJECT_REVIEW.md — all completed with the org transfer on May 6, 2026. * chore(governance): clean up CODEOWNERS Remove decorative section dividers, redundant comments, and duplicate entry. Consolidate contract path globs. * fix(ci): workflow correctness and consistency fixes Pin slither-analyzer==0.11.5, fix secrets-drift-guard false positives, fix verify-governance.sh dismiss_stale_reviews on dev, add canary to evidence-manifest trigger, fix inputs context, fix wait-port, add pull_request_target comments, add Docker layer caching. * feat(contracts): migrate AttestedSettlementVerifier to EIP-712 Replace hybrid EIP-191 pattern with standard EIP-712 typed data signing. Expose settlementDigest() for off-chain signers. Add NatSpec on proof encoding and contextHash. 71 tests pass. * chore: improve gitignore coverage Add .env/.env.*/*.env and supersim-logs/ to root gitignore. Add coverage/ to contracts gitignore. * fix(ci): reliability and correctness fixes Add timeout-minutes:15 to stuck jobs, replace rg with grep -Eo in smoke script, pin slither==0.11.5 in Makefile, add explicit invariant runs=256 to foundry.toml. * chore(deps): ignore transitive alerts from super-cli Ignore @hono/node-server, drizzle-orm, @stablelib/ed25519 scoped to vulnerable versions — all transitive from super-cli dev tool, no upstream fix available. * docs: add SECURITY.md Reporting channel, scope, response SLA, and supported versions. * chore(deps): bump @types/node from 22.13.1 to 25.6.1 Type definitions update. * chore(deps): bump typescript from 5.7.3 to 6.0.3 Add ignoreDeprecations:6.0 for baseUrl deprecation warning. * chore(deps): bump frontend-minor-patch group viem, debug, and other minor/patch updates. * chore(deps): bump docker/setup-buildx-action from 3 to 4 Node 24 runtime update. * chore(deps): bump frontend-minor-patch group Minor/patch frontend dependency updates. * fix: stale references and check name mismatches Remove chainId double-encoding from AttestedSettlementVerifier, fix stale iap/mark URLs, fix governance script check names to match actual CI output. * test(contracts): add bridge integration test against supersim Exercises MARKBridgeAdapter against live SuperchainTokenBridge on two supersim forks. Verifies cross-chain token transfer and rate limit enforcement. * test(contracts): add bridge adapter invariant fuzz tests Three invariants covering rate limiting: daily cap never exceeded, accumulator consistent with cap, zero address never holds operator role. 74 tests pass. * fix(governance): sync check lists and fix ruleset condition Fix ruleset condition bug (canary/main now covered), sync apply-governance.sh and verify-governance.sh with live branch protection, fix frontend check name prefix in docs. * chore(governance): document new ruleset structure Two focused rulesets: branch-protection (CodeQL alert gate) and tag-protection (v* tags). Replaces the broken develop ruleset. * feat(token): rename RYLA display name to 'RYLA Credits' name() returns 'RYLA Credits', symbol stays 'RYLA'. Test and verification script updated. * test Documents key roles and trust assumptions, attester key rotation procedure, break-glass procedure, production mode implications, and key storage recommendations for auditors and operators. * fix(ci): use matrix language as CodeQL job name Produces consistent check name 'Analyze (javascript-typescript)' matching branch protection requirements. * chore(config): harden staging profile and document environment setup Remove PRIVATE_KEY from staging.env, fix bridge destination to OP Sepolia, add key separation docs, fix env guard and drift guard for CI validation. * feat(frontend): replace dev dashboard with protocol info page Protocol info page with pre-production status, contract descriptions, and resource links. Providers updated to optimism/optimismSepolia. * chore(docs): cleanup and NatSpec improvements Fix README clone URL and naming, remove stale date from CONTRIBUTING.md, add eip712Domain NatSpec and no-pause design decision docs. * fix(contracts): document setVerifier interface check limitation Add @dev comment explaining code.length check rejects EOAs but not non-conforming contracts. * docs: add protocol philosophy to README Code is a rule. No DAO, no drama. Don't Trust, Verify. * fix(ci): add working-directory override to pre-checkout branch enforcement steps Fixes pre-checkout branch check failing with 'No such file or directory' in staging and production workflows. * fix(ops): enable post-deploy in rehearse-production-lock Enable MARK_RELEASE_RUN_POSTDEPLOY so activateProductionMode() is called during rehearsal. * fix(ops): export deployed verifier address to env before PostDeployMARKSetup Fixes VerifierRequiredWhenProofEnabled during staging rehearsal. * fix(ci): exclude Anvil default key from secrets drift guard Syncs Anvil key exclusion to dev. * test THREAT_MODEL.md: trust boundaries, role compromise impact, external dependencies, invariants, and explicit out-of-scope items. KNOWN_ISSUES.md: six accepted design decisions with rationale — attested verifier as ZK placeholder, no-pause design, setVerifier interface check limitation, counter overflow analysis, timestamp epoch manipulation, and transitive dep alerts. * fix(docs): correct two inaccurate invariants in THREAT_MODEL.md consumedIntents is set after proof validation, not before. Module balance invariant is per-operation, not absolute zero. * fix(contracts): move consumedIntents assignment before external call (CEI) Follows CEI pattern — marks intent consumed before external verifier call. No behaviour change for current view verifier. * chore(governance): set canary to 0 required approvals for solo maintainer Solo dev cannot self-approve. CI checks are the gate. Restore to 1 when second team member joins. * docs(contracts): add NatSpec to settleMint and settleBurn Documents pre-approval requirement for settleBurn. * fix(ops): wait for tx confirmation in staging rehearsal Add --slow to forge script broadcast so Foundry waits for each transaction receipt before the verify step runs. * fix(governance): set all branches to 0 required approvals Solo maintainer cannot approve own PRs. CI gates are the enforcement mechanism. Removes MAIN_REVIEW_COUNT/DEV_REVIEW_COUNT vars, adds approval count verification to verify-governance.sh. * fix(governance): restrict direct pushes to trade/maintainers team Restricts direct pushes on all branches to trade/maintainers team. Removes unused helper functions. verify-governance.sh now checks push restriction team slug. * fix(deps): update drizzle-orm dependabot ignore rule to 0.38.4 drizzle-orm@0.38.4 is transitive from @eth-optimism/super-cli. Updated ignore rule to match installed version. All four Dependabot alerts dismissed as tolerable risk. * feat(contracts): add Groth16SettlementVerifier Adds Groth16SettlementVerifier implementing IUTXOSettlementVerifier via swappable IGroth16Verifier. 12 unit tests passing. AttestedSettlementVerifier remains active production verifier. * feat(circuits): add UTXOSettlement circom circuit Adds UTXOSettlement circom circuit. Poseidon-based UTXO ownership proof. 602 constraints, 6 witness tests passing. * feat(contracts): add MARKPool ZK UTXO pool domain Adds MARKPool shielded RYLA transfer pool. 88 unit tests passing. * fix(contracts): rewrite MARKPool for MARK's 4-signal circuit Rewrites MARKPool from scratch for MARK's own UTXOSettlement circuit. UTXOVerifier.sol regenerated from MARK's own trusted setup. 84 unit tests passing. * fix(circuits): add range constraints and isMint burn path Range constraints on recipient/chainId/settlementModule/amount. isMint burn path in MARKPool. Trusted setup rerun. 84 tests passing. * feat(pool): add MARKPool ZK UTXO pool domain (#100) * feat(pool): add MARKPool ZK UTXO pool domain Introduces the full pool domain for private RYLA transfers: Contracts: - MARKPool: ZK UTXO pool with Merkle tree, fee policy, bridge-out/in, withdraw binding, AccessManaged access control - MARKWithdrawAdapter: EIP-712 signature-based withdrawal adapter - RYLACreditLedger: ICreditLedger adapter bridging MARKPool to RYLA mint/burn; restricted to pool caller only (onlyPool) - PoolFeePolicy, PoolPublicInputs, PoolValidation: pool support libraries - MARKPoolVerifier: Groth16 verifier generated from MARKPool circuit (13 public signals, pot15 trusted setup) Interfaces: ICreditLedger, IVerifier, IPoolBridge, IPoolNullifier Crypto: MerkleTree (Poseidon, depth-20), ProofUtils, PoseidonT3 Circuit: - circuits/mark/MARKPool.circom: MARK-native UTXO circuit (depth=20, 2-in/2-out, 13 public signals); renamed from prototype utxo.circom, domain constants documented as permanent, hardcoded fee policy removed - circuits/setup.mjs: trusted setup script (pot15) - circuits/test/MARKPool.test.mjs: 13 witness tests CI: circuits-ci.yml runs witness tests on every PR Tests: MARKPool.t.sol (22), MARKWithdrawAdapter.t.sol (9), RYLACreditLedger.t.sol (8) * fix(pool): fix PoolErrors, domain separators, remove dead code - PoolErrors.sol: rewrite to match Pool.sol, PoolValidation.sol, and MerkleTree.sol — adds 25 missing errors (build was broken), removes 18 errors only used by the old MARKPool prototype - MARKPool.sol: rename domain separator Pool.WithdrawBinding.v1 to MARKPool.WithdrawBinding.v1 (permanent, must be set before deploy) - MARKWithdrawAdapter.sol: rename domain separator WithdrawAdapter.Intent.v1 to MARKWithdrawAdapter.Intent.v1 - UTXOVerifier.sol: delete (built for old 4-signal circuit, wrong interface, superseded by MARKPoolVerifier.sol) - IUTXOVerifier.sol: delete (superseded by IVerifier.sol) - UTXOSettlement.circom: delete (superseded by MARKPool.circom) - Groth16SettlementVerifier.sol: update stale comment - KNOWN_ISSUES.md: add KI-7 (two-circuit architecture), KI-8 (pool domain access control model) - foundry.toml: via_ir = true for pool domain compilation * fix(pool): immutable naming, deploy script, docs, invariants, arch guard - MARKPool, MARKWithdrawAdapter: rename immutables to SCREAMING_SNAKE_CASE (assetLedger->ASSET_LEDGER, proofPool->PROOF_POOL) - MARKPool: remove _assetLedger from constructor; add setAssetLedger() one-time restricted setter to break circular deploy dependency with RYLACreditLedger - DeployMARKPool.s.sol: full deployment script for pool domain (AccessManager, MARKPool, RYLACreditLedger, MARKWithdrawAdapter) - MARKPool.sol: add withdrawal flow NatSpec (burn-to-claim model) - ARCHITECTURE.md: add pool/withdraw domains, dependency rules, and withdrawal flow section - MARKPoolInvariants.t.sol: 3 invariants (nullifiers never unspent, withdraw bindings immutable, root queue only grows) - architecture-guard.sh: add pool->settlement/bridge and withdraw->settlement/bridge isolation rules * fix(pool): fix deploy script role grant and ASSET_LEDGER null guard - DeployMARKPool.s.sol: grant POOL_ADMIN_ROLE to deployer during setup so setAssetLedger/setIntentSigner calls succeed when deployer != owner; revoke deployer role after setup completes - MARKPool._applyFee: revert InvalidAssetLedger if ASSET_LEDGER is not set and a non-zero fee is applied (prevents silent call to address(0)) * fix(ci): compile circuit before running witness tests circuits/build/ is gitignored so the WASM and witness_calculator.js are not in the repo. Add circom install and npm run build steps before npm test so CI compiles the circuit fresh on each run. * fix(ci): create build dir before circom compile * refactor(pool): pre-merge improvements - Rename immutables to SCREAMING_SNAKE_CASE: assetLedger->ASSET_LEDGER, proofPool->PROOF_POOL (MARKPool.sol, MARKWithdrawAdapter.sol) - MARKPool: remove _assetLedger from constructor, add setAssetLedger() one-time restricted setter to break circular deploy dependency with RYLACreditLedger - MARKPool: add withdrawal flow documentation to contract NatSpec - ARCHITECTURE.md: add pool/withdraw domains, dependency rules, and withdrawal flow explanation - DeployMARKPool.s.sol: deployment script for MARKPool, RYLACreditLedger, MARKWithdrawAdapter with AccessManager configuration - MARKPoolInvariants.t.sol: 3 invariants (nullifiers never unspent, withdraw bindings immutable, root queue only grows) - architecture-guard.sh: add pool and withdraw domain isolation rules * chore(pool): update circuits CI, setup, and pool errors - circuits-ci.yml: updated to run MARKPool witness tests - circuits/package.json: build/test scripts point to MARKPool.circom - circuits/setup.mjs: updated for MARKPool.circom trusted setup - circuits/test/MARKPool.test.mjs: cleaned up test file - contracts/KNOWN_ISSUES.md: updated KI-7 for current two-circuit state - contracts/src/pool/errors/PoolErrors.sol: add missing blank line * fix(pool): address CodeRabbit review findings - circuits-ci.yml: fix circom install permissions (use sudo mv to /usr/local/bin instead of direct write which fails on GH Actions) - PoolErrors.sol: add clarifying comment to FixedFeePolicy explaining it fires when minFee > 1 (not a fee-rate policy, a range guard) - MARKWithdrawAdapter.sol: document personal_sign intent on computeWithdrawIntentDigest (EIP-191 is intentional, not EIP-712) bridgeIn replay protection finding: already fixed in current code (processedBridgeMessages mapping + check at line 390) — stale finding. * fix(pool): address second round CodeRabbit findings - setup.mjs: use crypto.randomBytes for ceremony entropy (Date.now is predictable), add mkdirSync for build/, fix EJS template loading to use readFileSync instead of dynamic import with assert (unsupported in Node 20/22/24 ESM) - circuits-ci.yml: pin circom to v2.2.3 instead of latest, add version verification step - KNOWN_ISSUES.md: fix misleading 'settlement-specific verifier' wording — MARKPoolVerifier is a shared pool verifier, not settlement-specific - MARKPool.sol: fix NatSpec EIP-712 reference to EIP-191 (personal_sign) * feat(pool): add pool E2E test, fix RYLACreditLedger caller model RYLACreditLedger: - Separate credit (pool-only) and debit (adapter-only) callers - Add setAdapter() one-time setter to break circular deploy dependency (adapter constructor needs ledger, ledger needs adapter address) - Add AdapterAlreadySet error DeployMARKPool.s.sol: - Call ledger.setAdapter(adapter) after adapter deployment Tests: - RYLACreditLedger.t.sol: updated for new caller model, 11 tests - MARKWithdrawAdapter.t.sol: add setAdapter call in setUp - MARKPoolE2E.t.sol: full withdrawal flow E2E test (3 tests) - testFullWithdrawalFlow: mint RYLA -> transactWithWithdrawBinding -> withdrawWithSig -> verify RYLA burned, ETH received - testNullifierReplayRejected - testBindingMismatchRejected 134/134 tests pass * feat(pool): add ReleasePool.s.sol orchestrator and pool env vars - ReleasePool.s.sol: release orchestrator for pool stack following the same pattern as ReleaseMARK.s.sol — preflight checks, deploy via DeployMARKPool, post-deploy verification (wiring checks + RYLA roles), JSON artifact write - .env.example: add pool stack env vars (MARK_POOL_VERIFIER, MARK_POOL_OWNER, MARK_POOL_INTENT_SIGNER, release flags, artifact path, post-deploy verify addresses) * fix(pool): security fixes and dead code removal RYLACreditLedger: - Add OWNER immutable (set to msg.sender in constructor) - Restrict setAdapter to OWNER to prevent front-running between deployment and the setAdapter call in the release script - Add testSetAdapterRevertsForNonOwner test - Add clarifying NatSpec to totalCreditsOutstanding explaining it tracks only flows through this ledger, not total RYLA supply MARKWithdrawAdapter: - Move ETH transfer before ASSET_LEDGER.debit — if ETH transfer fails, RYLA is no longer burned (was a loss-of-funds bug) MARKPool: - Remove dead _seedRoot function (defined but never called) - Add NatSpec to computePublicInputsWithWithdraw clarifying chainId vs dstChainId semantics * fix(test): fix nullifier replay test to use fresh signatures testNullifierReplayRejected was reusing signatures computed for nonce N in the second withdrawWithSig call with nonce N+1, causing a NonceMismatch revert instead of exercising nullifier replay protection. Now recomputes the intent hash and signs with the updated nonce so the revert is caused by NullifierAlreadyClaimed as intended. * fix(pool): guard totalCreditsOutstanding against underflow * feat(pool): add pool release CI check and deploy script tests contracts-ci.yml: - Add pool release dry-run and execute smoke steps to the contracts-release-check job, reusing the Anvil instance and RYLA token deployed by the settlement release step - Assert pool release artifact schema (pool, ledger, adapter addresses) MARKPoolDeployScripts.t.sol: - testDeployMARKPoolWiresAllContracts: verifies all contract wiring (pool<->ledger, ledger<->adapter, RYLA roles) - testDeployMARKPoolSetsIntentSignerWhenProvided: verifies intent signer is configured when MARK_POOL_INTENT_SIGNER is set - testDeployMARKPoolRevertsWhenMissingTokenAdmin: verifies preflight check rejects deployer without RYLA admin role 138/138 tests pass * fix(pool): address final CodeRabbit findings - contracts-ci.yml: remove '|| true' from pool release dry-run step; use the deployed settlement module address as verifier (a real contract) so the preflight code.length check passes without masking failures - RYLACreditLedger.sol: fix NatSpec on totalCreditsOutstanding to accurately describe accounting scope — _totalBurned can exceed _totalMinted if RYLA is burned via other paths (e.g. settlement module) * fix(ci): fix pool release CI failure and address CodeRabbit finding contracts-ci.yml: - Add --skip-simulation to pool release broadcast — PoseidonT3 (55,856 bytes) exceeds EIP-170 limit and cannot be deployed without refactoring to a linked library; --skip-simulation tests script orchestration only - Fix jq assertion to use regex validation instead of zero-address check, rejecting null values and validating hex address format KNOWN_ISSUES.md: - Add KI-8 documenting PoseidonT3 contract size issue and required fix before mainnet (deploy as standalone contract, call via interface) * fix(ci): remove pool execute smoke, fix jq assertion, fix KI-7 wording contracts-ci.yml: - Remove pool release execute smoke step — MARKPool (24,841 bytes) and PoseidonT3 (55,856 bytes) exceed EIP-170 limit and cannot be broadcast to Anvil; pool deploy requires PoseidonT3 refactor (KI-8) first - Keep pool release dry-run only (validates script logic and preflight) - Remove the now-unused artifact assertion step KNOWN_ISSUES.md: - Fix KI-7: both pool and settlement systems use the same MARKPool circuit — remove implication of distinct circuit designs * fix(pool): add code.length checks to RYLACreditLedger constructor and setAdapter Prevents EOAs from being set as TOKEN, POOL, or ADAPTER. Adds InvalidContract error. 3 new tests cover the EOA rejection cases. setUp uses vm.etch to give mock addresses contract bytecode. * fix(contracts): harden settlement verifier flow and CI reliability * fix(review): address open CI and pool verifier feedback * refactor(pool): rename min fee guard error for clarity * fix(pool,settlement): replace require strings and wrong errors with custom errors PoolFeePolicy: - Replace require(maxFeeBurnBps != 0, string) and require(feeBurnBps <= maxFeeBurnBps, string) with custom error FeePolicyInvalidBps() — consistent with codebase style, lower gas Groth16SettlementVerifier: - Replace ZeroAddress() with VerifierNotAContract() for verifierContract code.length check - Replace ZeroAddress() with SettlementModuleNotAContract() for settlementModule code.length check - ZeroAddress was semantically wrong for non-zero addresses that have no code * ci: trigger fresh CI run * docs(pool): correct KI-8 — MARKPool itself is over EIP-170 size limit Investigation: MARKPool is 24,960 bytes (over 24,576 limit) even without PoseidonT3 inlining. via_ir=true already prevents PoseidonT3 from being inlined. The fix requires splitting MARKPool into smaller contracts, not just extracting PoseidonT3 as a standalone contract. Both are required. * fix(pool): reduce MARKPool below EIP-170 size limit (24200 < 24576 bytes) Size reductions (24961 -> 24200 bytes, -761 bytes): - Remove redundant verifierAddr.code.length check in _verifyAndConsume (already validated in setVerifier, cannot change after deployment) - Remove redundant tail != rootQueueTail guard in _insertCommitmentsValidated (always true after inserting 2 commitments) - Inline _requireCommitmentsValid wrapper (single-line delegation) - Inline _insertCommitments wrapper (only called from bridgeIn) - Remove computePublicInputs and computePublicInputsWithWithdraw public view functions from MARKPool — _buildPublicInputs now calls PoolPublicInputs.build directly; off-chain callers use PoolPublicInputs Bug fixes: - PoolValidation: move NullifierDuplicate check before the loop so duplicate nullifiers get the precise error, not NullifierUsed - MARKPool.pause(): document that unpause() does NOT auto-restore withdrawals (intentional asymmetry, requires explicit unpauseWithdrawals) * fix: address CodeRabbit findings (circuits, Makefile, architecture-guard) circuits/test/MARKPool.test.mjs: - Remove unused buildMerklePath helper (tests use buildTwoLeafRoot) circuits/setup.mjs: - Add r1cs existence check before trusted setup with clear error message contracts/Makefile: - Restore test-core to exclude invariant tests (--no-match-path) so ci-fast remains fast as documented contracts/script/ci/architecture-guard.sh: - Tighten all four import regexes to handle optional leading whitespace and any number of ../ segments (prevents bypass via indented imports or deeper relative paths) * fix: address remaining CodeRabbit findings contracts/src/pool/MARKPool.sol: - setVerifier: add code.length check (consistent with constructor) circuits/test/MARKPool.test.mjs: - expectFail: only treat constraint/assertion failures as PASS; rethrow other errors so regressions surface contracts/KNOWN_ISSUES.md: - KI-7: separate design capability from configuration state for settlement system wording * fix(circuits): lowercase error message comparison in expectFail * docs(deployment): add Groth16SettlementVerifier wiring step (Step 18) Documents the two post-deploy calls required to activate ZK-based settlement: setSettlementModule and setVerifierContract on Groth16SettlementVerifier, then setVerifier on MARKSettlementModule. AttestedSettlementVerifier remains the fallback until wiring is complete. * fix(settlement): return false on malformed proof in Groth16SettlementVerifier (#101) abi.decode reverts on malformed/short proof bytes, which propagated through MARKSettlementModule as a raw error instead of VerificationFailed. Fix: check proof.length == 672 before decoding (fixed ABI encoding size: uint256[2]+uint256[2][2]+uint256[2]+uint256[13] = 64+128+64+416 = 672). Malformed proofs now return false cleanly. Tests: testVerifySettlementReturnsFalseForMalformedProof, testVerifySettlementReturnsFalseForEmptyProof * fix(ci): exclude integration tests from test-core target (#102) test-core was running integration tests (which require supersim on port 9545) because --no-match-path on the command line overrides foundry.toml's no_match_path setting rather than adding to it. Use brace glob to exclude both invariant and integration tests. * fix(test): remove unverifiable cross-chain assertion from integration test (#103) testBridgeToTransfersTokensCrossChain switched to fork B and checked the recipient balance, but Foundry fork tests cannot simulate supersim's async message relay — the contract simply doesn't exist on the other fork. Fix: assert only the source-chain burn (which is fully verifiable in a fork test). Add a NatSpec note explaining the relay limitation. * docs(pool): correct KI-8 — PoseidonT3 inlined via via_ir, MARKPool deployable (#104) * docs(pool): correct KI-8 — PoseidonT3 is inlined via via_ir, MARKPool is deployable via_ir=true causes the compiler to inline PoseidonT3 into MARKPool rather than deploying it as a linked library. MARKPool has no link references and is 24,298 bytes (278 bytes under EIP-170). KI-8 was based on an earlier state where MARKPool exceeded the limit. Updated KI-8 to reflect accurate current state and note the tight margin. * refactor(crypto): use >>= 1 instead of /= 2 in MerkleTree insert * security: harden pool domain before testnet (#105) * security: harden pool domain before testnet - Add pool/withdraw/Groth16 contracts to slither-core scope - Document all slither exclusion rationale in Makefile - RYLACreditLedger: add Credit/Debit events, move before external calls (CEI) - MARKWithdrawAdapter: add test for recipient zero-check (existing check, missing test) - THREAT_MODEL.md: add pool stack overview, trust boundaries, role compromise impact, and 3 new invariants (nullifier replay, withdraw binding, debit approval) * fix(ci): use per-contract slither exclusions instead of global CodeRabbit correctly noted that global exclusions could suppress actionable findings in newly added contracts. Refactored slither-core to apply only the relevant exclusions per contract. Also added arbitrary-send-erc20 to MARKSettlementModule and RYLACreditLedger (both use safeTransferFrom with prior approval — not arbitrary). * fix(ci): add set -e to slither-core, fix preflight to use python3 -m slither Without set -e, a failing early slither invocation would be masked if the final command succeeds. Also align the preflight check with the actual invocation (python3 -m slither, not command -v slither). * ci: fix 4 workflow issues pre-testnet (#106) * ci: fix 4 workflow issues pre-testnet 1. Sync _reusable-contracts-slither.yml with Makefile - Delegate to 'make slither-core' (single source of truth) - Now covers all 8 contracts with per-contract exclusions - Previously only scanned 4 settlement contracts with global exclusions 2. Enable pool execute smoke in contracts-ci.yml - KI-8 resolved: via_ir inlines PoseidonT3, MARKPool is 24,298 bytes - Pool broadcast to Anvil now works; remove stale blocker comment 3. Fix integration test readiness check - Wait on ports 9545/9546 (actual RPC ports) not 8420 (admin port) - Use nc loop consistent with anvil readiness pattern 4. Pin foundry-rs/foundry-toolchain to v1.8.0 commit SHA - Floating @v1 could silently break on Foundry breaking changes - Pinned: c7450ba673e133f5ee30098b3b54f444d3a2ca2d (v1.8.0) * fix(ci): remove foundry version input from reusable slither workflow The version input was passed as 'v1.8.0' to the action's 'version' input which expects a Foundry binary tag (e.g. 'stable', 'nightly'), not the action version. This caused foundryup to fail extracting the tar archive. Use the action's default Foundry version instead. * fix(ci): revert pool execute smoke — Foundry rejects PoseidonT3 artifact size forge create/broadcast checks all library artifacts for EIP-170 compliance. PoseidonT3 is 55,856 bytes as a standalone artifact even though via_ir inlines it into MARKPool at compile time. The broadcast is blocked before deployment. Keep dry-run only. Update KI-8 with the precise diagnosis. * fix(pool): resolve PoseidonT3 deployment blocker via external interface (#107) PoseidonT3 is a Solidity library with a public function — it gets deployed as a separate linked contract (55,856 bytes) which exceeds EIP-170 (24,576). This blocked all pool deployments. Fix: replace the library call with an external interface (IPoseidonT3). MerkleTree now stores the Poseidon contract address in the Tree struct and calls it via DELEGATECALL-free external call. MARKPool constructor accepts a _poseidon address parameter. Default deployment address: 0xB43122Ecb241DD50062641f089876679fd06599a This is Semaphore's PoseidonT3 (PSE/Ethereum Foundation), deployed at the same address on all EVM networks via CREATE2. Verified compatible with our implementation: hash([0,0]) and hash([1,2]) produce identical outputs. MARKPool now has zero link references and is fully self-contained. MARKPool size: 24,231 bytes (345 bytes margin under EIP-170). Tests: deployCode('PoseidonT3.sol:PoseidonT3') in test setUp bypasses EIP-170 (Foundry test runner does not enforce the limit). * chore(circuits): remove stale UTXOSettlement artifacts (#108) * chore(circuits): remove stale UTXOSettlement artifacts UTXOSettlement circuit is superseded by MARKPool.circom. Remove the stale test file and old verification key artifact. The utxo/ source and build/ artifacts are already gitignored. * ci: trigger Release Gate Container for circuits-only PRs Add circuits/** to path filter so the required check runs and passes when only circuit files change (no contracts affected). * ci: add circuits/** to push paths for consistency * ci: remove path filter from release gate pull_request trigger * ci: add circuits/** to CodeQL path filter to unblock circuits-only PRs * fix: address codebase review findings (#109) Bug: RYLACreditLedger.debit() — move _totalBurned update before safeTransferFrom to follow CEI pattern. Previously the state update happened after the external call, creating a reentrancy window where _totalBurned was not yet incremented during the transfer callback. Docs: KNOWN_ISSUES.md KI-8 — update stale size figures and description. MARKPool is now 24,231 bytes (345 bytes margin). PoseidonT3 is no longer inlined via via_ir; MerkleTree calls it via IPoseidonT3 interface at 0xB43122... (Semaphore, same address on all EVM networks). Tests: add testConstructorRevertsOnZeroPoseidon and testConstructorRevertsOnEOAPoseidon to MARKPool.t.sol — the _poseidon constructor parameter added in PR #107 had no test coverage. * ci: pin action-shellcheck to commit SHA (#110) * ci: pin action-shellcheck to commit SHA ludeeus/action-shellcheck@2.0.0 was pinned by version tag only. Tags are mutable — a compromised tag could point to malicious code. Pin to the immutable commit SHA (00cae50) for supply chain safety. * ci: trigger CodeQL for all .github/workflows/** changes * chore(deps): bump actions/dependency-review-action from 4 to 5 (#90) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4 to 5. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump the frontend-minor-patch group across 1 directory with 21 updates (#91) Bumps the frontend-minor-patch group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.2.4` | `4.3.0` | | [tailwind-merge](https://github.com/dcastil/tailwind-merge) | `3.5.0` | `3.6.0` | | [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.2.4` | `4.3.0` | | [baseline-browser-mapping](https://github.com/web-platform-dx/baseline-browser-mapping) | `2.10.27` | `2.10.29` | | [electron-to-chromium](https://github.com/Kilian/electron-to-chromium) | `1.5.352` | `1.5.353` | | [get-east-asian-width](https://github.com/sindresorhus/get-east-asian-width) | `1.5.0` | `1.6.0` | Updates `@tailwindcss/vite` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/@tailwindcss-vite) Updates `tailwind-merge` from 3.5.0 to 3.6.0 - [Release notes](https://github.com/dcastil/tailwind-merge/releases) - [Commits](https://github.com/dcastil/tailwind-merge/compare/v3.5.0...v3.6.0) Updates `tailwindcss` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/tailwindcss) Updates `@tailwindcss/node` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/@tailwindcss-node) Updates `@tailwindcss/oxide-android-arm64` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/android-arm64) Updates `@tailwindcss/oxide-darwin-arm64` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/darwin-arm64) Updates `@tailwindcss/oxide-darwin-x64` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/darwin-x64) Updates `@tailwindcss/oxide-freebsd-x64` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/freebsd-x64) Updates `@tailwindcss/oxide-linux-arm-gnueabihf` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/linux-arm-gnueabihf) Updates `@tailwindcss/oxide-linux-arm64-gnu` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/linux-arm64-gnu) Updates `@tailwindcss/oxide-linux-arm64-musl` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/linux-arm64-musl) Updates `@tailwindcss/oxide-linux-x64-gnu` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/linux-x64-gnu) Updates `@tailwindcss/oxide-linux-x64-musl` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/linux-x64-musl) Updates `@tailwindcss/oxide-wasm32-wasi` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node) Updates `@tailwindcss/oxide-win32-arm64-msvc` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/win32-arm64-msvc) Updates `@tailwindcss/oxide-win32-x64-msvc` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node/npm/win32-x64-msvc) Updates `@tailwindcss/oxide` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/crates/node) Updates `baseline-browser-mapping` from 2.10.27 to 2.10.29 - [Release notes](https://github.com/web-platform-dx/baseline-browser-mapping/releases) - [Commits](https://github.com/web-platform-dx/baseline-browser-mapping/compare/v2.10.27...v2.10.29) Updates `electron-to-chromium` from 1.5.352 to 1.5.353 - [Changelog](https://github.com/Kilian/electron-to-chromium/blob/main/CHANGELOG.md) - [Commits](https://github.com/Kilian/electron-to-chromium/compare/v1.5.352...v1.5.353) Updates `enhanced-resolve` from 5.21.1 to 5.21.2 - [Release notes](https://github.com/webpack/enhanced-resolve/releases) - [Changelog](https://github.com/webpack/enhanced-resolve/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/enhanced-resolve/compare/v5.21.1...v5.21.2) Updates `get-east-asian-width` from 1.5.0 to 1.6.0 - [Release notes](https://github.com/sindresorhus/get-east-asian-width/releases) - [Commits](https://github.com/sindresorhus/get-east-asian-width/compare/v1.5.0...v1.6.0) --- updated-dependencies: - dependency-name: "@tailwindcss/node" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-android-arm64" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-darwin-arm64" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-darwin-x64" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-freebsd-x64" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-linux-arm-gnueabihf" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-linux-arm64-gnu" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-linux-arm64-musl" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-linux-x64-gnu" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-linux-x64-musl" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-wasm32-wasi" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-win32-arm64-msvc" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/oxide-win32-x64-msvc" dependency-version: 4.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: "@tailwindcss/vite" dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: baseline-browser-mapping dependency-version: 2.10.29 dependency-type: indirect update-type: version-update:semver-patch dependency-group: frontend-minor-patch - dependency-name: electron-to-chromium dependency-version: 1.5.353 dependency-type: indirect update-type: version-update:semver-patch dependency-group: frontend-minor-patch - dependency-name: enhanced-resolve dependency-version: 5.21.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: frontend-minor-patch - dependency-name: get-east-asian-width dependency-version: 1.6.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: tailwind-merge dependency-version: 3.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch - dependency-name: tailwindcss dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-minor-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Iko <6572003+iap@users.noreply.github.com> * chore: update LICENSE copyright to Trade 2026 (#111) * chore: update LICENSE copyright to Trade 2026 The project was scaffolded from an Optimism template but is original work. Update copyright holder from Optimism to Trade and year to 2026. * ci: remove path filter from CodeQL pull_request trigger CodeQL is a required check for all PRs. With a path filter, PRs that only touch files outside the filter (e.g. LICENSE, README) are blocked indefinitely waiting for CodeQL results that never come. Remove the pull_request path filter so CodeQL always runs on PRs. Keep the push path filter to avoid unnecessary runs on branch pushes. * chore: remove stale deploy-contracts step from mprocs.yaml (#112) deploy:supersim and deploy:counter-incrementer:supersim are template artifacts from the original Optimism scaffold. They no longer exist. Remove the stale deploy-contracts proc. --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/dependabot.yml | 2 +- .../workflows/_reusable-contracts-slither.yml | 25 +- .github/workflows/circuits-ci.yml | 43 + .github/workflows/codeql.yml | 6 - .github/workflows/contracts-ci.yml | 39 +- .../workflows/contracts-mainnet-readiness.yml | 2 +- .../contracts-production-lock-verify.yml | 2 +- .../contracts-release-gate-container.yml | 3 - .../workflows/contracts-staging-rehearsal.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/scripts-ci.yml | 2 +- DEPLOYMENT.md | 26 + LICENSE | 2 +- circuits/.gitignore | 9 + circuits/mark/MARKPool.circom | 229 +++ circuits/package-lock.json | 1466 +++++++++++++++ circuits/package.json | 17 + circuits/setup.mjs | 52 + circuits/test/MARKPool.test.mjs | 204 +++ contracts/.env.example | 22 + contracts/ARCHITECTURE.md | 24 +- contracts/KNOWN_ISSUES.md | 29 + contracts/Makefile | 44 +- contracts/README.md | 25 + contracts/RUNBOOK.md | 27 + contracts/THREAT_MODEL.md | 67 +- contracts/foundry.toml | 3 + contracts/script/ci/architecture-guard.sh | 16 +- .../script/deploy/pool/DeployMARKPool.s.sol | 146 ++ .../DeployMARKSettlementModule.s.sol | 32 + contracts/script/ops/pool/ReleasePool.s.sol | 169 ++ .../ops/settlement/PostDeployMARKSetup.s.sol | 31 + contracts/src/crypto/MerkleTree.sol | 71 + contracts/src/crypto/ProofUtils.sol | 22 + contracts/src/crypto/generated/PoseidonT3.sol | 1572 +++++++++++++++++ contracts/src/interfaces/ICreditLedger.sol | 12 + contracts/src/interfaces/IPoolBridge.sol | 20 + contracts/src/interfaces/IPoolNullifier.sol | 21 + contracts/src/interfaces/IPoseidonT3.sol | 10 + contracts/src/interfaces/IVerifier.sol | 11 + contracts/src/pool/MARKPool.sol | 529 ++++++ contracts/src/pool/PoolFeePolicy.sol | 18 + contracts/src/pool/PoolPublicInputs.sol | 60 + contracts/src/pool/PoolValidation.sol | 65 + contracts/src/pool/RYLACreditLedger.sol | 98 + contracts/src/pool/errors/PoolErrors.sol | 79 + .../src/pool/verifier/MARKPoolVerifier.sol | 252 +++ .../interfaces/IGroth16Verifier.sol | 38 +- .../verifier/Groth16SettlementVerifier.sol | 141 +- .../src/withdraw/MARKWithdrawAdapter.sol | 225 +++ contracts/src/withdraw/MARKWithdrawErrors.sol | 34 + contracts/test/e2e/pool/MARKPoolE2E.t.sol | 268 +++ .../bridge/MARKBridgeIntegration.t.sol | 11 +- .../invariant/pool/MARKPoolInvariants.t.sol | 171 ++ contracts/test/unit/MARKDeployScripts.t.sol | 39 + .../test/unit/MARKPoolDeployScripts.t.sol | 91 + contracts/test/unit/pool/MARKPool.t.sol | 390 ++++ .../test/unit/pool/RYLACreditLedger.t.sol | 133 ++ .../Groth16SettlementVerifier.t.sol | 208 ++- .../unit/withdraw/MARKWithdrawAdapter.t.sol | 462 +++++ mprocs.yaml | 3 - package.json | 6 +- pnpm-lock.yaml | 202 ++- scripts/github/apply-governance.sh | 81 +- scripts/github/verify-governance.sh | 25 +- 65 files changed, 7773 insertions(+), 363 deletions(-) create mode 100644 .github/workflows/circuits-ci.yml create mode 100644 circuits/.gitignore create mode 100644 circuits/mark/MARKPool.circom create mode 100644 circuits/package-lock.json create mode 100644 circuits/package.json create mode 100644 circuits/setup.mjs create mode 100644 circuits/test/MARKPool.test.mjs create mode 100644 contracts/script/deploy/pool/DeployMARKPool.s.sol create mode 100644 contracts/script/ops/pool/ReleasePool.s.sol create mode 100644 contracts/src/crypto/MerkleTree.sol create mode 100644 contracts/src/crypto/ProofUtils.sol create mode 100644 contracts/src/crypto/generated/PoseidonT3.sol create mode 100644 contracts/src/interfaces/ICreditLedger.sol create mode 100644 contracts/src/interfaces/IPoolBridge.sol create mode 100644 contracts/src/interfaces/IPoolNullifier.sol create mode 100644 contracts/src/interfaces/IPoseidonT3.sol create mode 100644 contracts/src/interfaces/IVerifier.sol create mode 100644 contracts/src/pool/MARKPool.sol create mode 100644 contracts/src/pool/PoolFeePolicy.sol create mode 100644 contracts/src/pool/PoolPublicInputs.sol create mode 100644 contracts/src/pool/PoolValidation.sol create mode 100644 contracts/src/pool/RYLACreditLedger.sol create mode 100644 contracts/src/pool/errors/PoolErrors.sol create mode 100644 contracts/src/pool/verifier/MARKPoolVerifier.sol create mode 100644 contracts/src/withdraw/MARKWithdrawAdapter.sol create mode 100644 contracts/src/withdraw/MARKWithdrawErrors.sol create mode 100644 contracts/test/e2e/pool/MARKPoolE2E.t.sol create mode 100644 contracts/test/invariant/pool/MARKPoolInvariants.t.sol create mode 100644 contracts/test/unit/MARKPoolDeployScripts.t.sol create mode 100644 contracts/test/unit/pool/MARKPool.t.sol create mode 100644 contracts/test/unit/pool/RYLACreditLedger.t.sol create mode 100644 contracts/test/unit/withdraw/MARKWithdrawAdapter.t.sol diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ae334ac..afacaa7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -44,6 +44,6 @@ updates: - dependency-name: "@hono/node-server" versions: ["<= 1.13.8"] - dependency-name: "drizzle-orm" - versions: ["<= 0.38.1"] + versions: ["<= 0.38.4"] - dependency-name: "@stablelib/ed25519" versions: ["<= 1.0.3"] diff --git a/.github/workflows/_reusable-contracts-slither.yml b/.github/workflows/_reusable-contracts-slither.yml index 618f863..c49b648 100644 --- a/.github/workflows/_reusable-contracts-slither.yml +++ b/.github/workflows/_reusable-contracts-slither.yml @@ -2,12 +2,6 @@ name: Reusable Contracts Slither on: workflow_call: - inputs: - foundry_version: - description: Foundry version used for Slither compile path - required: false - default: "1.5.0" - type: string jobs: slither-core: @@ -29,23 +23,8 @@ jobs: run: pip install slither-analyzer==0.11.5 - name: Setup Foundry - uses: ./.github/actions/setup-foundry - with: - foundry-version: ${{ inputs.foundry_version }} + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 - name: Run Slither on MARK core contracts working-directory: contracts - run: | - for target in \ - src/token/RYLA.sol \ - src/bridge/MARKBridgeAdapter.sol \ - src/settlement/MARKSettlementModule.sol \ - src/settlement/verifier/AttestedSettlementVerifier.sol - do - slither "$target" \ - --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ - --exclude-dependencies \ - --exclude "naming-convention,timestamp,arbitrary-send-erc20,reentrancy-balance,reentrancy-benign" \ - --filter-paths "lib|test|script|out|cache" \ - --fail-medium - done + run: make slither-core diff --git a/.github/workflows/circuits-ci.yml b/.github/workflows/circuits-ci.yml new file mode 100644 index 0000000..5c3421d --- /dev/null +++ b/.github/workflows/circuits-ci.yml @@ -0,0 +1,43 @@ +name: Circuits CI + +on: + pull_request: + push: + branches: + - main + - canary + - dev + +jobs: + circuits-test: + name: Circuits Witness Tests + runs-on: ubuntu-latest + permissions: + contents: read + defaults: + run: + working-directory: circuits + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + + - name: Install circom + run: | + CIRCOM_VERSION="v2.2.3" + CIRCOM_URL="https://github.com/iden3/circom/releases/download/${CIRCOM_VERSION}/circom-linux-amd64" + curl -L "$CIRCOM_URL" -o circom + chmod +x circom + sudo mv circom /usr/local/bin/circom + circom --version + + - name: Install dependencies + run: npm ci + + - name: Run witness tests + run: npm test diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7092e60..f876e56 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -3,12 +3,6 @@ name: CodeQL on: pull_request: branches: [main, canary, dev] - paths: - - "src/**" - - "contracts/**" - - "package.json" - - "pnpm-lock.yaml" - - ".github/workflows/codeql.yml" push: branches: [main, canary, dev] paths: diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index 5d5ff5b..fe778fb 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -30,7 +30,7 @@ jobs: submodules: recursive - name: Setup Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 - name: Enforce architecture boundaries run: make architecture-guard @@ -66,7 +66,7 @@ jobs: submodules: recursive - name: Setup Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 - name: Start anvil run: anvil --host 127.0.0.1 --port 8545 > /tmp/anvil.log 2>&1 & @@ -120,6 +120,28 @@ jobs: .gitCommit != null ' broadcast/mark-release-ci.json + - name: Run pool release orchestrator dry-run + run: | + TOKEN=$(jq -r '.token' broadcast/mark-release-ci.json) + POOL_VERIFIER=$(forge create src/pool/verifier/MARKPoolVerifier.sol:MARKPoolVerifier \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast \ + --json | jq -r '.deployedTo') + if [ -z "$POOL_VERIFIER" ] || [ "$POOL_VERIFIER" = "null" ]; then + echo "failed to deploy MARKPoolVerifier for pool dry-run" >&2 + exit 1 + fi + MARK_RYLA_TOKEN="$TOKEN" \ + MARK_POOL_VERIFIER="$POOL_VERIFIER" \ + forge script script/ops/pool/ReleasePool.s.sol --rpc-url $RPC_URL -vv + + # Pool execute smoke is omitted: Foundry's contract size check rejects the + # PoseidonT3 library artifact (55,856 bytes) during broadcast even though + # via_ir inlines it into MARKPool at compile time (MARKPool itself is 24,298 + # bytes and deployable). The dry-run above validates the pool release script + # logic without triggering the size check. See KI-8 in KNOWN_ISSUES.md. + - name: Print anvil logs on failure if: failure() run: tail -n 200 /tmp/anvil.log || true @@ -140,7 +162,7 @@ jobs: submodules: recursive - name: Setup Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 - name: Run production mode smoke target run: make smoke-production-mode @@ -184,14 +206,21 @@ jobs: working-directory: . - name: Setup Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 - name: Start supersim run: pnpm dev:supersim > /tmp/supersim.log 2>&1 & working-directory: . - name: Wait for supersim readiness - run: pnpm wait-port 8420 + run: | + for _ in $(seq 1 30); do + if nc -z 127.0.0.1 9545 && nc -z 127.0.0.1 9546; then exit 0; fi + sleep 2 + done + echo "supersim did not become ready on ports 9545/9546" >&2 + tail -n 100 /tmp/supersim.log || true + exit 1 working-directory: . - name: Run integration suite diff --git a/.github/workflows/contracts-mainnet-readiness.yml b/.github/workflows/contracts-mainnet-readiness.yml index 364e9d1..917e9d5 100644 --- a/.github/workflows/contracts-mainnet-readiness.yml +++ b/.github/workflows/contracts-mainnet-readiness.yml @@ -64,7 +64,7 @@ jobs: run: pip install slither-analyzer==0.11.5 - name: Setup Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 - name: Run mainnet readiness gate working-directory: contracts diff --git a/.github/workflows/contracts-production-lock-verify.yml b/.github/workflows/contracts-production-lock-verify.yml index 2f65bc0..82b50f8 100644 --- a/.github/workflows/contracts-production-lock-verify.yml +++ b/.github/workflows/contracts-production-lock-verify.yml @@ -65,7 +65,7 @@ jobs: submodules: recursive - name: Setup Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 - name: Run production lock verification run: make verify-production-lock diff --git a/.github/workflows/contracts-release-gate-container.yml b/.github/workflows/contracts-release-gate-container.yml index eadc1ac..c0f9794 100644 --- a/.github/workflows/contracts-release-gate-container.yml +++ b/.github/workflows/contracts-release-gate-container.yml @@ -2,9 +2,6 @@ name: Contracts Release Gate (Containerized) on: pull_request: - paths: - - "contracts/**" - - ".github/workflows/contracts-release-gate-container.yml" workflow_dispatch: inputs: gate_mode: diff --git a/.github/workflows/contracts-staging-rehearsal.yml b/.github/workflows/contracts-staging-rehearsal.yml index 3432a0f..beaf5be 100644 --- a/.github/workflows/contracts-staging-rehearsal.yml +++ b/.github/workflows/contracts-staging-rehearsal.yml @@ -97,7 +97,7 @@ jobs: } - name: Setup Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 - name: Run staging rehearsal (release + production lock verify) run: make rehearse-production-lock diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 4455621..0428468 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v6 - name: Review dependency changes - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@v5 with: fail-on-severity: high warn-only: false diff --git a/.github/workflows/scripts-ci.yml b/.github/workflows/scripts-ci.yml index 51a65a0..46dffda 100644 --- a/.github/workflows/scripts-ci.yml +++ b/.github/workflows/scripts-ci.yml @@ -26,6 +26,6 @@ jobs: uses: actions/checkout@v6 - name: Run shellcheck - uses: ludeeus/action-shellcheck@2.0.0 + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 with: scandir: "scripts contracts/script" diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index e907029..b819ddc 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -407,6 +407,32 @@ make verify-evidence-manifest make sign-evidence-manifest ``` +#### Step 18: Wire Groth16SettlementVerifier (if using ZK settlement) + +After deploying `Groth16SettlementVerifier`, two post-deploy calls are required +before ZK-based settlement is active. `AttestedSettlementVerifier` remains the +fallback until this is complete. + +```bash +# 1. Bind the verifier to the settlement module (prevents cross-module replay) +cast send $GROTH16_VERIFIER_ADDRESS \ + "setSettlementModule(address)" $SETTLEMENT_MODULE_ADDRESS \ + --rpc-url $MAINNET_RPC --private-key $DEPLOYER_KEY + +# 2. Set the MARKPoolVerifier contract +cast send $GROTH16_VERIFIER_ADDRESS \ + "setVerifierContract(address)" $MARK_POOL_VERIFIER_ADDRESS \ + --rpc-url $MAINNET_RPC --private-key $DEPLOYER_KEY + +# 3. Wire into settlement module +cast send $SETTLEMENT_MODULE_ADDRESS \ + "setVerifier(address,bool)" $GROTH16_VERIFIER_ADDRESS true \ + --rpc-url $MAINNET_RPC --private-key $DEPLOYER_KEY +``` + +See `contracts/RUNBOOK.md` → "Groth16 Direction Rollout" for the full +migration sequence before enabling production mode. + --- ## Verification & Monitoring diff --git a/LICENSE b/LICENSE index f5ab6aa..98af169 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ (The MIT License) -Copyright 2020-2025 Optimism +Copyright 2026 Trade Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/circuits/.gitignore b/circuits/.gitignore new file mode 100644 index 0000000..f412b0c --- /dev/null +++ b/circuits/.gitignore @@ -0,0 +1,9 @@ +build/ +node_modules/ +*.zkey +*.ptau +witness.wtns + +# Prototype files (superseded by circuits/mark/MARKPool.circom) +utxo/ +setup.js diff --git a/circuits/mark/MARKPool.circom b/circuits/mark/MARKPool.circom new file mode 100644 index 0000000..fee49c9 --- /dev/null +++ b/circuits/mark/MARKPool.circom @@ -0,0 +1,229 @@ +pragma circom 2.0.0; + +include "circomlib/circuits/poseidon.circom"; +include "circomlib/circuits/comparators.circom"; +include "circomlib/circuits/switcher.circom"; +include "circomlib/circuits/bitify.circom"; + +template MARKPool(depth, nIn, nOut) { + // Domain separation constants — PERMANENT. Never change after first deployment. + // These values are protocol-specific to MARK and must remain stable across upgrades + // to prevent cross-version commitment and nullifier reuse. + // DOMAIN_VERSION: 1 — protocol version tag + // DOMAIN_NOTE_COMMITMENT: 11 — note commitment hash domain + // DOMAIN_NULLIFIER: 12 — nullifier hash domain + var DOMAIN_VERSION = 1; + var DOMAIN_NOTE_COMMITMENT = 11; + var DOMAIN_NULLIFIER = 12; + + // Private inputs (notes to spend) + signal input inAmount[nIn]; + signal input inSecret[nIn]; + signal input inBlinding[nIn]; + signal input inPathElements[nIn][depth]; + signal input inPathIndices[nIn][depth]; + + // Private outputs (new notes) + signal input outAmount[nOut]; + signal input outSecret[nOut]; + signal input outBlinding[nOut]; + + // Public inputs. + // IMPORTANT: snarkjs publicSignals ordering follows signal declaration order. + // Canonical verifier order (13 signals): + // [merkleRoot, chainId, dstChainId, protocolEpoch, fee, relayer, + // nullifier[0], nullifier[1], outCommitment[0], outCommitment[1], + // withdrawOwner, withdrawRecipient, withdrawAmount] + signal input merkleRoot; + signal input chainId; + signal input dstChainId; + signal input protocolEpoch; + signal input fee; + signal input relayer; + signal input nullifier[nIn]; + signal input outCommitment[nOut]; + signal input withdrawOwner; + signal input withdrawRecipient; + signal input withdrawAmount; + + signal computedNullifier[nIn]; + signal computedOutCommitment[nOut]; + + // 1) Input commitments + nullifiers + component inCommitment[nIn]; + component inNullifier[nIn]; + var i; + for (i = 0; i < nIn; i++) { + inCommitment[i] = Poseidon(4); + inCommitment[i].inputs[0] <== DOMAIN_VERSION * 100 + DOMAIN_NOTE_COMMITMENT; + inCommitment[i].inputs[1] <== inAmount[i]; + inCommitment[i].inputs[2] <== inSecret[i]; + inCommitment[i].inputs[3] <== inBlinding[i]; + + inNullifier[i] = Poseidon(4); + inNullifier[i].inputs[0] <== DOMAIN_VERSION * 100 + DOMAIN_NULLIFIER; + inNullifier[i].inputs[1] <== inSecret[i]; + inNullifier[i].inputs[2] <== inCommitment[i].out; + inNullifier[i].inputs[3] <== chainId; + computedNullifier[i] <== inNullifier[i].out; + computedNullifier[i] === nullifier[i]; + } + + // Prevent zero nullifiers (double-spend protection) + component nullifierNonZero[nIn]; + for (i = 0; i < nIn; i++) { + nullifierNonZero[i] = IsZero(); + nullifierNonZero[i].in <== nullifier[i]; + nullifierNonZero[i].out === 0; + } + + // Prevent duplicate nullifiers within the same proof + component sameNullifier[nIn * (nIn - 1) / 2]; + var pairIndex = 0; + for (i = 0; i < nIn; i++) { + for (var j = i + 1; j < nIn; j++) { + sameNullifier[pairIndex] = IsEqual(); + sameNullifier[pairIndex].in[0] <== nullifier[i]; + sameNullifier[pairIndex].in[1] <== nullifier[j]; + sameNullifier[pairIndex].out === 0; + pairIndex++; + } + } + + // 2) Merkle inclusion for each input + signal cur[nIn][depth + 1]; + component sw[nIn][depth]; + component h[nIn][depth]; + var j; + for (i = 0; i < nIn; i++) { + cur[i][0] <== inCommitment[i].out; + for (j = 0; j < depth; j++) { + inPathIndices[i][j] * (inPathIndices[i][j] - 1) === 0; + sw[i][j] = Switcher(); + sw[i][j].sel <== inPathIndices[i][j]; + sw[i][j].L <== cur[i][j]; + sw[i][j].R <== inPathElements[i][j]; + h[i][j] = Poseidon(2); + h[i][j].inputs[0] <== sw[i][j].outL; + h[i][j].inputs[1] <== sw[i][j].outR; + cur[i][j + 1] <== h[i][j].out; + } + cur[i][depth] === merkleRoot; + } + + // Ensure merkle root is non-zero + component merkleRootNonZero = IsZero(); + merkleRootNonZero.in <== merkleRoot; + merkleRootNonZero.out === 0; + + // 3) Output commitments — bound to dstChainId to prevent cross-chain replay + component outCommit[nOut]; + for (i = 0; i < nOut; i++) { + outCommit[i] = Poseidon(4); + outCommit[i].inputs[0] <== DOMAIN_VERSION * 100 + DOMAIN_NOTE_COMMITMENT; + outCommit[i].inputs[1] <== outAmount[i]; + outCommit[i].inputs[2] <== outSecret[i]; + outCommit[i].inputs[3] <== outBlinding[i] + dstChainId; + computedOutCommitment[i] <== outCommit[i].out; + computedOutCommitment[i] === outCommitment[i]; + } + + // Prevent duplicate output commitments within the same proof + component sameOutCommitment[nOut * (nOut - 1) / 2]; + pairIndex = 0; + for (i = 0; i < nOut; i++) { + for (j = i + 1; j < nOut; j++) { + sameOutCommitment[pairIndex] = IsEqual(); + sameOutCommitment[pairIndex].in[0] <== outCommitment[i]; + sameOutCommitment[pairIndex].in[1] <== outCommitment[j]; + sameOutCommitment[pairIndex].out === 0; + pairIndex++; + } + } + + // 4) Range constraints + component inAmountBits[nIn]; + component inAmountPositive[nIn]; + for (i = 0; i < nIn; i++) { + inAmountBits[i] = Num2Bits(64); + inAmountBits[i].in <== inAmount[i]; + + inAmountPositive[i] = GreaterThan(64); + inAmountPositive[i].in[0] <== inAmount[i]; + inAmountPositive[i].in[1] <== 0; + inAmountPositive[i].out === 1; + } + + component outAmountBits[nOut]; + for (i = 0; i < nOut; i++) { + outAmountBits[i] = Num2Bits(64); + outAmountBits[i].in <== outAmount[i]; + // Output amounts may be zero (change outputs) + } + + component feeBits = Num2Bits(64); + feeBits.in <== fee; + + component relayerBits = Num2Bits(160); + relayerBits.in <== relayer; + + component withdrawRecipientBits = Num2Bits(160); + withdrawRecipientBits.in <== withdrawRecipient; + + component withdrawOwnerBits = Num2Bits(160); + withdrawOwnerBits.in <== withdrawOwner; + + component withdrawAmountBits = Num2Bits(64); + withdrawAmountBits.in <== withdrawAmount; + + component dstChainBits = Num2Bits(64); + dstChainBits.in <== dstChainId; + + component protocolEpochBits = Num2Bits(32); + protocolEpochBits.in <== protocolEpoch; + + // 5) Balance equation: sum(inputs) = sum(outputs) + fee + withdrawAmount + // Fee rate policy is enforced at the contract level (Pool.feeBurnBps), not here. + signal sumIn[nIn + 1]; + signal sumOut[nOut + 1]; + sumIn[0] <== 0; + sumOut[0] <== 0; + for (i = 0; i < nIn; i++) { + sumIn[i + 1] <== sumIn[i] + inAmount[i]; + } + for (i = 0; i < nOut; i++) { + sumOut[i + 1] <== sumOut[i] + outAmount[i]; + } + sumIn[nIn] === sumOut[nOut] + fee + withdrawAmount; + + // Withdraw binding: if withdrawAmount is zero, owner and recipient must be zero. + // If withdrawAmount is non-zero, owner and recipient must both be non-zero. + component withdrawAmountIsZero = IsZero(); + withdrawAmountIsZero.in <== withdrawAmount; + withdrawOwner * withdrawAmountIsZero.out === 0; + withdrawRecipient * withdrawAmountIsZero.out === 0; + + component withdrawOwnerIsZero = IsZero(); + withdrawOwnerIsZero.in <== withdrawOwner; + withdrawOwnerIsZero.out * (1 - withdrawAmountIsZero.out) === 0; + + component withdrawRecipientIsZero = IsZero(); + withdrawRecipientIsZero.in <== withdrawRecipient; + withdrawRecipientIsZero.out * (1 - withdrawAmountIsZero.out) === 0; +} + +// Public signal order (13 signals): +// [0] merkleRoot +// [1] chainId +// [2] dstChainId +// [3] protocolEpoch +// [4] fee +// [5] relayer +// [6] nullifier[0] +// [7] nullifier[1] +// [8] outCommitment[0] +// [9] outCommitment[1] +// [10] withdrawOwner +// [11] withdrawRecipient +// [12] withdrawAmount +component main {public [merkleRoot, chainId, dstChainId, protocolEpoch, fee, relayer, nullifier, outCommitment, withdrawOwner, withdrawRecipient, withdrawAmount]} = MARKPool(20, 2, 2); diff --git a/circuits/package-lock.json b/circuits/package-lock.json new file mode 100644 index 0000000..9d1d623 --- /dev/null +++ b/circuits/package-lock.json @@ -0,0 +1,1466 @@ +{ + "name": "@mark/circuits", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@mark/circuits", + "version": "0.1.0", + "dependencies": { + "circomlib": "2.0.5" + }, + "devDependencies": { + "circomlibjs": "^0.1.7", + "snarkjs": "0.7.5" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz", + "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", + "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", + "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", + "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/rlp": "^5.8.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", + "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.8.0.tgz", + "integrity": "sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", + "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", + "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.8.0.tgz", + "integrity": "sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", + "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.8.0.tgz", + "integrity": "sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/basex": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/pbkdf2": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/wordlists": "^5.8.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz", + "integrity": "sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hdnode": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/pbkdf2": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", + "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz", + "integrity": "sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/sha2": "^5.8.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", + "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.8.0.tgz", + "integrity": "sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/basex": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0", + "bech32": "1.1.4", + "ws": "8.18.0" + } + }, + "node_modules/@ethersproject/random": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.8.0.tgz", + "integrity": "sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", + "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz", + "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", + "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "bn.js": "^5.2.1", + "elliptic": "6.6.1", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.8.0.tgz", + "integrity": "sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", + "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", + "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.8.0.tgz", + "integrity": "sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.8.0.tgz", + "integrity": "sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/hdnode": "^5.8.0", + "@ethersproject/json-wallets": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/wordlists": "^5.8.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", + "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.8.0.tgz", + "integrity": "sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@iden3/bigarray": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@iden3/bigarray/-/bigarray-0.0.2.tgz", + "integrity": "sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g==", + "dev": true, + "license": "GPL-3.0" + }, + "node_modules/@iden3/binfileutils": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@iden3/binfileutils/-/binfileutils-0.0.12.tgz", + "integrity": "sha512-naAmzuDufRIcoNfQ1d99d7hGHufLA3wZSibtr4dMe6ZeiOPV1KwOZWTJ1YVz4HbaWlpDuzVU72dS4ATQS4PXBQ==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "fastfile": "0.0.20", + "ffjavascript": "^0.3.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bfj": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz", + "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.7.2", + "check-types": "^11.2.3", + "hoopy": "^0.1.4", + "jsonpath": "^1.1.1", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/blake-hash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/blake-hash/-/blake-hash-2.0.0.tgz", + "integrity": "sha512-Igj8YowDu1PRkRsxZA7NVkdFNxH5rKv5cpLxQ0CVXSIA77pVYwCPRQJ2sMew/oneUpfuYRyjG6r8SmmmnbZb1w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/blake2b": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.4.tgz", + "integrity": "sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==", + "dev": true, + "license": "ISC", + "dependencies": { + "blake2b-wasm": "^2.4.0", + "nanoassert": "^2.0.0" + } + }, + "node_modules/blake2b-wasm": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-2.4.0.tgz", + "integrity": "sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.0.1", + "nanoassert": "^2.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/check-types": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", + "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/circom_runtime": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/circom_runtime/-/circom_runtime-0.1.28.tgz", + "integrity": "sha512-ACagpQ7zBRLKDl5xRZ4KpmYIcZDUjOiNRuxvXLqhnnlLSVY1Dbvh73TI853nqoR0oEbihtWmMSjgc5f+pXf/jQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ffjavascript": "0.3.1" + }, + "bin": { + "calcwit": "calcwit.js" + } + }, + "node_modules/circomlib": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/circomlib/-/circomlib-2.0.5.tgz", + "integrity": "sha512-O7NQ8OS+J4eshBuoy36z/TwQU0YHw8W3zxZcs4hVwpEll3e4hDm3mgkIPqItN8FDeLEKZFK3YeT/+k8TiLF3/A==", + "license": "GPL-3.0" + }, + "node_modules/circomlibjs": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/circomlibjs/-/circomlibjs-0.1.7.tgz", + "integrity": "sha512-GRAUoAlKAsiiTa+PA725G9RmEmJJRc8tRFxw/zKktUxlQISGznT4hH4ESvW8FNTsrGg/nNd06sGP/Wlx0LUHVg==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "blake-hash": "^2.0.0", + "blake2b": "^2.1.3", + "ethers": "^5.5.1", + "ffjavascript": "^0.2.45" + } + }, + "node_modules/circomlibjs/node_modules/ffjavascript": { + "version": "0.2.63", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.63.tgz", + "integrity": "sha512-dBgdsfGks58b66JnUZeZpGxdMIDQ4QsD3VYlRJyFVrKQHb2kJy4R2gufx5oetrTxXPT+aEjg0dOvOLg1N0on4A==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.2", + "web-worker": "1.2.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ethers": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.8.0.tgz", + "integrity": "sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "5.8.0", + "@ethersproject/abstract-provider": "5.8.0", + "@ethersproject/abstract-signer": "5.8.0", + "@ethersproject/address": "5.8.0", + "@ethersproject/base64": "5.8.0", + "@ethersproject/basex": "5.8.0", + "@ethersproject/bignumber": "5.8.0", + "@ethersproject/bytes": "5.8.0", + "@ethersproject/constants": "5.8.0", + "@ethersproject/contracts": "5.8.0", + "@ethersproject/hash": "5.8.0", + "@ethersproject/hdnode": "5.8.0", + "@ethersproject/json-wallets": "5.8.0", + "@ethersproject/keccak256": "5.8.0", + "@ethersproject/logger": "5.8.0", + "@ethersproject/networks": "5.8.0", + "@ethersproject/pbkdf2": "5.8.0", + "@ethersproject/properties": "5.8.0", + "@ethersproject/providers": "5.8.0", + "@ethersproject/random": "5.8.0", + "@ethersproject/rlp": "5.8.0", + "@ethersproject/sha2": "5.8.0", + "@ethersproject/signing-key": "5.8.0", + "@ethersproject/solidity": "5.8.0", + "@ethersproject/strings": "5.8.0", + "@ethersproject/transactions": "5.8.0", + "@ethersproject/units": "5.8.0", + "@ethersproject/wallet": "5.8.0", + "@ethersproject/web": "5.8.0", + "@ethersproject/wordlists": "5.8.0" + } + }, + "node_modules/fastfile": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/fastfile/-/fastfile-0.0.20.tgz", + "integrity": "sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==", + "dev": true, + "license": "GPL-3.0" + }, + "node_modules/ffjavascript": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.1.tgz", + "integrity": "sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.2", + "web-worker": "1.2.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonpath": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.3.0.tgz", + "integrity": "sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "esprima": "1.2.5", + "static-eval": "2.1.1", + "underscore": "1.13.6" + } + }, + "node_modules/logplease": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/logplease/-/logplease-1.2.15.tgz", + "integrity": "sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nanoassert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", + "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", + "dev": true, + "license": "ISC" + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/r1csfile": { + "version": "0.0.48", + "resolved": "https://registry.npmjs.org/r1csfile/-/r1csfile-0.0.48.tgz", + "integrity": "sha512-kHRkKUJNaor31l05f2+RFzvcH5XSa7OfEfd/l4hzjte6NL6fjRkSMfZ4BjySW9wmfdwPOtq3mXurzPvPGEf5Tw==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "@iden3/bigarray": "0.0.2", + "@iden3/binfileutils": "0.0.12", + "fastfile": "0.0.20", + "ffjavascript": "0.3.0" + } + }, + "node_modules/r1csfile/node_modules/ffjavascript": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.0.tgz", + "integrity": "sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.2", + "web-worker": "1.2.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true, + "license": "MIT" + }, + "node_modules/snarkjs": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/snarkjs/-/snarkjs-0.7.5.tgz", + "integrity": "sha512-h+3c4rXZKLhLuHk4LHydZCk/h5GcNvk5GjVKRRkHmfb6Ntf8gHOA9zea3g656iclRuhqQ3iKDWFgiD9ypLrKiA==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "@iden3/binfileutils": "0.0.12", + "bfj": "^7.0.2", + "blake2b-wasm": "^2.4.0", + "circom_runtime": "0.1.28", + "ejs": "^3.1.6", + "fastfile": "0.0.20", + "ffjavascript": "0.3.1", + "js-sha3": "^0.8.0", + "logplease": "^1.2.15", + "r1csfile": "0.0.48" + }, + "bin": { + "snarkjs": "build/cli.cjs" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-eval": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", + "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escodegen": "^2.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wasmbuilder": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", + "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==", + "dev": true, + "license": "GPL-3.0" + }, + "node_modules/wasmcurves": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.2.tgz", + "integrity": "sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "wasmbuilder": "0.0.16" + } + }, + "node_modules/web-worker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/circuits/package.json b/circuits/package.json new file mode 100644 index 0000000..db61542 --- /dev/null +++ b/circuits/package.json @@ -0,0 +1,17 @@ +{ + "name": "@mark/circuits", + "version": "0.1.0", + "private": true, + "description": "MARK Protocol ZK circuits (circom)", + "scripts": { + "build": "circom mark/MARKPool.circom --r1cs --wasm --sym -l node_modules --output build", + "test": "mkdir -p build && npm run build && node test/MARKPool.test.mjs" + }, + "dependencies": { + "circomlib": "2.0.5" + }, + "devDependencies": { + "circomlibjs": "^0.1.7", + "snarkjs": "0.7.5" + } +} diff --git a/circuits/setup.mjs b/circuits/setup.mjs new file mode 100644 index 0000000..0a855f6 --- /dev/null +++ b/circuits/setup.mjs @@ -0,0 +1,52 @@ +// Trusted setup for MARKPool circuit. +// Generates build/MARKPoolVerifier.sol for use in contracts/src/pool/verifier/. +// Run: node setup.mjs +// +// Powers of tau: pot15 (2^15 = 32768 >= 26387*2 wires required by MARKPool(20,2,2)) + +import { randomBytes } from 'crypto'; +import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { zKey, powersOfTau } from 'snarkjs'; + +mkdirSync('build', { recursive: true }); + +const entropy1 = randomBytes(32).toString('hex'); +const entropy2 = randomBytes(32).toString('hex'); + +console.log('Step 1: Powers of Tau (pot15)...'); +await powersOfTau.newAccumulator('bn128', 15, 'build/pot15_0000.ptau'); + +console.log('Step 2: Contribute to Powers of Tau...'); +await powersOfTau.contribute('build/pot15_0000.ptau', 'build/pot15_final.ptau', + 'MARK Protocol', entropy1); + +console.log('Step 3: Prepare phase 2...'); +await powersOfTau.preparePhase2('build/pot15_final.ptau', 'build/pot15_phase2.ptau'); + +// Verify compiled circuit exists before attempting trusted setup +if (!existsSync('build/MARKPool.r1cs')) { + console.error('Error: build/MARKPool.r1cs not found. Run: npm run build'); + process.exit(1); +} + +console.log('Step 4: Phase 2 setup...'); +await zKey.newZKey('build/MARKPool.r1cs', 'build/pot15_phase2.ptau', 'build/markpool_0000.zkey'); + +console.log('Step 5: Contribute to zkey...'); +await zKey.contribute('build/markpool_0000.zkey', 'build/markpool_final.zkey', + 'MARK Protocol MARKPool', entropy2); + +console.log('Step 6: Export verification key...'); +const vKey = await zKey.exportVerificationKey('build/markpool_final.zkey'); +writeFileSync('build/markpool_verification_key.json', JSON.stringify(vKey, null, 2)); + +console.log('Step 7: Export Solidity verifier...'); +const templatePath = fileURLToPath( + new URL('node_modules/snarkjs/templates/verifier_groth16.sol.ejs', import.meta.url) +); +const solidityTemplate = readFileSync(templatePath, 'utf8'); +const verifier = await zKey.exportSolidityVerifier('build/markpool_final.zkey', { groth16: solidityTemplate }); +writeFileSync('build/MARKPoolVerifier.sol', verifier); + +console.log('Done. Copy build/MARKPoolVerifier.sol to contracts/src/pool/verifier/MARKPoolVerifier.sol'); diff --git a/circuits/test/MARKPool.test.mjs b/circuits/test/MARKPool.test.mjs new file mode 100644 index 0000000..3e1b15d --- /dev/null +++ b/circuits/test/MARKPool.test.mjs @@ -0,0 +1,204 @@ +// Witness tests for MARKPool circuit. +// Run: node test/MARKPool.test.mjs + +import { buildPoseidon } from "circomlibjs"; +import { readFileSync } from "fs"; +import { createRequire } from "module"; +import { fileURLToPath } from "url"; +import path from "path"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url); + +const poseidon = await buildPoseidon(); +const F = poseidon.F; + +function poseidonHash(...inputs) { + return F.toObject(poseidon(inputs.map(BigInt))); +} + +// Replicate MerkleTree.sol zero-value tree (depth=20, zero leaf = 0) +function buildZeroTree(depth) { + const zeros = [0n]; + for (let i = 1; i <= depth; i++) { + zeros.push(poseidonHash(zeros[i - 1], zeros[i - 1])); + } + return zeros; +} + +const wasmPath = path.join(__dirname, "../build/MARKPool_js/MARKPool.wasm"); +const WitnessCalculator = require(path.join(__dirname, "../build/MARKPool_js/witness_calculator.js")); +const wasm = readFileSync(wasmPath); +const wc = await WitnessCalculator(wasm); + +async function expectPass(label, input) { + try { + await wc.calculateWitness(input, false); + console.log(` PASS: ${label}`); + } catch (e) { + console.error(` FAIL: ${label} — ${e.message}`); + process.exit(1); + } +} + +async function expectFail(label, input) { + try { + await wc.calculateWitness(input, false); + console.error(` FAIL: ${label} — expected constraint failure`); + process.exit(1); + } catch (e) { + // Only treat constraint/assertion failures as expected. Rethrow anything else + // (malformed input, missing signal, internal error) so regressions surface. + const msg = (e?.message ?? '').toLowerCase(); + if (msg.includes('assert failed') || msg.includes('constraint') || msg.includes('error in template')) { + console.log(` PASS: ${label}`); + } else { + throw e; + } + } +} + +// Domain constants (must match MARKPool.circom) +const DOMAIN_VERSION = 1n; +const DOMAIN_NOTE_COMMITMENT = 11n; +const DOMAIN_NULLIFIER = 12n; +const DOMAIN_COMMITMENT = DOMAIN_VERSION * 100n + DOMAIN_NOTE_COMMITMENT; +const DOMAIN_NULLIFIER_TAG = DOMAIN_VERSION * 100n + DOMAIN_NULLIFIER; + +const DEPTH = 20; +const CHAIN_ID = 11155420n; // OP Sepolia + +// Build a valid note +function makeNote(amount, secret, blinding) { + const commitment = poseidonHash(DOMAIN_COMMITMENT, amount, secret, blinding); + return { amount, secret, blinding, commitment }; +} + +function makeNullifier(note, chainId) { + return poseidonHash(DOMAIN_NULLIFIER_TAG, note.secret, note.commitment, chainId); +} + +function makeOutCommitment(amount, secret, blinding, dstChainId) { + return poseidonHash(DOMAIN_COMMITMENT, amount, secret, blinding + dstChainId); +} + +// Base valid inputs: 2-in 2-out transact, no withdrawal +const in0 = makeNote(500n, 111n, 222n); +const in1 = makeNote(500n, 333n, 444n); +const out0Secret = 555n; const out0Blinding = 666n; const out0Amount = 400n; +const out1Secret = 777n; const out1Blinding = 888n; const out1Amount = 100n; +const fee = 500n; // 500 = 500 (in0+in1=1000, out0+out1=500, fee=500, withdraw=0) + +// After inserting in1 at index 1, the root changes — for simplicity use a single-leaf tree +// where in1 is also at index 0 in its own path (both share the same root for test purposes). +// Use a shared root: insert both into the same tree. +function buildTwoLeafRoot(leaf0, leaf1, depth) { + const zeros = buildZeroTree(depth); + // Level 0: leaf0 at 0, leaf1 at 1 + let cur0 = poseidonHash(leaf0, leaf1); // parent of both + let root = cur0; + for (let i = 1; i < depth; i++) { + root = poseidonHash(root, zeros[i]); + } + return { + root, + path0: { elements: [leaf1, ...zeros.slice(1, depth)], indices: Array(depth).fill(0n) }, + path1: { elements: [leaf0, ...zeros.slice(1, depth)], indices: [1n, ...Array(depth - 1).fill(0n)] }, + }; +} + +const tree = buildTwoLeafRoot(in0.commitment, in1.commitment, DEPTH); +const merkleRoot = tree.root; + +const nullifier0 = makeNullifier(in0, CHAIN_ID); +const nullifier1 = makeNullifier(in1, CHAIN_ID); +const outC0 = makeOutCommitment(out0Amount, out0Secret, out0Blinding, CHAIN_ID); +const outC1 = makeOutCommitment(out1Amount, out1Secret, out1Blinding, CHAIN_ID); + +const validBase = { + inAmount: [in0.amount, in1.amount], + inSecret: [in0.secret, in1.secret], + inBlinding: [in0.blinding, in1.blinding], + inPathElements: [tree.path0.elements, tree.path1.elements], + inPathIndices: [tree.path0.indices, tree.path1.indices], + outAmount: [out0Amount, out1Amount], + outSecret: [out0Secret, out1Secret], + outBlinding: [out0Blinding, out1Blinding], + merkleRoot, + chainId: CHAIN_ID, + dstChainId: CHAIN_ID, + protocolEpoch: 0n, + fee, + relayer: 0n, + nullifier: [nullifier0, nullifier1], + outCommitment: [outC0, outC1], + withdrawOwner: 0n, + withdrawRecipient: 0n, + withdrawAmount: 0n, +}; + +console.log("MARKPool circuit tests"); + +// Happy path +await expectPass("valid 2-in 2-out transact", validBase); + +// Balance equation +await expectFail("fee too low (balance broken)", { ...validBase, fee: fee - 1n }); +await expectFail("fee too high (balance broken)", { ...validBase, fee: fee + 1n }); + +// Withdrawal binding +const withdrawOwner = BigInt("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); +const withdrawRecipient = BigInt("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); +const withdrawAmount = 200n; +const feeWithWithdraw = 300n; // in0+in1=1000, out0+out1=500, fee=300, withdraw=200 +const validWithWithdraw = { + ...validBase, + fee: feeWithWithdraw, + withdrawOwner, + withdrawRecipient, + withdrawAmount, +}; +await expectPass("valid transact with withdraw binding", validWithWithdraw); +await expectFail("withdraw amount non-zero but owner zero", { + ...validWithWithdraw, + withdrawOwner: 0n, +}); +await expectFail("withdraw amount non-zero but recipient zero", { + ...validWithWithdraw, + withdrawRecipient: 0n, +}); +await expectFail("withdraw amount zero but owner non-zero", { + ...validBase, + withdrawOwner, + withdrawRecipient: 0n, + withdrawAmount: 0n, +}); + +// Nullifier constraints +await expectFail("wrong nullifier (tampered)", { + ...validBase, + nullifier: [nullifier0 + 1n, nullifier1], +}); +await expectFail("duplicate nullifiers", { + ...validBase, + nullifier: [nullifier0, nullifier0], +}); + +// Merkle root +await expectFail("wrong merkle root", { ...validBase, merkleRoot: merkleRoot + 1n }); +await expectFail("zero merkle root", { ...validBase, merkleRoot: 0n }); + +// Input amount constraints +await expectFail("zero input amount", { + ...validBase, + inAmount: [0n, in1.amount], + fee: in1.amount, +}); + +// Output commitment +await expectFail("wrong output commitment", { + ...validBase, + outCommitment: [outC0 + 1n, outC1], +}); + +console.log("\nAll tests passed."); diff --git a/contracts/.env.example b/contracts/.env.example index 504653d..3143043 100644 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -54,6 +54,28 @@ VERIFY_MARK_SETTLEMENT_PRODUCTION_MODE=false VERIFY_MARK_SETTLEMENT_VERIFIER=0x0000000000000000000000000000000000000000 VERIFY_MARK_SETTLEMENT_ATTESTER=0x0000000000000000000000000000000000000000 +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Pool stack deploy inputs (DeployMARKPool.s.sol / ReleasePool.s.sol) +# ----------------------------------------------------------------------------- +MARK_POOL_VERIFIER=0x0000000000000000000000000000000000000000 +# Poseidon T3 contract address. Defaults to Semaphore's deployment (same address on all EVM networks). +# Override only if deploying to a network where 0xB43122... is not available. +MARK_POOL_POSEIDON=0xB43122Ecb241DD50062641f089876679fd06599a +MARK_POOL_OWNER=0x0000000000000000000000000000000000000000 +MARK_POOL_INTENT_SIGNER=0x0000000000000000000000000000000000000000 + +# Pool release orchestrator (ReleasePool.s.sol) +MARK_POOL_RELEASE_EXECUTE=false +MARK_POOL_RELEASE_WRITE_ARTIFACT=false +MARK_POOL_RELEASE_ARTIFACT_PATH=broadcast/mark-pool-release-latest.json + +# Post-deploy verification (ReleasePool.s.sol _verify) +VERIFY_MARK_POOL=0x0000000000000000000000000000000000000000 +VERIFY_MARK_POOL_LEDGER=0x0000000000000000000000000000000000000000 +VERIFY_MARK_POOL_ADAPTER=0x0000000000000000000000000000000000000000 + # ----------------------------------------------------------------------------- # Local integration test endpoints (supersim) # ----------------------------------------------------------------------------- diff --git a/contracts/ARCHITECTURE.md b/contracts/ARCHITECTURE.md index 94953ae..c772935 100644 --- a/contracts/ARCHITECTURE.md +++ b/contracts/ARCHITECTURE.md @@ -5,14 +5,36 @@ - `src/token`: token primitives (`RYLA`). - `src/bridge`: bridge adapter domain. - `src/settlement`: settlement module + verifier domain. +- `src/pool`: ZK UTXO pool domain (`MARKPool`, `RYLACreditLedger`, support libraries). +- `src/withdraw`: withdrawal adapter domain (`MARKWithdrawAdapter`). +- `src/crypto`: shared cryptographic primitives (Merkle tree, Poseidon). +- `src/interfaces`: shared interfaces used across domains. - `src/errors`: shared error types. ## Dependency Rules - `src/bridge/**` must not import from `src/settlement/**`. - `src/settlement/**` must not import from `src/bridge/**`. +- `src/pool/**` must not import from `src/settlement/**` or `src/bridge/**`. +- `src/withdraw/**` must not import from `src/settlement/**` or `src/bridge/**`. - Cross-domain sharing should be done through narrow interfaces and shared types only. -- `src/token/**` is an allowed dependency for both bridge and settlement domains. +- `src/token/**` is an allowed dependency for all domains. +- `src/crypto/**` and `src/interfaces/**` are allowed dependencies for all domains. + +## Pool Withdrawal Flow (burn-to-claim model) + +Notes enter the pool via `transact()` (ZK proof) or `bridgeIn()` (restricted). The pool +is a nullifier registry — it does not hold tokens. + +To withdraw RYLA, a note owner: + +1. Calls `MARKPool.transactWithWithdrawBinding()` — verifies ZK proof, marks nullifiers + spent, records a withdraw binding (hash of owner/recipient/amount). No token transfer. +2. Calls `MARKWithdrawAdapter.withdrawWithSig()` — verifies the binding matches, verifies + EIP-712 signatures, calls `RYLACreditLedger.debit(owner, amount)` which burns RYLA. + +The owner must hold RYLA equal to the withdrawal amount and approve `RYLACreditLedger` +before step 2. The ZK proof proves note ownership; the RYLA burn redeems it. ## Enforcement diff --git a/contracts/KNOWN_ISSUES.md b/contracts/KNOWN_ISSUES.md index 3ae439b..fbbccc1 100644 --- a/contracts/KNOWN_ISSUES.md +++ b/contracts/KNOWN_ISSUES.md @@ -71,3 +71,32 @@ This document lists known limitations and intentional design decisions that audi **Impact:** None — these packages are not part of the deployed protocol. **Accepted because:** No upstream fix is available. The packages are scoped to development tooling only. + +--- + +## KI-7: Two separate ZK systems sharing the MARKPool 13-signal circuit + +**Scope:** `circuits/`, `src/pool/`, `src/settlement/verifier/Groth16SettlementVerifier.sol` + +**Description:** The project contains two contract domains that both use the same ZK circuit (`circuits/mark/MARKPool.circom`, 13 public signals): + +- **Pool system** (`MARKPool` + `MARKPoolVerifier`): uses the circuit directly for UTXO transfers. The circuit is compiled, the verifier is generated at `src/pool/verifier/MARKPoolVerifier.sol`, and witness tests pass. +- **Settlement system** (`MARKSettlementModule` + `Groth16SettlementVerifier`): the design supports the same 13-signal layout via `IGroth16Verifier` and is compatible with `MARKPoolVerifier`. However, `MARKPoolVerifier` has not yet been wired into `Groth16SettlementVerifier.setVerifierContract()` — this configuration step is required before ZK-based settlement is active. `AttestedSettlementVerifier` remains the production-safe fallback until that wiring is completed. + +**Impact:** Auditors should verify that `Groth16SettlementVerifier.verifierContract` is set to a deployed `MARKPoolVerifier` instance before evaluating ZK settlement security. Until then, settlement security depends on `AttestedSettlementVerifier` (EIP-712 signatures). + +**Accepted because:** `AttestedSettlementVerifier` provides meaningful security (role-gated, replay-protected, deadline-bound, module-bound). The pool circuit and verifier are consistent with each other. Settlement ZK integration is in progress. + +--- + +## KI-8: MARKPool and PoseidonT3 contract size + +**Contracts:** `src/pool/MARKPool.sol`, `src/crypto/generated/PoseidonT3.sol` + +**Description:** `MARKPool` is currently 24,231 bytes — 345 bytes under the EIP-170 24,576-byte limit. `PoseidonT3` is 55,856 bytes and cannot be deployed directly. `MerkleTree` calls Poseidon via `IPoseidonT3` interface at a configurable address; `MARKPool` has no link references and is fully self-contained. The default deployment address is `0xB43122Ecb241DD50062641f089876679fd06599a` (Semaphore's PoseidonT3, same address on all EVM networks via CREATE2). + +**Impact:** `MARKPool` is deployable. The 345-byte margin is tight — any significant feature addition risks exceeding the limit. CI runs pool release dry-run only (no execute smoke): Foundry's contract size check rejects the `PoseidonT3` library artifact (55,856 bytes) during broadcast. The dry-run validates the release script logic without triggering this check. + +**Required before mainnet:** Monitor `MARKPool` size on every change. If the margin drops below ~100 bytes, extract logic (e.g. bridge-out, fee policy, or root management) into a separate contract. + +**Accepted for now because:** The pool domain is pre-production. The settlement layer (which does not use `MARKPool`) is unaffected and can proceed to testnet independently. diff --git a/contracts/Makefile b/contracts/Makefile index 94c0aa9..10d99f7 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -4,7 +4,7 @@ ci-fast: architecture-guard layering-guard test-core test-core: - @FOUNDRY_OFFLINE=true forge test --no-match-path 'test/invariant/**' -q + @FOUNDRY_OFFLINE=true forge test --no-match-path 'test/{invariant,integration}/**' -q test-invariants: @FOUNDRY_OFFLINE=true FOUNDRY_INVARIANT_RUNS=64 forge test --match-path 'test/invariant/**/*.t.sol' -q @@ -78,24 +78,32 @@ verify-evidence-signature: @./script/ops/verify-evidence-signature.sh slither-install: - @python3 -m pip install --user slither-analyzer==0.11.5 + @python3 -m pip install --user slither-analyzer==0.11.4 slither-core: - @command -v slither >/dev/null 2>&1 || { \ - echo "slither not found in PATH. Try: export PATH=\"$$HOME/Library/Python/3.9/bin:$$PATH\""; \ - echo "or run: make slither-install"; \ + @python3 -m slither --version >/dev/null 2>&1 || { \ + echo "slither not found. Run: make slither-install"; \ exit 1; \ } - @for target in \ - src/token/RYLA.sol \ - src/bridge/MARKBridgeAdapter.sol \ - src/settlement/MARKSettlementModule.sol \ - src/settlement/verifier/AttestedSettlementVerifier.sol; \ - do \ - slither "$$target" \ - --solc-remaps "@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/" \ - --exclude-dependencies \ - --exclude "naming-convention,timestamp,arbitrary-send-erc20,reentrancy-balance,reentrancy-benign" \ - --filter-paths "lib|test|script|out|cache" \ - --fail-medium; \ - done + # Per-contract exclusion rationale: + # naming-convention — SCREAMING_SNAKE_CASE immutables are intentional (all contracts) + # timestamp — block.timestamp used only for root expiry windows, not randomness (all) + # arbitrary-send-erc20 — transferFrom requires prior approval from the token holder (bridge, settlement, ledger) + # reentrancy-balance — false positive on settlement module balance checks + # reentrancy-benign — benign reentrancy paths with no exploitable state (settlement + pool) + # incorrect-return — false positive on PoseidonT3 assembly return (correct EVM usage) + # assembly — PoseidonT3 uses inline assembly by design (ZK hash function) + # arbitrary-send-eth — MARKWithdrawAdapter sends ETH to recipient by design (withdrawal adapter) + # missing-zero-check — false positive: recipient checked in _validateWithdrawRequest before ETH send + # low-level-calls — .call{value} is intentional (supports contract recipients) + @REMAPS="@interop-lib/=lib/interop-lib/src/ @openzeppelin/=lib/createx/lib/openzeppelin-contracts/"; \ + FLAGS="--exclude-dependencies --filter-paths lib|test|script|out|cache --fail-medium"; \ + BASE="naming-convention,timestamp"; \ + python3 -m slither src/token/RYLA.sol --solc-remaps "$$REMAPS" $$FLAGS --exclude "$$BASE"; \ + python3 -m slither src/bridge/MARKBridgeAdapter.sol --solc-remaps "$$REMAPS" $$FLAGS --exclude "$$BASE,arbitrary-send-erc20"; \ + python3 -m slither src/settlement/MARKSettlementModule.sol --solc-remaps "$$REMAPS" $$FLAGS --exclude "$$BASE,arbitrary-send-erc20,reentrancy-balance,reentrancy-benign"; \ + python3 -m slither src/settlement/verifier/AttestedSettlementVerifier.sol --solc-remaps "$$REMAPS" $$FLAGS --exclude "$$BASE"; \ + python3 -m slither src/settlement/verifier/Groth16SettlementVerifier.sol --solc-remaps "$$REMAPS" $$FLAGS --exclude "$$BASE"; \ + python3 -m slither src/pool/MARKPool.sol --solc-remaps "$$REMAPS" $$FLAGS --exclude "$$BASE,reentrancy-benign,incorrect-return,assembly"; \ + python3 -m slither src/pool/RYLACreditLedger.sol --solc-remaps "$$REMAPS" $$FLAGS --exclude "$$BASE,arbitrary-send-erc20"; \ + python3 -m slither src/withdraw/MARKWithdrawAdapter.sol --solc-remaps "$$REMAPS" $$FLAGS --exclude "$$BASE,arbitrary-send-eth,missing-zero-check,low-level-calls" diff --git a/contracts/README.md b/contracts/README.md index 200a4a0..1fdc917 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -43,6 +43,15 @@ Pre-mainnet promotion criteria are documented in [STAGING_GO_NO_GO_CHECKLIST.md] - Proof format: - `abi.encode(uint256 deadline, bytes32 contextHash, uint8 v, bytes32 r, bytes32 s)` +### [Groth16SettlementVerifier.sol](./src/settlement/verifier/Groth16SettlementVerifier.sol) + +- Groth16-based settlement verifier adapter for 13-signal proofs. +- Binds verifier usage to one `MARKSettlementModule` via `setSettlementModule(address)`. +- Supports staged `isMint` direction enforcement: + - default (migration mode): `signals[7]` must be `0` + - strict mode: `signals[7]` must be `isMint ? 1 : 0` +- Strict mode is toggled by `setDirectionEnforcementEnabled(bool)`. + ## Development Note: legacy CrossChainCounter example contracts and tests were retired in favor of MARK protocol deployment/ops flows. Current CI and release gates focus on MARK stack contracts and governance evidence artifacts. @@ -192,6 +201,22 @@ Control behavior with: - `MARK_GIT_COMMIT` to tag artifact with commit id - `MARK_RELEASE_STRICT_VERIFY=true` to require explicit `VERIFY_MARK_SETTLEMENT_*` expectations during execute-mode verify - `MARK_SETTLEMENT_PRODUCTION_MODE=true` to lock settlement verifier/proof validation configuration in production +- `MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT=true|false` to control Groth16 strict direction binding during deploy/setup + +### Groth16 Direction Enforcement Rollout + +Use this sequence when `MARK_SETTLEMENT_VERIFIER` points to `Groth16SettlementVerifier`: + +1. Deploy/setup with migration-compatible mode: + - `MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT=false` + - This keeps legacy proof mapping (`signals[7] == 0`) valid. +2. Upgrade proof generation so `signals[7]` encodes direction: + - mint: `1` + - burn: `0` +3. Re-run staging tests and post-deploy verify. +4. Enable strict mode: + - `MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT=true` +5. Only then activate settlement production mode. ### Mainnet Readiness Gate diff --git a/contracts/RUNBOOK.md b/contracts/RUNBOOK.md index d9c5f84..f1a425a 100644 --- a/contracts/RUNBOOK.md +++ b/contracts/RUNBOOK.md @@ -366,6 +366,33 @@ Before activating production mode, confirm: - The attester key is in secure, long-term storage. - The verifier contract has been audited. - The admin key is in a hardware wallet or equivalent. +- If using `Groth16SettlementVerifier`: + - `settlementModule` is bound to the deployed module. + - `MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT` matches your proof format. + - Direction rollout has been completed (below) before enabling production mode. + +### Groth16 Direction Rollout (Required If Using Groth16SettlementVerifier) + +Goal: enforce `isMint` at proof-signal level without breaking legacy proofs during migration. + +1. Deploy or reconfigure verifier/module binding: + - Use `DeployMARKSettlementModule.s.sol` or `PostDeployMARKSetup.s.sol`. + - Ensure `MARK_SETTLEMENT_VERIFIER=`. +2. Start in compatibility mode: + - `MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT=false` + - Expected proof mapping: `signals[7] == 0`. +3. Upgrade prover/circuit output: + - mint proofs use `signals[7] = 1` + - burn proofs use `signals[7] = 0` +4. Validate in staging: + - run settlement tests and `VerifyMARKDeployment.s.sol`. +5. Enable strict mode: + - `MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT=true` +6. Re-run verify and only then lock production mode: + - `MARK_SETTLEMENT_PRODUCTION_MODE=true` + +No-Go rule: +- Do not enable production mode with Groth16 if strict direction expectations are ambiguous or untested in staging. ### Key Storage Recommendations diff --git a/contracts/THREAT_MODEL.md b/contracts/THREAT_MODEL.md index 272a593..20dbc2e 100644 --- a/contracts/THREAT_MODEL.md +++ b/contracts/THREAT_MODEL.md @@ -4,12 +4,19 @@ This document is intended for security auditors. It describes the trust assumpti ## System Overview -MARK is a settlement and bridging protocol on the Optimism Superchain. It consists of four contracts: +MARK is a settlement, bridging, and ZK UTXO privacy protocol on the Optimism Superchain. It consists of two independent stacks: +**Settlement stack** (production-bound): - **RYLA** — ERC-20 credit token with role-gated mint/burn - **MARKSettlementModule** — operator-gated settlement boundary; holds MINTER_ROLE and BURNER_ROLE on RYLA - **MARKBridgeAdapter** — operator-gated bridge adapter; routes RYLA cross-chain via SuperchainTokenBridge - **AttestedSettlementVerifier** — EIP-712 signature verifier; validates settlement attestations before mint/burn +- **Groth16SettlementVerifier** — Groth16 proof verifier adapter; validates 13-signal ZK proofs for settlement + +**Pool stack** (pre-production): +- **MARKPool** — ZK UTXO pool; nullifier registry with Merkle commitment tree; does not hold tokens +- **RYLACreditLedger** — ICreditLedger adapter; mints RYLA for relayer fees (via pool) and burns RYLA on withdrawal (via adapter) +- **MARKWithdrawAdapter** — EIP-191 signature-based withdrawal adapter; verifies withdraw bindings and sends ETH to recipients ## Trust Boundaries @@ -32,6 +39,30 @@ External actors External contracts └── SuperchainTokenBridge (predeploy 0x4200...0028) └── called by MARKBridgeAdapter.bridgeTo; trusted as a system predeploy + +Pool stack trust boundaries: + +``` +External actors + └── Pool Authority (AccessManager admin) + ├── grants/revokes restricted selectors on MARKPool and MARKWithdrawAdapter + ├── sets verifier on MARKPool (one-time per proof type) + └── sets asset ledger on MARKPool (one-time) + + └── Note owner (end user) + ├── submits ZK proofs to MARKPool.transact / transactWithWithdrawBinding + └── calls MARKWithdrawAdapter.withdrawWithSig with EIP-191 signatures + + └── Relayer (permissionless) + └── calls MARKPool.transact on behalf of note owners; receives fee credit via RYLACreditLedger + + └── Intent signer (hot key, configured on MARKWithdrawAdapter) + └── co-signs withdraw intents; prevents unauthorized withdrawals without owner signature + +External contracts + └── RYLACreditLedger + └── called by MARKPool.credit (relayer fees) and MARKWithdrawAdapter.debit (withdrawals) + └── holds MINTER_ROLE and BURNER_ROLE on RYLA ``` ## Role Compromise Impact @@ -85,6 +116,34 @@ Worst case: cross-chain token accounting failure. - `MARKBridgeAdapter` handles bridge failure via try/catch — clears approval and reverts `BridgeFailed()` if `sendERC20` fails - The bridge is a system predeploy — its security is outside the scope of this protocol +### Pool Authority (AccessManager admin) compromised + +Worst case: pool configuration takeover. + +- Attacker can replace the verifier with a malicious contract that accepts any proof +- Attacker can set the asset ledger to a malicious contract that mints unbounded RYLA +- Attacker can pause/unpause the pool and withdrawal adapter +- Attacker cannot replay already-consumed nullifiers (stored on-chain, immutable) +- Attacker cannot forge withdraw bindings for past transactions (bound to nullifier hashes) + +Mitigations: +- `setVerifier` and `setAssetLedger` are restricted to AccessManager — requires the authority contract to be compromised +- Nullifier registry is append-only — consumed nullifiers cannot be un-consumed +- Withdraw bindings are cryptographically bound to owner/recipient/amount at proof time + +### Intent signer key compromised + +Worst case: unauthorized withdrawals for notes whose nullifiers are already consumed. + +- Attacker can co-sign withdraw intents for any pending withdraw binding +- Attacker cannot create new withdraw bindings (requires a valid ZK proof submitted to MARKPool) +- Attacker cannot withdraw without the note owner's EIP-191 signature (dual-signature requirement) + +Mitigations: +- Dual-signature requirement: both owner signature and intent signer signature required +- Intent signer can be rotated via AccessManager +- Withdraw bindings are one-time use (nullifiers marked claimed after withdrawal) + ## External Dependencies | Dependency | Version | Trust level | Notes | @@ -95,7 +154,8 @@ Worst case: cross-chain token accounting failure. ## What Is Explicitly Out of Scope -- **AttestedSettlementVerifier is a placeholder** — it is a production-safe bridge step before ZK verifier integration. The ZK proof system has not been designed yet. Auditors should evaluate the attested verifier as a standalone ECDSA-based verifier, not as a ZK system. +- **AttestedSettlementVerifier is a production-safe baseline** — it is an ECDSA-based verifier intended as a bridge step before full ZK verifier integration. Auditors should evaluate it as a standalone ECDSA verifier. +- **MARKPool ZK circuit** — the Groth16 circuit (`circuits/mark/MARKPool.circom`) has not undergone a formal trusted setup ceremony. The current setup used a single contributor. A multi-party ceremony is required before mainnet. - **Off-chain operator infrastructure** — the protocol does not specify how operators construct or submit settlement intents. That is provider-layer responsibility. - **Frontend** — the frontend is a read-only info page with no wallet interaction or user funds. - **Deployment scripts** — `contracts/script/` contains operational tooling, not protocol logic. @@ -107,3 +167,6 @@ Worst case: cross-chain token accounting failure. 3. `bridgedInDailyCapEpoch` never exceeds `dailyCap` within a single epoch. 4. `productionMode` is irreversible once set — proof validation cannot be disabled. 5. The module's RYLA balance returns to its pre-settlement value after each `settleBurn` operation. The module does not accumulate tokens across settlements. Note: tokens sent directly to the module address outside of settlement flows are not covered by this invariant. +6. `nullifierUsed[nullifier]` is set to `true` before any state changes in `MARKPool.transact*` — nullifiers cannot be replayed even under reentrancy. +7. `nullifierWithdrawBinding` is written only after nullifiers are consumed — a withdraw binding cannot be created without a valid ZK proof. +8. `RYLACreditLedger.debit` requires `from` to have approved the ledger for at least `amount` RYLA — the burn cannot proceed without explicit token approval from the note owner. diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 8e3b0a0..0a7de05 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -8,6 +8,7 @@ broadcast = "broadcast" libs = ["lib"] no_match_path = "test/integration/**" fs_permissions = [{ access = "read-write", path = "./broadcast" }] +via_ir = true remappings = [ "@interop-lib/=lib/interop-lib/src/", "@openzeppelin/=lib/createx/lib/openzeppelin-contracts/" @@ -36,3 +37,5 @@ no_match_path = "test/never/**" + + diff --git a/contracts/script/ci/architecture-guard.sh b/contracts/script/ci/architecture-guard.sh index 70f71ae..1730d2c 100755 --- a/contracts/script/ci/architecture-guard.sh +++ b/contracts/script/ci/architecture-guard.sh @@ -32,13 +32,25 @@ check_no_imports() { # Bridge contracts must not depend on settlement concrete contracts. check_no_imports \ "src/bridge" \ - '^import\s+.*"(?:\.\./settlement/|\.\./\.\./src/settlement/|src/settlement/)' \ + '^\s*import\s+.*"(\.\.\/)+(?:src\/)?settlement\/' \ "bridge -> settlement" # Settlement contracts must not depend on bridge concrete contracts. check_no_imports \ "src/settlement" \ - '^import\s+.*"(?:\.\./bridge/|\.\./\.\./src/bridge/|src/bridge/)' \ + '^\s*import\s+.*"(\.\.\/)+(?:src\/)?bridge\/' \ "settlement -> bridge" +# Pool contracts must not depend on settlement or bridge concrete contracts. +check_no_imports \ + "src/pool" \ + '^\s*import\s+.*"(\.\.\/)+(?:src\/)?(?:settlement|bridge)\/' \ + "pool -> settlement/bridge" + +# Withdraw contracts must not depend on settlement or bridge concrete contracts. +check_no_imports \ + "src/withdraw" \ + '^\s*import\s+.*"(\.\.\/)+(?:src\/)?(?:settlement|bridge)\/' \ + "withdraw -> settlement/bridge" + echo "[architecture-guard] OK" diff --git a/contracts/script/deploy/pool/DeployMARKPool.s.sol b/contracts/script/deploy/pool/DeployMARKPool.s.sol new file mode 100644 index 0000000..d8912b1 --- /dev/null +++ b/contracts/script/deploy/pool/DeployMARKPool.s.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKPool} from "../../../src/pool/MARKPool.sol"; +import {MARKWithdrawAdapter} from "../../../src/withdraw/MARKWithdrawAdapter.sol"; +import {RYLACreditLedger} from "../../../src/pool/RYLACreditLedger.sol"; + +/// @notice Deploys MARKPool, RYLACreditLedger, and MARKWithdrawAdapter. +/// @dev Deployment sequence: +/// 1. Deploy AccessManager (admin = owner) +/// 2. Deploy MARKPool (authority = AccessManager, verifier) +/// 3. Deploy RYLACreditLedger (token, pool) — pool address now known +/// 4. Deploy MARKWithdrawAdapter (authority = AccessManager, ledger, pool) +/// 5. Configure restricted selectors on pool and adapter via AccessManager +/// 6. Call pool.setAssetLedger(ledger) — wires ledger for relayer fee credits +/// 7. Grant MINTER_ROLE and BURNER_ROLE on RYLA to RYLACreditLedger +/// +/// Required env vars: +/// PRIVATE_KEY — deployer private key +/// MARK_RYLA_TOKEN — deployed RYLA address +/// MARK_POOL_VERIFIER — deployed MARKPoolVerifier address +/// +/// Optional env vars: +/// MARK_POOL_OWNER — AccessManager admin (defaults to deployer) +/// MARK_POOL_INTENT_SIGNER — initial intent signer for MARKWithdrawAdapter +contract DeployMARKPool is Script { + bytes32 private constant DEFAULT_ADMIN_ROLE = 0x00; + uint64 private constant POOL_ADMIN_ROLE = 1; + + error MissingTokenAdminForRoleGrants(); + + struct Config { + uint256 deployerKey; + address deployer; + address tokenAddress; + address verifierAddress; + address owner; + address intentSigner; + address poseidonAddress; + } + + struct Deployed { + AccessManager accessManager; + MARKPool pool; + RYLACreditLedger ledger; + MARKWithdrawAdapter adapter; + } + + function run() external returns (Deployed memory d) { + Config memory cfg = _loadConfig(); + RYLA token = RYLA(cfg.tokenAddress); + + if (!token.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer)) revert MissingTokenAdminForRoleGrants(); + + vm.startBroadcast(cfg.deployerKey); + + // 1. AccessManager — admin is owner + d.accessManager = new AccessManager(cfg.owner); + + // 2. MARKPool — no ledger needed at construction + d.pool = new MARKPool(address(d.accessManager), cfg.verifierAddress, cfg.poseidonAddress); + + // 3. RYLACreditLedger — pool address now known + d.ledger = new RYLACreditLedger(cfg.tokenAddress, address(d.pool)); + + // 4. MARKWithdrawAdapter + d.adapter = new MARKWithdrawAdapter( + address(d.accessManager), + address(d.ledger), + address(d.pool) + ); + + // Wire adapter into ledger (one-time call — breaks circular deploy dependency) + d.ledger.setAdapter(address(d.adapter)); + + // 5. Grant POOL_ADMIN_ROLE to owner and deployer (deployer needs it for setup calls below) + d.accessManager.grantRole(POOL_ADMIN_ROLE, cfg.owner, 0); + if (cfg.deployer != cfg.owner) { + d.accessManager.grantRole(POOL_ADMIN_ROLE, cfg.deployer, 0); + } + + // 6. Assign restricted selectors on MARKPool to POOL_ADMIN_ROLE + bytes4[] memory poolSelectors = new bytes4[](14); + poolSelectors[0] = MARKPool.pause.selector; + poolSelectors[1] = MARKPool.unpause.selector; + poolSelectors[2] = MARKPool.pauseWithdrawals.selector; + poolSelectors[3] = MARKPool.unpauseWithdrawals.selector; + poolSelectors[4] = MARKPool.setVerifier.selector; + poolSelectors[5] = MARKPool.setProofTypeEnabled.selector; + poolSelectors[6] = MARKPool.emergencyDisableProofType.selector; + poolSelectors[7] = MARKPool.setMaxRootAge.selector; + poolSelectors[8] = MARKPool.setFeeBurnBps.selector; + poolSelectors[9] = MARKPool.setMinFee.selector; + poolSelectors[10] = MARKPool.setProtocolEpoch.selector; + poolSelectors[11] = MARKPool.setBridgeOutEntrypoint.selector; + poolSelectors[12] = MARKPool.bridgeIn.selector; + poolSelectors[13] = MARKPool.setAssetLedger.selector; + d.accessManager.setTargetFunctionRole(address(d.pool), poolSelectors, POOL_ADMIN_ROLE); + + // 7. Assign restricted selectors on MARKWithdrawAdapter to POOL_ADMIN_ROLE + bytes4[] memory adapterSelectors = new bytes4[](4); + adapterSelectors[0] = MARKWithdrawAdapter.pause.selector; + adapterSelectors[1] = MARKWithdrawAdapter.unpause.selector; + adapterSelectors[2] = MARKWithdrawAdapter.setMaxIntentValidity.selector; + adapterSelectors[3] = MARKWithdrawAdapter.setIntentSigner.selector; + d.accessManager.setTargetFunctionRole(address(d.adapter), adapterSelectors, POOL_ADMIN_ROLE); + + // 8. Wire ledger into pool (one-time call) + d.pool.setAssetLedger(address(d.ledger)); + + // 9. Set intent signer if provided + if (cfg.intentSigner != address(0)) { + d.adapter.setIntentSigner(cfg.intentSigner, true); + } + + // 10. Grant RYLA roles to ledger + token.setMinter(address(d.ledger), true); + token.setBurner(address(d.ledger), true); + + // 11. Revoke deployer's temporary admin role if deployer != owner + if (cfg.deployer != cfg.owner) { + d.accessManager.revokeRole(POOL_ADMIN_ROLE, cfg.deployer); + } + + vm.stopBroadcast(); + + console.log("AccessManager: ", address(d.accessManager)); + console.log("MARKPool: ", address(d.pool)); + console.log("RYLACreditLedger: ", address(d.ledger)); + console.log("MARKWithdrawAdapter:", address(d.adapter)); + } + + function _loadConfig() internal view returns (Config memory cfg) { + cfg.deployerKey = vm.envUint("PRIVATE_KEY"); + cfg.deployer = vm.addr(cfg.deployerKey); + cfg.tokenAddress = vm.envAddress("MARK_RYLA_TOKEN"); + cfg.verifierAddress = vm.envAddress("MARK_POOL_VERIFIER"); + cfg.owner = vm.envOr("MARK_POOL_OWNER", cfg.deployer); + cfg.intentSigner = vm.envOr("MARK_POOL_INTENT_SIGNER", address(0)); + // Default: Semaphore PoseidonT3 (same address on all EVM networks, BN254 compatible) + cfg.poseidonAddress = vm.envOr("MARK_POOL_POSEIDON", address(0xB43122Ecb241DD50062641f089876679fd06599a)); + } +} diff --git a/contracts/script/deploy/settlement/DeployMARKSettlementModule.s.sol b/contracts/script/deploy/settlement/DeployMARKSettlementModule.s.sol index 581cba9..0db1f85 100644 --- a/contracts/script/deploy/settlement/DeployMARKSettlementModule.s.sol +++ b/contracts/script/deploy/settlement/DeployMARKSettlementModule.s.sol @@ -23,6 +23,7 @@ contract DeployMARKSettlementModule is Script { bool deployAttestedVerifier; address verifierAttester; bool proofEnabled; + bool groth16DirectionEnforcement; } function run() external returns (MARKSettlementModule module) { @@ -55,6 +56,13 @@ contract DeployMARKSettlementModule is Script { module.setOperator(cfg.operator, true); } module.setVerifier(cfg.verifierAddress, cfg.proofEnabled); + if (cfg.verifierAddress != address(0)) { + _tryConfigureGroth16Verifier( + cfg.verifierAddress, + address(module), + cfg.groth16DirectionEnforcement + ); + } } bool deployerIsTokenAdmin = token.hasRole(DEFAULT_ADMIN_ROLE, cfg.deployer); @@ -84,5 +92,29 @@ contract DeployMARKSettlementModule is Script { cfg.deployAttestedVerifier = vm.envOr("MARK_DEPLOY_ATTESTED_VERIFIER", false); cfg.verifierAttester = vm.envOr("MARK_SETTLEMENT_ATTESTER", address(0)); cfg.proofEnabled = vm.envOr("MARK_SETTLEMENT_PROOF_ENABLED", false); + cfg.groth16DirectionEnforcement = vm.envOr("MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT", false); + } + + function _tryConfigureGroth16Verifier( + address verifierAddress, + address moduleAddress, + bool directionEnforcement + ) internal { + (bool hasSetModule,) = + verifierAddress.staticcall(abi.encodeWithSelector(bytes4(keccak256("settlementModule()")))); + if (!hasSetModule) return; + + // Must succeed for Groth16 verifier contracts during controlled deployment. + (bool okSetModule,) = + verifierAddress.call(abi.encodeWithSelector(bytes4(keccak256("setSettlementModule(address)")), moduleAddress)); + require(okSetModule, "Groth16 setSettlementModule failed"); + + (bool okSetDirection,) = verifierAddress.call( + abi.encodeWithSelector( + bytes4(keccak256("setDirectionEnforcementEnabled(bool)")), + directionEnforcement + ) + ); + require(okSetDirection, "Groth16 setDirectionEnforcementEnabled failed"); } } diff --git a/contracts/script/ops/pool/ReleasePool.s.sol b/contracts/script/ops/pool/ReleasePool.s.sol new file mode 100644 index 0000000..2eff3a2 --- /dev/null +++ b/contracts/script/ops/pool/ReleasePool.s.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKPool} from "../../../src/pool/MARKPool.sol"; +import {MARKWithdrawAdapter} from "../../../src/withdraw/MARKWithdrawAdapter.sol"; +import {RYLACreditLedger} from "../../../src/pool/RYLACreditLedger.sol"; +import {DeployMARKPool} from "../../deploy/pool/DeployMARKPool.s.sol"; + +/// @notice Release orchestrator for the MARKPool stack. +/// @dev Sequence: preflight checks -> deploy -> verify -> artifact. +/// +/// Required env vars: +/// PRIVATE_KEY — deployer private key +/// MARK_RYLA_TOKEN — deployed RYLA address +/// MARK_POOL_VERIFIER — deployed MARKPoolVerifier address +/// +/// Optional env vars: +/// MARK_POOL_OWNER — AccessManager admin (defaults to deployer) +/// MARK_POOL_INTENT_SIGNER — initial intent signer for MARKWithdrawAdapter +/// MARK_POOL_RELEASE_EXECUTE — set true to broadcast (default: false = dry-run) +/// MARK_POOL_RELEASE_WRITE_ARTIFACT — set true to write JSON artifact +/// MARK_POOL_RELEASE_ARTIFACT_PATH — artifact output path +/// MARK_GIT_COMMIT — git commit hash for artifact +contract ReleasePool is Script { + bytes32 private constant DEFAULT_ADMIN_ROLE = 0x00; + + error PreflightFailed(string reason); + + struct ReleaseResult { + bool execute; + address deployer; + address token; + address accessManager; + address pool; + address ledger; + address adapter; + } + + function run() external { + bool execute = vm.envOr("MARK_POOL_RELEASE_EXECUTE", false); + bool writeArtifact = vm.envOr("MARK_POOL_RELEASE_WRITE_ARTIFACT", false); + + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(deployerKey); + address tokenAddress = vm.envAddress("MARK_RYLA_TOKEN"); + address verifierAddress = vm.envAddress("MARK_POOL_VERIFIER"); + + // Preflight checks + _preflight(deployer, tokenAddress, verifierAddress); + + if (!execute) { + console.log("MARK_POOL_RELEASE_EXECUTE=false. Dry-run complete (no transactions broadcast)."); + if (writeArtifact) { + _writeArtifact(ReleaseResult({ + execute: false, + deployer: deployer, + token: tokenAddress, + accessManager: address(0), + pool: address(0), + ledger: address(0), + adapter: address(0) + })); + } + return; + } + + DeployMARKPool deployPool = new DeployMARKPool(); + DeployMARKPool.Deployed memory d = deployPool.run(); + + // Post-deploy verification + _verify(tokenAddress, d); + + ReleaseResult memory result = ReleaseResult({ + execute: true, + deployer: deployer, + token: tokenAddress, + accessManager: address(d.accessManager), + pool: address(d.pool), + ledger: address(d.ledger), + adapter: address(d.adapter) + }); + + if (writeArtifact) { + _writeArtifact(result); + } else { + console.log("Artifact write disabled (MARK_POOL_RELEASE_WRITE_ARTIFACT=false)."); + } + + console.log("Pool release complete."); + console.log(" AccessManager:", address(d.accessManager)); + console.log(" MARKPool: ", address(d.pool)); + console.log(" RYLACreditLedger:", address(d.ledger)); + console.log(" MARKWithdrawAdapter:", address(d.adapter)); + } + + function _preflight(address deployer, address tokenAddress, address verifierAddress) internal view { + if (tokenAddress == address(0)) revert PreflightFailed("MARK_RYLA_TOKEN not set"); + if (verifierAddress == address(0)) revert PreflightFailed("MARK_POOL_VERIFIER not set"); + if (tokenAddress.code.length == 0) revert PreflightFailed("MARK_RYLA_TOKEN is not a contract"); + if (verifierAddress.code.length == 0) revert PreflightFailed("MARK_POOL_VERIFIER is not a contract"); + + RYLA token = RYLA(tokenAddress); + if (!token.hasRole(DEFAULT_ADMIN_ROLE, deployer)) { + revert PreflightFailed("deployer does not have DEFAULT_ADMIN_ROLE on RYLA"); + } + } + + function _verify(address tokenAddress, DeployMARKPool.Deployed memory d) internal view { + // Pool is wired to the correct ledger + require(address(d.pool.ASSET_LEDGER()) == address(d.ledger), "pool ASSET_LEDGER mismatch"); + + // Ledger is wired to pool and adapter + require(d.ledger.POOL() == address(d.pool), "ledger POOL mismatch"); + require(d.ledger.ADAPTER() == address(d.adapter), "ledger ADAPTER mismatch"); + + // Adapter is wired to ledger and pool + require(address(d.adapter.ASSET_LEDGER()) == address(d.ledger), "adapter ASSET_LEDGER mismatch"); + require(address(d.adapter.PROOF_POOL()) == address(d.pool), "adapter PROOF_POOL mismatch"); + + // RYLA roles granted to ledger + RYLA token = RYLA(tokenAddress); + bytes32 minterRole = token.MINTER_ROLE(); + bytes32 burnerRole = token.BURNER_ROLE(); + require(token.hasRole(minterRole, address(d.ledger)), "ledger missing MINTER_ROLE"); + require(token.hasRole(burnerRole, address(d.ledger)), "ledger missing BURNER_ROLE"); + + console.log("Post-deploy verification passed."); + } + + function _writeArtifact(ReleaseResult memory result) internal { + string memory path = vm.envOr( + "MARK_POOL_RELEASE_ARTIFACT_PATH", + string("broadcast/mark-pool-release-latest.json") + ); + string memory root = "pool-release"; + _ensureParentDir(path); + + vm.serializeString(root, "protocol", "MARK"); + vm.serializeString(root, "component", "pool"); + vm.serializeBool(root, "execute", result.execute); + vm.serializeAddress(root, "deployer", result.deployer); + vm.serializeAddress(root, "token", result.token); + vm.serializeAddress(root, "accessManager", result.accessManager); + vm.serializeAddress(root, "pool", result.pool); + vm.serializeAddress(root, "ledger", result.ledger); + vm.serializeAddress(root, "adapter", result.adapter); + vm.serializeUint(root, "chainId", block.chainid); + vm.serializeUint(root, "timestamp", block.timestamp); + string memory json = vm.serializeString(root, "gitCommit", vm.envOr("MARK_GIT_COMMIT", string("unknown"))); + vm.writeJson(json, path); + + console.log("Pool release artifact written:", path); + } + + function _ensureParentDir(string memory path) internal { + bytes memory raw = bytes(path); + uint256 split = type(uint256).max; + for (uint256 i = raw.length; i > 0; i--) { + if (raw[i - 1] == "/") { split = i - 1; break; } + } + if (split == type(uint256).max || split == 0) return; + bytes memory parent = new bytes(split); + for (uint256 j = 0; j < split; j++) { parent[j] = raw[j]; } + vm.createDir(string(parent), true); + } +} diff --git a/contracts/script/ops/settlement/PostDeployMARKSetup.s.sol b/contracts/script/ops/settlement/PostDeployMARKSetup.s.sol index ad7994f..82ad300 100644 --- a/contracts/script/ops/settlement/PostDeployMARKSetup.s.sol +++ b/contracts/script/ops/settlement/PostDeployMARKSetup.s.sol @@ -33,6 +33,7 @@ contract PostDeployMARKSetup is Script { address settlementAttester; bool proofEnabled; bool settlementProductionMode; + bool groth16DirectionEnforcement; } struct Contracts { @@ -86,6 +87,7 @@ contract PostDeployMARKSetup is Script { cfg.settlementOperator = vm.envOr("MARK_SETTLEMENT_OPERATOR", address(0)); cfg.settlementAttester = vm.envOr("MARK_SETTLEMENT_ATTESTER", address(0)); cfg.settlementProductionMode = vm.envOr("MARK_SETTLEMENT_PRODUCTION_MODE", false); + cfg.groth16DirectionEnforcement = vm.envOr("MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT", false); } function _bindContracts(Config memory cfg) internal pure returns (Contracts memory ctr) { @@ -158,6 +160,13 @@ contract PostDeployMARKSetup is Script { if (cfg.verifierAddress != address(0) || cfg.proofEnabled) { ctr.module.setVerifier(cfg.verifierAddress, cfg.proofEnabled); } + if (cfg.verifierAddress != address(0)) { + _tryConfigureGroth16Verifier( + cfg.verifierAddress, + cfg.moduleAddress, + cfg.groth16DirectionEnforcement + ); + } if (cfg.settlementProductionMode) { ctr.module.activateProductionMode(); } @@ -212,4 +221,26 @@ contract PostDeployMARKSetup is Script { function _assertTrue(bool condition, string memory err) internal pure { if (!condition) revert(err); } + + function _tryConfigureGroth16Verifier( + address verifierAddress, + address moduleAddress, + bool directionEnforcement + ) internal { + (bool hasSetModule,) = + verifierAddress.staticcall(abi.encodeWithSelector(bytes4(keccak256("settlementModule()")))); + if (!hasSetModule) return; + + (bool okSetModule,) = + verifierAddress.call(abi.encodeWithSelector(bytes4(keccak256("setSettlementModule(address)")), moduleAddress)); + require(okSetModule, "Groth16 setSettlementModule failed"); + + (bool okSetDirection,) = verifierAddress.call( + abi.encodeWithSelector( + bytes4(keccak256("setDirectionEnforcementEnabled(bool)")), + directionEnforcement + ) + ); + require(okSetDirection, "Groth16 setDirectionEnforcementEnabled failed"); + } } diff --git a/contracts/src/crypto/MerkleTree.sol b/contracts/src/crypto/MerkleTree.sol new file mode 100644 index 0000000..3c5c182 --- /dev/null +++ b/contracts/src/crypto/MerkleTree.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IPoseidonT3} from "../interfaces/IPoseidonT3.sol"; +import {PoolErrors} from "../pool/errors/PoolErrors.sol"; + +library MerkleTree { + uint256 internal constant FIELD_SIZE = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + struct Tree { + uint256 depth; + uint256 nextLeafIndex; + bytes32 root; + address poseidon; + mapping(uint256 => bytes32) filledSubtrees; + mapping(uint256 => bytes32) zeros; + } + + function init(Tree storage tree, uint256 depth, address poseidon) internal { + if (tree.depth != 0) revert PoolErrors.TreeAlreadyInitialized(); + if (depth == 0 || depth > 32) revert PoolErrors.InvalidRoot(); + + tree.depth = depth; + tree.poseidon = poseidon; + + bytes32 current = bytes32(0); + for (uint256 i = 0; i < depth; i++) { + tree.zeros[i] = current; + tree.filledSubtrees[i] = current; + current = _hash(poseidon, current, current); + } + tree.root = current; + } + + function insert(Tree storage tree, bytes32 leaf) internal { + if (tree.depth == 0) revert PoolErrors.TreeNotInitialized(); + if (uint256(leaf) >= FIELD_SIZE) revert PoolErrors.LeafOutOfField(); + + uint256 maxLeaves = uint256(1) << tree.depth; + if (tree.nextLeafIndex >= maxLeaves) revert PoolErrors.TreeFull(); + + uint256 index = tree.nextLeafIndex; + tree.nextLeafIndex++; + + bytes32 current = leaf; + uint256 idx = index; + address poseidon = tree.poseidon; + + for (uint256 i = 0; i < tree.depth; i++) { + if (idx % 2 == 0) { + tree.filledSubtrees[i] = current; + current = _hash(poseidon, current, tree.zeros[i]); + } else { + current = _hash(poseidon, tree.filledSubtrees[i], current); + } + idx >>= 1; + } + + tree.root = current; + } + + function getRoot(Tree storage tree) internal view returns (bytes32) { + return tree.root; + } + + function _hash(address poseidon, bytes32 left, bytes32 right) private view returns (bytes32) { + uint256[2] memory inputs = [uint256(left), uint256(right)]; + return bytes32(IPoseidonT3(poseidon).hash(inputs)); + } +} diff --git a/contracts/src/crypto/ProofUtils.sol b/contracts/src/crypto/ProofUtils.sol new file mode 100644 index 0000000..c2c1a63 --- /dev/null +++ b/contracts/src/crypto/ProofUtils.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +library ProofUtils { + /** + * @dev Converts snarkjs proof format to Solidity verifier format + * Fixes G2 point coordinate ordering incompatibility + */ + function convertProof(uint256[2][2] memory bSnarkjs) + internal + pure + returns (uint256[2][2] memory) + { + // Fix G2 point coordinate ordering + uint256[2][2] memory bFixed = [ + [bSnarkjs[0][1], bSnarkjs[0][0]], // Swap coordinates + [bSnarkjs[1][1], bSnarkjs[1][0]] // Swap coordinates + ]; + + return bFixed; + } +} diff --git a/contracts/src/crypto/generated/PoseidonT3.sol b/contracts/src/crypto/generated/PoseidonT3.sol new file mode 100644 index 0000000..81c3574 --- /dev/null +++ b/contracts/src/crypto/generated/PoseidonT3.sol @@ -0,0 +1,1572 @@ +/// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +library PoseidonT3 { + uint256 constant M00 = + 0x109b7f411ba0e4c9b2b70caf5c36a7b194be7c11ad24378bfedb68592ba8118b; + uint256 constant M01 = + 0x2969f27eed31a480b9c36c764379dbca2cc8fdd1415c3dded62940bcde0bd771; + uint256 constant M02 = + 0x143021ec686a3f330d5f9e654638065ce6cd79e28c5b3753326244ee65a1b1a7; + uint256 constant M10 = + 0x16ed41e13bb9c0c66ae119424fddbcbc9314dc9fdbdeea55d6c64543dc4903e0; + uint256 constant M11 = + 0x2e2419f9ec02ec394c9871c832963dc1b89d743c8c7b964029b2311687b1fe23; + uint256 constant M12 = + 0x176cc029695ad02582a70eff08a6fd99d057e12e58e7d7b6b16cdfabc8ee2911; + + // See here for a simplified implementation: https://github.com/vimwitch/poseidon-solidity/blob/e57becdabb65d99fdc586fe1e1e09e7108202d53/contracts/Poseidon.sol#L40 + // Inspired by: https://github.com/iden3/circomlibjs/blob/v0.0.8/src/poseidon_slow.js + function hash(uint256[2] memory) public pure returns (uint256) { + assembly { + let F := + 21888242871839275222246405745257275088548364400416034343698204186575808495617 + let M20 := + 0x2b90bba00fca0589f617e7dcbfe82e0df706ab640ceb247b791a93b74e36736d + let M21 := + 0x101071f0032379b697315876690f053d148d4e109f5fb065c8aacc55a0f89bfa + let M22 := + 0x19a3fc0a56702bf417ba7fee3802593fa644470307043f7773279cd71d25d5e0 + + // load the inputs from memory + let state1 := + add( + mod(mload(0x80), F), + 0x00f1445235f2148c5986587169fc1bcd887b08d4d00868df5696fff40956e864 + ) + let state2 := + add( + mod(mload(0xa0), F), + 0x08dff3487e8ac99e1f29a058d0fa80b930c728730b7ab36ce879f3890ecf73f5 + ) + let scratch0 := mulmod(state1, state1, F) + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) + scratch0 := mulmod(state2, state2, F) + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) + scratch0 := add( + 0x2f27be690fdaee46c3ce28f7532b13c856c35342c84bda6e20966310fadc01d0, + add( + add( + 15452833169820924772166449970675545095234312153403844297388521437673434406763, + mulmod(state1, M10, F) + ), + mulmod(state2, M20, F) + ) + ) + let scratch1 := + add( + 0x2b2ae1acf68b7b8d2416bebf3d4f6234b763fe04b8043ee48b8327bebca16cf2, + add( + add( + 18674271267752038776579386132900109523609358935013267566297499497165104279117, + mulmod(state1, M11, F) + ), + mulmod(state2, M21, F) + ) + ) + let scratch2 := + add( + 0x0319d062072bef7ecca5eac06f97d4d55952c175ab6b03eae64b44c7dbf11cfa, + add( + add( + 14817777843080276494683266178512808687156649753153012854386334860566696099579, + mulmod(state1, M12, F) + ), + mulmod(state2, M22, F) + ) + ) + let state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := mulmod(scratch1, scratch1, F) + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) + state0 := mulmod(scratch2, scratch2, F) + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) + state0 := add( + 0x28813dcaebaeaa828a376df87af4a63bc8b7bf27ad49c6298ef7b387bf28526d, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x2727673b2ccbc903f181bf38e1c1d40d2033865200c352bc150928adddf9cb78, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x234ec45ca27727c2e74abd2b2a1494cd6efbd43e340587d6b8fb9e31e65cc632, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := mulmod(state1, state1, F) + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) + scratch0 := mulmod(state2, state2, F) + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) + scratch0 := add( + 0x15b52534031ae18f7f862cb2cf7cf760ab10a8150a337b1ccd99ff6e8797d428, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x0dc8fad6d9e4b35f5ed9a3d186b79ce38e0e8a8d1b58b132d701d4eecf68d1f6, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x1bcd95ffc211fbca600f705fad3fb567ea4eb378f62e1fec97805518a47e4d9c, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := mulmod(scratch1, scratch1, F) + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) + state0 := mulmod(scratch2, scratch2, F) + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) + state0 := add( + 0x10520b0ab721cadfe9eff81b016fc34dc76da36c2578937817cb978d069de559, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x1f6d48149b8e7f7d9b257d8ed5fbbaf42932498075fed0ace88a9eb81f5627f6, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x1d9655f652309014d29e00ef35a2089bfff8dc1c816f0dc9ca34bdb5460c8705, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x04df5a56ff95bcafb051f7b1cd43a99ba731ff67e47032058fe3d4185697cc7d, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x0672d995f8fff640151b3d290cedaf148690a10a8c8424a7f6ec282b6e4be828, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x099952b414884454b21200d7ffafdd5f0c9a9dcc06f2708e9fc1d8209b5c75b9, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x052cba2255dfd00c7c483143ba8d469448e43586a9b4cd9183fd0e843a6b9fa6, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x0b8badee690adb8eb0bd74712b7999af82de55707251ad7716077cb93c464ddc, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x119b1590f13307af5a1ee651020c07c749c15d60683a8050b963d0a8e4b2bdd1, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x03150b7cd6d5d17b2529d36be0f67b832c4acfc884ef4ee5ce15be0bfb4a8d09, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x2cc6182c5e14546e3cf1951f173912355374efb83d80898abe69cb317c9ea565, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x005032551e6378c450cfe129a404b3764218cadedac14e2b92d2cd73111bf0f9, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x233237e3289baa34bb147e972ebcb9516469c399fcc069fb88f9da2cc28276b5, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x05c8f4f4ebd4a6e3c980d31674bfbe6323037f21b34ae5a4e80c2d4c24d60280, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x0a7b1db13042d396ba05d818a319f25252bcf35ef3aeed91ee1f09b2590fc65b, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x2a73b71f9b210cf5b14296572c9d32dbf156e2b086ff47dc5df542365a404ec0, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x1ac9b0417abcc9a1935107e9ffc91dc3ec18f2c4dbe7f22976a760bb5c50c460, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x12c0339ae08374823fabb076707ef479269f3e4d6cb104349015ee046dc93fc0, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x0b7475b102a165ad7f5b18db4e1e704f52900aa3253baac68246682e56e9a28e, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x037c2849e191ca3edb1c5e49f6e8b8917c843e379366f2ea32ab3aa88d7f8448, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x05a6811f8556f014e92674661e217e9bd5206c5c93a07dc145fdb176a716346f, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x29a795e7d98028946e947b75d54e9f044076e87a7b2883b47b675ef5f38bd66e, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x20439a0c84b322eb45a3857afc18f5826e8c7382c8a1585c507be199981fd22f, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x2e0ba8d94d9ecf4a94ec2050c7371ff1bb50f27799a84b6d4a2a6f2a0982c887, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x143fd115ce08fb27ca38eb7cce822b4517822cd2109048d2e6d0ddcca17d71c8, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x0c64cbecb1c734b857968dbbdcf813cdf8611659323dbcbfc84323623be9caf1, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x028a305847c683f646fca925c163ff5ae74f348d62c2b670f1426cef9403da53, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x2e4ef510ff0b6fda5fa940ab4c4380f26a6bcb64d89427b824d6755b5db9e30c, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x0081c95bc43384e663d79270c956ce3b8925b4f6d033b078b96384f50579400e, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x2ed5f0c91cbd9749187e2fade687e05ee2491b349c039a0bba8a9f4023a0bb38, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x30509991f88da3504bbf374ed5aae2f03448a22c76234c8c990f01f33a735206, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x1c3f20fd55409a53221b7c4d49a356b9f0a1119fb2067b41a7529094424ec6ad, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x10b4e7f3ab5df003049514459b6e18eec46bb2213e8e131e170887b47ddcb96c, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x2a1982979c3ff7f43ddd543d891c2abddd80f804c077d775039aa3502e43adef, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x1c74ee64f15e1db6feddbead56d6d55dba431ebc396c9af95cad0f1315bd5c91, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x07533ec850ba7f98eab9303cace01b4b9e4f2e8b82708cfa9c2fe45a0ae146a0, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x21576b438e500449a151e4eeaf17b154285c68f42d42c1808a11abf3764c0750, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x2f17c0559b8fe79608ad5ca193d62f10bce8384c815f0906743d6930836d4a9e, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x2d477e3862d07708a79e8aae946170bc9775a4201318474ae665b0b1b7e2730e, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x162f5243967064c390e095577984f291afba2266c38f5abcd89be0f5b2747eab, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x2b4cb233ede9ba48264ecd2c8ae50d1ad7a8596a87f29f8a7777a70092393311, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x2c8fbcb2dd8573dc1dbaf8f4622854776db2eece6d85c4cf4254e7c35e03b07a, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x1d6f347725e4816af2ff453f0cd56b199e1b61e9f601e9ade5e88db870949da9, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x204b0c397f4ebe71ebc2d8b3df5b913df9e6ac02b68d31324cd49af5c4565529, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x0c4cb9dc3c4fd8174f1149b3c63c3c2f9ecb827cd7dc25534ff8fb75bc79c502, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x174ad61a1448c899a25416474f4930301e5c49475279e0639a616ddc45bc7b54, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x1a96177bcf4d8d89f759df4ec2f3cde2eaaa28c177cc0fa13a9816d49a38d2ef, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x066d04b24331d71cd0ef8054bc60c4ff05202c126a233c1a8242ace360b8a30a, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x2a4c4fc6ec0b0cf52195782871c6dd3b381cc65f72e02ad527037a62aa1bd804, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x13ab2d136ccf37d447e9f2e14a7cedc95e727f8446f6d9d7e55afc01219fd649, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x1121552fca26061619d24d843dc82769c1b04fcec26f55194c2e3e869acc6a9a, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x00ef653322b13d6c889bc81715c37d77a6cd267d595c4a8909a5546c7c97cff1, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x0e25483e45a665208b261d8ba74051e6400c776d652595d9845aca35d8a397d3, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x29f536dcb9dd7682245264659e15d88e395ac3d4dde92d8c46448db979eeba89, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x2a56ef9f2c53febadfda33575dbdbd885a124e2780bbea170e456baace0fa5be, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x1c8361c78eb5cf5decfb7a2d17b5c409f2ae2999a46762e8ee416240a8cb9af1, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x151aff5f38b20a0fc0473089aaf0206b83e8e68a764507bfd3d0ab4be74319c5, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x04c6187e41ed881dc1b239c88f7f9d43a9f52fc8c8b6cdd1e76e47615b51f100, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x13b37bd80f4d27fb10d84331f6fb6d534b81c61ed15776449e801b7ddc9c2967, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x01a5c536273c2d9df578bfbd32c17b7a2ce3664c2a52032c9321ceb1c4e8a8e4, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x2ab3561834ca73835ad05f5d7acb950b4a9a2c666b9726da832239065b7c3b02, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x1d4d8ec291e720db200fe6d686c0d613acaf6af4e95d3bf69f7ed516a597b646, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x041294d2cc484d228f5784fe7919fd2bb925351240a04b711514c9c80b65af1d, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x154ac98e01708c611c4fa715991f004898f57939d126e392042971dd90e81fc6, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x0b339d8acca7d4f83eedd84093aef51050b3684c88f8b0b04524563bc6ea4da4, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x0955e49e6610c94254a4f84cfbab344598f0e71eaff4a7dd81ed95b50839c82e, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x06746a6156eba54426b9e22206f15abca9a6f41e6f535c6f3525401ea0654626, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x0f18f5a0ecd1423c496f3820c549c27838e5790e2bd0a196ac917c7ff32077fb, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x04f6eeca1751f7308ac59eff5beb261e4bb563583ede7bc92a738223d6f76e13, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x2b56973364c4c4f5c1a3ec4da3cdce038811eb116fb3e45bc1768d26fc0b3758, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x123769dd49d5b054dcd76b89804b1bcb8e1392b385716a5d83feb65d437f29ef, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x2147b424fc48c80a88ee52b91169aacea989f6446471150994257b2fb01c63e9, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x0fdc1f58548b85701a6c5505ea332a29647e6f34ad4243c2ea54ad897cebe54d, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x12373a8251fea004df68abcf0f7786d4bceff28c5dbbe0c3944f685cc0a0b1f2, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x21e4f4ea5f35f85bad7ea52ff742c9e8a642756b6af44203dd8a1f35c1a90035, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x16243916d69d2ca3dfb4722224d4c462b57366492f45e90d8a81934f1bc3b147, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x1efbe46dd7a578b4f66f9adbc88b4378abc21566e1a0453ca13a4159cac04ac2, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x07ea5e8537cf5dd08886020e23a7f387d468d5525be66f853b672cc96a88969a, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x05a8c4f9968b8aa3b7b478a30f9a5b63650f19a75e7ce11ca9fe16c0b76c00bc, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x20f057712cc21654fbfe59bd345e8dac3f7818c701b9c7882d9d57b72a32e83f, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x04a12ededa9dfd689672f8c67fee31636dcd8e88d01d49019bd90b33eb33db69, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x27e88d8c15f37dcee44f1e5425a51decbd136ce5091a6767e49ec9544ccd101a, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x2feed17b84285ed9b8a5c8c5e95a41f66e096619a7703223176c41ee433de4d1, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x1ed7cc76edf45c7c404241420f729cf394e5942911312a0d6972b8bd53aff2b8, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x15742e99b9bfa323157ff8c586f5660eac6783476144cdcadf2874be45466b1a, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x1aac285387f65e82c895fc6887ddf40577107454c6ec0317284f033f27d0c785, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x25851c3c845d4790f9ddadbdb6057357832e2e7a49775f71ec75a96554d67c77, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x15a5821565cc2ec2ce78457db197edf353b7ebba2c5523370ddccc3d9f146a67, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x2411d57a4813b9980efa7e31a1db5966dcf64f36044277502f15485f28c71727, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x002e6f8d6520cd4713e335b8c0b6d2e647e9a98e12f4cd2558828b5ef6cb4c9b, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x2ff7bc8f4380cde997da00b616b0fcd1af8f0e91e2fe1ed7398834609e0315d2, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x00b9831b948525595ee02724471bcd182e9521f6b7bb68f1e93be4febb0d3cbe, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x0a2f53768b8ebf6a86913b0e57c04e011ca408648a4743a87d77adbf0c9c3512, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x00248156142fd0373a479f91ff239e960f599ff7e94be69b7f2a290305e1198d, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x171d5620b87bfb1328cf8c02ab3f0c9a397196aa6a542c2350eb512a2b2bcda9, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x170a4f55536f7dc970087c7c10d6fad760c952172dd54dd99d1045e4ec34a808, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x29aba33f799fe66c2ef3134aea04336ecc37e38c1cd211ba482eca17e2dbfae1, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x1e9bc179a4fdd758fdd1bb1945088d47e70d114a03f6a0e8b5ba650369e64973, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x1dd269799b660fad58f7f4892dfb0b5afeaad869a9c4b44f9c9e1c43bdaf8f09, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x22cdbc8b70117ad1401181d02e15459e7ccd426fe869c7c95d1dd2cb0f24af38, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x0ef042e454771c533a9f57a55c503fcefd3150f52ed94a7cd5ba93b9c7dacefd, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x11609e06ad6c8fe2f287f3036037e8851318e8b08a0359a03b304ffca62e8284, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x1166d9e554616dba9e753eea427c17b7fecd58c076dfe42708b08f5b783aa9af, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x2de52989431a859593413026354413db177fbf4cd2ac0b56f855a888357ee466, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x3006eb4ffc7a85819a6da492f3a8ac1df51aee5b17b8e89d74bf01cf5f71e9ad, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x2af41fbb61ba8a80fdcf6fff9e3f6f422993fe8f0a4639f962344c8225145086, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x119e684de476155fe5a6b41a8ebc85db8718ab27889e85e781b214bace4827c3, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x1835b786e2e8925e188bea59ae363537b51248c23828f047cff784b97b3fd800, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x28201a34c594dfa34d794996c6433a20d152bac2a7905c926c40e285ab32eeb6, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x083efd7a27d1751094e80fefaf78b000864c82eb571187724a761f88c22cc4e7, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x0b6f88a3577199526158e61ceea27be811c16df7774dd8519e079564f61fd13b, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x0ec868e6d15e51d9644f66e1d6471a94589511ca00d29e1014390e6ee4254f5b, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x2af33e3f866771271ac0c9b3ed2e1142ecd3e74b939cd40d00d937ab84c98591, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x0b520211f904b5e7d09b5d961c6ace7734568c547dd6858b364ce5e47951f178, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x0b2d722d0919a1aad8db58f10062a92ea0c56ac4270e822cca228620188a1d40, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x1f790d4d7f8cf094d980ceb37c2453e957b54a9991ca38bbe0061d1ed6e562d4, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x0171eb95dfbf7d1eaea97cd385f780150885c16235a2a6a8da92ceb01e504233, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x0c2d0e3b5fd57549329bf6885da66b9b790b40defd2c8650762305381b168873, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x1162fb28689c27154e5a8228b4e72b377cbcafa589e283c35d3803054407a18d, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x2f1459b65dee441b64ad386a91e8310f282c5a92a89e19921623ef8249711bc0, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x1e6ff3216b688c3d996d74367d5cd4c1bc489d46754eb712c243f70d1b53cfbb, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x01ca8be73832b8d0681487d27d157802d741a6f36cdc2a0576881f9326478875, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x1f7735706ffe9fc586f976d5bdf223dc680286080b10cea00b9b5de315f9650e, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x2522b60f4ea3307640a0c2dce041fba921ac10a3d5f096ef4745ca838285f019, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x23f0bee001b1029d5255075ddc957f833418cad4f52b6c3f8ce16c235572575b, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x2bc1ae8b8ddbb81fcaac2d44555ed5685d142633e9df905f66d9401093082d59, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x0f9406b8296564a37304507b8dba3ed162371273a07b1fc98011fcd6ad72205f, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x2360a8eb0cc7defa67b72998de90714e17e75b174a52ee4acb126c8cd995f0a8, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x15871a5cddead976804c803cbaef255eb4815a5e96df8b006dcbbc2767f88948, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x193a56766998ee9e0a8652dd2f3b1da0362f4f54f72379544f957ccdeefb420f, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x2a394a43934f86982f9be56ff4fab1703b2e63c8ad334834e4309805e777ae0f, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x1859954cfeb8695f3e8b635dcb345192892cd11223443ba7b4166e8876c0d142, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x04e1181763050e58013444dbcb99f1902b11bc25d90bbdca408d3819f4fed32b, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x0fdb253dee83869d40c335ea64de8c5bb10eb82db08b5e8b1f5e5552bfd05f23, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x058cbe8a9a5027bdaa4efb623adead6275f08686f1c08984a9d7c5bae9b4f1c0, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x1382edce9971e186497eadb1aeb1f52b23b4b83bef023ab0d15228b4cceca59a, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x03464990f045c6ee0819ca51fd11b0be7f61b8eb99f14b77e1e6634601d9e8b5, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x23f7bfc8720dc296fff33b41f98ff83c6fcab4605db2eb5aaa5bc137aeb70a58, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x0a59a158e3eec2117e6e94e7f0e9decf18c3ffd5e1531a9219636158bbaf62f2, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x06ec54c80381c052b58bf23b312ffd3ce2c4eba065420af8f4c23ed0075fd07b, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x118872dc832e0eb5476b56648e867ec8b09340f7a7bcb1b4962f0ff9ed1f9d01, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x13d69fa127d834165ad5c7cba7ad59ed52e0b0f0e42d7fea95e1906b520921b1, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x169a177f63ea681270b1c6877a73d21bde143942fb71dc55fd8a49f19f10c77b, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x04ef51591c6ead97ef42f287adce40d93abeb032b922f66ffb7e9a5a7450544d, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x256e175a1dc079390ecd7ca703fb2e3b19ec61805d4f03ced5f45ee6dd0f69ec, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x30102d28636abd5fe5f2af412ff6004f75cc360d3205dd2da002813d3e2ceeb2, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x10998e42dfcd3bbf1c0714bc73eb1bf40443a3fa99bef4a31fd31be182fcc792, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x193edd8e9fcf3d7625fa7d24b598a1d89f3362eaf4d582efecad76f879e36860, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x18168afd34f2d915d0368ce80b7b3347d1c7a561ce611425f2664d7aa51f0b5d, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x29383c01ebd3b6ab0c017656ebe658b6a328ec77bc33626e29e2e95b33ea6111, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x10646d2f2603de39a1f4ae5e7771a64a702db6e86fb76ab600bf573f9010c711, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x0beb5e07d1b27145f575f1395a55bf132f90c25b40da7b3864d0242dcb1117fb, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x16d685252078c133dc0d3ecad62b5c8830f95bb2e54b59abdffbf018d96fa336, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x0a6abd1d833938f33c74154e0404b4b40a555bbbec21ddfafd672dd62047f01a, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x1a679f5d36eb7b5c8ea12a4c2dedc8feb12dffeec450317270a6f19b34cf1860, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x0980fb233bd456c23974d50e0ebfde4726a423eada4e8f6ffbc7592e3f1b93d6, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x161b42232e61b84cbf1810af93a38fc0cece3d5628c9282003ebacb5c312c72b, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x0ada10a90c7f0520950f7d47a60d5e6a493f09787f1564e5d09203db47de1a0b, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x1a730d372310ba82320345a29ac4238ed3f07a8a2b4e121bb50ddb9af407f451, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x2c8120f268ef054f817064c369dda7ea908377feaba5c4dffbda10ef58e8c556, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x1c7c8824f758753fa57c00789c684217b930e95313bcb73e6e7b8649a4968f70, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x2cd9ed31f5f8691c8e39e4077a74faa0f400ad8b491eb3f7b47b27fa3fd1cf77, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x23ff4f9d46813457cf60d92f57618399a5e022ac321ca550854ae23918a22eea, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x09945a5d147a4f66ceece6405dddd9d0af5a2c5103529407dff1ea58f180426d, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x188d9c528025d4c2b67660c6b771b90f7c7da6eaa29d3f268a6dd223ec6fc630, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x3050e37996596b7f81f68311431d8734dba7d926d3633595e0c0d8ddf4f0f47f, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x15af1169396830a91600ca8102c35c426ceae5461e3f95d89d829518d30afd78, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x1da6d09885432ea9a06d9f37f873d985dae933e351466b2904284da3320d8acc, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := add( + 0x2796ea90d269af29f5f8acf33921124e4e4fad3dbe658945e546ee411ddaa9cb, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x202d7dd1da0f6b4b0325c8b3307742f01e15612ec8e9304a7cb0319e01d32d60, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x096d6790d05bb759156a952ba263d672a2d7f9c788f4c831a29dace4c0f8be5f, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := add( + 0x054efa1f65b0fce283808965275d877b438da23ce5b13e1963798cb1447d25a4, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x1b162f83d917e93edb3308c29802deb9d8aa690113b2e14864ccf6e18e4165f1, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x21e5241e12564dd6fd9f1cdd2a0de39eedfefc1466cc568ec5ceb745a0506edc, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := mulmod(scratch1, scratch1, F) + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) + state0 := mulmod(scratch2, scratch2, F) + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) + state0 := add( + 0x1cfb5662e8cf5ac9226a80ee17b36abecb73ab5f87e161927b4349e10e4bdf08, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x0f21177e302a771bbae6d8d1ecb373b62c99af346220ac0129c53f666eb24100, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x1671522374606992affb0dd7f71b12bec4236aede6290546bcef7e1f515c2320, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := mulmod(state1, state1, F) + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) + scratch0 := mulmod(state2, state2, F) + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) + scratch0 := add( + 0x0fa3ec5b9488259c2eb4cf24501bfad9be2ec9e42c5cc8ccd419d2a692cad870, + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ) + ) + scratch1 := add( + 0x193c0e04e0bd298357cb266c1506080ed36edce85c648cc085e8c57b1ab54bba, + add( + add(mulmod(state0, M01, F), mulmod(state1, M11, F)), + mulmod(state2, M21, F) + ) + ) + scratch2 := add( + 0x102adf8ef74735a27e9128306dcbc3c99f6f7291cd406578ce14ea2adaba68f8, + add( + add(mulmod(state0, M02, F), mulmod(state1, M12, F)), + mulmod(state2, M22, F) + ) + ) + state0 := mulmod(scratch0, scratch0, F) + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) + state0 := mulmod(scratch1, scratch1, F) + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) + state0 := mulmod(scratch2, scratch2, F) + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) + state0 := add( + 0x0fe0af7858e49859e2a54d6f1ad945b1316aa24bfbdd23ae40a6d0cb70c3eab1, + add( + add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), + mulmod(scratch2, M20, F) + ) + ) + state1 := add( + 0x216f6717bbc7dedb08536a2220843f4e2da5f1daa9ebdefde8a5ea7344798d22, + add( + add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), + mulmod(scratch2, M21, F) + ) + ) + state2 := add( + 0x1da55cc900f0d21f4a3e694391918a1b3c23b2ac773c6b3ef88e2e4228325161, + add( + add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), + mulmod(scratch2, M22, F) + ) + ) + scratch0 := mulmod(state0, state0, F) + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) + scratch0 := mulmod(state1, state1, F) + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) + scratch0 := mulmod(state2, state2, F) + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) + + mstore( + 0x0, + mod( + add( + add(mulmod(state0, M00, F), mulmod(state1, M10, F)), + mulmod(state2, M20, F) + ), + F + ) + ) + + return(0, 0x20) + } + } +} diff --git a/contracts/src/interfaces/ICreditLedger.sol b/contracts/src/interfaces/ICreditLedger.sol new file mode 100644 index 0000000..832987b --- /dev/null +++ b/contracts/src/interfaces/ICreditLedger.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +interface ICreditLedger { + function credit(address to, uint256 amount) external; + function debit(address from, uint256 amount) external; + function creditBalanceOf(address account) external view returns (uint256); + function totalCreditsMinted() external view returns (uint256); + function totalCreditsBurned() external view returns (uint256); + function totalCreditsOutstanding() external view returns (uint256); + function maxCredits() external view returns (uint256); +} diff --git a/contracts/src/interfaces/IPoolBridge.sol b/contracts/src/interfaces/IPoolBridge.sol new file mode 100644 index 0000000..fcddc4f --- /dev/null +++ b/contracts/src/interfaces/IPoolBridge.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Minimal Pool surface required by BridgeAdapter. +interface IPoolBridge { + function bridgeOut( + bytes32 merkleRoot, + bytes32[2] calldata nullifiers, + bytes32[2] calldata outCommitments, + uint256 fee, + address relayer, + uint256 dstChainId, + uint256[2] calldata a, + uint256[2][2] calldata bSnarkjs, + uint256[2] calldata c + ) external; + + function bridgeIn(uint256 srcChainId, bytes32[2] calldata outCommitments) + external; +} diff --git a/contracts/src/interfaces/IPoolNullifier.sol b/contracts/src/interfaces/IPoolNullifier.sol new file mode 100644 index 0000000..7c5df6e --- /dev/null +++ b/contracts/src/interfaces/IPoolNullifier.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Minimal Pool surface required by WithdrawAdapter. +interface IPoolNullifier { + function isNullifierUsedGlobal(bytes32 nullifier) + external + view + returns (bool); + + function nullifierWithdrawBinding(bytes32 nullifier) + external + view + returns (bytes32); + + function computeWithdrawBindingHash( + address owner, + address recipient, + uint256 amount + ) external view returns (bytes32); +} diff --git a/contracts/src/interfaces/IPoseidonT3.sol b/contracts/src/interfaces/IPoseidonT3.sol new file mode 100644 index 0000000..394cdef --- /dev/null +++ b/contracts/src/interfaces/IPoseidonT3.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @title IPoseidonT3 +/// @notice Interface for a deployed Poseidon T3 hash contract (BN254, t=3, 2 inputs). +/// @dev Compatible with Semaphore's deployment at 0xB43122Ecb241DD50062641f089876679fd06599a +/// (same address on Ethereum, OP Mainnet, OP Sepolia, Arbitrum, Base, and others). +interface IPoseidonT3 { + function hash(uint256[2] memory inputs) external pure returns (uint256); +} diff --git a/contracts/src/interfaces/IVerifier.sol b/contracts/src/interfaces/IVerifier.sol new file mode 100644 index 0000000..41ba676 --- /dev/null +++ b/contracts/src/interfaces/IVerifier.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +interface IVerifier { + function verifyProof( + uint256[2] calldata a, + uint256[2][2] calldata b, + uint256[2] calldata c, + uint256[13] calldata input + ) external view returns (bool); +} diff --git a/contracts/src/pool/MARKPool.sol b/contracts/src/pool/MARKPool.sol new file mode 100644 index 0000000..5f0bd61 --- /dev/null +++ b/contracts/src/pool/MARKPool.sol @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; +import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; +import {ICreditLedger} from "../interfaces/ICreditLedger.sol"; +import {IVerifier} from "../interfaces/IVerifier.sol"; +import {ProofUtils} from "../crypto/ProofUtils.sol"; +import {MerkleTree} from "../crypto/MerkleTree.sol"; +import {PoolFeePolicy} from "./PoolFeePolicy.sol"; +import {PoolPublicInputs} from "./PoolPublicInputs.sol"; +import {PoolValidation} from "./PoolValidation.sol"; +import {PoolErrors} from "./errors/PoolErrors.sol"; + +/// @title MARKPool +/// @notice ZK UTXO pool for private RYLA transfers with Merkle tree membership proofs. +/// @dev Withdrawal flow (burn-to-claim model): +/// Notes enter the pool via transact() or bridgeIn() — both require a valid ZK proof +/// or restricted access respectively. Notes do NOT deposit tokens into the pool; +/// the pool is a nullifier registry backed by a Merkle tree. +/// +/// To withdraw RYLA, a note owner calls transactWithWithdrawBinding(), which: +/// 1. Verifies the ZK proof (Merkle membership + balance equation) +/// 2. Marks nullifiers as spent (prevents double-spend) +/// 3. Records a withdraw binding: hash(owner, recipient, amount) per nullifier +/// 4. Does NOT transfer any tokens +/// +/// The note owner then calls MARKWithdrawAdapter.withdrawWithSig(), which: +/// 1. Verifies the withdraw binding matches the pool's recorded binding +/// 2. Verifies owner + intent signer signatures (EIP-191 personal_sign) +/// 3. Calls RYLACreditLedger.debit(owner, amount) — burns RYLA from owner +/// +/// The owner must hold RYLA tokens equal to the withdrawal amount and approve +/// RYLACreditLedger before calling withdrawWithSig. The ZK proof proves the owner +/// controls the note; the RYLA burn proves they are redeeming it. +/// +/// Relayer fees are credited via ASSET_LEDGER.credit(relayer, fee) during transact(). +/// ASSET_LEDGER must be set via setAssetLedger() after deployment. +contract MARKPool is ReentrancyGuard, AccessManaged, Pausable, PoolErrors { + using MerkleTree for MerkleTree.Tree; + + struct VerifyContext { + bytes32 merkleRoot; + uint256 dstChainId; + uint256 protocolEpoch; + uint256 fee; + address relayer; + address withdrawOwner; + address withdrawRecipient; + uint256 withdrawAmount; + } + + uint8 public constant PROOF_TYPE_TRANSFER = 1; + bytes32 public constant WITHDRAW_BINDING_DOMAIN = keccak256("MARKPool.WithdrawBinding.v1"); + uint256 public constant MAX_ALLOWED_ROOT_AGE = 30 days; + uint256 public constant MAX_FEE_BURN_BPS = 10_000; + uint256 public constant MAX_MIN_FEE = type(uint64).max; + + ICreditLedger public ASSET_LEDGER; + bool public withdrawalsPaused; + uint256 public maxRootAge; + uint256 public feeBurnBps; + uint256 public minFee; + uint256 public protocolEpoch; + address public bridgeOutEntrypoint; + + MerkleTree.Tree private tree; + mapping(uint8 => address) private verifiers; + mapping(uint8 => bool) public proofTypeEnabled; + mapping(bytes32 => bool) private usedNullifiersGlobal; + mapping(bytes32 => bool) public processedBridgeMessages; + mapping(bytes32 => bytes32) public nullifierWithdrawBinding; + mapping(bytes32 => bool) public knownRoots; + mapping(bytes32 => uint256) public rootTimestamps; + mapping(uint256 => bytes32) private rootQueue; + uint256 public rootQueueHead; + uint256 public rootQueueTail; + + event WithdrawalsPaused(address indexed account); + event WithdrawalsUnpaused(address indexed account); + event VerifierSet(uint8 indexed proofType, address indexed verifier); + event ProofTypeEnabled(uint8 indexed proofType, bool enabled); + event RootAdded(bytes32 indexed root); + event MaxRootAgeSet(uint256 maxRootAge); + event FeeBurnBpsSet(uint256 feeBurnBps); + event MinFeeSet(uint256 minFee); + event ProtocolEpochSet(uint256 previousProtocolEpoch, uint256 newProtocolEpoch); + event NoteSpent(bytes32 indexed nullifier); + event WithdrawBindingRecorded( + bytes32 indexed nullifier, + bytes32 indexed bindingHash, + address indexed owner, + address recipient, + uint256 amount + ); + event NoteCreated(bytes32 indexed commitment); + event FeePaid(address indexed relayer, uint256 fee); + event FeeBurned(uint256 amount); + event AssetLedgerSet(address indexed assetLedger); + event BridgeOutEntrypointSet(address indexed entrypoint); + event RootPruned(bytes32 indexed root); + event BridgeOut( + uint256 indexed dstChainId, + bytes32 indexed commitment0, + bytes32 indexed commitment1, + uint256 fee, + address relayer + ); + event BridgeIn( + uint256 indexed srcChainId, + bytes32 indexed commitment0, + bytes32 indexed commitment1 + ); + + constructor(address initialAuthority, address _verifier, address _poseidon) + AccessManaged(initialAuthority) + { + if (_verifier == address(0)) revert InvalidVerifier(); + if (_verifier.code.length == 0) revert VerifierMustBeContract(); + if (_poseidon == address(0)) revert InvalidPoseidon(); + if (_poseidon.code.length == 0) revert PoseidonMustBeContract(); + + verifiers[PROOF_TYPE_TRANSFER] = _verifier; + proofTypeEnabled[PROOF_TYPE_TRANSFER] = true; + + tree.init(20, _poseidon); + bytes32 initialRoot = tree.getRoot(); + knownRoots[initialRoot] = true; + rootTimestamps[initialRoot] = block.timestamp; + rootQueue[0] = initialRoot; + rootQueueHead = 0; + rootQueueTail = 1; + + emit VerifierSet(PROOF_TYPE_TRANSFER, _verifier); + emit ProofTypeEnabled(PROOF_TYPE_TRANSFER, true); + emit RootAdded(initialRoot); + } + + modifier whenWithdrawalsNotPaused() { + if (withdrawalsPaused) revert WithdrawalsArePaused(); + _; + } + + function pause() external restricted { + if (paused()) revert AlreadyPaused(); + _pause(); + // Also pause withdrawals when the contract is paused. + // Note: unpause() does NOT automatically restore withdrawals — call unpauseWithdrawals() explicitly. + if (!withdrawalsPaused) { + withdrawalsPaused = true; + emit WithdrawalsPaused(msg.sender); + } + } + + function unpause() external restricted { + if (!paused()) revert NotPaused(); + _unpause(); + } + + function pauseWithdrawals() external restricted { + if (withdrawalsPaused) revert WithdrawalsAlreadyPaused(); + withdrawalsPaused = true; + emit WithdrawalsPaused(msg.sender); + } + + function unpauseWithdrawals() external restricted { + if (!withdrawalsPaused) revert WithdrawalsNotPaused(); + withdrawalsPaused = false; + emit WithdrawalsUnpaused(msg.sender); + } + + function verifier() external view returns (address) { + return verifiers[PROOF_TYPE_TRANSFER]; + } + + function verifierForType(uint8 proofType) external view returns (address) { + return verifiers[proofType]; + } + + function setVerifier(uint8 proofType, address verifierAddr) external restricted { + if (proofType == 0) revert InvalidProofType(); + if (verifierAddr == address(0)) revert InvalidVerifier(); + if (verifierAddr.code.length == 0) revert VerifierMustBeContract(); + if (verifiers[proofType] == verifierAddr) revert NoStateChange(); + if (proofTypeEnabled[proofType] && !withdrawalsPaused) revert WithdrawalsNotPaused(); + verifiers[proofType] = verifierAddr; + emit VerifierSet(proofType, verifierAddr); + } + + function setProofTypeEnabled(uint8 proofType, bool enabled) external restricted { + if (proofType == 0) revert InvalidProofType(); + if (proofTypeEnabled[proofType] == enabled) revert NoStateChange(); + if (enabled) { + if (verifiers[proofType] == address(0)) revert VerifierNotConfigured(); + if (!withdrawalsPaused) revert WithdrawalsNotPaused(); + } + proofTypeEnabled[proofType] = enabled; + emit ProofTypeEnabled(proofType, enabled); + } + + function emergencyDisableProofType(uint8 proofType) external restricted { + if (proofType == 0) revert InvalidProofType(); + proofTypeEnabled[proofType] = false; + emit ProofTypeEnabled(proofType, false); + } + + function setMaxRootAge(uint256 newMaxRootAge) external restricted { + if (newMaxRootAge > MAX_ALLOWED_ROOT_AGE) revert RootAgeTooLarge(); + if (newMaxRootAge == maxRootAge) revert NoStateChange(); + bool tightening = (maxRootAge == 0 && newMaxRootAge != 0) + || (maxRootAge != 0 && newMaxRootAge != 0 && newMaxRootAge < maxRootAge); + if (tightening && !withdrawalsPaused) revert WithdrawalsNotPaused(); + maxRootAge = newMaxRootAge; + emit MaxRootAgeSet(newMaxRootAge); + } + + function setFeeBurnBps(uint256 newFeeBurnBps) external restricted { + if (newFeeBurnBps > MAX_FEE_BURN_BPS) revert InvalidBurnBps(); + if (newFeeBurnBps == feeBurnBps) revert NoStateChange(); + feeBurnBps = newFeeBurnBps; + emit FeeBurnBpsSet(newFeeBurnBps); + } + + function setMinFee(uint256 newMinFee) external restricted { + // Circuit enforces percentage fee; runtime floor is a narrow safety guard only. + // Allowed values are intentionally constrained to 0/1 credit unit. + if (newMinFee > 1) revert MinFeeTooLarge(); + if (newMinFee != minFee) { + minFee = newMinFee; + emit MinFeeSet(newMinFee); + } + } + + function setProtocolEpoch(uint256 newProtocolEpoch) external restricted { + if (newProtocolEpoch > type(uint32).max) revert EpochExceedsCircuitRange(); + uint256 currentProtocolEpoch = protocolEpoch; + if (newProtocolEpoch == currentProtocolEpoch) revert NoStateChange(); + if (newProtocolEpoch < currentProtocolEpoch) revert EpochCanOnlyIncrease(); + if (!withdrawalsPaused) revert WithdrawalsNotPaused(); + protocolEpoch = newProtocolEpoch; + emit ProtocolEpochSet(currentProtocolEpoch, newProtocolEpoch); + } + + function setBridgeOutEntrypoint(address entrypoint) external restricted { + if (entrypoint != address(0) && entrypoint.code.length == 0) revert EntrypointMustBeContract(); + if (entrypoint == bridgeOutEntrypoint) revert NoStateChange(); + bool tightening = (bridgeOutEntrypoint == address(0) && entrypoint != address(0)) + || (bridgeOutEntrypoint != address(0) && entrypoint != address(0) && bridgeOutEntrypoint != entrypoint); + if (tightening && !withdrawalsPaused) revert WithdrawalsNotPaused(); + bridgeOutEntrypoint = entrypoint; + emit BridgeOutEntrypointSet(entrypoint); + } + + /// @notice Sets the asset ledger used for relayer fee credits. Can only be set once. + /// @dev Separated from the constructor to break the circular dependency between + /// MARKPool and RYLACreditLedger (each needs the other's address at construction). + function setAssetLedger(address ledgerAddress) external restricted { + if (address(ASSET_LEDGER) != address(0)) revert NoStateChange(); + if (ledgerAddress == address(0)) revert InvalidAssetLedger(); + if (ledgerAddress.code.length == 0) revert AssetLedgerMustBeContract(); + ASSET_LEDGER = ICreditLedger(ledgerAddress); + emit AssetLedgerSet(ledgerAddress); + } + + function pruneRoots(uint256 maxToPrune) external returns (uint256 pruned) { + return _pruneRoots(maxToPrune); + } + + function _pruneRoots(uint256 maxToPrune) internal returns (uint256 pruned) { + if (maxRootAge == 0 || maxToPrune == 0) return 0; + // slither-disable-next-line timestamp + if (block.timestamp <= maxRootAge) return 0; + + uint256 cutoff = block.timestamp - maxRootAge; + uint256 head = rootQueueHead; + uint256 tail = rootQueueTail; + + // Keep at least one root (the newest) to preserve transaction liveness. + while (head + 1 < tail && pruned < maxToPrune) { + bytes32 root = rootQueue[head]; + // slither-disable-next-line timestamp + if (rootTimestamps[root] > cutoff) break; + delete knownRoots[root]; + delete rootTimestamps[root]; + delete rootQueue[head]; + emit RootPruned(root); + head++; + pruned++; + } + + if (head != rootQueueHead) { + rootQueueHead = head; + } + } + + function getMerkleRoot() external view returns (bytes32) { + return tree.getRoot(); + } + + function isRootUsable(bytes32 root) public view returns (bool) { + if (!knownRoots[root]) return false; + // Always allow the latest root so the system can advance even in low activity periods. + if (root == tree.getRoot()) return true; + if (maxRootAge == 0) return true; + // slither-disable-next-line timestamp + return block.timestamp <= rootTimestamps[root] + maxRootAge; + } + + function isNullifierUsedGlobal(bytes32 nullifier) external view returns (bool) { + return usedNullifiersGlobal[nullifier]; + } + + /// @notice Executes a private transfer. Permissionless — the ZK proof is the authorization. + /// @dev Any caller may submit a valid proof. Access is gated by proof validity, not by role. + /// The proof binds to merkleRoot, chainId, protocolEpoch, nullifiers, and outCommitments, + /// preventing cross-chain, cross-epoch, and replay attacks without requiring a privileged caller. + function transact( + bytes32 merkleRoot, + bytes32[2] calldata nullifiers, + bytes32[2] calldata outCommitments, + uint256 fee, + address relayer, + uint256[2] calldata a, + uint256[2][2] calldata bSnarkjs, + uint256[2] calldata c + ) external nonReentrant whenNotPaused whenWithdrawalsNotPaused { + _verifyAndConsume(merkleRoot, block.chainid, nullifiers, outCommitments, fee, relayer, address(0), address(0), 0, a, bSnarkjs, c); + _insertCommitmentsValidated(outCommitments); + _applyFee(fee, relayer); + } + + /// @notice Executes a private transfer with a withdraw binding. Permissionless — the ZK proof is the authorization. + /// @dev Identical access model to transact. The withdraw binding additionally commits the proof + /// to a specific (withdrawOwner, withdrawRecipient, withdrawAmount) tuple, enabling + /// the WithdrawAdapter to claim the output without a second ZK proof. + function transactWithWithdrawBinding( + bytes32 merkleRoot, + bytes32[2] calldata nullifiers, + bytes32[2] calldata outCommitments, + uint256 fee, + address relayer, + address withdrawOwner, + address withdrawRecipient, + uint256 withdrawAmount, + uint256[2] calldata a, + uint256[2][2] calldata bSnarkjs, + uint256[2] calldata c + ) external nonReentrant whenNotPaused whenWithdrawalsNotPaused { + if (withdrawAmount == 0) revert InvalidWithdrawAmount(); + _verifyAndConsume(merkleRoot, block.chainid, nullifiers, outCommitments, fee, relayer, withdrawOwner, withdrawRecipient, withdrawAmount, a, bSnarkjs, c); + _insertCommitmentsValidated(outCommitments); + _applyFee(fee, relayer); + _recordWithdrawBinding(nullifiers, withdrawOwner, withdrawRecipient, withdrawAmount); + } + + /// @notice Initiates a cross-chain transfer. Restricted to the configured bridgeOutEntrypoint. + /// @dev The proof binds to dstChainId instead of block.chainid, committing the output notes + /// to the destination chain. Only the bridgeOutEntrypoint may call this — not permissionless. + function bridgeOut( + bytes32 merkleRoot, + bytes32[2] calldata nullifiers, + bytes32[2] calldata outCommitments, + uint256 fee, + address relayer, + uint256 dstChainId, + uint256[2] calldata a, + uint256[2][2] calldata bSnarkjs, + uint256[2] calldata c + ) external nonReentrant whenNotPaused whenWithdrawalsNotPaused { + address configuredEntrypoint = bridgeOutEntrypoint; + if (configuredEntrypoint == address(0)) revert BridgeOutDisabled(); + if (msg.sender != configuredEntrypoint) revert UnauthorizedBridgeOutCaller(); + if (dstChainId == 0) revert InvalidDestination(); + if (dstChainId == block.chainid) revert DestinationIsSource(); + _verifyAndConsume(merkleRoot, dstChainId, nullifiers, outCommitments, fee, relayer, address(0), address(0), 0, a, bSnarkjs, c); + _applyFee(fee, relayer); + emit BridgeOut(dstChainId, outCommitments[0], outCommitments[1], fee, relayer); + } + + /// @notice Inserts incoming cross-chain commitments into the Merkle tree. Restricted. + /// @dev Called by the bridge relay after a bridgeOut on the source chain is confirmed. + /// Restricted to prevent unauthorized note insertion. + /// `messageId` is a unique identifier for the source-chain message (e.g. the + /// SuperchainTokenBridge message hash) and prevents duplicate delivery. + function bridgeIn(uint256 srcChainId, bytes32 messageId, bytes32[2] calldata outCommitments) + external + restricted + whenNotPaused + { + if (srcChainId == 0) revert InvalidSource(); + if (srcChainId == block.chainid) revert SourceIsDestination(); + if (messageId == bytes32(0)) revert InvalidMessageId(); + if (processedBridgeMessages[messageId]) revert BridgeMessageAlreadyProcessed(); + processedBridgeMessages[messageId] = true; + PoolValidation.requireCommitmentsValid(outCommitments); + _insertCommitmentsValidated(outCommitments); + emit BridgeIn(srcChainId, outCommitments[0], outCommitments[1]); + } + + function _verifyAndConsume( + bytes32 merkleRoot, + uint256 dstChainId, + bytes32[2] calldata nullifiers, + bytes32[2] calldata outCommitments, + uint256 fee, + address relayer, + address withdrawOwner, + address withdrawRecipient, + uint256 withdrawAmount, + uint256[2] calldata a, + uint256[2][2] calldata bSnarkjs, + uint256[2] calldata c + ) internal { + VerifyContext memory ctx = VerifyContext({ + merkleRoot: merkleRoot, + dstChainId: dstChainId, + protocolEpoch: protocolEpoch, + fee: fee, + relayer: relayer, + withdrawOwner: withdrawOwner, + withdrawRecipient: withdrawRecipient, + withdrawAmount: withdrawAmount + }); + + PoolValidation.requireDestEpochAndFeeWithinCircuitRange(ctx.dstChainId, ctx.protocolEpoch, ctx.fee); + PoolValidation.requireWithdrawBindingWithinCircuitRange(ctx.withdrawOwner, ctx.withdrawRecipient, ctx.withdrawAmount); + PoolValidation.requireRootWithinCircuitRange(ctx.merkleRoot); + if (!proofTypeEnabled[PROOF_TYPE_TRANSFER]) revert ProofTypeDisabled(); + if (!knownRoots[ctx.merkleRoot]) revert UnknownRoot(); + if (!isRootUsable(ctx.merkleRoot)) revert RootExpired(); + if (ctx.fee < minFee) revert FeeTooLow(); + + address verifierAddr = verifiers[PROOF_TYPE_TRANSFER]; + if (verifierAddr == address(0)) revert VerifierNotConfigured(); + + PoolValidation.requireNullifiersFresh(nullifiers, usedNullifiersGlobal); + PoolValidation.requireCommitmentsValid(outCommitments); + + uint256[13] memory publicInputs = _buildPublicInputs(ctx, nullifiers, outCommitments); + if (!_verifyProof(IVerifier(verifierAddr), publicInputs, a, bSnarkjs, c)) revert InvalidProof(); + + for (uint256 i = 0; i < nullifiers.length; i++) { + usedNullifiersGlobal[nullifiers[i]] = true; + emit NoteSpent(nullifiers[i]); + } + } + + + function _insertCommitmentsValidated(bytes32[2] calldata outCommitments) internal { + uint256 tail = rootQueueTail; + for (uint256 i = 0; i < outCommitments.length; i++) { + tree.insert(outCommitments[i]); + bytes32 newRoot = tree.getRoot(); + knownRoots[newRoot] = true; + rootTimestamps[newRoot] = block.timestamp; + rootQueue[tail] = newRoot; + tail++; + emit NoteCreated(outCommitments[i]); + emit RootAdded(newRoot); + } + { + rootQueueTail = tail; + } + } + + + function _applyFee(uint256 fee, address relayer) internal { + if (fee == 0) return; + (uint256 burnAmount, uint256 relayerAmount) = PoolFeePolicy.split(fee, feeBurnBps, MAX_FEE_BURN_BPS); + // "Burn" is applied by withholding mint; total supply increases only by relayerAmount. + if (relayerAmount > 0) { + if (relayer == address(0)) revert InvalidRelayer(); + if (address(ASSET_LEDGER) == address(0)) revert InvalidAssetLedger(); + ASSET_LEDGER.credit(relayer, relayerAmount); + emit FeePaid(relayer, relayerAmount); + } + if (burnAmount > 0) { + emit FeeBurned(burnAmount); + } + } + + function _verifyProof( + IVerifier selectedVerifier, + uint256[13] memory publicInputs, + uint256[2] memory a, + uint256[2][2] memory bSnarkjs, + uint256[2] memory c + ) internal view returns (bool) { + uint256[2][2] memory bFixed = ProofUtils.convertProof(bSnarkjs); + return selectedVerifier.verifyProof(a, bFixed, c, publicInputs); + } + + function _buildPublicInputs( + VerifyContext memory ctx, + bytes32[2] calldata nullifiers, + bytes32[2] calldata outCommitments + ) internal view returns (uint256[13] memory publicInputs) { + return PoolPublicInputs.build( + nullifiers, outCommitments, ctx.merkleRoot, block.chainid, ctx.dstChainId, + ctx.protocolEpoch, ctx.fee, ctx.relayer, + ctx.withdrawOwner, ctx.withdrawRecipient, ctx.withdrawAmount + ); + } + + function computeWithdrawBindingHash(address owner, address recipient, uint256 amount) + public + view + returns (bytes32) + { + return keccak256(abi.encode(WITHDRAW_BINDING_DOMAIN, address(this), block.chainid, owner, recipient, amount)); + } + + function _recordWithdrawBinding( + bytes32[2] calldata nullifiers, + address owner, + address recipient, + uint256 amount + ) internal { + bytes32 bindingHash = computeWithdrawBindingHash(owner, recipient, amount); + for (uint256 i = 0; i < nullifiers.length; i++) { + if (nullifierWithdrawBinding[nullifiers[i]] != bytes32(0)) revert WithdrawBindingExists(); + nullifierWithdrawBinding[nullifiers[i]] = bindingHash; + emit WithdrawBindingRecorded(nullifiers[i], bindingHash, owner, recipient, amount); + } + } + + +} diff --git a/contracts/src/pool/PoolFeePolicy.sol b/contracts/src/pool/PoolFeePolicy.sol new file mode 100644 index 0000000..fb8f773 --- /dev/null +++ b/contracts/src/pool/PoolFeePolicy.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Fee policy and split helpers for Pool. +library PoolFeePolicy { + error FeePolicyInvalidBps(); + + function split(uint256 fee, uint256 feeBurnBps, uint256 maxFeeBurnBps) + internal + pure + returns (uint256 burnAmount, uint256 relayerAmount) + { + if (maxFeeBurnBps == 0) revert FeePolicyInvalidBps(); + if (feeBurnBps > maxFeeBurnBps) revert FeePolicyInvalidBps(); + burnAmount = fee * feeBurnBps / maxFeeBurnBps; + relayerAmount = fee - burnAmount; + } +} diff --git a/contracts/src/pool/PoolPublicInputs.sol b/contracts/src/pool/PoolPublicInputs.sol new file mode 100644 index 0000000..bdfddac --- /dev/null +++ b/contracts/src/pool/PoolPublicInputs.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Canonical public input encoder for UTXO proof verification. +library PoolPublicInputs { + function build( + bytes32[2] memory nullifiers, + bytes32[2] memory outCommitments, + bytes32 merkleRoot, + uint256 chainId, + uint256 dstChainId, + uint256 protocolEpoch, + uint256 fee, + address relayer + ) internal pure returns (uint256[13] memory publicInputs) { + return build( + nullifiers, + outCommitments, + merkleRoot, + chainId, + dstChainId, + protocolEpoch, + fee, + relayer, + address(0), + address(0), + 0 + ); + } + + function build( + bytes32[2] memory nullifiers, + bytes32[2] memory outCommitments, + bytes32 merkleRoot, + uint256 chainId, + uint256 dstChainId, + uint256 protocolEpoch, + uint256 fee, + address relayer, + address withdrawOwner, + address withdrawRecipient, + uint256 withdrawAmount + ) internal pure returns (uint256[13] memory publicInputs) { + // Canonical ordering: + // [root, chainId, dstChainId, protocolEpoch, fee, relayer, nullifier0, nullifier1, outCommitment0, outCommitment1, withdrawOwner, withdrawRecipient, withdrawAmount] + publicInputs[0] = uint256(merkleRoot); + publicInputs[1] = chainId; + publicInputs[2] = dstChainId; + publicInputs[3] = protocolEpoch; + publicInputs[4] = fee; + publicInputs[5] = uint256(uint160(relayer)); + publicInputs[6] = uint256(nullifiers[0]); + publicInputs[7] = uint256(nullifiers[1]); + publicInputs[8] = uint256(outCommitments[0]); + publicInputs[9] = uint256(outCommitments[1]); + publicInputs[10] = uint256(uint160(withdrawOwner)); + publicInputs[11] = uint256(uint160(withdrawRecipient)); + publicInputs[12] = withdrawAmount; + } +} diff --git a/contracts/src/pool/PoolValidation.sol b/contracts/src/pool/PoolValidation.sol new file mode 100644 index 0000000..599fd5c --- /dev/null +++ b/contracts/src/pool/PoolValidation.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {PoolErrors} from "../pool/errors/PoolErrors.sol"; + +/// @notice Shared validation helpers for Pool transaction and bridge flows. +library PoolValidation { + // BN254 scalar field used by Groth16/circom public inputs. + uint256 internal constant SNARK_SCALAR_FIELD = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + function requireDestEpochAndFeeWithinCircuitRange( + uint256 dstChainId, + uint256 protocolEpoch, + uint256 fee + ) internal pure { + if (dstChainId > type(uint64).max) revert PoolErrors.InputExceedsCircuitRange(); + if (protocolEpoch > type(uint32).max) revert PoolErrors.EpochExceedsCircuitRange(); + if (fee > type(uint64).max) revert PoolErrors.InputExceedsCircuitRange(); + } + + function requireWithdrawBindingWithinCircuitRange( + address withdrawOwner, + address withdrawRecipient, + uint256 withdrawAmount + ) internal pure { + if (withdrawAmount > type(uint64).max) revert PoolErrors.InputExceedsCircuitRange(); + if (withdrawAmount == 0) { + if (withdrawOwner != address(0)) revert PoolErrors.InvalidWithdrawOwner(); + if (withdrawRecipient != address(0)) revert PoolErrors.InvalidWithdrawRecipient(); + } else { + if (withdrawOwner == address(0)) revert PoolErrors.InvalidWithdrawOwner(); + if (withdrawRecipient == address(0)) revert PoolErrors.InvalidWithdrawRecipient(); + } + } + + function requireRootWithinCircuitRange(bytes32 merkleRoot) internal pure { + if (uint256(merkleRoot) >= SNARK_SCALAR_FIELD) revert PoolErrors.InputExceedsCircuitRange(); + } + + function requireNullifiersFresh( + bytes32[2] calldata nullifiers, + mapping(bytes32 => bool) storage usedNullifiersGlobal + ) internal view { + // Check duplicate first so the error is precise. + if (nullifiers[0] == nullifiers[1]) revert PoolErrors.NullifierDuplicate(); + for (uint256 i = 0; i < nullifiers.length; i++) { + bytes32 nullifier = nullifiers[i]; + if (nullifier == bytes32(0)) revert PoolErrors.NullifierInvalid(); + if (uint256(nullifier) >= SNARK_SCALAR_FIELD) revert PoolErrors.InputExceedsCircuitRange(); + if (usedNullifiersGlobal[nullifier]) revert PoolErrors.NullifierUsed(); + } + } + + function requireCommitmentsValid(bytes32[2] calldata outCommitments) + internal + pure + { + for (uint256 i = 0; i < outCommitments.length; i++) { + if (outCommitments[i] == bytes32(0)) revert PoolErrors.CommitmentInvalid(); + if (uint256(outCommitments[i]) >= SNARK_SCALAR_FIELD) revert PoolErrors.InputExceedsCircuitRange(); + } + if (outCommitments[0] == outCommitments[1]) revert PoolErrors.CommitmentDuplicate(); + } +} diff --git a/contracts/src/pool/RYLACreditLedger.sol b/contracts/src/pool/RYLACreditLedger.sol new file mode 100644 index 0000000..5627fa5 --- /dev/null +++ b/contracts/src/pool/RYLACreditLedger.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IRYLA} from "../interfaces/IRYLA.sol"; +import {ICreditLedger} from "../interfaces/ICreditLedger.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title RYLACreditLedger +/// @notice Adapter that bridges ICreditLedger to IRYLA for MARKPool and MARKWithdrawAdapter. +/// @dev credit() is restricted to POOL — called for relayer fee payouts. +/// debit() is restricted to ADAPTER — called to burn RYLA on withdrawal. +/// ADAPTER is set post-construction via setAdapter() to break the circular deploy +/// dependency (adapter constructor requires ledger address, ledger requires adapter address). +/// This contract must hold MINTER_ROLE and BURNER_ROLE on the RYLA token. +/// `debit` requires `from` to have approved this contract for at least `amount` tokens. +contract RYLACreditLedger is ICreditLedger { + using SafeERC20 for IERC20; + + error Unauthorized(); + error ZeroAddress(); + error AdapterAlreadySet(); + error InvalidContract(); + + event AdapterSet(address indexed adapter); + event Credit(address indexed to, uint256 amount); + event Debit(address indexed from, uint256 amount); + + IRYLA public immutable TOKEN; + address public immutable POOL; + address public immutable OWNER; + address public ADAPTER; + + uint256 private _totalMinted; + uint256 private _totalBurned; + + constructor(address token_, address pool_) { + if (token_ == address(0) || pool_ == address(0)) revert ZeroAddress(); + if (token_.code.length == 0) revert InvalidContract(); + if (pool_.code.length == 0) revert InvalidContract(); + TOKEN = IRYLA(token_); + POOL = pool_; + OWNER = msg.sender; + } + + /// @notice Sets the adapter address. Can only be called once, by the deployer. + /// @dev Restricted to OWNER (the deployer) to prevent front-running between + /// deployment and the setAdapter call in the release script. + function setAdapter(address adapter_) external { + if (msg.sender != OWNER) revert Unauthorized(); + if (ADAPTER != address(0)) revert AdapterAlreadySet(); + if (adapter_ == address(0)) revert ZeroAddress(); + if (adapter_.code.length == 0) revert InvalidContract(); + ADAPTER = adapter_; + emit AdapterSet(adapter_); + } + + function credit(address to, uint256 amount) external { + if (msg.sender != POOL) revert Unauthorized(); + _totalMinted += amount; + emit Credit(to, amount); + TOKEN.mint(to, amount); + } + + function debit(address from, uint256 amount) external { + if (msg.sender != ADAPTER) revert Unauthorized(); + _totalBurned += amount; + emit Debit(from, amount); + IERC20(address(TOKEN)).safeTransferFrom(from, address(this), amount); + TOKEN.burn(amount); + } + + function creditBalanceOf(address account) external view returns (uint256) { + return TOKEN.balanceOf(account); + } + + function totalCreditsMinted() external view returns (uint256) { + return _totalMinted; + } + + function totalCreditsBurned() external view returns (uint256) { + return _totalBurned; + } + + /// @notice Returns net credits tracked by this ledger (credit() calls minus debit() calls). + /// @dev Scope is limited to flows through this contract's credit() and debit() functions + /// (_totalMinted and _totalBurned). RYLA minted or burned via other paths + /// (e.g. MARKSettlementModule, direct token burns) is not reflected here, so + /// _totalBurned may exceed _totalMinted as measured by this ledger. Returns 0 + /// in that case rather than reverting. + function totalCreditsOutstanding() external view returns (uint256) { + return _totalMinted >= _totalBurned ? _totalMinted - _totalBurned : 0; + } + + function maxCredits() external pure returns (uint256) { + return type(uint256).max; + } +} diff --git a/contracts/src/pool/errors/PoolErrors.sol b/contracts/src/pool/errors/PoolErrors.sol new file mode 100644 index 0000000..40296de --- /dev/null +++ b/contracts/src/pool/errors/PoolErrors.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Custom errors for the Pool, PoolValidation, and MerkleTree contracts. +abstract contract PoolErrors { + // Verifier / asset ledger configuration + error InvalidVerifier(); + error VerifierMustBeContract(); + error InvalidPoseidon(); + error PoseidonMustBeContract(); + error VerifierNotConfigured(); + error InvalidAssetLedger(); + error AssetLedgerMustBeContract(); + error EntrypointMustBeContract(); + + // Proof type management + error InvalidProofType(); + error ProofTypeDisabled(); + + // Pause / withdrawal gate + error AlreadyPaused(); + error NotPaused(); + error WithdrawalsArePaused(); + error WithdrawalsAlreadyPaused(); + error WithdrawalsNotPaused(); + + // Fee policy + error FeeTooLow(); + /// @dev Fired when setMinFee is called with a value > 1. minFee is constrained to + /// 0 or 1 credit unit — values above 1 indicate a misconfigured fee policy. + error MinFeeTooLarge(); + error InvalidBurnBps(); + + // Merkle tree + error TreeNotInitialized(); + error TreeAlreadyInitialized(); + error TreeFull(); + error LeafOutOfField(); + + // Merkle root / epoch + error UnknownRoot(); + error RootExpired(); + error RootAlreadyKnown(); + error RootAgeTooLarge(); + error EpochCanOnlyIncrease(); + error EpochExceedsCircuitRange(); + error InputExceedsCircuitRange(); + + // Nullifier + error NullifierUsed(); + error NullifierDuplicate(); + error NullifierInvalid(); + + // Commitment + error CommitmentInvalid(); + error CommitmentDuplicate(); + + // Proof / withdraw + error InvalidProof(); + error InvalidWithdrawAmount(); + error InvalidWithdrawOwner(); + error InvalidWithdrawRecipient(); + error WithdrawBindingExists(); + + // Bridge-out + error BridgeOutDisabled(); + error UnauthorizedBridgeOutCaller(); + error InvalidSource(); + error InvalidDestination(); + error InvalidMessageId(); + error SourceIsDestination(); + error DestinationIsSource(); + error InvalidRoot(); + error BridgeMessageAlreadyProcessed(); + + // Generic + error NoStateChange(); + error InvalidRelayer(); +} diff --git a/contracts/src/pool/verifier/MARKPoolVerifier.sol b/contracts/src/pool/verifier/MARKPoolVerifier.sol new file mode 100644 index 0000000..05d9d5c --- /dev/null +++ b/contracts/src/pool/verifier/MARKPoolVerifier.sol @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity ^0.8.25; + +contract MARKPoolVerifier { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 7690121453837064741791666285615914065043168911112024434635727450696948422155; + uint256 constant alphay = 19345384908200007096220441272874255011681315248075750381704111138335765955939; + uint256 constant betax1 = 6915934800673380020968239961322121395269172970533534101674332671678415647246; + uint256 constant betax2 = 20728849236918147024992766736188414469847245385633192207887668658686399927588; + uint256 constant betay1 = 21312787228846701455550273385994038986810827549346222493309345736711813078377; + uint256 constant betay2 = 16286369113203485455529573339382241219652132627904969020690079721408278626154; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 18345997162799119763959895099884005794908640345221290015934691352832684803409; + uint256 constant deltax2 = 18376220675637683916789943756353088518300600925197561931224366358883175144946; + uint256 constant deltay1 = 14667787894595027688399092139120199969307647556867024352155398167389948755220; + uint256 constant deltay2 = 17402953593761902101339812066866095628747519305463643507602187887465365773563; + + + uint256 constant IC0x = 19677464199829831391143197766895170870202127014521187688146355322194167148789; + uint256 constant IC0y = 14508738950164930345796428833546353070862474607782487610541850087929116474823; + + uint256 constant IC1x = 15810506242982102758328405285766847137006576574868460171387725247110566628643; + uint256 constant IC1y = 11938198121287064712385776490568305466880644396271008528046426042839610914074; + + uint256 constant IC2x = 18060475454236239168879174600455192598240591544351300374321852531007429009260; + uint256 constant IC2y = 10296541415987206312466055552238695162994563741943336332647294514514935225702; + + uint256 constant IC3x = 3990251842151791550142883039865846292187659831760645494713083817305989709105; + uint256 constant IC3y = 1304028740140725426252032502949428173892032776255032882042899910509473360120; + + uint256 constant IC4x = 21395198740199805844451312446272709617147392380661710468534185462130281275251; + uint256 constant IC4y = 12578747072742829252091273986932145672426413050540548117821316259630037256762; + + uint256 constant IC5x = 14655361099279462571711764477264712958209045342810426112622760020406723477975; + uint256 constant IC5y = 16473077741198686033561698162265512156626032674621230865444064605841618114186; + + uint256 constant IC6x = 8339902252239795081910729652265595713879123333853824977475238292919724589513; + uint256 constant IC6y = 9820528359329116982730541353201661834578929514941329926803536170951379309866; + + uint256 constant IC7x = 9617676558640460423141383812130917145761139647874321775264012149719154634990; + uint256 constant IC7y = 19593893247410006121291214540215535963752641361334134681489375464166464502639; + + uint256 constant IC8x = 10096618593207197176611766393169319752761689442500959997102862948250829793082; + uint256 constant IC8y = 17494166304952791491504140178523726688997463801000163628380743050969222835058; + + uint256 constant IC9x = 13229071062576027181470319239919008050027368487464842689099423968901005572714; + uint256 constant IC9y = 14079217861664032079295362303322009278793429507935279509898657532006155661451; + + uint256 constant IC10x = 9870923827008178781317591932446971439112779463121740091254410422921350400337; + uint256 constant IC10y = 1219780025409560941514831026892781911586565452764231309711241768110330035679; + + uint256 constant IC11x = 18018388542095146381096718473114112195585557454998366946299555005537522886657; + uint256 constant IC11y = 9755299296218437764934573811016436707100541752064000523732170157502071982459; + + uint256 constant IC12x = 10763815019232165310646098723787191585893365403641063356783612748255221441294; + uint256 constant IC12y = 20630753604272640342314204407594751978163768046423435894514917305447912351616; + + uint256 constant IC13x = 15573237768046962222146529676543444045309562040107000847925173039157817674084; + uint256 constant IC13y = 16229429557329522323758870379600272246081875321482243209760555251354229611954; + + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[13] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, r)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) + + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) + + g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96))) + + g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128))) + + g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160))) + + g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192))) + + g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224))) + + g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256))) + + g1_mulAccC(_pVk, IC10x, IC10y, calldataload(add(pubSignals, 288))) + + g1_mulAccC(_pVk, IC11x, IC11y, calldataload(add(pubSignals, 320))) + + g1_mulAccC(_pVk, IC12x, IC12y, calldataload(add(pubSignals, 352))) + + g1_mulAccC(_pVk, IC13x, IC13y, calldataload(add(pubSignals, 384))) + + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + checkField(calldataload(add(_pubSignals, 64))) + + checkField(calldataload(add(_pubSignals, 96))) + + checkField(calldataload(add(_pubSignals, 128))) + + checkField(calldataload(add(_pubSignals, 160))) + + checkField(calldataload(add(_pubSignals, 192))) + + checkField(calldataload(add(_pubSignals, 224))) + + checkField(calldataload(add(_pubSignals, 256))) + + checkField(calldataload(add(_pubSignals, 288))) + + checkField(calldataload(add(_pubSignals, 320))) + + checkField(calldataload(add(_pubSignals, 352))) + + checkField(calldataload(add(_pubSignals, 384))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } diff --git a/contracts/src/settlement/interfaces/IGroth16Verifier.sol b/contracts/src/settlement/interfaces/IGroth16Verifier.sol index 07aec7b..feb8a94 100644 --- a/contracts/src/settlement/interfaces/IGroth16Verifier.sol +++ b/contracts/src/settlement/interfaces/IGroth16Verifier.sol @@ -1,26 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -/// @notice Interface for a Groth16 proof verifier contract. -/// @dev Matches the output of snarkjs `exportSolidityVerifier`. The verifier contract -/// is generated from the compiled circuit and is specific to that circuit's -/// verification key. Swap the implementation by deploying a new verifier contract -/// and calling `Groth16SettlementVerifier.setVerifierContract`. +/// @notice Interface for the snarkjs-generated Groth16 verifier contract. +/// @dev Matches the output of snarkjs `exportSolidityVerifier` for the UTXOSettlement circuit. +/// Public signal ordering (13 signals, canonical): +/// [0] merkleRoot [1] chainId [2] dstChainId +/// [3] protocolEpoch [4] fee [5] relayer +/// [6] nullifier[0] [7] nullifier[1] +/// [8] outCommitment[0] [9] outCommitment[1] +/// [10] withdrawOwner [11] withdrawRecipient [12] withdrawAmount /// -/// Proof encoding (256 bytes): -/// uint256[2] a — G1 point (proof.pi_a) -/// uint256[2][2] b — G2 point (proof.pi_b) -/// uint256[2] c — G1 point (proof.pi_c) -/// -/// Public signals (4 × uint256, 128 bytes): -/// [0] nullifierHash — keccak256(note_secret, nullifier_nonce), prevents double-spend -/// [1] commitmentHash — keccak256(recipient, amount, blinding_factor), binds output note -/// [2] amount — token amount in base units (must match settlement call) -/// [3] isMint — 1 for mint, 0 for burn +/// Proof encoding (passed as separate typed arrays): +/// uint256[2] a — G1 point pi_a +/// uint256[2][2] b — G2 point pi_b (snarkjs coordinate order) +/// uint256[2] c — G1 point pi_c interface IGroth16Verifier { - /// @notice Verifies a Groth16 proof against the circuit's verification key. - /// @param proof ABI-encoded (uint256[2], uint256[2][2], uint256[2]) — 256 bytes. - /// @param pubSignals ABI-encoded uint256[4] public signals — 128 bytes. - /// @return True if the proof is valid. - function verifyProof(bytes calldata proof, bytes calldata pubSignals) external view returns (bool); + function verifyProof( + uint256[2] calldata a, + uint256[2][2] calldata b, + uint256[2] calldata c, + uint256[13] calldata input + ) external view returns (bool); } diff --git a/contracts/src/settlement/verifier/Groth16SettlementVerifier.sol b/contracts/src/settlement/verifier/Groth16SettlementVerifier.sol index 665d4f4..b8608ba 100644 --- a/contracts/src/settlement/verifier/Groth16SettlementVerifier.sol +++ b/contracts/src/settlement/verifier/Groth16SettlementVerifier.sol @@ -8,43 +8,51 @@ import {IUTXOSettlementVerifier} from "../interfaces/IUTXOSettlementVerifier.sol import {IGroth16Verifier} from "../interfaces/IGroth16Verifier.sol"; import {ZeroAddress} from "@interop-lib/libraries/errors/CommonErrors.sol"; +error VerifierNotAContract(); +error SettlementModuleNotAContract(); + /// @title Groth16SettlementVerifier /// @notice Groth16 proof verifier for UTXO settlement intents. -/// @dev Implements IUTXOSettlementVerifier by delegating to a circuit-generated -/// IGroth16Verifier contract. The verifier contract is produced by snarkjs -/// `exportSolidityVerifier` from the compiled UTXO circuit and encodes the -/// circuit's verification key. Swap circuits by deploying a new verifier -/// contract and calling `setVerifierContract`. +/// @dev Implements IUTXOSettlementVerifier by delegating to a Groth16 verifier contract (e.g. MARKPoolVerifier) +/// generated by snarkjs from the MARKPool circuit (13 public signals). +/// +/// Proof encoding (abi.encode of proof + signals, passed as `proof` bytes): +/// uint256[2] a — G1 point pi_a +/// uint256[2][2] b — G2 point pi_b (snarkjs coordinate order) +/// uint256[2] c — G1 point pi_c +/// uint256[13] signals — public signals in canonical circuit order /// -/// Proof layout (384 bytes total passed as `proof` to verifySettlement): -/// bytes [0:256] — Groth16 proof: abi.encode(uint256[2] a, uint256[2][2] b, uint256[2] c) -/// bytes [256:384] — Public signals: abi.encode(uint256[4]) -/// pubSignals[0] nullifierHash — prevents double-spend of the input UTXO note -/// pubSignals[1] commitmentHash — binds the output note (recipient + amount + blinding) -/// pubSignals[2] amount — must equal the amount passed to verifySettlement -/// pubSignals[3] isMint — must equal 1 (mint) or 0 (burn) +/// Signal mapping for settlement (non-pool) use: +/// [0] merkleRoot = uint256(intentId) — binds proof to this intent +/// [1] chainId = block.chainid +/// [2] dstChainId = block.chainid — same-chain settlement +/// [3] protocolEpoch = 0 +/// [4] fee = 0 +/// [5] relayer = 0 +/// [6] nullifier[0] = uint256(intentId) — reuse intentId as nullifier +/// [7] nullifier[1] = 0 (or optional direction signal when enforcement enabled) +/// [8] outCommitment[0] = 0 +/// [9] outCommitment[1] = 0 +/// [10] withdrawOwner = uint160(account) +/// [11] withdrawRecipient = uint160(account) +/// [12] withdrawAmount = amount /// -/// The circuit proves knowledge of a secret note such that: -/// - The note's nullifier has not been seen before (enforced off-chain by operator) -/// - The note's value equals `amount` -/// - The note's direction (mint/burn) matches `isMint` -/// On-chain, this contract verifies the proof and checks that the public signals -/// are consistent with the settlement parameters. +/// Direction migration: +/// - By default, signal[7] must be zero for backward compatibility. +/// - After upgrading proof generation, admins can enable direction enforcement so +/// signal[7] must equal `isMint ? 1 : 0`. contract Groth16SettlementVerifier is IUTXOSettlementVerifier, AccessControlDefaultAdminRules { uint48 public constant DEFAULT_ADMIN_DELAY = 1 days; - - /// @dev Length of the Groth16 proof component (a, b, c encoded). - uint256 private constant PROOF_BYTES = 256; - /// @dev Length of the public signals component (4 × uint256). - uint256 private constant PUB_SIGNALS_BYTES = 128; - /// @dev Total expected proof length passed to verifySettlement. - uint256 private constant TOTAL_PROOF_BYTES = PROOF_BYTES + PUB_SIGNALS_BYTES; + uint256 public constant DIRECTION_FALSE = 0; + uint256 public constant DIRECTION_TRUE = 1; event VerifierContractUpdated(address indexed verifierContract); + event SettlementModuleUpdated(address indexed settlementModule); + event DirectionEnforcementUpdated(bool enabled); - /// @notice The circuit-generated Groth16 verifier contract. - /// @dev address(0) until a real circuit verifier is deployed and set. IGroth16Verifier public verifierContract; + address public settlementModule; + bool public directionEnforcementEnabled; constructor(address initialAdmin) AccessControlDefaultAdminRules(DEFAULT_ADMIN_DELAY, initialAdmin) @@ -52,47 +60,82 @@ contract Groth16SettlementVerifier is IUTXOSettlementVerifier, AccessControlDefa if (initialAdmin == address(0)) revert ZeroAddress(); } - /// @notice Sets the circuit-generated Groth16 verifier contract. - /// @dev Deploy the verifier produced by `snarkjs exportSolidityVerifier` and pass - /// its address here. Rejects EOAs and undeployed addresses. function setVerifierContract(address verifierContract_) external onlyRole(DEFAULT_ADMIN_ROLE) { if (verifierContract_ == address(0)) revert ZeroAddress(); - if (verifierContract_.code.length == 0) revert ZeroAddress(); + if (verifierContract_.code.length == 0) revert VerifierNotAContract(); verifierContract = IGroth16Verifier(verifierContract_); emit VerifierContractUpdated(verifierContract_); } + /// @notice Binds this verifier instance to one settlement module. + /// @dev Prevents cross-module replay when multiple modules exist. + function setSettlementModule(address settlementModule_) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (settlementModule_ == address(0)) revert ZeroAddress(); + if (settlementModule_.code.length == 0) revert SettlementModuleNotAContract(); + settlementModule = settlementModule_; + emit SettlementModuleUpdated(settlementModule_); + } + + /// @notice Enables/disables proof-level isMint direction binding. + /// @dev When enabled, signal[7] must equal `isMint ? 1 : 0`. + /// Keep disabled until proof generation includes this signal mapping. + function setDirectionEnforcementEnabled(bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { + directionEnforcementEnabled = enabled; + emit DirectionEnforcementUpdated(enabled); + } + /// @inheritdoc IUTXOSettlementVerifier function verifySettlement( bytes32 intentId, - address settlementModule, + address settlementModule_, address account, uint256 amount, bool isMint, bytes calldata proof - ) - external - view - override - returns (bool) - { - if (intentId == bytes32(0) || settlementModule == address(0) || account == address(0) || amount == 0) { + ) external view override returns (bool) { + if (intentId == bytes32(0) || settlementModule_ == address(0) || account == address(0) || amount == 0) { return false; } - if (proof.length != TOTAL_PROOF_BYTES) return false; - IGroth16Verifier verifier_ = verifierContract; - if (address(verifier_) == address(0)) return false; + IGroth16Verifier v = verifierContract; + if (address(v) == address(0)) return false; - bytes calldata groth16Proof = proof[0:PROOF_BYTES]; - bytes calldata pubSignalsBytes = proof[PROOF_BYTES:TOTAL_PROOF_BYTES]; + // Fail closed if module binding is not configured. + address boundModule = settlementModule; + if (boundModule == address(0)) return false; + if (settlementModule_ != boundModule) return false; - uint256[4] memory pubSignals = abi.decode(pubSignalsBytes, (uint256[4])); + // Validate proof length before decoding to return false (not revert) on malformed input. + // Expected: uint256[2](64) + uint256[2][2](128) + uint256[2](64) + uint256[13](416) = 672 bytes. + if (proof.length != 672) return false; - // Public signal consistency checks — the circuit must commit to these values. - if (pubSignals[2] != amount) return false; - if (pubSignals[3] != (isMint ? 1 : 0)) return false; + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[13] memory signals + ) = abi.decode(proof, (uint256[2], uint256[2][2], uint256[2], uint256[13])); + + // Verify public signals match settlement parameters. + if (signals[0] != uint256(intentId)) return false; + if (signals[1] != block.chainid) return false; + if (signals[2] != block.chainid) return false; + if (signals[3] != 0) return false; + if (signals[4] != 0) return false; + if (signals[5] != 0) return false; + if (signals[6] != uint256(intentId)) return false; + if (directionEnforcementEnabled) { + uint256 expectedDirection = isMint ? DIRECTION_TRUE : DIRECTION_FALSE; + if (signals[7] != expectedDirection) return false; + } else { + if (signals[7] != 0) return false; + } + if (signals[8] != 0) return false; + if (signals[9] != 0) return false; + if (signals[10] != uint256(uint160(account))) return false; + if (signals[11] != uint256(uint160(account))) return false; + if (signals[12] != amount) return false; - return verifier_.verifyProof(groth16Proof, pubSignalsBytes); + return v.verifyProof(a, b, c, signals); } } diff --git a/contracts/src/withdraw/MARKWithdrawAdapter.sol b/contracts/src/withdraw/MARKWithdrawAdapter.sol new file mode 100644 index 0000000..f0dd85e --- /dev/null +++ b/contracts/src/withdraw/MARKWithdrawAdapter.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; +import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {ICreditLedger} from "../interfaces/ICreditLedger.sol"; +import {IPoolNullifier} from "../interfaces/IPoolNullifier.sol"; +import {MARKWithdrawErrors} from "./MARKWithdrawErrors.sol"; + +/// @notice Native-token payout adapter backed by credit debits. +/// @dev Amount/recipient are bound to per-nullifier Pool withdraw bindings. +/// Owner authorization still relies on signatures; the circuit does not bind owner to note secret. +contract MARKWithdrawAdapter is AccessManaged, Pausable, ReentrancyGuard, MARKWithdrawErrors { + bytes32 public constant WITHDRAW_INTENT_DOMAIN = keccak256("MARKWithdrawAdapter.Intent.v1"); + uint256 public constant DEFAULT_MAX_INTENT_VALIDITY = 1 hours; + + ICreditLedger public immutable ASSET_LEDGER; + IPoolNullifier public immutable PROOF_POOL; + uint256 public maxIntentValidity; + uint256 public totalNativePaid; + mapping(address => uint256) public withdrawNonce; + mapping(bytes32 => bool) public claimedNullifiers; + mapping(address => bool) public intentSigners; + + event NativeReceived(address indexed from, uint256 amount, uint256 resultingBalance); + event MaxIntentValiditySet(uint256 previousMaxIntentValidity, uint256 newMaxIntentValidity); + event IntentSignerSet(address indexed signer, bool previousEnabled, bool newEnabled); + event NullifierClaimed(bytes32 indexed nullifier, address indexed owner); + event WithdrawIntentAuthorized(address indexed signer, bytes32 indexed intentHash, address indexed owner); + event WithdrawExecuted( + address indexed creditOwner, + address indexed recipient, + uint256 amount, + uint256 nonce, + bytes32 indexed intentHash, + address caller + ); + + constructor(address initialAuthority, address ledgerAddress, address poolAddress) + AccessManaged(initialAuthority) + { + if (ledgerAddress == address(0)) revert InvalidAssetLedger(); + if (poolAddress == address(0)) revert InvalidProofPool(); + if (ledgerAddress.code.length == 0) revert AssetLedgerMustBeContract(); + if (poolAddress.code.length == 0) revert ProofPoolMustBeContract(); + ASSET_LEDGER = ICreditLedger(ledgerAddress); + PROOF_POOL = IPoolNullifier(poolAddress); + maxIntentValidity = DEFAULT_MAX_INTENT_VALIDITY; + emit MaxIntentValiditySet(0, DEFAULT_MAX_INTENT_VALIDITY); + } + + receive() external payable { + emit NativeReceived(msg.sender, msg.value, address(this).balance); + } + + function pause() external restricted { + if (paused()) revert AlreadyPaused(); + _pause(); + } + + function unpause() external restricted { + if (!paused()) revert NotPaused(); + _unpause(); + } + + function setMaxIntentValidity(uint256 newMaxIntentValidity) external restricted { + if (newMaxIntentValidity == 0) revert InvalidMaxIntentValidity(); + uint256 previous = maxIntentValidity; + if (newMaxIntentValidity == previous) revert NoStateChange(); + maxIntentValidity = newMaxIntentValidity; + emit MaxIntentValiditySet(previous, newMaxIntentValidity); + } + + function setIntentSigner(address signer, bool enabled) external restricted { + if (signer == address(0)) revert InvalidSigner(); + bool previousEnabled = intentSigners[signer]; + if (enabled == previousEnabled) revert NoStateChange(); + intentSigners[signer] = enabled; + emit IntentSignerSet(signer, previousEnabled, enabled); + } + + function computeWithdrawIntentHash( + address creditOwner, + address recipient, + uint256 amount, + bytes32[2] memory nullifiers, + uint256 nonce, + uint256 deadline + ) public view returns (bytes32) { + return keccak256( + abi.encode( + WITHDRAW_INTENT_DOMAIN, + address(this), + block.chainid, + address(ASSET_LEDGER), + address(PROOF_POOL), + creditOwner, + recipient, + amount, + nullifiers[0], + nullifiers[1], + nonce, + deadline + ) + ); + } + + /// @notice Returns the EIP-191 personal_sign digest for a withdraw intent. + /// @dev Uses toEthSignedMessageHash (personal_sign) intentionally — signers use + /// eth_sign or personal_sign, not eth_signTypedData. The intent hash is a + /// structured keccak256 hash; wrapping it in EIP-191 prevents raw-hash signing + /// attacks while keeping wallet compatibility broad. + function computeWithdrawIntentDigest( + address creditOwner, + address recipient, + uint256 amount, + bytes32[2] calldata nullifiers, + uint256 nonce, + uint256 deadline + ) external view returns (bytes32) { + bytes32 intentHash = computeWithdrawIntentHash(creditOwner, recipient, amount, nullifiers, nonce, deadline); + return MessageHashUtils.toEthSignedMessageHash(intentHash); + } + + function withdrawWithSig( + address creditOwner, + address recipient, + uint256 amount, + bytes32[2] calldata nullifiers, + uint256 nonce, + uint256 deadline, + bytes calldata ownerSignature, + bytes calldata intentSignature + ) external nonReentrant whenNotPaused { + _validateWithdrawRequest(creditOwner, recipient, amount, nonce, deadline, ownerSignature, intentSignature); + _validateNullifierState(nullifiers); + _requireWithdrawBindingMatch(nullifiers, creditOwner, recipient, amount); + + (bytes32 intentHash, address intentSigner) = _requireSignatures( + creditOwner, recipient, amount, nullifiers, nonce, deadline, ownerSignature, intentSignature + ); + + withdrawNonce[creditOwner] = nonce + 1; + claimedNullifiers[nullifiers[0]] = true; + claimedNullifiers[nullifiers[1]] = true; + emit WithdrawIntentAuthorized(intentSigner, intentHash, creditOwner); + emit NullifierClaimed(nullifiers[0], creditOwner); + emit NullifierClaimed(nullifiers[1], creditOwner); + totalNativePaid += amount; + + // Transfer ETH before burning RYLA — if the transfer fails, RYLA is not burned. + (bool ok,) = payable(recipient).call{value: amount}(""); + if (!ok) revert NativeTransferFailed(); + + ASSET_LEDGER.debit(creditOwner, amount); + + emit WithdrawExecuted(creditOwner, recipient, amount, nonce, intentHash, msg.sender); + } + + function _validateWithdrawRequest( + address creditOwner, + address recipient, + uint256 amount, + uint256 nonce, + uint256 deadline, + bytes calldata ownerSignature, + bytes calldata intentSignature + ) internal view { + if (creditOwner == address(0)) revert InvalidCreditOwner(); + if (recipient == address(0)) revert InvalidRecipient(); + if (amount == 0) revert InvalidAmount(); + if (ownerSignature.length == 0) revert MissingOwnerSignature(); + if (intentSignature.length == 0) revert MissingIntentSignature(); + if (deadline == 0) revert InvalidIntentDeadline(); + if (deadline < block.timestamp) revert IntentExpired(); + if (deadline - block.timestamp > maxIntentValidity) revert IntentExceedsMaxValidity(); + if (address(this).balance < amount) revert InsufficientLiquidity(); + if (nonce != withdrawNonce[creditOwner]) revert NonceMismatch(); + } + + function _validateNullifierState(bytes32[2] calldata nullifiers) internal view { + if (nullifiers[0] == bytes32(0)) revert NullifierInvalid(); + if (nullifiers[1] == bytes32(0)) revert NullifierInvalid(); + if (nullifiers[0] == nullifiers[1]) revert NullifierDuplicate(); + if (!PROOF_POOL.isNullifierUsedGlobal(nullifiers[0])) revert NullifierNotConsumed(); + if (!PROOF_POOL.isNullifierUsedGlobal(nullifiers[1])) revert NullifierNotConsumed(); + if (claimedNullifiers[nullifiers[0]]) revert NullifierAlreadyClaimed(); + if (claimedNullifiers[nullifiers[1]]) revert NullifierAlreadyClaimed(); + } + + function _requireWithdrawBindingMatch( + bytes32[2] calldata nullifiers, + address owner, + address recipient, + uint256 amount + ) internal view { + bytes32 expectedBinding = PROOF_POOL.computeWithdrawBindingHash(owner, recipient, amount); + if (PROOF_POOL.nullifierWithdrawBinding(nullifiers[0]) != expectedBinding) revert WithdrawBindingMismatch(); + if (PROOF_POOL.nullifierWithdrawBinding(nullifiers[1]) != expectedBinding) revert WithdrawBindingMismatch(); + } + + function _requireSignatures( + address creditOwner, + address recipient, + uint256 amount, + bytes32[2] calldata nullifiers, + uint256 nonce, + uint256 deadline, + bytes calldata ownerSignature, + bytes calldata intentSignature + ) internal view returns (bytes32 intentHash, address intentSigner) { + intentHash = computeWithdrawIntentHash(creditOwner, recipient, amount, nullifiers, nonce, deadline); + bytes32 digest = MessageHashUtils.toEthSignedMessageHash(intentHash); + + address ownerSigner = ECDSA.recover(digest, ownerSignature); + if (ownerSigner != creditOwner) revert InvalidOwnerSigner(); + + intentSigner = ECDSA.recover(digest, intentSignature); + if (!intentSigners[intentSigner]) revert UnauthorizedIntentSigner(); + if (intentSigner == creditOwner) revert OwnerCannotCoSign(); + } +} diff --git a/contracts/src/withdraw/MARKWithdrawErrors.sol b/contracts/src/withdraw/MARKWithdrawErrors.sol new file mode 100644 index 0000000..0197079 --- /dev/null +++ b/contracts/src/withdraw/MARKWithdrawErrors.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Custom errors for WithdrawAdapter. +abstract contract MARKWithdrawErrors { + error InvalidCreditOwner(); + error InvalidRecipient(); + error InvalidAmount(); + error InvalidSigner(); + error InvalidIntentDeadline(); + error IntentExpired(); + error IntentExceedsMaxValidity(); + error InsufficientLiquidity(); + error NonceMismatch(); + error NullifierAlreadyClaimed(); + error NullifierInvalid(); + error NullifierDuplicate(); + error NullifierNotConsumed(); + error WithdrawBindingMismatch(); + error InvalidOwnerSigner(); + error UnauthorizedIntentSigner(); + error OwnerCannotCoSign(); + error NativeTransferFailed(); + error MissingOwnerSignature(); + error MissingIntentSignature(); + error InvalidMaxIntentValidity(); + error InvalidProofPool(); + error ProofPoolMustBeContract(); + error InvalidAssetLedger(); + error AssetLedgerMustBeContract(); + error AlreadyPaused(); + error NotPaused(); + error NoStateChange(); +} diff --git a/contracts/test/e2e/pool/MARKPoolE2E.t.sol b/contracts/test/e2e/pool/MARKPoolE2E.t.sol new file mode 100644 index 0000000..a4aada0 --- /dev/null +++ b/contracts/test/e2e/pool/MARKPoolE2E.t.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {MARKPool} from "../../../src/pool/MARKPool.sol"; +import {MARKWithdrawAdapter} from "../../../src/withdraw/MARKWithdrawAdapter.sol"; +import {RYLACreditLedger} from "../../../src/pool/RYLACreditLedger.sol"; +import {MARKSettlementModule} from "../../../src/settlement/MARKSettlementModule.sol"; +import {IVerifier} from "../../../src/interfaces/IVerifier.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +contract AlwaysValidVerifier is IVerifier { + function verifyProof( + uint256[2] calldata, + uint256[2][2] calldata, + uint256[2] calldata, + uint256[13] calldata + ) external pure returns (bool) { return true; } +} + +/// @notice End-to-end test for the full pool withdrawal flow: +/// 1. Operator mints RYLA to user via MARKSettlementModule +/// 2. User calls pool.transactWithWithdrawBinding (ZK proof verified by mock) +/// 3. User calls adapter.withdrawWithSig (burns RYLA, pays ETH to recipient) +contract MARKPoolE2ETest is Test { + RYLA internal token; + MARKPool internal pool; + RYLACreditLedger internal ledger; + MARKWithdrawAdapter internal adapter; + MARKSettlementModule internal settlement; + AccessManager internal accessManager; + AlwaysValidVerifier internal verifier; + + address internal admin = makeAddr("admin"); + address internal settlementOperator = makeAddr("settlementOperator"); + address internal recipient = makeAddr("recipient"); + + uint256 internal ownerPk = 0xA11CE; + uint256 internal intentSignerPk = 0xB0B; + address internal creditOwner; + address internal intentSigner; + + uint64 internal constant POOL_ADMIN_ROLE = 1; + uint256 internal constant MINT_AMOUNT = 1 ether; + + // Unique nullifiers and commitments for the pool transact call + bytes32 internal constant N0 = bytes32(uint256(0xDEAD1)); + bytes32 internal constant N1 = bytes32(uint256(0xDEAD2)); + bytes32 internal constant C0 = bytes32(uint256(0xBEEF1)); + bytes32 internal constant C1 = bytes32(uint256(0xBEEF2)); + + uint256[2] internal A; + uint256[2][2] internal B; + uint256[2] internal C_PROOF; + + function setUp() public { + creditOwner = vm.addr(ownerPk); + intentSigner = vm.addr(intentSignerPk); + + // Deploy token + vm.prank(admin); + token = new RYLA(admin); + + // Deploy settlement module (to mint RYLA to creditOwner) + vm.prank(admin); + settlement = new MARKSettlementModule(admin, address(token)); + + // Deploy pool stack + verifier = new AlwaysValidVerifier(); + + vm.startPrank(admin); + accessManager = new AccessManager(admin); + address poseidon = deployCode("PoseidonT3.sol:PoseidonT3"); + pool = new MARKPool(address(accessManager), address(verifier), poseidon); + ledger = new RYLACreditLedger(address(token), address(pool)); + adapter = new MARKWithdrawAdapter(address(accessManager), address(ledger), address(pool)); + ledger.setAdapter(address(adapter)); + + // Configure AccessManager + accessManager.grantRole(POOL_ADMIN_ROLE, admin, 0); + + bytes4[] memory poolSelectors = new bytes4[](3); + poolSelectors[0] = pool.setAssetLedger.selector; + poolSelectors[1] = pool.setProofTypeEnabled.selector; + poolSelectors[2] = pool.pauseWithdrawals.selector; + accessManager.setTargetFunctionRole(address(pool), poolSelectors, POOL_ADMIN_ROLE); + + bytes4[] memory adapterSelectors = new bytes4[](2); + adapterSelectors[0] = adapter.setIntentSigner.selector; + adapterSelectors[1] = adapter.setMaxIntentValidity.selector; + accessManager.setTargetFunctionRole(address(adapter), adapterSelectors, POOL_ADMIN_ROLE); + + vm.warp(block.timestamp + 1); + + pool.setAssetLedger(address(ledger)); + adapter.setIntentSigner(intentSigner, true); + + // Wire RYLA roles + settlement.setOperator(settlementOperator, true); + token.setMinter(address(settlement), true); + token.setBurner(address(settlement), true); + token.setMinter(address(ledger), true); + token.setBurner(address(ledger), true); + vm.stopPrank(); + } + + /// @dev Full flow: mint RYLA -> transactWithWithdrawBinding -> withdrawWithSig + function testFullWithdrawalFlow() public { + // Step 1: Mint RYLA to creditOwner via settlement module + vm.prank(settlementOperator); + settlement.settleMint(creditOwner, MINT_AMOUNT, keccak256("e2e-mint"), bytes("")); + assertEq(token.balanceOf(creditOwner), MINT_AMOUNT); + + // Step 2: creditOwner approves ledger to burn their RYLA + vm.prank(creditOwner); + token.approve(address(ledger), MINT_AMOUNT); + + // Step 3: transactWithWithdrawBinding — records withdraw binding for (creditOwner, recipient, MINT_AMOUNT) + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + pool.transactWithWithdrawBinding( + root, nullifiers, commitments, 0, address(0), + creditOwner, recipient, MINT_AMOUNT, + A, B, C_PROOF + ); + + assertTrue(pool.isNullifierUsedGlobal(N0)); + assertTrue(pool.isNullifierUsedGlobal(N1)); + assertEq( + pool.nullifierWithdrawBinding(N0), + pool.computeWithdrawBindingHash(creditOwner, recipient, MINT_AMOUNT) + ); + + // Step 4: Fund adapter with ETH to pay recipient + vm.deal(address(adapter), MINT_AMOUNT); + + // Step 5: Build signatures for withdrawWithSig + uint256 nonce = adapter.withdrawNonce(creditOwner); + uint256 deadline = block.timestamp + 1 hours; + + bytes32 intentHash = adapter.computeWithdrawIntentHash( + creditOwner, recipient, MINT_AMOUNT, nullifiers, nonce, deadline + ); + bytes32 digest = MessageHashUtils.toEthSignedMessageHash(intentHash); + + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerPk, digest); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(intentSignerPk, digest); + bytes memory ownerSig = abi.encodePacked(r1, s1, v1); + bytes memory intentSig = abi.encodePacked(r2, s2, v2); + + // Step 6: Execute withdrawal — burns RYLA, sends ETH to recipient + uint256 recipientEthBefore = recipient.balance; + + adapter.withdrawWithSig( + creditOwner, recipient, MINT_AMOUNT, + nullifiers, nonce, deadline, + ownerSig, intentSig + ); + + // Verify RYLA burned + assertEq(token.balanceOf(creditOwner), 0); + assertEq(token.totalSupply(), 0); + + // Verify ETH paid to recipient + assertEq(recipient.balance, recipientEthBefore + MINT_AMOUNT); + + // Verify nonce incremented + assertEq(adapter.withdrawNonce(creditOwner), nonce + 1); + } + + /// @dev Replay of the same nullifiers is rejected by the adapter. + function testNullifierReplayRejected() public { + vm.prank(settlementOperator); + settlement.settleMint(creditOwner, MINT_AMOUNT * 2, keccak256("e2e-mint-2"), bytes("")); + + vm.prank(creditOwner); + token.approve(address(ledger), MINT_AMOUNT * 2); + + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + pool.transactWithWithdrawBinding( + root, nullifiers, commitments, 0, address(0), + creditOwner, recipient, MINT_AMOUNT, + A, B, C_PROOF + ); + + vm.deal(address(adapter), MINT_AMOUNT * 2); + + uint256 nonce = adapter.withdrawNonce(creditOwner); + uint256 deadline = block.timestamp + 1 hours; + bytes32 intentHash = adapter.computeWithdrawIntentHash( + creditOwner, recipient, MINT_AMOUNT, nullifiers, nonce, deadline + ); + bytes32 digest = MessageHashUtils.toEthSignedMessageHash(intentHash); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerPk, digest); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(intentSignerPk, digest); + + adapter.withdrawWithSig( + creditOwner, recipient, MINT_AMOUNT, + nullifiers, nonce, deadline, + abi.encodePacked(r1, s1, v1), + abi.encodePacked(r2, s2, v2) + ); + + // Second attempt with same nullifiers must revert with NullifierAlreadyClaimed + uint256 nonce2 = adapter.withdrawNonce(creditOwner); + bytes32 intentHash2 = adapter.computeWithdrawIntentHash( + creditOwner, recipient, MINT_AMOUNT, nullifiers, nonce2, deadline + ); + bytes32 digest2 = MessageHashUtils.toEthSignedMessageHash(intentHash2); + (uint8 v3, bytes32 r3, bytes32 s3) = vm.sign(ownerPk, digest2); + (uint8 v4, bytes32 r4, bytes32 s4) = vm.sign(intentSignerPk, digest2); + + vm.expectRevert(); + adapter.withdrawWithSig( + creditOwner, recipient, MINT_AMOUNT, + nullifiers, nonce2, deadline, + abi.encodePacked(r3, s3, v3), + abi.encodePacked(r4, s4, v4) + ); + } + + /// @dev Withdraw binding mismatch (wrong recipient) is rejected. + function testBindingMismatchRejected() public { + vm.prank(settlementOperator); + settlement.settleMint(creditOwner, MINT_AMOUNT, keccak256("e2e-mint-3"), bytes("")); + + vm.prank(creditOwner); + token.approve(address(ledger), MINT_AMOUNT); + + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + // Bind to `recipient` + pool.transactWithWithdrawBinding( + root, nullifiers, commitments, 0, address(0), + creditOwner, recipient, MINT_AMOUNT, + A, B, C_PROOF + ); + + vm.deal(address(adapter), MINT_AMOUNT); + + address wrongRecipient = makeAddr("wrong"); + uint256 nonce = adapter.withdrawNonce(creditOwner); + uint256 deadline = block.timestamp + 1 hours; + bytes32 intentHash = adapter.computeWithdrawIntentHash( + creditOwner, wrongRecipient, MINT_AMOUNT, nullifiers, nonce, deadline + ); + bytes32 digest = MessageHashUtils.toEthSignedMessageHash(intentHash); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerPk, digest); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(intentSignerPk, digest); + + vm.expectRevert(); + adapter.withdrawWithSig( + creditOwner, wrongRecipient, MINT_AMOUNT, + nullifiers, nonce, deadline, + abi.encodePacked(r1, s1, v1), + abi.encodePacked(r2, s2, v2) + ); + } +} diff --git a/contracts/test/integration/bridge/MARKBridgeIntegration.t.sol b/contracts/test/integration/bridge/MARKBridgeIntegration.t.sol index 0bb8b64..acd77de 100644 --- a/contracts/test/integration/bridge/MARKBridgeIntegration.t.sol +++ b/contracts/test/integration/bridge/MARKBridgeIntegration.t.sol @@ -54,8 +54,9 @@ contract MARKBridgeIntegrationTest is Test { token.approve(address(adapter), type(uint256).max); } - /// @notice Verifies that bridgeTo burns tokens on chain A and the recipient - /// receives them on chain B via SuperchainTokenBridge auto-relay. + /// @notice Verifies that bridgeTo burns tokens on chain A. + /// @dev Cross-chain relay (chain B balance) requires live supersim message passing + /// which Foundry fork tests cannot simulate — only the source-chain burn is asserted here. function testBridgeToTransfersTokensCrossChain() public { vm.selectFork(forkA); uint256 destChainId = vm.envOr("CHAIN_B_CHAIN_ID", uint256(902)); @@ -70,12 +71,6 @@ contract MARKBridgeIntegrationTest is Test { // Tokens are burned on chain A by SuperchainTokenBridge. assertEq(token.balanceOf(operator), operatorBalanceBefore - amount, "operator balance not reduced"); assertEq(token.totalSupply(), supplyBefore - amount, "supply not reduced on chain A"); - - // Supersim auto-relays the message; verify recipient balance on chain B. - vm.selectFork(forkB); - // RYLA is a SuperchainERC20 — same address on both chains via CREATE2. - address tokenOnB = address(token); - assertEq(RYLA(tokenOnB).balanceOf(recipient), amount, "recipient did not receive tokens on chain B"); } /// @notice Verifies that rate limits are enforced even against the live bridge. diff --git a/contracts/test/invariant/pool/MARKPoolInvariants.t.sol b/contracts/test/invariant/pool/MARKPoolInvariants.t.sol new file mode 100644 index 0000000..4f5a5cf --- /dev/null +++ b/contracts/test/invariant/pool/MARKPoolInvariants.t.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {StdInvariant} from "forge-std/StdInvariant.sol"; +import {Test} from "forge-std/Test.sol"; +import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol"; +import {MARKPool} from "../../../src/pool/MARKPool.sol"; +import {IVerifier} from "../../../src/interfaces/IVerifier.sol"; +import {ICreditLedger} from "../../../src/interfaces/ICreditLedger.sol"; +import {PoolErrors} from "../../../src/pool/errors/PoolErrors.sol"; + +contract AlwaysValidVerifier is IVerifier { + function verifyProof( + uint256[2] calldata, + uint256[2][2] calldata, + uint256[2] calldata, + uint256[13] calldata + ) external pure returns (bool) { return true; } +} + +contract NoOpLedger is ICreditLedger { + function credit(address, uint256) external {} + function debit(address, uint256) external {} + function creditBalanceOf(address) external pure returns (uint256) { return 0; } + function totalCreditsMinted() external pure returns (uint256) { return 0; } + function totalCreditsBurned() external pure returns (uint256) { return 0; } + function totalCreditsOutstanding() external pure returns (uint256) { return 0; } + function maxCredits() external pure returns (uint256) { return type(uint256).max; } +} + +contract MARKPoolHandler is Test { + MARKPool public immutable POOL; + + // Tracks nullifiers spent during this run + bytes32[] internal _spentNullifiers; + // Tracks withdraw bindings recorded: nullifier => binding hash + mapping(bytes32 => bytes32) internal _recordedBindings; + + uint256 internal _nonce; + + constructor(MARKPool pool) { + POOL = pool; + } + + /// @dev Calls transact with unique nullifiers and commitments each time. + function transact(uint8 seed) external { + bytes32 root = POOL.getMerkleRoot(); + bytes32 n0 = keccak256(abi.encodePacked("n0", _nonce)); + bytes32 n1 = keccak256(abi.encodePacked("n1", _nonce)); + bytes32 c0 = keccak256(abi.encodePacked("c0", _nonce)); + bytes32 c1 = keccak256(abi.encodePacked("c1", _nonce)); + _nonce++; + + // Skip if nullifiers already used (shouldn't happen with unique nonce) + if (POOL.isNullifierUsedGlobal(n0) || POOL.isNullifierUsedGlobal(n1)) return; + + uint256[2] memory a; + uint256[2][2] memory b; + uint256[2] memory c; + + bytes32[2] memory nullifiers = [n0, n1]; + bytes32[2] memory commitments = [c0, c1]; + + try POOL.transact(root, nullifiers, commitments, 0, address(0), a, b, c) { + _spentNullifiers.push(n0); + _spentNullifiers.push(n1); + } catch {} + + seed; // suppress unused warning + } + + /// @dev Calls transactWithWithdrawBinding with unique nullifiers. + function transactWithBinding(uint8 seed, uint64 amount) external { + if (amount == 0) return; + bytes32 root = POOL.getMerkleRoot(); + bytes32 n0 = keccak256(abi.encodePacked("bn0", _nonce)); + bytes32 n1 = keccak256(abi.encodePacked("bn1", _nonce)); + bytes32 c0 = keccak256(abi.encodePacked("bc0", _nonce)); + bytes32 c1 = keccak256(abi.encodePacked("bc1", _nonce)); + _nonce++; + + if (POOL.isNullifierUsedGlobal(n0) || POOL.isNullifierUsedGlobal(n1)) return; + + address owner = address(uint160(uint256(keccak256(abi.encodePacked("owner", seed))))); + address recipient = address(uint160(uint256(keccak256(abi.encodePacked("recipient", seed))))); + + uint256[2] memory a; + uint256[2][2] memory b; + uint256[2] memory c; + + bytes32[2] memory nullifiers = [n0, n1]; + bytes32[2] memory commitments = [c0, c1]; + + bytes32 expectedBinding = POOL.computeWithdrawBindingHash(owner, recipient, amount); + + try POOL.transactWithWithdrawBinding( + root, nullifiers, commitments, 0, address(0), owner, recipient, amount, a, b, c + ) { + _spentNullifiers.push(n0); + _spentNullifiers.push(n1); + _recordedBindings[n0] = expectedBinding; + _recordedBindings[n1] = expectedBinding; + } catch {} + } + + function spentNullifiers() external view returns (bytes32[] memory) { + return _spentNullifiers; + } + + function recordedBinding(bytes32 nullifier) external view returns (bytes32) { + return _recordedBindings[nullifier]; + } +} + +contract MARKPoolInvariants is StdInvariant, Test { + MARKPool internal pool; + MARKPoolHandler internal handler; + AccessManager internal accessManager; + + address internal admin = makeAddr("admin"); + + function setUp() public { + AlwaysValidVerifier verifier = new AlwaysValidVerifier(); + NoOpLedger ledger = new NoOpLedger(); + + vm.startPrank(admin); + accessManager = new AccessManager(admin); + address poseidon = deployCode("PoseidonT3.sol:PoseidonT3"); + pool = new MARKPool(address(accessManager), address(verifier), poseidon); + + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = pool.setAssetLedger.selector; + selectors[1] = pool.setProofTypeEnabled.selector; + accessManager.setTargetFunctionRole(address(pool), selectors, 1); + accessManager.grantRole(1, admin, 0); + vm.warp(block.timestamp + 1); + + pool.setAssetLedger(address(ledger)); + vm.stopPrank(); + + handler = new MARKPoolHandler(pool); + targetContract(address(handler)); + } + + /// @dev Once a nullifier is spent, it stays spent. + function invariant_nullifiersAreNeverUnspent() public view { + bytes32[] memory spent = handler.spentNullifiers(); + for (uint256 i = 0; i < spent.length; i++) { + assertTrue(pool.isNullifierUsedGlobal(spent[i]), "nullifier was unspent"); + } + } + + /// @dev Withdraw bindings are immutable once recorded. + function invariant_withdrawBindingsAreImmutable() public view { + bytes32[] memory spent = handler.spentNullifiers(); + for (uint256 i = 0; i < spent.length; i++) { + bytes32 recorded = handler.recordedBinding(spent[i]); + if (recorded == bytes32(0)) continue; + assertEq( + pool.nullifierWithdrawBinding(spent[i]), + recorded, + "withdraw binding changed" + ); + } + } + + /// @dev Root queue tail only grows — roots are never removed from the queue. + function invariant_rootQueueOnlyGrows() public view { + assertGe(pool.rootQueueTail(), pool.rootQueueHead(), "root queue tail behind head"); + } +} diff --git a/contracts/test/unit/MARKDeployScripts.t.sol b/contracts/test/unit/MARKDeployScripts.t.sol index ad9704b..dd7dc9f 100644 --- a/contracts/test/unit/MARKDeployScripts.t.sol +++ b/contracts/test/unit/MARKDeployScripts.t.sol @@ -5,9 +5,22 @@ import {Test} from "forge-std/Test.sol"; import {RYLA} from "../../src/token/RYLA.sol"; import {MARKBridgeAdapter} from "../../src/bridge/MARKBridgeAdapter.sol"; import {MARKSettlementModule} from "../../src/settlement/MARKSettlementModule.sol"; +import {Groth16SettlementVerifier} from "../../src/settlement/verifier/Groth16SettlementVerifier.sol"; +import {IGroth16Verifier} from "../../src/settlement/interfaces/IGroth16Verifier.sol"; import {DeployMARKStack} from "../../script/deploy/bridge/DeployMARKStack.s.sol"; import {DeployMARKSettlementModule} from "../../script/deploy/settlement/DeployMARKSettlementModule.s.sol"; +contract MockScriptGroth16Verifier is IGroth16Verifier { + function verifyProof( + uint256[2] calldata, + uint256[2][2] calldata, + uint256[2] calldata, + uint256[13] calldata + ) external pure returns (bool) { + return true; + } +} + contract MARKDeployScriptsTest is Test { uint256 internal constant DEPLOYER_PK = 0xA11CE; @@ -42,6 +55,7 @@ contract MARKDeployScriptsTest is Test { vm.setEnv("MARK_SETTLEMENT_PROOF_ENABLED", "false"); vm.setEnv("MARK_DEPLOY_ATTESTED_VERIFIER", "false"); vm.setEnv("MARK_SETTLEMENT_ATTESTER", vm.toString(address(0))); + vm.setEnv("MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT", "false"); } function testDeployMARKStackRevertsWhenConfigRequestedWithoutAdapterAdmin() public { @@ -115,4 +129,29 @@ contract MARKDeployScriptsTest is Test { assertTrue(token.hasRole(token.MINTER_ROLE(), address(module))); assertTrue(token.hasRole(token.BURNER_ROLE(), address(module))); } + + function testDeployMARKSettlementBindsGroth16VerifierModule() public { + vm.prank(deployer); + RYLA token = new RYLA(deployer); + + vm.startPrank(deployer); + Groth16SettlementVerifier groth = new Groth16SettlementVerifier(deployer); + MockScriptGroth16Verifier inner = new MockScriptGroth16Verifier(); + groth.setVerifierContract(address(inner)); + vm.stopPrank(); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(token))); + vm.setEnv("MARK_MODULE_OWNER", vm.toString(deployer)); + vm.setEnv("MARK_SETTLEMENT_OPERATOR", vm.toString(operator)); + vm.setEnv("MARK_SETTLEMENT_VERIFIER", vm.toString(address(groth))); + vm.setEnv("MARK_SETTLEMENT_PROOF_ENABLED", "true"); + vm.setEnv("MARK_DEPLOY_ATTESTED_VERIFIER", "false"); + vm.setEnv("MARK_SETTLEMENT_GROTH16_DIRECTION_ENFORCEMENT", "true"); + assertEq(vm.envAddress("MARK_SETTLEMENT_VERIFIER"), address(groth)); + + MARKSettlementModule module = deploySettlement.run(); + + assertEq(groth.settlementModule(), address(module)); + assertTrue(groth.directionEnforcementEnabled()); + } } diff --git a/contracts/test/unit/MARKPoolDeployScripts.t.sol b/contracts/test/unit/MARKPoolDeployScripts.t.sol new file mode 100644 index 0000000..4c56aab --- /dev/null +++ b/contracts/test/unit/MARKPoolDeployScripts.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLA} from "../../src/token/RYLA.sol"; +import {MARKPool} from "../../src/pool/MARKPool.sol"; +import {MARKWithdrawAdapter} from "../../src/withdraw/MARKWithdrawAdapter.sol"; +import {RYLACreditLedger} from "../../src/pool/RYLACreditLedger.sol"; +import {IVerifier} from "../../src/interfaces/IVerifier.sol"; +import {DeployMARKPool} from "../../script/deploy/pool/DeployMARKPool.s.sol"; + +contract MockVerifier is IVerifier { + function verifyProof( + uint256[2] calldata, + uint256[2][2] calldata, + uint256[2] calldata, + uint256[13] calldata + ) external pure returns (bool) { return true; } +} + +contract MARKPoolDeployScriptsTest is Test { + uint256 internal constant DEPLOYER_PK = 0xA11CE; + + DeployMARKPool internal deployPool; + MockVerifier internal verifier; + + address internal deployer; + address internal intentSigner; + + function setUp() public { + deployPool = new DeployMARKPool(); + verifier = new MockVerifier(); + + deployer = vm.addr(DEPLOYER_PK); + intentSigner = makeAddr("intentSigner"); + + vm.setEnv("PRIVATE_KEY", vm.toString(DEPLOYER_PK)); + vm.setEnv("MARK_POOL_VERIFIER", vm.toString(address(verifier))); + vm.setEnv("MARK_POOL_INTENT_SIGNER", vm.toString(address(0))); + // Deploy local PoseidonT3 (test runner bypasses EIP-170 size check) + address poseidon = deployCode("PoseidonT3.sol:PoseidonT3"); + vm.setEnv("MARK_POOL_POSEIDON", vm.toString(poseidon)); + } + + function testDeployMARKPoolWiresAllContracts() public { + vm.prank(deployer); + RYLA token = new RYLA(deployer); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(token))); + + DeployMARKPool.Deployed memory d = deployPool.run(); + + // Pool wired to ledger + assertEq(address(d.pool.ASSET_LEDGER()), address(d.ledger)); + + // Ledger wired to pool and adapter + assertEq(d.ledger.POOL(), address(d.pool)); + assertEq(d.ledger.ADAPTER(), address(d.adapter)); + + // Adapter wired to ledger and pool + assertEq(address(d.adapter.ASSET_LEDGER()), address(d.ledger)); + assertEq(address(d.adapter.PROOF_POOL()), address(d.pool)); + + // RYLA roles granted to ledger + assertTrue(token.hasRole(token.MINTER_ROLE(), address(d.ledger))); + assertTrue(token.hasRole(token.BURNER_ROLE(), address(d.ledger))); + } + + function testDeployMARKPoolSetsIntentSignerWhenProvided() public { + vm.prank(deployer); + RYLA token = new RYLA(deployer); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(token))); + vm.setEnv("MARK_POOL_INTENT_SIGNER", vm.toString(intentSigner)); + + DeployMARKPool.Deployed memory d = deployPool.run(); + + assertTrue(d.adapter.intentSigners(intentSigner)); + } + + function testDeployMARKPoolRevertsWhenMissingTokenAdmin() public { + address nonAdmin = makeAddr("nonAdmin"); + vm.prank(nonAdmin); + RYLA token = new RYLA(nonAdmin); + + vm.setEnv("MARK_RYLA_TOKEN", vm.toString(address(token))); + + vm.expectRevert(DeployMARKPool.MissingTokenAdminForRoleGrants.selector); + deployPool.run(); + } +} diff --git a/contracts/test/unit/pool/MARKPool.t.sol b/contracts/test/unit/pool/MARKPool.t.sol new file mode 100644 index 0000000..ca5fc98 --- /dev/null +++ b/contracts/test/unit/pool/MARKPool.t.sol @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol"; +import {MARKPool} from "../../../src/pool/MARKPool.sol"; +import {PoolErrors} from "../../../src/pool/errors/PoolErrors.sol"; +import {IVerifier} from "../../../src/interfaces/IVerifier.sol"; +import {ICreditLedger} from "../../../src/interfaces/ICreditLedger.sol"; + +contract MockVerifier is IVerifier { + bool private _result; + constructor(bool result) { _result = result; } + function verifyProof( + uint256[2] calldata, + uint256[2][2] calldata, + uint256[2] calldata, + uint256[13] calldata + ) external view returns (bool) { return _result; } +} + +contract MockLedger is ICreditLedger { + mapping(address => uint256) public balances; + uint256 public minted; + uint256 public burned; + + function credit(address to, uint256 amount) external { balances[to] += amount; minted += amount; } + function debit(address from, uint256 amount) external { balances[from] -= amount; burned += amount; } + function creditBalanceOf(address account) external view returns (uint256) { return balances[account]; } + function totalCreditsMinted() external view returns (uint256) { return minted; } + function totalCreditsBurned() external view returns (uint256) { return burned; } + function totalCreditsOutstanding() external view returns (uint256) { return minted - burned; } + function maxCredits() external pure returns (uint256) { return type(uint256).max; } +} + +contract MARKPoolTest is Test { + MARKPool internal pool; + MockVerifier internal mockOk; + MockVerifier internal mockFail; + MockLedger internal ledger; + AccessManager internal accessManager; + + address internal admin = makeAddr("admin"); + + // Valid BN254 field elements for commitments/nullifiers + bytes32 internal constant C0 = bytes32(uint256(1)); + bytes32 internal constant C1 = bytes32(uint256(2)); + bytes32 internal constant N0 = bytes32(uint256(3)); + bytes32 internal constant N1 = bytes32(uint256(4)); + + uint256[2] internal A; + uint256[2][2] internal B; + uint256[2] internal C_PROOF; + + function setUp() public { + mockOk = new MockVerifier(true); + mockFail = new MockVerifier(false); + ledger = new MockLedger(); + + vm.startPrank(admin); + accessManager = new AccessManager(admin); + address poseidon = deployCode("PoseidonT3.sol:PoseidonT3"); + pool = new MARKPool(address(accessManager), address(mockOk), poseidon); + + // Grant admin all restricted selectors via a custom role (role 1) + bytes4[] memory selectors = new bytes4[](14); + selectors[0] = pool.pause.selector; + selectors[1] = pool.unpause.selector; + selectors[2] = pool.pauseWithdrawals.selector; + selectors[3] = pool.unpauseWithdrawals.selector; + selectors[4] = pool.setVerifier.selector; + selectors[5] = pool.setProofTypeEnabled.selector; + selectors[6] = pool.emergencyDisableProofType.selector; + selectors[7] = pool.setMaxRootAge.selector; + selectors[8] = pool.setFeeBurnBps.selector; + selectors[9] = pool.setMinFee.selector; + selectors[10] = pool.setProtocolEpoch.selector; + selectors[11] = pool.setBridgeOutEntrypoint.selector; + selectors[12] = pool.bridgeIn.selector; + selectors[13] = pool.setAssetLedger.selector; + accessManager.setTargetFunctionRole(address(pool), selectors, 1); + accessManager.grantRole(1, admin, 0); + vm.warp(block.timestamp + 1); // ensure role grant is active + pool.setAssetLedger(address(ledger)); + vm.stopPrank(); + } + + // --- transact --- + + function testTransactHappyPath() public { + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + pool.transact(root, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + + assertTrue(pool.isNullifierUsedGlobal(N0)); + assertTrue(pool.isNullifierUsedGlobal(N1)); + } + + function testTransactRevertsOnNullifierReplay() public { + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + pool.transact(root, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + + // New commitments, same nullifiers + bytes32[2] memory commitments2 = [bytes32(uint256(5)), bytes32(uint256(6))]; + vm.expectRevert(PoolErrors.NullifierUsed.selector); + pool.transact(root, nullifiers, commitments2, 0, address(0), A, B, C_PROOF); + } + + function testTransactRevertsOnUnknownRoot() public { + bytes32 badRoot = bytes32(uint256(999)); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.UnknownRoot.selector); + pool.transact(badRoot, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + } + + function testTransactRevertsOnInvalidProof() public { + // Swap to failing verifier + vm.startPrank(admin); + pool.pauseWithdrawals(); + pool.setVerifier(pool.PROOF_TYPE_TRANSFER(), address(mockFail)); + pool.unpauseWithdrawals(); + vm.stopPrank(); + + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.InvalidProof.selector); + pool.transact(root, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + } + + function testTransactRevertsOnDuplicateNullifiers() public { + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N0]; // duplicate + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.NullifierDuplicate.selector); + pool.transact(root, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + } + + function testTransactRevertsOnDuplicateCommitments() public { + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C0]; // duplicate + + vm.expectRevert(PoolErrors.CommitmentDuplicate.selector); + pool.transact(root, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + } + + function testTransactRevertsWhenWithdrawalsPaused() public { + vm.prank(admin); + pool.pauseWithdrawals(); + + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.WithdrawalsArePaused.selector); + pool.transact(root, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + } + + // --- root expiry --- + + function testRootExpiresAfterMaxRootAge() public { + // First transact to add a new root + bytes32 initialRoot = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + pool.transact(initialRoot, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + + // Set max root age (tightening from 0 requires pause) + vm.startPrank(admin); + pool.pauseWithdrawals(); + pool.setMaxRootAge(1 days); + pool.unpauseWithdrawals(); + vm.stopPrank(); + + // Warp past expiry + vm.warp(block.timestamp + 2 days); + + // initialRoot should now be expired (not the latest root) + assertFalse(pool.isRootUsable(initialRoot)); + } + + function testLatestRootAlwaysUsable() public { + vm.startPrank(admin); + pool.pauseWithdrawals(); + pool.setMaxRootAge(1 days); + pool.unpauseWithdrawals(); + vm.stopPrank(); + + vm.warp(block.timestamp + 2 days); + + // Latest root is always usable regardless of age + assertTrue(pool.isRootUsable(pool.getMerkleRoot())); + } + + // --- fee --- + + function testFeeCreditedToRelayer() public { + address relayer = makeAddr("relayer"); + // feeBurnBps defaults to 0, so all fee goes to relayer — no state change needed + + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + pool.transact(root, nullifiers, commitments, 100, relayer, A, B, C_PROOF); + + assertEq(ledger.balances(relayer), 100); + } + + function testFeeRevertsWithZeroRelayerWhenFeeNonZero() public { + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.InvalidRelayer.selector); + pool.transact(root, nullifiers, commitments, 100, address(0), A, B, C_PROOF); + } + + function testFeeTooLowReverts() public { + vm.prank(admin); + pool.setMinFee(1); + + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.FeeTooLow.selector); + pool.transact(root, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + } + + // --- pruneRoots --- + + function testPruneRootsRemovesExpiredRoots() public { + bytes32 initialRoot = pool.getMerkleRoot(); + + vm.startPrank(admin); + pool.pauseWithdrawals(); + pool.setMaxRootAge(1 days); + pool.unpauseWithdrawals(); + vm.stopPrank(); + + // Add a new root + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + pool.transact(initialRoot, nullifiers, commitments, 0, address(0), A, B, C_PROOF); + + vm.warp(block.timestamp + 2 days); + + uint256 pruned = pool.pruneRoots(10); + assertGt(pruned, 0); + assertFalse(pool.knownRoots(initialRoot)); + } + + // --- bridgeOut access control --- + + function testBridgeOutRevertsWhenEntrypointNotSet() public { + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.BridgeOutDisabled.selector); + pool.bridgeOut(root, nullifiers, commitments, 0, address(0), 902, A, B, C_PROOF); + } + + function testBridgeOutRevertsWhenCallerNotEntrypoint() public { + address entrypoint = makeAddr("entrypoint"); + vm.etch(entrypoint, hex"00"); + + vm.startPrank(admin); + pool.pauseWithdrawals(); + pool.setBridgeOutEntrypoint(entrypoint); + pool.unpauseWithdrawals(); + vm.stopPrank(); + + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.UnauthorizedBridgeOutCaller.selector); + pool.bridgeOut(root, nullifiers, commitments, 0, address(0), 902, A, B, C_PROOF); + } + + // --- bridgeIn access control --- + + function testBridgeInRevertsWhenCallerNotRestricted() public { + bytes32[2] memory commitments = [C0, C1]; + vm.expectRevert(abi.encodeWithSignature("AccessManagedUnauthorized(address)", address(this))); + pool.bridgeIn(901, bytes32(uint256(1)), commitments); + } + + function testBridgeInRevertsOnSameChain() public { + bytes32[2] memory commitments = [C0, C1]; + vm.prank(admin); + vm.expectRevert(PoolErrors.SourceIsDestination.selector); + pool.bridgeIn(block.chainid, bytes32(uint256(1)), commitments); + } + + function testBridgeInRevertsOnMessageReplay() public { + bytes32[2] memory commitments = [C0, C1]; + bytes32 messageId = bytes32(uint256(123)); + + vm.prank(admin); + pool.bridgeIn(901, messageId, commitments); + + vm.prank(admin); + vm.expectRevert(PoolErrors.BridgeMessageAlreadyProcessed.selector); + pool.bridgeIn(901, messageId, commitments); + } + + // --- transactWithWithdrawBinding --- + + function testTransactWithWithdrawBindingRecordsBinding() public { + address owner = makeAddr("owner"); + address recipient = makeAddr("recipient"); + uint256 amount = 1 ether; + + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + pool.transactWithWithdrawBinding(root, nullifiers, commitments, 0, address(0), owner, recipient, amount, A, B, C_PROOF); + + bytes32 expectedBinding = pool.computeWithdrawBindingHash(owner, recipient, amount); + assertEq(pool.nullifierWithdrawBinding(N0), expectedBinding); + assertEq(pool.nullifierWithdrawBinding(N1), expectedBinding); + } + + function testTransactWithWithdrawBindingRevertsOnZeroAmount() public { + bytes32 root = pool.getMerkleRoot(); + bytes32[2] memory nullifiers = [N0, N1]; + bytes32[2] memory commitments = [C0, C1]; + + vm.expectRevert(PoolErrors.InvalidWithdrawAmount.selector); + pool.transactWithWithdrawBinding(root, nullifiers, commitments, 0, address(0), makeAddr("o"), makeAddr("r"), 0, A, B, C_PROOF); + } + + // --- admin config --- + + function testSetVerifierRequiresPauseFirst() public { + // proof type is enabled, withdrawals not paused — setVerifier should revert + MockVerifier newVerifier = new MockVerifier(true); + uint8 proofType = pool.PROOF_TYPE_TRANSFER(); // read before expectRevert + vm.startPrank(admin); + vm.expectRevert(PoolErrors.WithdrawalsNotPaused.selector); + pool.setVerifier(proofType, address(newVerifier)); + vm.stopPrank(); + } + + function testSetProtocolEpochCanOnlyIncrease() public { + vm.startPrank(admin); + pool.pauseWithdrawals(); + pool.setProtocolEpoch(5); + vm.expectRevert(PoolErrors.EpochCanOnlyIncrease.selector); + pool.setProtocolEpoch(3); + vm.stopPrank(); + } + + function testEmergencyDisableProofType() public { + vm.startPrank(admin); + pool.emergencyDisableProofType(pool.PROOF_TYPE_TRANSFER()); + vm.stopPrank(); + assertFalse(pool.proofTypeEnabled(pool.PROOF_TYPE_TRANSFER())); + } + function testConstructorRevertsOnZeroPoseidon() public { + vm.startPrank(admin); + AccessManager am = new AccessManager(admin); + vm.expectRevert(PoolErrors.InvalidPoseidon.selector); + new MARKPool(address(am), address(mockOk), address(0)); + vm.stopPrank(); + } + + function testConstructorRevertsOnEOAPoseidon() public { + vm.startPrank(admin); + AccessManager am = new AccessManager(admin); + vm.expectRevert(PoolErrors.PoseidonMustBeContract.selector); + new MARKPool(address(am), address(mockOk), makeAddr("eoa")); + vm.stopPrank(); + } + +} \ No newline at end of file diff --git a/contracts/test/unit/pool/RYLACreditLedger.t.sol b/contracts/test/unit/pool/RYLACreditLedger.t.sol new file mode 100644 index 0000000..802e7a9 --- /dev/null +++ b/contracts/test/unit/pool/RYLACreditLedger.t.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {RYLACreditLedger} from "../../../src/pool/RYLACreditLedger.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; + +contract RYLACreditLedgerTest is Test { + RYLACreditLedger internal ledger; + RYLA internal token; + + address internal admin = makeAddr("admin"); + address internal pool = makeAddr("pool"); + address internal adapter = makeAddr("adapter"); + address internal user = makeAddr("user"); + address internal other = makeAddr("other"); + + function setUp() public { + // Give pool and adapter contract bytecode so code.length checks pass + vm.etch(pool, hex"00"); + vm.etch(adapter, hex"00"); + + vm.startPrank(admin); + token = new RYLA(admin); + ledger = new RYLACreditLedger(address(token), pool); + ledger.setAdapter(adapter); + token.setMinter(address(ledger), true); + token.setBurner(address(ledger), true); + vm.stopPrank(); + } + + function testCreditMintsToRecipient() public { + vm.prank(pool); + ledger.credit(user, 100e18); + assertEq(token.balanceOf(user), 100e18); + } + + function testCreditTracksTotal() public { + vm.prank(pool); + ledger.credit(user, 100e18); + assertEq(ledger.totalCreditsMinted(), 100e18); + assertEq(ledger.totalCreditsOutstanding(), 100e18); + } + + function testDebitBurnsTokens() public { + vm.prank(pool); + ledger.credit(user, 100e18); + + vm.prank(user); + token.approve(address(ledger), 100e18); + + vm.prank(adapter); + ledger.debit(user, 100e18); + + assertEq(token.balanceOf(user), 0); + assertEq(ledger.totalCreditsBurned(), 100e18); + assertEq(ledger.totalCreditsOutstanding(), 0); + } + + function testCreditRevertsForNonPool() public { + vm.prank(other); + vm.expectRevert(RYLACreditLedger.Unauthorized.selector); + ledger.credit(user, 100e18); + } + + function testDebitRevertsForNonAdapter() public { + vm.prank(other); + vm.expectRevert(RYLACreditLedger.Unauthorized.selector); + ledger.debit(user, 100e18); + } + + function testDebitRevertsForPool() public { + vm.prank(pool); + vm.expectRevert(RYLACreditLedger.Unauthorized.selector); + ledger.debit(user, 100e18); + } + + function testSetAdapterRevertsIfAlreadySet() public { + vm.prank(admin); + vm.expectRevert(RYLACreditLedger.AdapterAlreadySet.selector); + ledger.setAdapter(makeAddr("other-adapter")); + } + + function testSetAdapterRevertsOnZeroAddress() public { + vm.prank(admin); + RYLACreditLedger fresh = new RYLACreditLedger(address(token), pool); + vm.prank(admin); + vm.expectRevert(RYLACreditLedger.ZeroAddress.selector); + fresh.setAdapter(address(0)); + } + + function testSetAdapterRevertsForNonOwner() public { + vm.prank(admin); + RYLACreditLedger fresh = new RYLACreditLedger(address(token), pool); + vm.prank(other); + vm.expectRevert(RYLACreditLedger.Unauthorized.selector); + fresh.setAdapter(makeAddr("attacker")); + } + + function testConstructorRevertsOnZeroToken() public { + vm.expectRevert(RYLACreditLedger.ZeroAddress.selector); + new RYLACreditLedger(address(0), pool); + } + + function testConstructorRevertsOnZeroPool() public { + vm.expectRevert(RYLACreditLedger.ZeroAddress.selector); + new RYLACreditLedger(address(token), address(0)); + } + + function testCreditBalanceOf() public { + vm.prank(pool); + ledger.credit(user, 50e18); + assertEq(ledger.creditBalanceOf(user), 50e18); + } + + function testConstructorRevertsOnEOAToken() public { + vm.expectRevert(RYLACreditLedger.InvalidContract.selector); + new RYLACreditLedger(makeAddr("eoa-token"), pool); + } + + function testConstructorRevertsOnEOAPool() public { + vm.expectRevert(RYLACreditLedger.InvalidContract.selector); + new RYLACreditLedger(address(token), makeAddr("eoa-pool")); + } + + function testSetAdapterRevertsOnEOA() public { + vm.prank(admin); + RYLACreditLedger fresh = new RYLACreditLedger(address(token), pool); + vm.prank(admin); + vm.expectRevert(RYLACreditLedger.InvalidContract.selector); + fresh.setAdapter(makeAddr("eoa-adapter")); + } +} diff --git a/contracts/test/unit/settlement/Groth16SettlementVerifier.t.sol b/contracts/test/unit/settlement/Groth16SettlementVerifier.t.sol index fef788c..161bad0 100644 --- a/contracts/test/unit/settlement/Groth16SettlementVerifier.t.sol +++ b/contracts/test/unit/settlement/Groth16SettlementVerifier.t.sol @@ -5,23 +5,24 @@ import {Test} from "forge-std/Test.sol"; import {Groth16SettlementVerifier} from "../../../src/settlement/verifier/Groth16SettlementVerifier.sol"; import {IGroth16Verifier} from "../../../src/settlement/interfaces/IGroth16Verifier.sol"; -/// @dev Minimal mock that returns a configurable result for any proof. contract MockGroth16Verifier is IGroth16Verifier { bool private _result; - - constructor(bool result) { - _result = result; - } - - function verifyProof(bytes calldata, bytes calldata) external view returns (bool) { - return _result; - } + constructor(bool result) { _result = result; } + function verifyProof( + uint256[2] calldata, + uint256[2][2] calldata, + uint256[2] calldata, + uint256[13] calldata + ) external view returns (bool) { return _result; } } +contract MockSettlementModule {} + contract Groth16SettlementVerifierTest is Test { Groth16SettlementVerifier internal verifier; - MockGroth16Verifier internal mockVerifierOk; - MockGroth16Verifier internal mockVerifierFail; + MockGroth16Verifier internal mockOk; + MockGroth16Verifier internal mockFail; + MockSettlementModule internal mockModule; address internal owner = makeAddr("owner"); address internal module = makeAddr("module"); @@ -33,77 +34,108 @@ contract Groth16SettlementVerifierTest is Test { function setUp() public { vm.prank(owner); verifier = new Groth16SettlementVerifier(owner); - mockVerifierOk = new MockGroth16Verifier(true); - mockVerifierFail = new MockGroth16Verifier(false); - + mockOk = new MockGroth16Verifier(true); + mockFail = new MockGroth16Verifier(false); + mockModule = new MockSettlementModule(); vm.prank(owner); - verifier.setVerifierContract(address(mockVerifierOk)); - } - - function _buildProof(uint256 amount, bool isMint) internal pure returns (bytes memory) { - // Groth16 proof component: 256 bytes (a, b, c — all zeroed for mock) - bytes memory proof = new bytes(256); - // Public signals: nullifierHash, commitmentHash, amount, isMint - uint256[4] memory sigs; - sigs[0] = uint256(keccak256("nullifier")); - sigs[1] = uint256(keccak256("commitment")); - sigs[2] = amount; - sigs[3] = isMint ? 1 : 0; - return abi.encodePacked(proof, abi.encode(sigs)); + verifier.setVerifierContract(address(mockOk)); + vm.prank(owner); + verifier.setSettlementModule(address(mockModule)); + module = address(mockModule); + } + + function _buildProof(bytes32 intentId, address account, uint256 amount) internal view returns (bytes memory) { + return _buildProofWithDirection(intentId, account, amount, 0); + } + + function _buildProofWithDirection( + bytes32 intentId, + address account, + uint256 amount, + uint256 direction + ) internal view returns (bytes memory) { + uint256[2] memory a; + uint256[2][2] memory b; + uint256[2] memory c; + uint256[13] memory signals; + signals[0] = uint256(intentId); + signals[1] = block.chainid; + signals[2] = block.chainid; + signals[6] = uint256(intentId); + signals[7] = direction; + signals[10] = uint256(uint160(account)); + signals[11] = uint256(uint160(account)); + signals[12] = amount; + return abi.encode(a, b, c, signals); } function testVerifySettlementReturnsTrueForValidProof() public view { - bytes memory proof = _buildProof(AMOUNT, true); + bytes memory proof = _buildProof(INTENT, user, AMOUNT); assertTrue(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); } function testVerifySettlementReturnsFalseWhenCircuitVerifierFails() public { vm.prank(owner); - verifier.setVerifierContract(address(mockVerifierFail)); - bytes memory proof = _buildProof(AMOUNT, true); + verifier.setVerifierContract(address(mockFail)); + bytes memory proof = _buildProof(INTENT, user, AMOUNT); assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); } function testVerifySettlementReturnsFalseWhenNoVerifierSet() public { vm.prank(owner); Groth16SettlementVerifier fresh = new Groth16SettlementVerifier(owner); - bytes memory proof = _buildProof(AMOUNT, true); + vm.prank(owner); + fresh.setSettlementModule(address(mockModule)); + bytes memory proof = _buildProof(INTENT, user, AMOUNT); + assertFalse(fresh.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); + } + + function testVerifySettlementReturnsFalseWhenSettlementModuleNotConfigured() public { + vm.prank(owner); + Groth16SettlementVerifier fresh = new Groth16SettlementVerifier(owner); + vm.prank(owner); + fresh.setVerifierContract(address(mockOk)); + bytes memory proof = _buildProof(INTENT, user, AMOUNT); assertFalse(fresh.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); } function testVerifySettlementReturnsFalseForAmountMismatch() public view { - // proof commits to AMOUNT but call passes AMOUNT+1 - bytes memory proof = _buildProof(AMOUNT, true); + bytes memory proof = _buildProof(INTENT, user, AMOUNT); assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT + 1, true, proof)); } - function testVerifySettlementReturnsFalseForIsMintMismatch() public view { - // proof commits to isMint=true but call passes false - bytes memory proof = _buildProof(AMOUNT, true); - assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, false, proof)); + function testVerifySettlementReturnsFalseForIntentMismatch() public view { + bytes memory proof = _buildProof(keccak256("other"), user, AMOUNT); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); } - function testVerifySettlementReturnsFalseForMalformedProof() public view { - assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, hex"1234")); + function testVerifySettlementReturnsFalseForAccountMismatch() public view { + bytes memory proof = _buildProof(INTENT, address(0xdead), AMOUNT); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); } function testVerifySettlementReturnsFalseForZeroIntentId() public view { - bytes memory proof = _buildProof(AMOUNT, true); + bytes memory proof = _buildProof(bytes32(0), user, AMOUNT); assertFalse(verifier.verifySettlement(bytes32(0), module, user, AMOUNT, true, proof)); } function testVerifySettlementReturnsFalseForZeroModule() public view { - bytes memory proof = _buildProof(AMOUNT, true); + bytes memory proof = _buildProof(INTENT, user, AMOUNT); assertFalse(verifier.verifySettlement(INTENT, address(0), user, AMOUNT, true, proof)); } + function testVerifySettlementReturnsFalseForModuleMismatch() public { + bytes memory proof = _buildProof(INTENT, user, AMOUNT); + assertFalse(verifier.verifySettlement(INTENT, makeAddr("other-module"), user, AMOUNT, true, proof)); + } + function testVerifySettlementReturnsFalseForZeroAccount() public view { - bytes memory proof = _buildProof(AMOUNT, true); + bytes memory proof = _buildProof(INTENT, address(0), AMOUNT); assertFalse(verifier.verifySettlement(INTENT, module, address(0), AMOUNT, true, proof)); } function testVerifySettlementReturnsFalseForZeroAmount() public view { - bytes memory proof = _buildProof(0, true); + bytes memory proof = _buildProof(INTENT, user, 0); assertFalse(verifier.verifySettlement(INTENT, module, user, 0, true, proof)); } @@ -118,4 +150,92 @@ contract Groth16SettlementVerifierTest is Test { vm.expectRevert(); verifier.setVerifierContract(makeAddr("eoa")); } -} + + function testSetSettlementModuleRevertsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert(); + verifier.setSettlementModule(address(0)); + } + + function testSetSettlementModuleRevertsForEOA() public { + vm.prank(owner); + vm.expectRevert(); + verifier.setSettlementModule(makeAddr("eoa")); + } + + function testVerifySettlementReturnsFalseForChainIdMismatch() public view { + uint256[2] memory a; + uint256[2][2] memory b; + uint256[2] memory c; + uint256[13] memory signals; + signals[0] = uint256(INTENT); + signals[1] = block.chainid + 1; + signals[2] = block.chainid; + signals[6] = uint256(INTENT); + signals[10] = uint256(uint160(user)); + signals[11] = uint256(uint160(user)); + signals[12] = AMOUNT; + bytes memory proof = abi.encode(a, b, c, signals); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); + } + + function testVerifySettlementReturnsFalseForContextSignalMismatch() public view { + uint256[2] memory a; + uint256[2][2] memory b; + uint256[2] memory c; + uint256[13] memory signals; + signals[0] = uint256(INTENT); + signals[1] = block.chainid; + signals[2] = block.chainid; + signals[4] = 1; // fee must be zero for settlement mapping + signals[6] = uint256(INTENT); + signals[10] = uint256(uint160(user)); + signals[11] = uint256(uint160(user)); + signals[12] = AMOUNT; + bytes memory proof = abi.encode(a, b, c, signals); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); + } + + function testVerifySettlementDirectionEnforcementDisabledAcceptsLegacyZeroDirection() public view { + bytes memory proof = _buildProofWithDirection(INTENT, user, AMOUNT, 0); + assertTrue(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, proof)); + assertTrue(verifier.verifySettlement(INTENT, module, user, AMOUNT, false, proof)); + } + + function testVerifySettlementDirectionEnforcementEnabledAcceptsMatchingDirection() public { + vm.prank(owner); + verifier.setDirectionEnforcementEnabled(true); + + bytes memory mintProof = _buildProofWithDirection(INTENT, user, AMOUNT, 1); + assertTrue(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, mintProof)); + + bytes memory burnProof = _buildProofWithDirection(INTENT, user, AMOUNT, 0); + assertTrue(verifier.verifySettlement(INTENT, module, user, AMOUNT, false, burnProof)); + } + + function testVerifySettlementDirectionEnforcementEnabledRejectsMismatchedDirection() public { + vm.prank(owner); + verifier.setDirectionEnforcementEnabled(true); + + bytes memory mintProofWithZero = _buildProofWithDirection(INTENT, user, AMOUNT, 0); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, true, mintProofWithZero)); + + bytes memory burnProofWithOne = _buildProofWithDirection(INTENT, user, AMOUNT, 1); + assertFalse(verifier.verifySettlement(INTENT, module, user, AMOUNT, false, burnProofWithOne)); + } + function testVerifySettlementReturnsFalseForMalformedProof() public view { + // Malformed proof (wrong length) must return false, not revert. + bool result = verifier.verifySettlement( + INTENT, address(module), user, AMOUNT, true, bytes("malformed") + ); + assertFalse(result); + } + + function testVerifySettlementReturnsFalseForEmptyProof() public view { + bool result = verifier.verifySettlement( + INTENT, address(module), user, AMOUNT, true, bytes("") + ); + assertFalse(result); + } + +} \ No newline at end of file diff --git a/contracts/test/unit/withdraw/MARKWithdrawAdapter.t.sol b/contracts/test/unit/withdraw/MARKWithdrawAdapter.t.sol new file mode 100644 index 0000000..340aff3 --- /dev/null +++ b/contracts/test/unit/withdraw/MARKWithdrawAdapter.t.sol @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {MARKWithdrawAdapter} from "../../../src/withdraw/MARKWithdrawAdapter.sol"; +import {MARKWithdrawErrors} from "../../../src/withdraw/MARKWithdrawErrors.sol"; +import {RYLACreditLedger} from "../../../src/pool/RYLACreditLedger.sol"; +import {RYLA} from "../../../src/token/RYLA.sol"; +import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol"; + +/// @notice Mock Pool for testing WithdrawAdapter. +/// @dev computeWithdrawBindingHash must match Pool.computeWithdrawBindingHash exactly, +/// including the domain separator, address(this), and block.chainid. +contract MockPool { + bytes32 public constant WITHDRAW_BINDING_DOMAIN = + keccak256("MARKPool.WithdrawBinding.v1"); + + mapping(bytes32 => bytes32) public nullifierWithdrawBinding; + mapping(bytes32 => bool) public nullifierUsed; + + function setWithdrawBinding( + bytes32 nullifier, + address owner, + address recipient, + uint256 amount + ) external { + nullifierWithdrawBinding[nullifier] = + computeWithdrawBindingHash(owner, recipient, amount); + nullifierUsed[nullifier] = true; + } + + function computeWithdrawBindingHash( + address owner, + address recipient, + uint256 amount + ) public view returns (bytes32) { + return keccak256( + abi.encode( + WITHDRAW_BINDING_DOMAIN, + address(this), + block.chainid, + owner, + recipient, + amount + ) + ); + } + + function isNullifierUsedGlobal(bytes32 nullifier) + external + view + returns (bool) + { + return nullifierUsed[nullifier]; + } +} + +/// @notice Unit test for WithdrawAdapter +/// @dev Tests withdrawal execution with signature verification +contract MARKWithdrawAdapterTest is Test { + MockPool public pool; + MARKWithdrawAdapter public adapter; + RYLACreditLedger public ledger; + RYLA public token; + AccessManager public accessManager; + + address public admin = address(0x1); + address public intentSigner = address(0x3); + address public user = address(0x4); + address public recipient = address(0x5); + + uint256 public userPrivateKey = 0xA11CE; + uint256 public intentSignerPrivateKey = 0xB0B; + + function setUp() public { + user = vm.addr(userPrivateKey); + intentSigner = vm.addr(intentSignerPrivateKey); + + accessManager = new AccessManager(admin); + + vm.prank(admin); + token = new RYLA(admin); + + pool = new MockPool(); + ledger = new RYLACreditLedger(address(token), address(pool)); + + adapter = new MARKWithdrawAdapter( + address(accessManager), + address(ledger), + address(pool) + ); + ledger.setAdapter(address(adapter)); + + vm.startPrank(admin); + + bytes4[] memory selectors = new bytes4[](4); + selectors[0] = adapter.setIntentSigner.selector; + selectors[1] = adapter.pause.selector; + selectors[2] = adapter.unpause.selector; + selectors[3] = adapter.setMaxIntentValidity.selector; + + accessManager.setTargetFunctionRole(address(adapter), selectors, 1); + accessManager.grantRole(1, admin, 0); + + vm.warp(block.timestamp + 1 days + 1); + + token.grantRole(token.MINTER_ROLE(), address(ledger)); + token.grantRole(token.BURNER_ROLE(), address(ledger)); + + adapter.setIntentSigner(intentSigner, true); + + vm.deal(address(adapter), 100 ether); + + vm.stopPrank(); + } + + function testComputeWithdrawIntentHash() public view { + bytes32[2] memory nullifiers = [ + keccak256("nullifier1"), + keccak256("nullifier2") + ]; + + bytes32 intentHash = adapter.computeWithdrawIntentHash( + user, + recipient, + 1 ether, + nullifiers, + 0, + block.timestamp + 1 hours + ); + + assertTrue(intentHash != bytes32(0)); + } + + function testComputeWithdrawIntentDigest() public view { + bytes32[2] memory nullifiers = [ + keccak256("nullifier1"), + keccak256("nullifier2") + ]; + + bytes32 digest = adapter.computeWithdrawIntentDigest( + user, + recipient, + 1 ether, + nullifiers, + 0, + block.timestamp + 1 hours + ); + + assertTrue(digest != bytes32(0)); + } + + function testWithdrawWithSigHappyPathIncrementsNonceAndClaimsNullifiers() + public + { + bytes32[2] memory nullifiers = [ + keccak256("n-happy-1"), + keccak256("n-happy-2") + ]; + uint256 amount = 2 ether; + uint256 nonce = adapter.withdrawNonce(user); + uint256 deadline = block.timestamp + 30 minutes; + + _configureBindingAndMint(user, recipient, amount, nullifiers); + (bytes memory ownerSig, bytes memory intentSig) = _signWithdraw( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + userPrivateKey, + intentSignerPrivateKey + ); + + uint256 recipientBefore = recipient.balance; + uint256 userTokenBefore = token.balanceOf(user); + + adapter.withdrawWithSig( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + ownerSig, + intentSig + ); + + assertEq(adapter.withdrawNonce(user), nonce + 1); + assertTrue(adapter.claimedNullifiers(nullifiers[0])); + assertTrue(adapter.claimedNullifiers(nullifiers[1])); + assertEq(recipient.balance, recipientBefore + amount); + assertEq(token.balanceOf(user), userTokenBefore - amount); + } + + function testWithdrawWithSigRevertsForInsufficientLiquidity() public { + MARKWithdrawAdapter emptyAdapter = new MARKWithdrawAdapter( + address(accessManager), + address(ledger), + address(pool) + ); + + bytes32[2] memory nullifiers = [ + keccak256("n-empty-1"), + keccak256("n-empty-2") + ]; + uint256 amount = 1 ether; + uint256 nonce = 0; + uint256 deadline = block.timestamp + 30 minutes; + + _configureBindingAndMint(user, recipient, amount, nullifiers); + + bytes32 intentHash = emptyAdapter.computeWithdrawIntentHash( + user, + recipient, + amount, + nullifiers, + nonce, + deadline + ); + bytes32 digest = keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", intentHash) + ); + + (uint8 ov, bytes32 or_, bytes32 os) = vm.sign(userPrivateKey, digest); + (uint8 iv, bytes32 ir, bytes32 is_) = vm.sign( + intentSignerPrivateKey, + digest + ); + + bytes memory ownerSig = abi.encodePacked(or_, os, ov); + bytes memory intentSig = abi.encodePacked(ir, is_, iv); + + vm.expectRevert(MARKWithdrawErrors.InsufficientLiquidity.selector); + emptyAdapter.withdrawWithSig( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + ownerSig, + intentSig + ); + } + + function testWithdrawWithSigRevertsOnReplayByNonce() public { + bytes32[2] memory nullifiers = [ + keccak256("n-replay-1"), + keccak256("n-replay-2") + ]; + uint256 amount = 1 ether; + uint256 nonce = adapter.withdrawNonce(user); + uint256 deadline = block.timestamp + 30 minutes; + + _configureBindingAndMint(user, recipient, amount, nullifiers); + (bytes memory ownerSig, bytes memory intentSig) = _signWithdraw( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + userPrivateKey, + intentSignerPrivateKey + ); + + adapter.withdrawWithSig( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + ownerSig, + intentSig + ); + + vm.expectRevert(MARKWithdrawErrors.NonceMismatch.selector); + adapter.withdrawWithSig( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + ownerSig, + intentSig + ); + } + + function testWithdrawWithSigRevertsOnBindingMismatch() public { + bytes32[2] memory nullifiers = [ + keccak256("n-bind-1"), + keccak256("n-bind-2") + ]; + uint256 amount = 1 ether; + uint256 nonce = adapter.withdrawNonce(user); + uint256 deadline = block.timestamp + 30 minutes; + + _configureBindingAndMint(user, recipient, amount, nullifiers); + (bytes memory ownerSig, bytes memory intentSig) = _signWithdraw( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + userPrivateKey, + intentSignerPrivateKey + ); + + vm.expectRevert(MARKWithdrawErrors.WithdrawBindingMismatch.selector); + adapter.withdrawWithSig( + user, + makeAddr("other-recipient"), + amount, + nullifiers, + nonce, + deadline, + ownerSig, + intentSig + ); + } + + function testWithdrawWithSigRevertsOnUnauthorizedIntentSigner() public { + bytes32[2] memory nullifiers = [ + keccak256("n-auth-1"), + keccak256("n-auth-2") + ]; + uint256 amount = 1 ether; + uint256 nonce = adapter.withdrawNonce(user); + uint256 deadline = block.timestamp + 30 minutes; + + _configureBindingAndMint(user, recipient, amount, nullifiers); + (bytes memory ownerSig, bytes memory intentSig) = _signWithdraw( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + userPrivateKey, + 0xDEAD + ); + + vm.expectRevert(MARKWithdrawErrors.UnauthorizedIntentSigner.selector); + adapter.withdrawWithSig( + user, + recipient, + amount, + nullifiers, + nonce, + deadline, + ownerSig, + intentSig + ); + } + + function testAdapterReceivesNativeTokens() public { + uint256 balanceBefore = address(adapter).balance; + + vm.deal(address(this), 1 ether); + (bool ok, ) = address(adapter).call{value: 1 ether}(""); + + assertTrue(ok); + assertEq(address(adapter).balance, balanceBefore + 1 ether); + } + + function testSetMaxIntentValidity() public { + vm.prank(admin); + adapter.setMaxIntentValidity(2 hours); + + assertEq(adapter.maxIntentValidity(), 2 hours); + } + + function testSetIntentSigner() public { + address newSigner = address(0x999); + + vm.prank(admin); + adapter.setIntentSigner(newSigner, true); + + assertTrue(adapter.intentSigners(newSigner)); + + vm.prank(admin); + adapter.setIntentSigner(newSigner, false); + + assertFalse(adapter.intentSigners(newSigner)); + } + + function testAdapterPause() public { + vm.prank(admin); + adapter.pause(); + + assertTrue(adapter.paused()); + + vm.prank(admin); + adapter.unpause(); + + assertFalse(adapter.paused()); + } + + function _configureBindingAndMint( + address owner, + address withdrawRecipient, + uint256 amount, + bytes32[2] memory nullifiers + ) internal { + pool.setWithdrawBinding(nullifiers[0], owner, withdrawRecipient, amount); + pool.setWithdrawBinding(nullifiers[1], owner, withdrawRecipient, amount); + + vm.prank(address(pool)); + ledger.credit(owner, amount); + + vm.prank(owner); + token.approve(address(ledger), type(uint256).max); + } + + function _signWithdraw( + address owner, + address withdrawRecipient, + uint256 amount, + bytes32[2] memory nullifiers, + uint256 nonce, + uint256 deadline, + uint256 ownerPk, + uint256 intentPk + ) internal view returns (bytes memory ownerSig, bytes memory intentSig) { + bytes32 intentHash = adapter.computeWithdrawIntentHash( + owner, + withdrawRecipient, + amount, + nullifiers, + nonce, + deadline + ); + bytes32 digest = keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", intentHash) + ); + + (uint8 ov, bytes32 or_, bytes32 os) = vm.sign(ownerPk, digest); + (uint8 iv, bytes32 ir, bytes32 is_) = vm.sign(intentPk, digest); + + ownerSig = abi.encodePacked(or_, os, ov); + intentSig = abi.encodePacked(ir, is_, iv); + } + function testWithdrawWithSigRevertsForZeroRecipient() public { + vm.expectRevert(MARKWithdrawErrors.InvalidRecipient.selector); + adapter.withdrawWithSig( + user, + address(0), + 1 ether, + [keccak256("n1"), keccak256("n2")], + 0, + block.timestamp + 1, + bytes(""), + bytes("") + ); + } + +} \ No newline at end of file diff --git a/mprocs.yaml b/mprocs.yaml index 86b023c..662eed1 100644 --- a/mprocs.yaml +++ b/mprocs.yaml @@ -11,6 +11,3 @@ procs: frontend: cwd: . shell: pnpm dev:frontend - deploy-contracts: - cwd: . - shell: pnpm wait-port http://:8420/ready && pnpm deploy:supersim && pnpm deploy:counter-incrementer:supersim diff --git a/package.json b/package.json index e5e440c..286f214 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@eth-optimism/viem": "^0.4.15", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", - "@tailwindcss/vite": "^4.2.4", + "@tailwindcss/vite": "^4.3.0", "@tanstack/react-query": "^5.100.9", "abitype": "^1.2.4", "class-variance-authority": "^0.7.1", @@ -36,8 +36,8 @@ "lucide-react": "^0.475.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "tailwind-merge": "^3.5.0", - "tailwindcss": "^4.2.4", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.0", "tailwindcss-animate": "^1.0.7", "viem": "^2.48.11", "wagmi": "^2.14.11" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index caea721..751a5d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^1.2.4 version: 1.2.4(@types/react@18.3.18)(react@18.3.1) '@tailwindcss/vite': - specifier: ^4.2.4 - version: 4.2.4(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)) + specifier: ^4.3.0 + version: 4.3.0(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)) '@tanstack/react-query': specifier: ^5.100.9 version: 5.100.9(react@18.3.1) @@ -42,14 +42,14 @@ importers: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) tailwind-merge: - specifier: ^3.5.0 - version: 3.5.0 + specifier: ^3.6.0 + version: 3.6.0 tailwindcss: - specifier: ^4.2.4 - version: 4.2.4 + specifier: ^4.3.0 + version: 4.3.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@4.2.4) + version: 1.0.7(tailwindcss@4.3.0) viem: specifier: ^2.48.11 version: 2.48.11(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@6.0.6)(zod@3.24.1) @@ -852,6 +852,7 @@ packages: '@safe-global/safe-gateway-typescript-sdk@3.23.1': resolution: {integrity: sha512-6ORQfwtEJYpalCeVO21L4XXGSdbEMfyp2hEv6cP82afKXSwvse6d3sdelgaPWUxHIsFRkWvHDdzh8IyyKHZKxw==} engines: {node: '>=16'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. '@scure/base@1.1.9': resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} @@ -928,65 +929,65 @@ packages: '@stablelib/x25519@1.0.3': resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==} - '@tailwindcss/node@4.2.4': - resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} - '@tailwindcss/oxide-android-arm64@4.2.4': - resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.4': - resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.4': - resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.4': - resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': - resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': - resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': - resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': - resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.2.4': - resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.2.4': - resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -997,24 +998,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': - resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': - resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.4': - resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.2.4': - resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} + '@tailwindcss/vite@4.3.0': + resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 @@ -1345,8 +1346,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.27: - resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} + baseline-browser-mapping@2.10.29: + resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1690,8 +1691,8 @@ packages: resolution: {integrity: sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} - electron-to-chromium@1.5.352: - resolution: {integrity: sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg==} + electron-to-chromium@1.5.353: + resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==} elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} @@ -1715,8 +1716,8 @@ packages: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - enhanced-resolve@5.21.1: - resolution: {integrity: sha512-8p7DUVq6XJnZEz9W4oSwiwycxBIjHjRzYb3Je3zVN+geKTRQKzAkR/K4PBExlS0090d9nshak6phMUxr3PDjmQ==} + enhanced-resolve@5.21.2: + resolution: {integrity: sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==} engines: {node: '>=10.13.0'} environment@1.1.0: @@ -1951,8 +1952,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -2738,6 +2739,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -2866,16 +2872,16 @@ packages: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} engines: {node: '>=8'} - tailwind-merge@3.5.0: - resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@4.2.4: - resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} tapable@2.3.3: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} @@ -3932,7 +3938,7 @@ snapshots: '@ethereumjs/tx': 4.2.0 '@types/debug': 4.1.13 debug: 4.4.3 - semver: 7.7.4 + semver: 7.8.0 superstruct: 1.0.4 transitivePeerDependencies: - supports-color @@ -3960,7 +3966,7 @@ snapshots: '@types/debug': 4.1.13 debug: 4.4.3 pony-cause: 2.1.11 - semver: 7.7.4 + semver: 7.8.0 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -4268,72 +4274,72 @@ snapshots: '@stablelib/random': 1.0.2 '@stablelib/wipe': 1.0.1 - '@tailwindcss/node@4.2.4': + '@tailwindcss/node@4.3.0': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.1 + enhanced-resolve: 5.21.2 jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.4 + tailwindcss: 4.3.0 - '@tailwindcss/oxide-android-arm64@4.2.4': + '@tailwindcss/oxide-android-arm64@4.3.0': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.4': + '@tailwindcss/oxide-darwin-arm64@4.3.0': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.4': + '@tailwindcss/oxide-darwin-x64@4.3.0': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.4': + '@tailwindcss/oxide-freebsd-x64@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.4': + '@tailwindcss/oxide-linux-x64-musl@4.3.0': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.4': + '@tailwindcss/oxide-wasm32-wasi@4.3.0': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': optional: true - '@tailwindcss/oxide@4.2.4': + '@tailwindcss/oxide@4.3.0': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-x64': 4.2.4 - '@tailwindcss/oxide-freebsd-x64': 4.2.4 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-x64-musl': 4.2.4 - '@tailwindcss/oxide-wasm32-wasi': 4.2.4 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 - - '@tailwindcss/vite@4.2.4(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0))': - dependencies: - '@tailwindcss/node': 4.2.4 - '@tailwindcss/oxide': 4.2.4 - tailwindcss: 4.2.4 + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + + '@tailwindcss/vite@4.3.0(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0))': + dependencies: + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + tailwindcss: 4.3.0 vite: 6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0) '@tanstack/query-core@5.100.9': {} @@ -4980,7 +4986,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.27: {} + baseline-browser-mapping@2.10.29: {} bn.js@4.12.3: {} @@ -5001,9 +5007,9 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.27 + baseline-browser-mapping: 2.10.29 caniuse-lite: 1.0.30001792 - electron-to-chromium: 1.5.352 + electron-to-chromium: 1.5.353 node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -5210,7 +5216,7 @@ snapshots: '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 - electron-to-chromium@1.5.352: {} + electron-to-chromium@1.5.353: {} elliptic@6.6.1: dependencies: @@ -5246,7 +5252,7 @@ snapshots: engine.io-parser@5.2.3: {} - enhanced-resolve@5.21.1: + enhanced-resolve@5.21.2: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -5507,7 +5513,7 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.5.0: {} + get-east-asian-width@1.6.0: {} get-intrinsic@1.3.0: dependencies: @@ -5702,7 +5708,7 @@ snapshots: is-fullwidth-code-point@5.0.0: dependencies: - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 is-generator-function@1.1.2: dependencies: @@ -6284,6 +6290,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.0: {} + set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -6380,7 +6388,7 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.4.0 - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.1.0 string_decoder@1.1.1: @@ -6422,13 +6430,13 @@ snapshots: has-flag: 4.0.0 supports-color: 7.2.0 - tailwind-merge@3.5.0: {} + tailwind-merge@3.6.0: {} - tailwindcss-animate@1.0.7(tailwindcss@4.2.4): + tailwindcss-animate@1.0.7(tailwindcss@4.3.0): dependencies: - tailwindcss: 4.2.4 + tailwindcss: 4.3.0 - tailwindcss@4.2.4: {} + tailwindcss@4.3.0: {} tapable@2.3.3: {} diff --git a/scripts/github/apply-governance.sh b/scripts/github/apply-governance.sh index 4bce9b1..7d4cde3 100755 --- a/scripts/github/apply-governance.sh +++ b/scripts/github/apply-governance.sh @@ -2,22 +2,18 @@ set -euo pipefail # Applies repository governance defaults: -# - branch protection for main and dev +# - branch protection for dev, canary, and main # - creates/updates production environment # +# All branches use 0 required approvals. The sole maintainer cannot approve +# their own PRs, so CI gates are the enforcement mechanism. +# Direct pushes are restricted to the trade/maintainers team on all branches. +# # Required env: # GH_PAT= # Optional env: # GH_REPO=owner/repo (default: inferred from git remote origin) -# MAIN_REVIEW_COUNT=2 -# DEV_REVIEW_COUNT=1 # PRODUCTION_REVIEWER_IDS=12345,67890 # GitHub user IDs -# MAIN_PUSH_ALLOW_USERS=user1,user2 # optional login allowlist for direct push -# MAIN_PUSH_ALLOW_TEAMS=team-slug # optional team slug allowlist -# MAIN_PUSH_ALLOW_APPS=app-slug # optional GitHub App allowlist -# DEV_PUSH_ALLOW_USERS=user1,user2 # optional login allowlist for direct push -# DEV_PUSH_ALLOW_TEAMS=team-slug # optional team slug allowlist -# DEV_PUSH_ALLOW_APPS=app-slug # optional GitHub App allowlist if ! command -v curl >/dev/null 2>&1; then echo "curl is required" >&2 @@ -53,8 +49,6 @@ infer_repo_from_remote() { } GH_REPO="${GH_REPO:-$(infer_repo_from_remote)}" -MAIN_REVIEW_COUNT="${MAIN_REVIEW_COUNT:-2}" -DEV_REVIEW_COUNT="${DEV_REVIEW_COUNT:-1}" owner="${GH_REPO%%/*}" repo="${GH_REPO##*/}" @@ -68,51 +62,6 @@ auth_headers=( echo "Applying governance to ${GH_REPO}..." -csv_to_json_array() { - local csv="${1:-}" - if [[ -z "$csv" ]]; then - echo "[]" - return - fi - - awk -v csv="$csv" 'BEGIN { - n=split(csv, a, ","); - printf("["); - first=1; - for (i=1; i<=n; i++) { - gsub(/^[ \t]+|[ \t]+$/, "", a[i]); - if (a[i] == "") continue; - gsub(/\\/,"\\\\",a[i]); - gsub(/"/,"\\\"",a[i]); - if (!first) printf(","); - printf("\"%s\"", a[i]); - first=0; - } - printf("]"); - }' -} - -build_restrictions_json() { - local users_csv="${1:-}" - local teams_csv="${2:-}" - local apps_csv="${3:-}" - local users_json teams_json apps_json - users_json="$(csv_to_json_array "$users_csv")" - teams_json="$(csv_to_json_array "$teams_csv")" - apps_json="$(csv_to_json_array "$apps_csv")" - - if [[ "$users_json" == "[]" && "$teams_json" == "[]" && "$apps_json" == "[]" ]]; then - echo "null" - return - fi - - jq -n \ - --argjson users "$users_json" \ - --argjson teams "$teams_json" \ - --argjson apps "$apps_json" \ - '{users: $users, teams: $teams, apps: $apps}' -} - apply_branch_protection() { local branch="$1" local review_count="$2" @@ -250,22 +199,20 @@ MAIN_CHECKS_JSON='[ "Validate Release Evidence" ]' -MAIN_RESTRICTIONS_JSON="$(build_restrictions_json "${MAIN_PUSH_ALLOW_USERS:-}" "${MAIN_PUSH_ALLOW_TEAMS:-}" "${MAIN_PUSH_ALLOW_APPS:-}")" -CANARY_RESTRICTIONS_JSON="$(build_restrictions_json "${CANARY_PUSH_ALLOW_USERS:-}" "${CANARY_PUSH_ALLOW_TEAMS:-}" "${CANARY_PUSH_ALLOW_APPS:-}")" -DEV_RESTRICTIONS_JSON="$(build_restrictions_json "${DEV_PUSH_ALLOW_USERS:-}" "${DEV_PUSH_ALLOW_TEAMS:-}" "${DEV_PUSH_ALLOW_APPS:-}")" +MAINTAINERS_RESTRICTIONS_JSON='{"users":[],"teams":["maintainers"],"apps":[]}' -if [[ "$MAIN_RESTRICTIONS_JSON" == "null" ]]; then - echo " - note: MAIN push restrictions not set (provide MAIN_PUSH_ALLOW_* to restrict direct push safely)" +if [[ "$MAINTAINERS_RESTRICTIONS_JSON" == "null" ]]; then + echo " - note: push restrictions not set" fi -# main: strict, no direct pushes -apply_branch_protection "main" "${MAIN_REVIEW_COUNT}" "$MAIN_CHECKS_JSON" "$MAIN_RESTRICTIONS_JSON" +# main: strict, restricted to trade/maintainers team +apply_branch_protection "main" "0" "$MAIN_CHECKS_JSON" "$MAINTAINERS_RESTRICTIONS_JSON" -# canary: PR + checks; stabilisation track -apply_branch_protection "canary" "0" "$CANARY_CHECKS_JSON" "$CANARY_RESTRICTIONS_JSON" +# canary: stabilisation track, restricted to trade/maintainers team +apply_branch_protection "canary" "0" "$CANARY_CHECKS_JSON" "$MAINTAINERS_RESTRICTIONS_JSON" -# dev: PR + checks; direct pushes configurable by changing restrict_pushes here -apply_branch_protection "dev" "${DEV_REVIEW_COUNT}" "$DEV_CHECKS_JSON" "$DEV_RESTRICTIONS_JSON" +# dev: integration track, restricted to trade/maintainers team +apply_branch_protection "dev" "0" "$DEV_CHECKS_JSON" "$MAINTAINERS_RESTRICTIONS_JSON" # Ensure production environment exists echo " - ensuring environment: production" diff --git a/scripts/github/verify-governance.sh b/scripts/github/verify-governance.sh index 0ecf938..0cd42d2 100755 --- a/scripts/github/verify-governance.sh +++ b/scripts/github/verify-governance.sh @@ -82,7 +82,8 @@ get_protection() { check_branch() { local branch="$1" local require_stale="$2" - shift 2 + local expected_approvals="$3" + shift 3 local -a expected=("$@") echo "[verify] branch=${branch}" @@ -105,6 +106,20 @@ check_branch() { fi fi + local actual_approvals + actual_approvals="$(jq -r '.required_pull_request_reviews.required_approving_review_count // 0' <<<"$json")" + if [[ "$actual_approvals" != "$expected_approvals" ]]; then + echo " FAIL: required_approving_review_count is ${actual_approvals}, expected ${expected_approvals} for ${branch}" >&2 + return 1 + fi + + local push_team + push_team="$(jq -r '.restrictions.teams[]?.slug // empty' <<<"$json" | head -1)" + if [[ "$push_team" != "maintainers" ]]; then + echo " FAIL: push restrictions do not include maintainers team for ${branch} (got: ${push_team:-none})" >&2 + return 1 + fi + local missing=0 for check in "${expected[@]}"; do if ! jq -e --arg c "$check" '.required_status_checks.checks[]?.context | select(. == $c)' <<<"$json" >/dev/null; then @@ -120,9 +135,9 @@ check_branch() { echo " PASS" } -# dev has 0 required approvals so dismiss_stale_reviews is not applicable. -check_branch dev false "${require_checks_dev[@]}" -check_branch canary false "${require_checks_dev[@]}" -check_branch main true "${require_checks_main[@]}" +# All branches use 0 required approvals — sole maintainer cannot approve own PRs. +check_branch dev false 0 "${require_checks_dev[@]}" +check_branch canary false 0 "${require_checks_dev[@]}" +check_branch main true 0 "${require_checks_main[@]}" echo "[verify] governance baseline active for ${GH_REPO}" From fd9965dafa947f64e51e33db23f989335e0bb014 Mon Sep 17 00:00:00 2001 From: Iko Date: Sun, 17 May 2026 11:13:31 +0700 Subject: [PATCH 37/38] ci: trigger rerun From d7589e1e07286e7f61df330f2d992d1ffd55ed56 Mon Sep 17 00:00:00 2001 From: Iko Date: Sun, 17 May 2026 11:30:13 +0700 Subject: [PATCH 38/38] fix: restore via_ir=true to foundry.toml (lost in merge) --- contracts/foundry.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 0bd298c..0a7de05 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -8,6 +8,7 @@ broadcast = "broadcast" libs = ["lib"] no_match_path = "test/integration/**" fs_permissions = [{ access = "read-write", path = "./broadcast" }] +via_ir = true remappings = [ "@interop-lib/=lib/interop-lib/src/", "@openzeppelin/=lib/createx/lib/openzeppelin-contracts/" @@ -35,3 +36,6 @@ no_match_path = "test/never/**" + + +