diff --git a/.circleci/config.yml b/.circleci/config.yml index e4cad04e8446f..7e7248c9a0c18 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,7 @@ orbs: slack: circleci/slack@5.1.1 shellcheck: circleci/shellcheck@3.2.0 codecov: codecov/codecov@5.0.3 - utils: ethereum-optimism/circleci-utils@1.0.19 + utils: karlb/circleci-utils@0.1.0 docker: circleci/docker@2.8.2 github-cli: circleci/github-cli@2.7.0 @@ -162,12 +162,8 @@ commands: type: string default: "" steps: - - slack/notify: - channel: << parameters.channel >> - event: fail - template: basic_fail_1 - branch_pattern: develop - mentions: "<< parameters.mentions >>" + - run: + command: "true" # No notifications setup up for celo-org fork - run: name: "Notify Discord" command: | @@ -207,8 +203,9 @@ commands: jobs: cannon-go-lint-and-test: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge parameters: skip_slow_tests: type: boolean @@ -310,8 +307,9 @@ jobs: working_directory: cannon/mipsevm/tests/open_mips_tests diff-asterisc-bytecode: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge steps: - utils/checkout-with-mise - run: @@ -385,28 +383,6 @@ jobs: - "packages/contracts-bedrock/forge-artifacts" - notify-failures-on-develop - check-kontrol-build: - docker: - - image: <> - resource_class: xlarge - steps: - - utils/checkout-with-mise - - attach_workspace: { at: "." } - - install-contracts-dependencies - - check-changed: - patterns: contracts-bedrock - - setup_remote_docker: - docker_layer_caching: true - - run: - name: Run Kontrol build - command: just kontrol-summary-full - working_directory: packages/contracts-bedrock - - run: - name: Build Kontrol summary files - command: forge build ./test/kontrol/proofs - working_directory: packages/contracts-bedrock - - notify-failures-on-develop - docker-build: environment: DOCKER_BUILDKIT: 1 @@ -600,6 +576,7 @@ jobs: --attestor-key-id="//cloudkms.googleapis.com/v1/projects/$ATTESTOR_PROJECT_NAME/locations/global/keyRings/$ATTESTOR_NAME-key-ring/cryptoKeys/$ATTESTOR_NAME-key/cryptoKeyVersions/1" # Verify newly published images (built on AMD machine) will run on ARM + check-cross-platform: docker: - image: <> @@ -824,10 +801,11 @@ jobs: key: forked-state-contracts-bedrock-tests-upgrade-{{ checksum "packages/contracts-bedrock/pinnedBlockNumber.txt" }} - run: name: Run coverage tests - command: just coverage-lcov-all <> + command: | + export ETH_RPC_URL="$MAINNET_RPC_URL" + just coverage-lcov-all <> environment: FOUNDRY_PROFILE: <> - ETH_RPC_URL: https://ci-mainnet-l1-archive.optimism.io working_directory: packages/contracts-bedrock no_output_timeout: <> - run: @@ -836,7 +814,6 @@ jobs: just test-rerun | tee failed-test-traces.log environment: FOUNDRY_PROFILE: <> - ETH_RPC_URL: https://ci-mainnet-l1-archive.optimism.io working_directory: packages/contracts-bedrock when: on_fail - codecov/upload: @@ -896,7 +873,9 @@ jobs: key: forked-state-contracts-bedrock-tests-upgrade-<>-<>-{{ checksum "packages/contracts-bedrock/pinnedBlockNumber.txt" }} - run: name: Run tests - command: just test-upgrade + command: | + export ETH_RPC_URL="$MAINNET_RPC_URL" + just test-upgrade environment: FOUNDRY_FUZZ_SEED: 42424242 FOUNDRY_FUZZ_RUNS: 1 @@ -936,8 +915,9 @@ jobs: - notify-failures-on-develop contracts-bedrock-checks: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge steps: - utils/checkout-with-mise - attach_workspace: { at: "." } @@ -1029,8 +1009,9 @@ jobs: when: always go-lint: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge steps: - utils/checkout-with-mise - run: @@ -1052,7 +1033,7 @@ jobs: resource_class: description: Machine resource class type: string - default: ethereum-optimism/latitude-1-go-e2e + default: xlarge no_output_timeout: description: Timeout for when CircleCI kills the job if there's no output type: string @@ -1074,6 +1055,9 @@ jobs: - utils/checkout-with-mise - attach_workspace: at: "." + - run: + name: do mise install (did not work otherwise for unknown reason) + command: /home/circleci/.local/bin/mise install - run: name: build op-program-client command: make op-program-client @@ -1105,8 +1089,8 @@ jobs: export OP_E2E_SKIP_SLOW_TEST=true export OP_E2E_USE_HTTP=true export ENABLE_ANVIL=true - export SEPOLIA_RPC_URL="https://ci-sepolia-l1-archive.optimism.io" - export MAINNET_RPC_URL="https://ci-mainnet-l1-archive.optimism.io" + export SEPOLIA_RPC_URL="$SEPOLIA_RPC_URL" + export MAINNET_RPC_URL="$MAINNET_RPC_URL" export PARALLEL=$(nproc) export OP_TESTLOG_FILE_LOGGER_OUTDIR=$(realpath ./tmp/testlogs) @@ -1271,10 +1255,10 @@ jobs: command: make sanitize-program GUEST_PROGRAM=../op-program/bin/op-program-client.elf working_directory: cannon - cannon-prestate-quick: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge steps: - utils/checkout-with-mise - restore_cache: @@ -1313,8 +1297,9 @@ jobs: - "op-program/bin/meta*" publish-cannon-prestates: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge steps: - utils/checkout-with-mise - attach_workspace: @@ -1391,7 +1376,7 @@ jobs: parameters: diff_branch: type: string - default: develop + default: HEAD scan_command: type: string default: semgrep ci --timeout=100 @@ -1469,8 +1454,9 @@ jobs: working_directory: op-program op-program-compat: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge steps: - utils/checkout-with-mise - run: @@ -1480,8 +1466,9 @@ jobs: working_directory: op-program check-generated-mocks-op-node: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge steps: - utils/checkout-with-mise - check-changed: @@ -1491,8 +1478,9 @@ jobs: command: make generate-mocks-op-node && git diff --exit-code check-generated-mocks-op-service: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: cimg/base:2025.02 + resource_class: xlarge steps: - utils/checkout-with-mise - check-changed: @@ -1533,33 +1521,6 @@ jobs: working_directory: ./packages/contracts-bedrock - notify-failures-on-develop - publish-contract-artifacts: - machine: true - resource_class: ethereum-optimism/latitude-1 - steps: - - gcp-cli/install - - gcp-oidc-authenticate: - gcp_cred_config_file_path: /tmp/gcp_cred_config.json - oidc_token_file_path: /tmp/oidc_token.json - project_id: GCP_TOOLS_ARTIFACTS_PROJECT_ID - service_account_email: GCP_CONTRACTS_PUBLISHER_SERVICE_ACCOUNT_EMAIL - - utils/checkout-with-mise - - install-contracts-dependencies - - run: - name: Pull artifacts - command: bash scripts/ops/pull-artifacts.sh - working_directory: packages/contracts-bedrock - - run: - name: Build contracts - environment: - FOUNDRY_PROFILE: ci - command: just forge-build - working_directory: packages/contracts-bedrock - - run: - name: Publish artifacts - command: bash scripts/ops/publish-artifacts.sh - working_directory: packages/contracts-bedrock - go-release: parameters: module: @@ -1674,12 +1635,6 @@ workflows: context: - circleci-repo-readonly-authenticated-github-token - discord - - check-kontrol-build: - requires: - - contracts-bedrock-build - context: - - circleci-repo-readonly-authenticated-github-token - - discord - contracts-bedrock-tests: # Test everything except PreimageOracle.t.sol since it's slow. name: contracts-bedrock-tests @@ -1694,15 +1649,6 @@ workflows: context: - circleci-repo-readonly-authenticated-github-token - discord - - contracts-bedrock-tests: - # Heavily fuzz any fuzz tests within added or modified test files. - name: contracts-bedrock-tests-heavy-fuzz-modified - test_list: git diff origin/develop...HEAD --name-only --diff-filter=AM -- './test/**/*.t.sol' | sed 's|packages/contracts-bedrock/||' - test_timeout: 1h - test_profile: ciheavy - context: - - circleci-repo-readonly-authenticated-github-token - - discord - contracts-bedrock-coverage: # Generate coverage reports. name: contracts-bedrock-coverage @@ -1722,42 +1668,12 @@ workflows: context: - circleci-repo-readonly-authenticated-github-token - discord - - contracts-bedrock-tests-upgrade: - name: contracts-bedrock-tests-upgrade base-mainnet - fork_op_chain: base - fork_base_chain: mainnet - fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io - context: - - circleci-repo-readonly-authenticated-github-token - - discord - - contracts-bedrock-tests-upgrade: - name: contracts-bedrock-tests-upgrade ink-mainnet - fork_op_chain: ink - fork_base_chain: mainnet - fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io - context: - - circleci-repo-readonly-authenticated-github-token - - discord - - contracts-bedrock-tests-upgrade: - name: contracts-bedrock-tests-upgrade unichain-mainnet - fork_op_chain: unichain - fork_base_chain: mainnet - fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io - context: - - circleci-repo-readonly-authenticated-github-token - - discord - contracts-bedrock-checks: requires: - contracts-bedrock-build context: - circleci-repo-readonly-authenticated-github-token - discord - - contracts-bedrock-frozen-code: - requires: - - contracts-bedrock-build - context: - - circleci-repo-readonly-authenticated-github-token - - discord - diff-fetcher-forge-artifacts: context: - circleci-repo-readonly-authenticated-github-token @@ -1778,6 +1694,10 @@ workflows: context: - circleci-repo-readonly-authenticated-github-token - discord + - cannon-prestate-quick: + context: + - circleci-repo-readonly-authenticated-github-token + - discord - go-lint: context: - circleci-repo-readonly-authenticated-github-token @@ -1855,10 +1775,10 @@ workflows: - discord # TODO(#15353) - Need to regenerate data used in op-program-compat and then reenable this test # See: https://github.com/ethereum-optimism/chain-test-data?tab=readme-ov-file#generating-new-data -# - op-program-compat: -# context: -# - circleci-repo-readonly-authenticated-github-token -# - discord + # - op-program-compat: + # context: + # - circleci-repo-readonly-authenticated-github-token + # - discord - bedrock-go-tests: requires: - go-lint @@ -1871,40 +1791,11 @@ workflows: # See: https://github.com/ethereum-optimism/chain-test-data?tab=readme-ov-file#generating-new-data # - op-program-compat # Temporarily disable this requirement # Not needed for the devnet but we want to make sure they build successfully - - cannon-docker-build - - op-dispute-mon-docker-build - - op-program-docker-build - - op-supervisor-docker-build - go-tests - sanitize-op-program context: - circleci-repo-readonly-authenticated-github-tokens - discord - - docker-build: - name: <>-docker-build - docker_tags: <>,<> - save_image_tag: <> - matrix: - parameters: - docker_name: - - op-node - - op-batcher - - op-program - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - da-server - - op-supervisor - - cannon - - op-dripper - context: - - circleci-repo-readonly-authenticated-github-token - - discord - - cannon-prestate-quick: - context: - - circleci-repo-readonly-authenticated-github-token - - discord - sanitize-op-program: requires: - cannon-prestate-quick @@ -1935,19 +1826,6 @@ workflows: context: - circleci-repo-readonly-authenticated-github-token - discord - - todo-issues: - name: todo-issues-check - check_closed: false - context: - - circleci-repo-readonly-authenticated-github-token - - discord - - shellcheck/check: - name: shell-check - # We don't need the `exclude` key as the orb detects the `.shellcheckrc` - dir: . - ignore-dirs: ./packages/contracts-bedrock/lib - context: - - circleci-repo-readonly-authenticated-github-token go-release-deployer: jobs: @@ -2114,7 +1992,7 @@ workflows: mentions: "@proofs-team" no_output_timeout: 60m test_timeout: 59m - resource_class: ethereum-optimism/latitude-fps-1 + resource_class: xlarge environment_overrides: | export OP_E2E_CANNON_ENABLED="true" export PARALLEL=24 diff --git a/.dockerignore b/.dockerignore index 11e5eb817c5ab..fef57cbe46cd9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,3 +11,6 @@ build/_workspace build/bin build/_bin tests/testdata + +# Ignore generated credentials from google-github-actions/auth +gha-creds-*.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5a416c9a7bc51..c7c676e4bdd85 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,7 @@ updates: day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 commit-message: prefix: "dependabot(gomod): " labels: diff --git a/.github/workflows/contracts-celo.yaml b/.github/workflows/contracts-celo.yaml new file mode 100644 index 0000000000000..4db01c9406a1d --- /dev/null +++ b/.github/workflows/contracts-celo.yaml @@ -0,0 +1,99 @@ +name: Alfajores-Holesky Deploy Celo4 L1 Contracts +on: + workflow_dispatch: + inputs: + deploy_contracts: + required: false + type: boolean + default: true + contracts_tag: + required: false + type: string + default: 'celo4' + deployment_context: + required: false + type: string + default: 'test-celo4' + l2_chain_id: + required: false + default: '42069' + +jobs: + deploy-contracts: + runs-on: ubuntu-latest + permissions: # Must change the job token permissions to use Akeyless JWT auth + id-token: write + contents: read + if: ${{ ! startsWith(github.triggering_actor, 'akeyless') }} + env: + DEPLOY_CONTRACTS: ${{ github.event_name == 'push' && 'true' || inputs.deploy_contracts }} + CONTRACTS_TAG: ${{ github.event_name == 'push' && 'op-contracts/v1.3.0' || inputs.contracts_tag }} + DEPLOYMENT_CONTEXT: ${{ github.event_name == 'push' && 'test' || inputs.deployment_context }} + L2_CHAIN_ID: ${{ github.event_name == 'push' && '42069' || inputs.l2_chain_id }} + L1_CHAIN_ID: '17000' # Holesky + L1_RPC_URL: 'https://ethereum-holesky-rpc.publicnode.com' + GS_ADMIN_ADDRESS: '0xb2397dF29AFB4B4661559436180019bEb7912985' + GS_BATCHER_ADDRESS: '0x7fDBe8F4D22ab511340667d7Ce5675568d09eBB4' + GS_PROPOSER_ADDRESS: '0xdCf30236Fa0aBE2ca0BEc2eE0a2F40b16A144DB3' + GS_SEQUENCER_ADDRESS: '0x3e2Df8efB6fA1d6E6021572a99BB67BA9ab2C59D' + steps: + + - name: "Get GitHub Token from Akeyless" + id: get_auth_token + uses: + docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + dynamic-secrets: '{"/dynamic-secrets/keys/github/optimism/contents=write,pull_requests=write":"PAT"}' + + # "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/HOLESKY_QUICKNODE_URL":"L1_RPC_URL", + - name: Akeyless get secrets + uses: docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + static-secrets: '{ + "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/GS_ADMIN_PRIVATE_KEY":"GS_ADMIN_PRIVATE_KEY" + }' + + - name: "Checkout" + uses: actions/checkout@v4 + with: + token: ${{ env.PAT }} + submodules: recursive + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Generate config JSON + run: | + cd packages/contracts-bedrock + ./scripts/getting-started/config-vars-celo.sh + + - name: Deploy L1 contracts + if: ${{ env.DEPLOY_CONTRACTS != 'false' }} + run: | + export IMPL_SALT=$(openssl rand -hex 32) + cd packages/contracts-bedrock + echo "Broadcasting ..." + forge script scripts/Deploy.s.sol:Deploy --private-key $GS_ADMIN_PRIVATE_KEY --broadcast --rpc-url $L1_RPC_URL --legacy + + - name: Generate genesis files + run: | + mkdir -p l2-config-files/$DEPLOYMENT_CONTEXT + cd op-node + go run cmd/main.go genesis l2 \ + --deploy-config ../packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json \ + --l1-deployments ../packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy \ + --outfile.l2 ../l2-config-files/$DEPLOYMENT_CONTEXT/genesis-$(date +%s).json \ + --outfile.rollup ../l2-config-files/$DEPLOYMENT_CONTEXT/rollup-$(date +%s).json \ + --l1-rpc $L1_RPC_URL + + - name: "Commit genesis files" + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: '[Automatic] - Commit genesis files' + branch: alvarof2/contracts + file_pattern: 'l2-config-files packages/contracts-bedrock/**' diff --git a/.github/workflows/contracts-op-stack.yaml b/.github/workflows/contracts-op-stack.yaml new file mode 100644 index 0000000000000..7c7efb2409c08 --- /dev/null +++ b/.github/workflows/contracts-op-stack.yaml @@ -0,0 +1,117 @@ +name: Alfajores-Holesky Deploy OP-Stack L1 Contracts +on: + workflow_dispatch: + inputs: + deploy_contracts: + required: false + type: boolean + default: true + contracts_tag: + required: false + type: string + default: 'op-contracts/v1.3.0' + deployment_context: + required: false + type: string + default: 'test-alvaro' + l2_chain_id: + required: false + default: '42069' + +jobs: + deploy-contracts: + runs-on: ubuntu-latest + permissions: # Must change the job token permissions to use Akeyless JWT auth + id-token: write + contents: read + if: ${{ ! startsWith(github.triggering_actor, 'akeyless') }} + env: + DEPLOY_CONTRACTS: ${{ github.event_name == 'push' && 'true' || inputs.deploy_contracts }} + CONTRACTS_TAG: ${{ github.event_name == 'push' && 'op-contracts/v1.3.0' || inputs.contracts_tag }} + DEPLOYMENT_CONTEXT: ${{ github.event_name == 'push' && 'test' || inputs.deployment_context }} + L2_CHAIN_ID: ${{ github.event_name == 'push' && '42069' || inputs.l2_chain_id }} + L1_CHAIN_ID: '17000' # Holesky + L1_RPC_URL: 'https://ethereum-holesky-rpc.publicnode.com' + GS_ADMIN_ADDRESS: '0xb2397dF29AFB4B4661559436180019bEb7912985' + GS_BATCHER_ADDRESS: '0x7fDBe8F4D22ab511340667d7Ce5675568d09eBB4' + GS_PROPOSER_ADDRESS: '0xdCf30236Fa0aBE2ca0BEc2eE0a2F40b16A144DB3' + GS_SEQUENCER_ADDRESS: '0x3e2Df8efB6fA1d6E6021572a99BB67BA9ab2C59D' + steps: + + - name: "Get GitHub Token from Akeyless" + id: get_auth_token + uses: + docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + dynamic-secrets: '{"/dynamic-secrets/keys/github/optimism/contents=write,pull_requests=write":"PAT"}' + + # "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/HOLESKY_QUICKNODE_URL":"L1_RPC_URL", + - name: Akeyless get secrets + uses: docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + static-secrets: '{ + "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/GS_ADMIN_PRIVATE_KEY":"GS_ADMIN_PRIVATE_KEY" + }' + + - name: "Checkout" + uses: actions/checkout@v4 + with: + token: ${{ env.PAT }} + submodules: recursive + fetch-depth: 0 + + - name: "Checkout OP Repo" + uses: actions/checkout@v4 + with: + repository: 'ethereum-optimism/optimism' + ref: '${{ env.CONTRACTS_TAG }}' + path: ethereum-optimism + submodules: recursive + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Generate config JSON + run: | + cd packages/contracts-bedrock + ./scripts/getting-started/config-vars-op-stack.sh + cp deploy-config/$DEPLOYMENT_CONTEXT.json /home/runner/work/optimism/optimism/ethereum-optimism/packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json + + - name: Deploy L1 contracts + if: ${{ env.DEPLOY_CONTRACTS != 'false' }} + run: | + export IMPL_SALT=$(openssl rand -hex 32) + cd ethereum-optimism/packages/contracts-bedrock + echo "Broadcasting ..." + forge script scripts/Deploy.s.sol:Deploy --private-key $GS_ADMIN_PRIVATE_KEY --broadcast --rpc-url $L1_RPC_URL --legacy + mkdir -p /home/runner/work/optimism/optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT + cp deployments/$DEPLOYMENT_CONTEXT/.deploy /home/runner/work/optimism/optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy + + - name: Copy old .deploy file if contracts not deployed + if: ${{ env.DEPLOY_CONTRACTS == 'false' }} + run: | + mkdir -p ethereum-optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT + cp packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy ethereum-optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy + + - name: Generate genesis files + run: | + mkdir -p l2-config-files/$DEPLOYMENT_CONTEXT + cd ethereum-optimism/op-node + go run cmd/main.go genesis l2 \ + --deploy-config ../packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json \ + --l1-deployments ../packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy \ + --outfile.l2 ../../l2-config-files/$DEPLOYMENT_CONTEXT/genesis-$(date +%s).json \ + --outfile.rollup ../../l2-config-files/$DEPLOYMENT_CONTEXT/rollup-$(date +%s).json \ + --l1-rpc $L1_RPC_URL + + - name: "Commit genesis files" + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: '[Automatic] - Commit genesis files' + branch: alvarof2/contracts + file_pattern: 'l2-config-files packages/contracts-bedrock/**' diff --git a/.github/workflows/docker-build-scan.yaml b/.github/workflows/docker-build-scan.yaml new file mode 100644 index 0000000000000..dbf614e84ef12 --- /dev/null +++ b/.github/workflows/docker-build-scan.yaml @@ -0,0 +1,70 @@ +name: Docker Build Scan +on: + pull_request: + branches: + - 'master' + - 'celo*' + push: + branches: + - 'master' + - 'celo*' + workflow_dispatch: + +jobs: + detect-files-changed: + runs-on: ubuntu-latest + outputs: + files-changed: ${{ steps.detect-files-changed.outputs.all_changed_files }} + steps: + - uses: actions/checkout@v4 + - name: Detect files changed + id: detect-files-changed + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 + with: + separator: ',' + + # Build op-node op-batcher op-proposer using docker-bake + build-op-stack: + runs-on: ubuntu-latest + needs: detect-files-changed + if: | + contains(needs.detect-files-changed.outputs.files-changed, 'go.sum') || + contains(needs.detect-files-changed.outputs.files-changed, 'ops/docker') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-node/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-batcher/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-conductor/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-challenger/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-dispute-mon/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-proposer/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-service/') || + contains(needs.detect-files-changed.outputs.files-changed, '.github/workflows/docker-build-scan.yaml') || + github.event_name == 'workflow_dispatch' || + true + permissions: + contents: read + id-token: write + security-events: write + env: + GIT_COMMIT: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + GIT_DATE: ${{ github.event.head_commit.timestamp }} + IMAGE_TAGS: ${{ (github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/celo')) && 'latest,' || '') }}${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + REGISTRY: us-west1-docker.pkg.dev + REPOSITORY: blockchaintestsglobaltestnet/dev-images + steps: + - uses: actions/checkout@v4 + - name: Login at GCP Artifact Registry + uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@v2.0 + with: + workload-id-provider: 'projects/1094498259535/locations/global/workloadIdentityPools/gh-optimism/providers/github-by-repos' + service-account: 'celo-optimism-gh@devopsre.iam.gserviceaccount.com' + docker-gcp-registries: us-west1-docker.pkg.dev + # We need a custom steps as it's using docker bake + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push + uses: docker/bake-action@v5 + with: + push: true + source: . + files: docker-bake.hcl + targets: op-node,op-batcher,op-proposer,op-conductor,op-challenger,op-dispute-mon diff --git a/.github/workflows/docker-op-ufm-build-push.yaml b/.github/workflows/docker-op-ufm-build-push.yaml new file mode 100644 index 0000000000000..e4a0ab33b1033 --- /dev/null +++ b/.github/workflows/docker-op-ufm-build-push.yaml @@ -0,0 +1,41 @@ +--- +name: Build op-ufm container and push to cLabs registry +on: + push: + branches: + - cel4 + paths: + # Run if any of the following files are changed + - 'op-ufm/**' + workflow_dispatch: + +jobs: + build: + runs-on: ['self-hosted', 'org', '8-cpu'] + permissions: # Required for workload identity auth and push the trivy results to GitHub + contents: read + id-token: write + security-events: write + steps: + + - name: Checkout + uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@main + with: + workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-optimism/providers/github-by-repos + service-account: celo-optimism-gh@devopsre.iam.gserviceaccount.com + access-token-lifetime: "60m" + docker-gcp-registries: us-west1-docker.pkg.dev + + - name: Build, push and scan the container + uses: celo-org/reusable-workflows/.github/actions/build-container@main + with: + platforms: linux/amd64 + registry: us-west1-docker.pkg.dev/devopsre/dev-images/op-ufm + tags: test + context: . + dockerfile: op-ufm/Dockerfile + push: true + trivy: false diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000000000..075443e414da2 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,36 @@ +name: Build and publish forkdiff github-pages +permissions: + contents: write +on: + push: + branches: + - celo[0-9]+ + + workflow_dispatch: + +jobs: + deploy: + concurrency: ci-${{ github.ref }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # make sure to fetch the old commit we diff against + + - name: Build forkdiff + uses: "docker://protolambda/forkdiff:0.1.0" + with: + args: -repo=/github/workspace -fork=/github/workspace/fork.yaml -out=/github/workspace/index.html + + - name: Build pages + run: | + mkdir -p tmp/pages + mv index.html tmp/pages/index.html + touch tmp/pages/.nojekyll + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: tmp/pages + clean: true diff --git a/.github/workflows/update-geth.yaml b/.github/workflows/update-geth.yaml new file mode 100644 index 0000000000000..9fa2e2febc84d --- /dev/null +++ b/.github/workflows/update-geth.yaml @@ -0,0 +1,64 @@ +name: "Update celo-org/op-geth" +on: + schedule: + - cron: "00 8 * * Mon" + workflow_dispatch: + +env: + OP_GETH_BASE_BRANCH: "celo-rebase-13" + +jobs: + job_id: + # Add "id-token" with the intended permissions. + permissions: + contents: write + pull-requests: write + id-token: "write" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Login at GCP Artifact Registry + uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@v2.0 + with: + workload-id-provider: "projects/1094498259535/locations/global/workloadIdentityPools/gh-optimism-ro/providers/github-by-repos" + service-account: "celo-optimism-gh-ro@devopsre.iam.gserviceaccount.com" + docker-gcp-registries: us-west1-docker.pkg.dev + access-token-lifetime: "2m" + - name: "Set up Cloud SDK" + uses: "google-github-actions/setup-gcloud@v2" + with: + version: ">= 363.0.0" + - name: Run the update-geth script + id: geth-update-script + run: | + GETH_COMMIT=$(git ls-remote https://github.com/celo-org/op-geth/ "$OP_GETH_BASE_BRANCH" | awk '{print $1}') + if [ -z "$GETH_COMMIT" ]; then + echo "Could not find branch '$OP_GETH_BASE_BRANCH' in 'celo-org/op-geth'" >&2 + exit 1 + fi + echo "GETH_COMMIT=${GETH_COMMIT}" >> $GITHUB_OUTPUT + python3 ops/scripts/celo-update-op-geth.py "$OP_GETH_BASE_BRANCH" + - name: Create pull request + uses: peter-evans/create-pull-request@v7 + env: + TITLE: "[Automatic] - Update op-geth dependencies" + MESSAGE: | + Update the go package dependency and the devnet + docker container reference of the `l2` service + to the latest commit (`${{ steps.geth-update-script.outputs.GETH_COMMIT }}`) + in the `${{ env.OP_GETH_BASE_BRANCH }}` ref. + with: + add-paths: | + go.mod + go.sum + commit-message: | + ${{ env.TITLE }} + + ${{ env.MESSAGE }} + signoff: false + branch: update/op-geth + base: "${{ env.OP_GETH_BASE_BRANCH }}" + delete-branch: true + title: "${{ env.TITLE }}" + body: "${{ env.MESSAGE }}" + draft: false diff --git a/.gitignore b/.gitignore index ea86d7c683480..4c08736af3304 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,12 @@ __pycache__ # Ignore echidna artifacts crytic-export + +# Ignore generated credentials from google-github-actions/auth +gha-creds-*.json + +# vscode +.vscode/ + +# Ignore generated credentials from google-github-actions/auth +gha-creds-*.json diff --git a/cannon/mipsevm/testutil/evm.go b/cannon/mipsevm/testutil/evm.go index 25f05f124baf6..989df74960269 100644 --- a/cannon/mipsevm/testutil/evm.go +++ b/cannon/mipsevm/testutil/evm.go @@ -109,7 +109,9 @@ func NewEVMEnv(contracts *ContractMetadata) (*vm.EVM, *state.StateDB) { if err != nil { panic(fmt.Errorf("failed to create memory state db: %w", err)) } - blockContext := core.NewEVMBlockContext(header, bc, nil, chainCfg, state) + + feeCurrencyContext := core.GetFeeCurrencyContext(header, chainCfg, state) + blockContext := core.NewEVMBlockContext(header, bc, nil, chainCfg, state, feeCurrencyContext) vmCfg := vm.Config{} env := vm.NewEVM(blockContext, state, chainCfg, vmCfg) diff --git a/devnet-sdk/system/periphery/go-ethereum/fees_test.go b/devnet-sdk/system/periphery/go-ethereum/fees_test.go index ffe375bf93f60..809447e8c571a 100644 --- a/devnet-sdk/system/periphery/go-ethereum/fees_test.go +++ b/devnet-sdk/system/periphery/go-ethereum/fees_test.go @@ -274,3 +274,11 @@ func (m *mockBlockType) HasOptimismWithdrawalsRoot(blkTime uint64) bool { func (m *mockBlockType) IsIsthmus(blkTime uint64) bool { return false } + +func (m *mockBlockType) IsGingerbread(blockNum *big.Int) bool { + return false +} + +func (m *mockBlockType) IsMigratedChain() bool { + return false +} diff --git a/fork.yaml b/fork.yaml new file mode 100644 index 0000000000000..1cd9d2a527d0a --- /dev/null +++ b/fork.yaml @@ -0,0 +1,141 @@ +title: "CELO <> OP optimism forkdiff" +footer: | + Fork-diff overview of changes made in [Celo's `optimism`](https://github.com/celo-org/optimism), + a fork of [Optimism's `optimism`](https://github.com/ethereum-optimism/optimism). + +base: + name: OP + url: https://github.com/celo-org/optimism + ref: refs/remotes/origin/celo11-upstream +fork: + name: CELO + url: https://github.com/celo-org/optimism + ref: HEAD +def: + title: "Celo's optimism" + description: | + This is an overview of the changes in [Celo's `optimism` implementation](https://github.com/celo-org/optimism), + a fork of [Optimism's `optimism`](https://github.com/ethereum-optimism/optimism). + + Changes are currently separated by sub-package or component. Check out the [README](https://github.com/celo-org/optimism/blob/develop/README.md) + for more details about each of these components and packages. + + sub: + - title: "packages/*" + description: "" + sub: + - title: "common-ts" + description: "" + globs: + - "packages/common-ts/*" + - title: "contracts-bedrock" + description: "" + globs: + - "packages/contracts-bedrock/*" + - "packages/contracts-bedrock/*/*" + - "packages/contracts-bedrock/*/*/*" + - title: "core-utils" + description: "" + globs: + - "packages/core-utils/*" + - title: "chain-mon" + description: "" + globs: + - "packages/chain-mon/*" + - title: "sdk" + description: "" + globs: + - "packages/sdk/*" + - title: "op-bindings" + description: "" + globs: + - "op-bindings/*" + - "op-bindings/*/*" + - title: "op-batcher" + description: "" + globs: + - "op-batcher/*" + - title: "op-bootnode" + description: "" + globs: + - "op-bootnode/*" + - title: "op-chain-ops" + description: "" + globs: + - "op-chain-ops/*" + - "op-chain-ops/*/*" + - "op-chain-ops/*/*/*" + - title: "op-challenger" + description: "" + globs: + - "op-challenger/*" + - "op-challenger/*/*" + - "op-challenger/*/*/*" + - title: "op-e2e" + description: "" + globs: + - "op-e2e/*" + - "op-e2e/*/*" + - "op-e2e/*/*/*" + - "op-e2e/*/*/*/*" + + - title: "op-exporter" + description: "" + globs: + - "op-exporter/*" + - title: "op-node" + description: "" + globs: + - "op-node/*" + - "op-node/*/*" + - "op-node/*/*/*" + - title: "op-program" + description: "" + globs: + - "op-program/*" + - title: "op-proposer" + description: "" + globs: + - "op-proposer/*" + - title: "op-service" + description: "" + globs: + - "op-service/*" + - title: "op-signer" + description: "" + globs: + - "op-signer/*" + - title: "op-wheel" + description: "" + globs: + - "op-wheel/*" + - title: "ops-bedrock" + description: "" + globs: + - "ops-bedrock/*" + - title: "proxyd" + description: "" + globs: + - "proxyd/*" + - title: "specs" + description: "" + globs: + - "specs/*" + - title: "indexer" + description: "" + globs: + - "indexer/*" + - "indexer/*/*" + - "indexer/*/*/*" + +# ignored globally, does not count towards line count +ignore: + - ".circleci/*" + - "*.sum" + - "go.mod" + - "fork.yaml" + - ".github/workflows/*" + - ".changeset/*" + - ".github/*" + - "CONTRIBUTING.md" + - "pnpm-lock.yaml" diff --git a/funding.json b/funding.json new file mode 100644 index 0000000000000..a6b4c73180af2 --- /dev/null +++ b/funding.json @@ -0,0 +1,5 @@ +{ + "opRetro": { + "projectId": "0x839f24397fbcd261408f074eaf35aee98f500f5185a27e6c470c5307e967c017" + } +} diff --git a/go.mod b/go.mod index 599c284ef2413..08f7309c38f23 100644 --- a/go.mod +++ b/go.mod @@ -275,7 +275,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101503.4-rc.1 +replace github.com/ethereum/go-ethereum => github.com/celo-org/op-geth v1.101411.1-0.20250604093504-9074dd26d0a8 //replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index cfd91aab99342..219d233d4b4bb 100644 --- a/go.sum +++ b/go.sum @@ -86,6 +86,8 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/celo-org/op-geth v1.101411.1-0.20250604093504-9074dd26d0a8 h1:9PKBVXOwJUHJ2M096nVom4rELVaAZZrVhQvl5mwre7Q= +github.com/celo-org/op-geth v1.101411.1-0.20250604093504-9074dd26d0a8/go.mod h1:Ti5NReEoY9vwgJs47kdWjgQDApGMRLk7bpBSgxKYVlA= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= @@ -204,8 +206,6 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101503.4-rc.1 h1:ddcoFOmABL7bwz6b0pllSE4Org9iFTwMN6r1G6NRy3w= -github.com/ethereum-optimism/op-geth v1.101503.4-rc.1/go.mod h1:QUo3fn+45vWqJWzJW+rIzRHUV7NmhhHLPdI87mAn1M8= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250314162817-2c60e5723c64 h1:teDhU4h4ryaE8rSBl+vJJiwKHjxdnnHPkKZ9iNr2R8k= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250314162817-2c60e5723c64/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= @@ -824,6 +824,8 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/kurtosis-devnet/.gitignore b/kurtosis-devnet/.gitignore index 7eb455c4b8a40..6c1a9bd261608 100644 --- a/kurtosis-devnet/.gitignore +++ b/kurtosis-devnet/.gitignore @@ -1 +1,3 @@ *-user.json +optimism-contracts +op-geth \ No newline at end of file diff --git a/kurtosis-devnet/celo-isthmus.yaml b/kurtosis-devnet/celo-isthmus.yaml new file mode 100644 index 0000000000000..9bb4696386c2b --- /dev/null +++ b/kurtosis-devnet/celo-isthmus.yaml @@ -0,0 +1,120 @@ +optimism_package: + chains: + - participants: + - el_type: op-geth + el_image: {{ localDockerImage "op-geth" }} + el_log_level: "" + el_extra_env_vars: {} + el_extra_labels: {} + el_extra_params: + - "--rpc.txfeecap=0" + - "--txpool.nolocals" + el_tolerations: [] + el_volume_size: 0 + el_min_cpu: 0 + el_max_cpu: 0 + el_min_mem: 0 + el_max_mem: 0 + cl_type: op-node + cl_image: {{ localDockerImage "op-node" }} + cl_log_level: "" + cl_extra_env_vars: {} + cl_extra_labels: {} + cl_extra_params: + - "--sequencer.use-finalized=true" + cl_tolerations: [] + cl_volume_size: 0 + cl_min_cpu: 0 + cl_max_cpu: 0 + cl_min_mem: 0 + cl_max_mem: 0 + node_selectors: {} + tolerations: [] + count: 1 + network_params: + network: "kurtosis" + network_id: "42220" + seconds_per_slot: 2 + name: "op-kurtosis" + fjord_time_offset: 0 + granite_time_offset: 0 + holocene_time_offset: 0 + isthmus_time_offset: 0 + fund_dev_accounts: true + deploy_celo_contracts: true + use_dev_celo_token_l1: true + batcher_params: + image: {{ localDockerImage "op-batcher" }} + extra_params: + - --max-channel-duration=10 + - --altda.max-concurrent-da-requests=10 + - --max-pending-tx=10 + - --target-num-frames=5 + - --max-l1-tx-size-bytes=1000 + - --batch-type=1 + - --throttle-threshold=500_000 + - --throttle-block-size=1000 + challenger_params: + image: {{ localDockerImage "op-challenger" }} + cannon_prestate_path: "" + cannon_prestates_url: {{ localPrestate.URL }} + extra_params: [] + proposer_params: + image: {{ localDockerImage "op-proposer" }} + extra_params: [] + game_type: 1 + proposal_interval: 10m + mev_params: + rollup_boost_image: "" + builder_host: "" + builder_port: "" + additional_services: + - "da_server" + da_server_params: + cmd: + - --addr=0.0.0.0 + - --port=3100 + - --storage.backends-to-enable=V1,V2 + - --storage.dispersal-backend=v2 + - --api-enabled=admin + - --eigenda.v2.max-blob-length=1MiB + - --memstore.enabled + - --memstore.expiration=180m + image: ghcr.io/layr-labs/eigenda-proxy:v1.7.0 + op_contract_deployer_params: + image: {{ localDockerImage "op-deployer" }} + image_genesis: {{ localDockerImage "op-deployer-genesis" }} + l1_artifacts_locator: {{ localContractArtifacts "l1" }} + l2_artifacts_locator: {{ localContractArtifacts "l2" }} + global_deploy_overrides: + faultGameAbsolutePrestate: {{ localPrestate.Hashes.prestate }} + altda_deploy_config: + da_bond_size: 0 + da_challenge_window: 16 + da_commitment_type: GenericCommitment + da_resolve_window: 16 + da_resolver_refund_percentage: 0 + use_altda: true + global_log_level: "info" + global_node_selectors: {} + global_tolerations: [] + persistent: false +ethereum_package: + participants: + - el_type: geth + el_extra_params: + - "--miner.gasprice=1" + cl_type: teku + cl_image: consensys/teku:25.5.0 + network_params: + preset: minimal + genesis_delay: 5 + additional_preloaded_contracts: | + { + "0x4e59b44847b379578588920cA78FbF26c0B4956C": { + "balance": "0ETH", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "storage": {}, + "nonce": "1" + } + } diff --git a/kurtosis-devnet/justfile b/kurtosis-devnet/justfile index af4f64fc0f7ca..7180bf1a9734f 100644 --- a/kurtosis-devnet/justfile +++ b/kurtosis-devnet/justfile @@ -6,7 +6,7 @@ test: go test --tags=testonly ./... _kurtosis-run PACKAGE_NAME ARG_FILE ENCLAVE: - kurtosis run {{PACKAGE_NAME}} --args-file {{ARG_FILE}} --enclave {{ENCLAVE}} --show-enclave-inspect=false --image-download=missing + kurtosis run {{PACKAGE_NAME}} --args-file {{ARG_FILE}} --enclave {{ENCLAVE}} --show-enclave-inspect=true --image-download=missing _prestate-build PATH='.': docker buildx build --output {{PATH}} --progress plain -f ../op-program/Dockerfile.repro ../ @@ -27,6 +27,8 @@ _docker_build TAG TARGET CONTEXT DOCKERFILE *ARGS: {{CONTEXT}} _docker_build_stack TAG TARGET *ARGS: (_docker_build TAG TARGET "../" "ops/docker/op-stack-go/Dockerfile" ARGS) +_docker_build_stack_deployer TAG TARGET *ARGS: (_docker_build TAG TARGET "./optimism-contracts/" "ops/docker/op-stack-go/Dockerfile" ARGS) +_docker_build_stack_op_geth TAG TARGET *ARGS: (_docker_build TAG TARGET "./op-geth/" "Dockerfile" ARGS) cannon-image TAG='cannon:devnet': (_docker_build_stack TAG "cannon-target") da-server-image TAG='da-server:devnet': (_docker_build_stack TAG "da-server-target") @@ -36,7 +38,8 @@ op-batcher-image TAG='op-batcher:devnet': (_docker_build_stack TAG "op-batcher-t # docker-bake.hcl to do the right thing). op-challenger-image TAG='op-challenger:devnet': (_docker_build_stack TAG "op-challenger-target" "--build-arg" "KONA_VERSION=0.1.0-beta.15" "--build-arg" "ASTERISC_VERSION=v1.2.0") op-conductor-image TAG='op-conductor:devnet': (_docker_build_stack TAG "op-conductor-target") -op-deployer-image TAG='op-deployer:devnet': (_docker_build_stack TAG "op-deployer-target") +op-deployer-image TAG='op-deployer:devnet':(_docker_build_stack_deployer TAG "op-deployer-target") +op-deployer-genesis-image TAG='op-deployer-genesis:devnet':(_docker_build_stack TAG "op-deployer-target") op-dispute-mon-image TAG='op-dispute-mon:devnet': (_docker_build_stack TAG "op-dispute-mon-target") op-node-image TAG='op-node:devnet': (_docker_build_stack TAG "op-node-target") op-program-image TAG='op-program:devnet': (_docker_build_stack TAG "op-program-target") @@ -44,10 +47,11 @@ op-proposer-image TAG='op-proposer:devnet': (_docker_build_stack TAG "op-propose op-supervisor-image TAG='op-supervisor:devnet': (_docker_build_stack TAG "op-supervisor-target") op-wheel-image TAG='op-wheel:devnet': (_docker_build_stack TAG "op-wheel-target") +op-geth-image TAG='op-geth:devnet': (_docker_build_stack_op_geth TAG "") + op-program-builder-image TAG='op-program-builder:devnet': just op-program-svc/op-program-svc {{TAG}} - # Devnet template recipe devnet TEMPLATE_FILE DATA_FILE="" NAME="" PACKAGE=KURTOSIS_PACKAGE: #!/usr/bin/env bash @@ -95,6 +99,23 @@ pectra-devnet: (devnet "pectra.yaml") # Isthmus devnet isthmus-devnet: (devnet "isthmus.yaml") +# Celo Isthmus devnet +celo-isthmus-devnet: + # Use the celo-rebase-13-no-HF-overrides branch to avoid forcibly setting the activation times of Holocene and Isthmus upgrades + ./setup-local-repos.sh Kourin1996/celo-rebase-13-contracts-devnet celo-rebase-13-no-HF-overrides + kurtosis clean -a + {{just_executable()}} devnet celo-isthmus.yaml + # subshells enter-devnet DEVNET CHAIN='Ethereum' NODE_INDEX='0': exec go run ../devnet-sdk/shell/cmd/enter/main.go --devnet kt://{{DEVNET}} --chain {{CHAIN}} --node-index {{NODE_INDEX}} + +eigenda-devnet-failover ENCLAVE_NAME="celo-isthmus-minimal-devnet": + #!/usr/bin/env bash + PROXY_ENDPOINT=$(kurtosis port print {{ENCLAVE_NAME}} da-server-op-kurtosis http) + curl -X PATCH $PROXY_ENDPOINT/memstore/config -d '{"PutReturnsFailoverError": true}' + +eigenda-devnet-failback ENCLAVE_NAME="celo-isthmus-minimal-devnet": + #!/usr/bin/env bash + PROXY_ENDPOINT=$(kurtosis port print {{ENCLAVE_NAME}} da-server-op-kurtosis http) + curl -X PATCH $PROXY_ENDPOINT/memstore/config -d '{"PutReturnsFailoverError": false}' \ No newline at end of file diff --git a/kurtosis-devnet/optimism-package-trampoline/kurtosis.yml b/kurtosis-devnet/optimism-package-trampoline/kurtosis.yml index 714877b52a59c..e24df4cc2979a 100644 --- a/kurtosis-devnet/optimism-package-trampoline/kurtosis.yml +++ b/kurtosis-devnet/optimism-package-trampoline/kurtosis.yml @@ -2,7 +2,7 @@ name: github.com/ethereum-optimism/optimism/kurtosis-devnet/optimism-package-tra description: |- A trampoline package for optimism-package. This one is reproducible, due to the replace directives below. replace: - github.com/ethpandaops/optimism-package: github.com/ethpandaops/optimism-package@1cf76907eaa437ee9fcf902167714fece027962a + github.com/ethpandaops/optimism-package: github.com/celo-org/optimism-package@celo-rebase-13-devnet github.com/ethpandaops/ethereum-package: github.com/ethpandaops/ethereum-package@83830d44823767af65eda7dfe6b26c87c536c4cf github.com/kurtosis-tech/prometheus-package: github.com/kurtosis-tech/prometheus-package@f5ce159aec728898e3deb827f6b921f8ecfc527f github.com/kurtosis-tech/postgres-package: github.com/kurtosis-tech/postgres-package@2d363be1bc42524f6b0575cac0bbc0fd194ae173 diff --git a/kurtosis-devnet/pkg/build/contracts.go b/kurtosis-devnet/pkg/build/contracts.go index 688b64019ab63..352dcc2b6188e 100644 --- a/kurtosis-devnet/pkg/build/contracts.go +++ b/kurtosis-devnet/pkg/build/contracts.go @@ -44,7 +44,7 @@ type ContractBuilder struct { const ( contractsCmdTemplateStr = "just {{ .ContractsPath }}/build-no-tests" - relativeContractsPath = "../packages/contracts-bedrock" + relativeContractsPath = "./optimism-contracts/packages/contracts-bedrock" solidityCachePath = "cache/solidity-files-cache.json" ) diff --git a/kurtosis-devnet/setup-local-repos.sh b/kurtosis-devnet/setup-local-repos.sh new file mode 100755 index 0000000000000..cc4c2c4baa81c --- /dev/null +++ b/kurtosis-devnet/setup-local-repos.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Sync local copies of target Optimism and op-geth branches +OPTIMISM_CONTRACT_DIRECTORY="optimism-contracts" +OP_GETH_DIRECTORY="op-geth" + +DEFAULT_OPTIMISM_CONTRACT_BRANCH="Kourin1996/celo-rebase-13-contracts-devnet" +DEFAULT_OP_GETH_BRANCH="celo-rebase-13" +OPTIMISN_CONTRACT_BRANCH="${1:-$DEFAULT_OPTIMISM_CONTRACT_BRANCH}" +OP_GETH_BRANCH="${2:-$DEFAULT_OP_GETH_BRANCH}" + +# optimism +echo "Preparing optimism repository in $OPTIMISM_CONTRACT_DIRECTORY" +if [ ! -d "$OPTIMISM_CONTRACT_DIRECTORY" ]; then + echo "Cloning optimism into $OPTIMISM_CONTRACT_DIRECTORY" + git clone https://github.com/celo-org/optimism.git "$OPTIMISM_CONTRACT_DIRECTORY" +fi + +( + echo "Check out / pull $OPTIMISN_CONTRACT_BRANCH" + cd "$OPTIMISM_CONTRACT_DIRECTORY" + git fetch origin "$OPTIMISN_CONTRACT_BRANCH" + git checkout "$OPTIMISN_CONTRACT_BRANCH" + git pull --ff-only origin "$OPTIMISN_CONTRACT_BRANCH" +) + +# op-geth +echo "Preparing op-geth reository in $OP_GETH_DIRECTORY" +if [ ! -d "$OP_GETH_DIRECTORY" ]; then + echo "Cloning op-geth into $OP_GETH_DIRECTORY" + git clone https://github.com/celo-org/op-geth.git "$OP_GETH_DIRECTORY" +fi + +( + echo "Check out / pull $OP_GETH_BRANCH" + cd "$OP_GETH_DIRECTORY" + git fetch origin "$OP_GETH_BRANCH" + git checkout "$OP_GETH_BRANCH" + git pull --ff-only origin "$OP_GETH_BRANCH" +) diff --git a/op-alt-da/cli.go b/op-alt-da/cli.go index 84364e47952a7..72c6fa5259539 100644 --- a/op-alt-da/cli.go +++ b/op-alt-da/cli.go @@ -102,8 +102,12 @@ func (c CLIConfig) Check() error { return nil } -func (c CLIConfig) NewDAClient() *DAClient { - return &DAClient{url: c.DAServerURL, verify: c.VerifyOnRead, precompute: !c.GenericDA, getTimeout: c.GetTimeout, putTimeout: c.PutTimeout} +func (c CLIConfig) NewDAClient() (*DAClient, error) { + err := c.Check() + if err != nil { + return nil, fmt.Errorf("check daclient CLIConfig: %w", err) + } + return &DAClient{url: c.DAServerURL, verify: c.VerifyOnRead, precompute: !c.GenericDA, getTimeout: c.GetTimeout, putTimeout: c.PutTimeout}, nil } func ReadCLIConfig(c *cli.Context) CLIConfig { diff --git a/op-alt-da/daclient.go b/op-alt-da/daclient.go index 9f0bdab11fbd9..dc690bbbbc881 100644 --- a/op-alt-da/daclient.go +++ b/op-alt-da/daclient.go @@ -16,6 +16,11 @@ var ErrNotFound = errors.New("not found") // ErrInvalidInput is returned when the input is not valid for posting to the DA storage. var ErrInvalidInput = errors.New("invalid input") +// ErrAltDADown is returned when the alt DA returns a 503 status code. +// It is used to signify that the alt DA is down and the client should failover to the eth DA. +// See https://github.com/ethereum-optimism/specs/issues/434 +var ErrAltDADown = errors.New("alt DA is down: failover to eth DA") + // DAClient is an HTTP client to communicate with a DA storage service. // It creates commitments and retrieves input data + verifies if needed. type DAClient struct { @@ -131,6 +136,9 @@ func (c *DAClient) setInput(ctx context.Context, img []byte) (CommitmentData, er return nil, err } defer resp.Body.Close() + if resp.StatusCode == http.StatusServiceUnavailable { + return nil, ErrAltDADown + } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to store data: %v", resp.StatusCode) } diff --git a/op-alt-da/daclient_test.go b/op-alt-da/daclient_test.go index d9f7902aadee1..bee1030c7a5e1 100644 --- a/op-alt-da/daclient_test.go +++ b/op-alt-da/daclient_test.go @@ -27,7 +27,8 @@ func TestDAClientPrecomputed(t *testing.T) { } require.NoError(t, cfg.Check()) - client := cfg.NewDAClient() + client, err := cfg.NewDAClient() + require.NoError(t, err) rng := rand.New(rand.NewSource(1234)) @@ -85,7 +86,8 @@ func TestDAClientService(t *testing.T) { } require.NoError(t, cfg.Check()) - client := cfg.NewDAClient() + client, err := cfg.NewDAClient() + require.NoError(t, err) rng := rand.New(rand.NewSource(1234)) diff --git a/op-alt-da/damgr.go b/op-alt-da/damgr.go index 15814263c4ffa..2397d27b7e0cc 100644 --- a/op-alt-da/damgr.go +++ b/op-alt-da/damgr.go @@ -78,8 +78,12 @@ type DA struct { } // NewAltDA creates a new AltDA instance with the given log and CLIConfig. -func NewAltDA(log log.Logger, cli CLIConfig, cfg Config, metrics Metricer) *DA { - return NewAltDAWithStorage(log, cfg, cli.NewDAClient(), metrics) +func NewAltDA(log log.Logger, cli CLIConfig, cfg Config, metrics Metricer) (*DA, error) { + daClient, err := cli.NewDAClient() + if err != nil { + return nil, fmt.Errorf("new DAClient: %w", err) + } + return NewAltDAWithStorage(log, cfg, daClient, metrics), nil } // NewAltDAWithStorage creates a new AltDA instance with the given log and DAStorage interface. @@ -117,8 +121,15 @@ func (d *DA) OnFinalizedHeadSignal(f HeadSignalFn) { func (d *DA) updateFinalizedHead(l1Finalized eth.L1BlockRef) { d.l1FinalizedHead = l1Finalized // Prune the state to the finalized head - d.state.Prune(l1Finalized.ID()) - d.finalizedHead = d.state.lastPrunedCommitment + lastPrunedCommIncBlock := d.state.Prune(l1Finalized.ID()) + d.log.Debug("updateFinalizedHead", "currFinalizedHead", d.finalizedHead.Number, "lastPrunedCommIncBlock", lastPrunedCommIncBlock.Number, "l1Finalized", l1Finalized.Number) + // If a commitment was pruned, set the finalized head to that commitment's inclusion block + // When no commitments are left to be pruned (one example is if we have failed over to ethda) + // then updateFinalizedFromL1 becomes the main driver of the finalized head. + // Note that updateFinalizedFromL1 is only called when d.state.NoCommitments() is true. + if lastPrunedCommIncBlock != (eth.L1BlockRef{}) { + d.finalizedHead = lastPrunedCommIncBlock + } } // updateFinalizedFromL1 updates the finalized head based on the challenge window. @@ -133,6 +144,7 @@ func (d *DA) updateFinalizedFromL1(ctx context.Context, l1 L1Fetcher) error { if err != nil { return err } + d.log.Debug("updateFinalizedFromL1", "currFinalizedHead", d.finalizedHead.Number, "newFinalizedHead", ref.Number, "l1FinalizedHead", d.l1FinalizedHead.Number, "challengeWindow", d.cfg.ChallengeWindow) d.finalizedHead = ref return nil } @@ -413,6 +425,7 @@ func (d *DA) fetchChallengeLogs(ctx context.Context, l1 L1Fetcher, block eth.Blo } for _, log := range rec.Logs { if log.Address == d.cfg.DAChallengeContractAddress && len(log.Topics) > 0 && log.Topics[0] == ChallengeStatusEventABIHash { + d.log.Info("found challenge event", "block", block.Number, "log", log.Index) logs = append(logs, log) } } diff --git a/op-alt-da/damgr_test.go b/op-alt-da/damgr_test.go index b487fc85c98de..9255134ed2cf1 100644 --- a/op-alt-da/damgr_test.go +++ b/op-alt-da/damgr_test.go @@ -53,12 +53,12 @@ func TestFinalization(t *testing.T) { require.NoError(t, state.ExpireCommitments(bID(8))) require.Empty(t, state.commitments) - state.Prune(bID(bn1)) - require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment) - state.Prune(bID(7)) - require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment) - state.Prune(bID(8)) - require.Equal(t, l1Ref(bn1), state.lastPrunedCommitment) + lastPrunedCommitment := state.Prune(bID(bn1)) + require.Equal(t, eth.L1BlockRef{}, lastPrunedCommitment) + lastPrunedCommitment = state.Prune(bID(7)) + require.Equal(t, eth.L1BlockRef{}, lastPrunedCommitment) + lastPrunedCommitment = state.Prune(bID(8)) + require.Equal(t, l1Ref(bn1), lastPrunedCommitment) // Track a commitment, challenge it, & then resolve it c2 := RandomCommitment(rng) @@ -83,12 +83,12 @@ func TestFinalization(t *testing.T) { require.Empty(t, state.challenges) // Now finalize everything - state.Prune(bID(20)) - require.Equal(t, l1Ref(bn1), state.lastPrunedCommitment) - state.Prune(bID(28)) - require.Equal(t, l1Ref(bn1), state.lastPrunedCommitment) - state.Prune(bID(32)) - require.Equal(t, l1Ref(bn2), state.lastPrunedCommitment) + lastPrunedCommitment = state.Prune(bID(20)) + require.Equal(t, eth.L1BlockRef{}, lastPrunedCommitment) + lastPrunedCommitment = state.Prune(bID(28)) + require.Equal(t, eth.L1BlockRef{}, lastPrunedCommitment) + lastPrunedCommitment = state.Prune(bID(32)) + require.Equal(t, l1Ref(bn2), lastPrunedCommitment) } // TestExpireChallenges expires challenges and prunes the state for longer windows @@ -175,8 +175,8 @@ func TestDAChallengeDetached(t *testing.T) { require.ErrorIs(t, err, ErrReorgRequired) // pruning finalized block is safe. It should not prune any commitments yet. - state.Prune(bID(1)) - require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment) + lastPrunedCommitment := state.Prune(bID(1)) + require.Equal(t, eth.L1BlockRef{}, lastPrunedCommitment) // Perform reorg back to bn2 state.ClearCommitments() diff --git a/op-alt-da/damock.go b/op-alt-da/damock.go index ad388d0b26535..2c3a0d286b23c 100644 --- a/op-alt-da/damock.go +++ b/op-alt-da/damock.go @@ -2,7 +2,9 @@ package altda import ( "context" + "encoding/binary" "errors" + "fmt" "io" "net/http" "sync" @@ -16,11 +18,16 @@ import ( ) // MockDAClient mocks a DA storage provider to avoid running an HTTP DA server -// in unit tests. +// in unit tests. MockDAClient is goroutine-safe. type MockDAClient struct { - CommitmentType CommitmentType - store ethdb.KeyValueStore - log log.Logger + mu sync.Mutex + CommitmentType CommitmentType + GenericCommitmentCount uint16 // next generic commitment (use counting commitment instead of hash to help with testing) + store ethdb.KeyValueStore + StoreCount int + log log.Logger + dropEveryNthPut uint // 0 means nothing gets dropped, 1 means every put errors, etc. + setInputRequestCount uint // number of put requests received, irrespective of whether they were successful } func NewMockDAClient(log log.Logger) *MockDAClient { @@ -31,7 +38,30 @@ func NewMockDAClient(log log.Logger) *MockDAClient { } } +// NewCountingGenericCommitmentMockDAClient creates a MockDAClient that uses counting commitments. +// Its commitments are big-endian encoded uint16s of 0, 1, 2, etc. instead of actual hash or altda-layer related commitments. +// Used for testing to make sure we receive commitments in order following Holocene strict ordering rules. +func NewCountingGenericCommitmentMockDAClient(log log.Logger) *MockDAClient { + return &MockDAClient{ + CommitmentType: GenericCommitmentType, + store: memorydb.New(), + log: log, + } +} + +// Fakes a da server that drops/errors on every Nth put request. +// Useful for testing the batcher's error handling. +// 0 means nothing gets dropped, 1 means every put errors, etc. +func (c *MockDAClient) DropEveryNthPut(n uint) { + c.mu.Lock() + defer c.mu.Unlock() + c.dropEveryNthPut = n +} + func (c *MockDAClient) GetInput(ctx context.Context, key CommitmentData) ([]byte, error) { + c.mu.Lock() + defer c.mu.Unlock() + c.log.Debug("Getting input", "key", key) bytes, err := c.store.Get(key.Encode()) if err != nil { return nil, ErrNotFound @@ -40,14 +70,50 @@ func (c *MockDAClient) GetInput(ctx context.Context, key CommitmentData) ([]byte } func (c *MockDAClient) SetInput(ctx context.Context, data []byte) (CommitmentData, error) { - key := NewCommitmentData(c.CommitmentType, data) - return key, c.store.Put(key.Encode(), data) + c.mu.Lock() + defer c.mu.Unlock() + c.setInputRequestCount++ + var key CommitmentData + if c.CommitmentType == GenericCommitmentType { + countCommitment := make([]byte, 2) + binary.BigEndian.PutUint16(countCommitment, c.GenericCommitmentCount) + key = NewGenericCommitment(countCommitment) + } else { + key = NewKeccak256Commitment(data) + } + var action string = "put" + if c.dropEveryNthPut > 0 && c.setInputRequestCount%c.dropEveryNthPut == 0 { + action = "dropped" + } + c.log.Debug("Setting input", "action", action, "key", key, "data", fmt.Sprintf("%x", data)) + if action == "dropped" { + return nil, errors.New("put dropped") + } + err := c.store.Put(key.Encode(), data) + if err == nil { + c.GenericCommitmentCount++ + c.StoreCount++ + } + return key, err } func (c *MockDAClient) DeleteData(key []byte) error { - return c.store.Delete(key) + c.mu.Lock() + defer c.mu.Unlock() + c.log.Debug("Deleting data", "key", key) + // memorydb.Delete() returns nil even when the key doesn't exist, so we need to check if the key exists + // before decrementing StoreCount. + var err error + if _, err = c.store.Get(key); err == nil { + if err = c.store.Delete(key); err == nil { + c.StoreCount-- + } + } + return err } +// DAErrFaker is a DA client that can be configured to return errors on GetInput +// and SetInput calls. type DAErrFaker struct { Client *MockDAClient @@ -105,12 +171,26 @@ func (d *AltDADisabled) AdvanceL1Origin(ctx context.Context, l1 L1Fetcher, block } // FakeDAServer is a fake DA server for e2e tests. -// It is a small wrapper around DAServer that allows for setting request latencies, -// to mimic a DA service with slow responses (eg. eigenDA with 10 min batching interval). +// It is a small wrapper around DAServer that allows for setting: +// - request latencies, to mimic a DA service with slow responses +// (eg. eigenDA with 10 min batching interval). +// - response status codes, to mimic a DA service that is down. +// +// We use this FakeDaServer as opposed to the DAErrFaker client in the op-e2e altda system tests +// because the batcher service only has a constructor to build from CLI flags (no dependency injection), +// meaning the da client is built from an rpc url config instead of being injected. type FakeDAServer struct { *DAServer putRequestLatency time.Duration getRequestLatency time.Duration + // next failoverCount Put requests will return 503 status code for failover testing + failoverCount uint64 + // outOfOrderResponses is a flag that, when set, causes the server to send responses out of order. + // It will only respond to pairs of request, returning the second response first, and waiting 1 second before sending the first response. + // This is used to test the batcher's ability to handle out of order responses, while still ensuring holocene's strict ordering rules. + outOfOrderResponses bool + oooMu sync.Mutex + oooWaitChan chan struct{} } func NewFakeDAServer(host string, port int, log log.Logger) *FakeDAServer { @@ -130,6 +210,26 @@ func (s *FakeDAServer) HandleGet(w http.ResponseWriter, r *http.Request) { func (s *FakeDAServer) HandlePut(w http.ResponseWriter, r *http.Request) { time.Sleep(s.putRequestLatency) + if s.failoverCount > 0 { + w.WriteHeader(http.StatusServiceUnavailable) + s.failoverCount-- + return + } + if s.outOfOrderResponses { + s.oooMu.Lock() + if s.oooWaitChan == nil { + s.log.Info("Received put request while in out-of-order mode, waiting for next request") + s.oooWaitChan = make(chan struct{}) + s.oooMu.Unlock() + <-s.oooWaitChan + time.Sleep(1 * time.Second) + } else { + s.log.Info("Received second put request in out-of-order mode, responding to this one first, then the first one") + close(s.oooWaitChan) + s.oooWaitChan = nil + s.oooMu.Unlock() + } + } s.DAServer.HandlePut(w, r) } @@ -147,13 +247,28 @@ func (s *FakeDAServer) Start() error { } func (s *FakeDAServer) SetPutRequestLatency(latency time.Duration) { + s.log.Info("Setting put request latency", "latency", latency) s.putRequestLatency = latency } func (s *FakeDAServer) SetGetRequestLatency(latency time.Duration) { + s.log.Info("Setting get request latency", "latency", latency) s.getRequestLatency = latency } +// SetResponseStatusForNRequests sets the next n Put requests to return 503 status code. +func (s *FakeDAServer) SetPutFailoverForNRequests(n uint64) { + s.failoverCount = n +} + +// When ooo=true, causes the server to send responses out of order. +// It will only respond to pairs of request, returning the second response first, and waiting 1 second before sending the first response. +// This is used to test the batcher's ability to handle out of order responses, while still ensuring holocene's strict ordering rules. +func (s *FakeDAServer) SetOutOfOrderResponses(ooo bool) { + s.log.Info("Setting out of order responses", "ooo", ooo) + s.outOfOrderResponses = ooo +} + type MemStore struct { db map[string][]byte lock sync.RWMutex diff --git a/op-alt-da/damock_test.go b/op-alt-da/damock_test.go new file mode 100644 index 0000000000000..3d651e3bd9193 --- /dev/null +++ b/op-alt-da/damock_test.go @@ -0,0 +1,65 @@ +package altda + +import ( + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/log" +) + +func TestFakeDAServer_OutOfOrderResponses(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + daServer := NewFakeDAServer("localhost", 0, logger) + daServer.SetOutOfOrderResponses(true) + + // Channel to track completion order + completionOrder := make(chan int, 2) + + // Start two concurrent requests + var wg sync.WaitGroup + wg.Add(2) + + // First request + go func() { + defer wg.Done() + w := httptest.NewRecorder() + r := httptest.NewRequest("PUT", "/data", nil) + + daServer.HandlePut(w, r) + completionOrder <- 1 + }() + + // Small delay to ensure first request starts first + time.Sleep(100 * time.Millisecond) + + // Second request + go func() { + defer wg.Done() + w := httptest.NewRecorder() + r := httptest.NewRequest("PUT", "/data", nil) + + daServer.HandlePut(w, r) + completionOrder <- 2 + }() + + // Wait for both requests to complete + wg.Wait() + close(completionOrder) + + // Check completion order + var order []int + for n := range completionOrder { + order = append(order, n) + } + + // Second request should complete before first + if len(order) != 2 { + t.Fatalf("expected 2 requests to complete, got %d", len(order)) + } + if order[0] != 2 || order[1] != 1 { + t.Errorf("expected completion order [2,1], got %v", order) + } +} diff --git a/op-alt-da/dastate.go b/op-alt-da/dastate.go index 66a2aee1f31ef..5d26841ec5118 100644 --- a/op-alt-da/dastate.go +++ b/op-alt-da/dastate.go @@ -52,15 +52,14 @@ func challengeKey(comm CommitmentData, inclusionBlockNumber uint64) string { // In the special case of a L2 reorg, challenges are still tracked but commitments are removed. // This will allow the altDA fetcher to find the expired challenge. type State struct { - commitments []Commitment // commitments where the challenge/resolve period has not expired yet - expiredCommitments []Commitment // commitments where the challenge/resolve period has expired but not finalized - challenges []*Challenge // challenges ordered by L1 inclusion - expiredChallenges []*Challenge // challenges ordered by L1 inclusion - challengesMap map[string]*Challenge // challenges by serialized comm + block number for easy lookup - lastPrunedCommitment eth.L1BlockRef // the last commitment to be pruned - cfg Config - log log.Logger - metrics Metricer + commitments []Commitment // commitments where the challenge/resolve period has not expired yet + expiredCommitments []Commitment // commitments where the challenge/resolve period has expired but not finalized + challenges []*Challenge // challenges ordered by L1 inclusion + expiredChallenges []*Challenge // challenges ordered by L1 inclusion + challengesMap map[string]*Challenge // challenges by serialized comm + block number for easy lookup + cfg Config + log log.Logger + metrics Metricer } func NewState(log log.Logger, m Metricer, cfg Config) *State { @@ -207,15 +206,18 @@ func (s *State) ExpireChallenges(origin eth.BlockID) { } // Prune removes challenges & commitments which have an expiry block number beyond the given block number. -func (s *State) Prune(origin eth.BlockID) { +// It returns the last pruned commitment's inclusion block number, or eth.L1BlockRef{} if no commitments were pruned. +func (s *State) Prune(origin eth.BlockID) eth.L1BlockRef { // Commitments rely on challenges, so we prune commitments first. - s.pruneCommitments(origin) + lastPrunedCommIncBlock := s.pruneCommitments(origin) s.pruneChallenges(origin) + return lastPrunedCommIncBlock } // pruneCommitments removes commitments which have are beyond a given block number. // It will remove commitments in order of inclusion until it finds a commitment which is not beyond the given block number. -func (s *State) pruneCommitments(origin eth.BlockID) { +func (s *State) pruneCommitments(origin eth.BlockID) eth.L1BlockRef { + var lastPrunedCommIncBlock eth.L1BlockRef for len(s.expiredCommitments) > 0 { c := s.expiredCommitments[0] challenge, ok := s.GetChallenge(c.data, c.inclusionBlock.Number) @@ -236,8 +238,9 @@ func (s *State) pruneCommitments(origin eth.BlockID) { s.expiredCommitments = s.expiredCommitments[1:] // Record the latest inclusion block to be returned - s.lastPrunedCommitment = c.inclusionBlock + lastPrunedCommIncBlock = c.inclusionBlock } + return lastPrunedCommIncBlock } // pruneChallenges removes challenges which have are beyond a given block number. diff --git a/op-batcher/batcher/channel.go b/op-batcher/batcher/channel.go index 6b936c112d346..c84748d49f928 100644 --- a/op-batcher/batcher/channel.go +++ b/op-batcher/batcher/channel.go @@ -3,6 +3,7 @@ package batcher import ( "math" + altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" @@ -20,7 +21,16 @@ type channel struct { // pending channel builder channelBuilder *ChannelBuilder - // Set of unconfirmed txID -> tx data. For tx resubmission + // Temporary cache for altDACommitments that are received potentially out of order from the da layer. + // Map: first frameNumber in txData -> txData (that contains an altDACommitment) + // Once the txData containing altDAFrameCursor is received, it will be pulled out of the + // channel on the next driver iteration, and sent to L1. + altDACommitments map[uint16]txData + // Points to the next frame number to send to L1 in order to maintain holocene strict ordering rules. + // When altDACommitments[altDAFrameCursor] is non-nil, it will be sent to L1. + altDAFrameCursor uint16 + // Set of unconfirmed txID -> tx data. For tx resubmission. + // Also used for altda for the entirity of the submission (data -> commitment -> tx). pendingTransactions map[string]txData // Set of confirmed txID -> inclusion block. For determining if the channel is timed out confirmedTransactions map[string]eth.BlockID @@ -38,26 +48,83 @@ func newChannel(log log.Logger, metr metrics.Metricer, cfg ChannelConfig, rollup metr: metr, cfg: cfg, channelBuilder: cb, + altDACommitments: make(map[uint16]txData), pendingTransactions: make(map[string]txData), confirmedTransactions: make(map[string]eth.BlockID), minInclusionBlock: math.MaxUint64, } } +// CacheAltDACommitment caches the commitment received from the DA layer for the given txData. +// We cannot submit it directly to L1 yet, as we need to make sure the commitments are submitted in order, +// according to the holocene rules. Therefore, we cache the commitment and let the channelManager +// decide when to pull them out of the channel and send them to L1. +func (c *channel) CacheAltDACommitment(txData txData, commitment altda.CommitmentData) { + if commitment == nil { + panic("expected non-nil commitment") + } + if len(txData.frames) == 0 { + panic("expected txData to have frames") + } + txData.altDACommitment = commitment + c.log.Debug("caching altDA commitment", "frame", txData.frames[0].id.frameNumber, "commitment", commitment.String()) + c.altDACommitments[txData.frames[0].id.frameNumber] = txData +} + +func (c *channel) rewindAltDAFrameCursor(txData txData) { + if len(txData.frames) == 0 { + panic("expected txData to have frames") + } + c.altDAFrameCursor = txData.frames[0].id.frameNumber +} + +// AltDASubmissionFailed records an AltDA blob dispersal as having failed. +// It rewinds the channelBuilder's frameCursor to the first frame of the failed txData, +// so that the frames can be resubmitted. failoverToEthDA should be set to true when using altDA +// and altDA is down. This will switch the channel to submit frames to ethDA instead. +// TODO: add a metric for altDA submission failures. +func (c *channel) AltDASubmissionFailed(id string, failoverToEthDA bool) { + // We coopt TxFailed to rewind the frame cursor. + // This will force a resubmit of all the following frames as well, + // even if they had already successfully been submitted and their commitment cached. + // Ideally we'd have another way but for simplicity and to not tangle the altda code + // too much with the non altda code, we reuse the FrameCursor feature. + // TODO: Is there a better abstraction for altda channels? FrameCursors are not well suited + // since frames do not have to be sent in order to the altda, only their commitment does. + c.TxFailed(id) + if failoverToEthDA { + // We failover to calldata txs because in altda mode the channel and channelManager + // are configured to use a calldataConfigManager, as opposed to DynamicEthChannelConfig + // which can use both calldata and blobs. Failover should happen extremely rarely, + // and is only used while the altDA is down, so we can afford to be inefficient here. + // TODO: figure out how to switch to blobs/auto instead. Might need to make + // batcherService.initChannelConfig function stateless so that we can reuse it. + c.cfg.DaType = DaTypeCalldata + c.metr.RecordFailoverToEthDA() + } +} + // TxFailed records a transaction as failed. It will attempt to resubmit the data // in the failed transaction. func (c *channel) TxFailed(id string) { if data, ok := c.pendingTransactions[id]; ok { c.log.Trace("marked transaction as failed", "id", id) - // Rewind to the first frame of the failed tx - // -- the frames are ordered, and we want to send them - // all again. - c.channelBuilder.RewindFrameCursor(data.Frames()[0]) + if data.altDACommitment != nil { + // In altDA mode, we don't want to rewind the channelBuilder's frameCursor + // because that will lead to resubmitting the same data to the da layer. + // We simply need to rewind the altDAFrameCursor to the first frame of the failed txData, + // to force a resubmit of the cached altDACommitment. + c.rewindAltDAFrameCursor(data) + } else { + // Rewind to the first frame of the failed tx + // -- the frames are ordered, and we want to send them + // all again. + c.channelBuilder.RewindFrameCursor(data.Frames()[0]) + } delete(c.pendingTransactions, id) } else { c.log.Warn("unknown transaction marked as failed", "id", id) } - c.metr.RecordBatchTxFailed() } @@ -89,7 +156,16 @@ func (c *channel) TxConfirmed(id string, inclusionBlock eth.BlockID) bool { // and then reset this state so it can try to build a new channel. if c.isTimedOut() { c.metr.RecordChannelTimedOut(c.ID()) - c.log.Warn("Channel timed out", "id", c.ID(), "min_inclusion_block", c.minInclusionBlock, "max_inclusion_block", c.maxInclusionBlock) + var chanFirstL2BlockNum, chanLastL2BlockNum uint64 + if c.channelBuilder.blocks.Len() > 0 { + chanFirstL2Block, _ := c.channelBuilder.blocks.Peek() + chanLastL2Block, _ := c.channelBuilder.blocks.PeekN(c.channelBuilder.blocks.Len() - 1) + chanFirstL2BlockNum = chanFirstL2Block.NumberU64() + chanLastL2BlockNum = chanLastL2Block.NumberU64() + } + c.log.Warn("Channel timed out", "id", c.ID(), + "min_l1_inclusion_block", c.minInclusionBlock, "max_l1_inclusion_block", c.maxInclusionBlock, + "first_l2_block", chanFirstL2BlockNum, "last_l2_block", chanLastL2BlockNum) return true } @@ -124,22 +200,44 @@ func (c *channel) ID() derive.ChannelID { return c.channelBuilder.ID() } +// NextAltDACommitment checks if it has already received the altDA commitment +// of the txData whose first frame is altDAFrameCursor. If it has, it returns +// the txData and true. Otherwise, it returns an empty txData and false. +func (c *channel) NextAltDACommitment() (txData, bool) { + if txData, ok := c.altDACommitments[c.altDAFrameCursor]; ok { + if txData.altDACommitment == nil { + panic("expected altDACommitment to be non-nil") + } + if len(txData.frames) == 0 { + panic("expected txData to have frames") + } + // update altDAFrameCursor to the first frame of the next txData + lastFrame := txData.frames[len(txData.frames)-1] + c.altDAFrameCursor = lastFrame.id.frameNumber + 1 + // We also store it in pendingTransactions so that TxFailed can know + // that this tx's altDA commitment was already cached. + c.pendingTransactions[txData.ID().String()] = txData + return txData, true + } + return txData{}, false +} + // NextTxData dequeues the next frames from the channel and returns them encoded in a tx data packet. -// If cfg.UseBlobs is false, it returns txData with a single frame. -// If cfg.UseBlobs is true, it will read frames from its channel builder +// If cfg.DaType == DaTypeCalldata, it returns txData with a single frame. +// Else when cfg.DaType == DaTypeBlob or DaTypeAltDA, it will read frames from its channel builder // until it either doesn't have more frames or the target number of frames is reached. // // NextTxData should only be called after HasTxData returned true. func (c *channel) NextTxData() txData { nf := c.cfg.MaxFramesPerTx() - txdata := txData{frames: make([]frameData, 0, nf), asBlob: c.cfg.UseBlobs} + txdata := txData{frames: make([]frameData, 0, nf), daType: c.cfg.DaType} for i := 0; i < nf && c.channelBuilder.HasPendingFrame(); i++ { frame := c.channelBuilder.NextFrame() txdata.frames = append(txdata.frames, frame) } id := txdata.ID().String() - c.log.Debug("returning next tx data", "id", id, "num_frames", len(txdata.frames), "as_blob", txdata.asBlob) + c.log.Debug("returning next tx data", "id", id, "num_frames", len(txdata.frames), "da_type", txdata.daType) c.pendingTransactions[id] = txdata return txdata @@ -147,7 +245,7 @@ func (c *channel) NextTxData() txData { func (c *channel) HasTxData() bool { if c.IsFull() || // If the channel is full, we should start to submit it - !c.cfg.UseBlobs { // If using calldata, we only send one frame per tx + c.cfg.DaType == DaTypeCalldata { // If using calldata, we only send one frame per tx return c.channelBuilder.HasPendingFrame() } // Collect enough frames if channel is not full yet diff --git a/op-batcher/batcher/channel_config.go b/op-batcher/batcher/channel_config.go index bf0f5ffb4adbe..5054bb3989507 100644 --- a/op-batcher/batcher/channel_config.go +++ b/op-batcher/batcher/channel_config.go @@ -46,9 +46,12 @@ type ChannelConfig struct { // BatchType indicates whether the channel uses SingularBatch or SpanBatch. BatchType uint - // UseBlobs indicates that this channel should be sent as a multi-blob - // transaction with one blob per frame. - UseBlobs bool + // DaType indicates how the frames in this channel should be sent to the L1. + DaType DaType +} + +func (cc ChannelConfig) UseBlobs() bool { + return cc.DaType == DaTypeBlob } // ChannelConfig returns a copy of the receiver. @@ -93,7 +96,7 @@ func (cc *ChannelConfig) ReinitCompressorConfig() { } func (cc *ChannelConfig) MaxFramesPerTx() int { - if !cc.UseBlobs { + if cc.DaType == DaTypeCalldata { return 1 } return cc.TargetNumFrames diff --git a/op-batcher/batcher/channel_config_provider_test.go b/op-batcher/batcher/channel_config_provider_test.go index 95e51a921e5fd..fccc26d649219 100644 --- a/op-batcher/batcher/channel_config_provider_test.go +++ b/op-batcher/batcher/channel_config_provider_test.go @@ -31,11 +31,12 @@ func TestDynamicEthChannelConfig_ChannelConfig(t *testing.T) { calldataCfg := ChannelConfig{ MaxFrameSize: 120_000 - 1, TargetNumFrames: 1, + DaType: DaTypeCalldata, } blobCfg := ChannelConfig{ MaxFrameSize: eth.MaxBlobDataSize - 1, TargetNumFrames: 3, // gets closest to amortized fixed tx costs - UseBlobs: true, + DaType: DaTypeBlob, } tests := []struct { diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index acb63b74c503c..9ed179e7ef09e 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -6,6 +6,7 @@ import ( "io" "math" + altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" @@ -53,7 +54,7 @@ type channelManager struct { currentChannel *channel // channels to read frame data from, for writing batches onchain channelQueue []*channel - // used to lookup channels by tx ID upon tx success / failure + // used to lookup channels by tx ID upon altda and tx success / failure txChannels map[string]*channel } @@ -94,6 +95,41 @@ func (s *channelManager) pendingBlocks() int { return s.blocks.Len() - s.blockCursor } +// CacheAltDACommitment caches the commitment received from the DA layer for the given txData. +// We cannot submit it directly to L1 yet, as we need to make sure the commitments are submitted in order, +// according to the holocene rules. Therefore, we cache them and let the channelManager decide when to submit them. +func (s *channelManager) CacheAltDACommitment(txData txData, commitment altda.CommitmentData) { + if len(txData.frames) == 0 { + panic("no frames in txData") + } + firstFrame, lastFrame := txData.frames[0], txData.frames[len(txData.frames)-1] + if firstFrame.id.chID != lastFrame.id.chID { + // The current implementation caches commitments inside channels, + // so it assumes that a txData only contains frames from a single channel. + // If this ever panics (hopefully in tests...) it shouldn't be too hard to fix. + panic("commitment spans multiple channels") + } + if channel, ok := s.txChannels[txData.ID().String()]; ok { + channel.CacheAltDACommitment(txData, commitment) + } else { + s.log.Warn("Trying to cache altda commitment for txData from unknown channel. Probably some state reset (from reorg?) happened.", "id", txData.ID()) + } +} + +// AltDASubmissionFailed marks a DA submission as having failed to be submitted to the DA layer. +// The frames will be pushed back into the corresponding channel such that they can be pulled again by the +// driver main loop and resent to the DA layer. failoverToEthDA should be set to true when using altDA +// and altDA is down. This will switch the channel to submit frames to ethDA instead. +func (s *channelManager) AltDASubmissionFailed(_id txID, failoverToEthDA bool) { + id := _id.String() + if channel, ok := s.txChannels[id]; ok { + delete(s.txChannels, id) + channel.AltDASubmissionFailed(id, failoverToEthDA) + } else { + s.log.Warn("transaction from unknown channel marked as failed", "id", id) + } +} + // TxFailed records a transaction as failed. It will attempt to resubmit the data // in the failed transaction. func (s *channelManager) TxFailed(_id txID) { @@ -216,6 +252,20 @@ func (s *channelManager) nextTxData(channel *channel) (txData, error) { return tx, nil } +func (s *channelManager) getNextAltDACommitment() (txData, bool) { + for _, channel := range s.channelQueue { + // if all frames have already been sent to altda, skip this channel + if int(channel.altDAFrameCursor) == channel.channelBuilder.TotalFrames() { + continue + } + if txData, ok := channel.NextAltDACommitment(); ok { + return txData, true + } + break // We need to send the commitments in order, so we can't skip to the next channel + } + return emptyTxData, false +} + // TxData returns the next tx data that should be submitted to L1. // // If the current channel is @@ -226,6 +276,10 @@ func (s *channelManager) nextTxData(channel *channel) (txData, error) { // When switching DA type, the channelManager state will be rebuilt // with a new ChannelConfig. func (s *channelManager) TxData(l1Head eth.BlockID, isPectra bool) (txData, error) { + // if any altda commitment is ready, return it + if txdata, ok := s.getNextAltDACommitment(); ok { + return txdata, nil + } channel, err := s.getReadyChannel(l1Head) if err != nil { return emptyTxData, err @@ -240,16 +294,16 @@ func (s *channelManager) TxData(l1Head eth.BlockID, isPectra bool) (txData, erro newCfg := s.cfgProvider.ChannelConfig(isPectra) // No change: - if newCfg.UseBlobs == s.defaultCfg.UseBlobs { + if newCfg.UseBlobs() == s.defaultCfg.UseBlobs() { s.log.Debug("Recomputing optimal ChannelConfig: no need to switch DA type", - "useBlobs", s.defaultCfg.UseBlobs) + "useBlobs", s.defaultCfg.UseBlobs()) return s.nextTxData(channel) } // Change: s.log.Info("Recomputing optimal ChannelConfig: changing DA type and requeing blocks...", - "useBlobsBefore", s.defaultCfg.UseBlobs, - "useBlobsAfter", newCfg.UseBlobs) + "useBlobsBefore", s.defaultCfg.UseBlobs(), + "useBlobsAfter", newCfg.UseBlobs()) // Invalidate the channel so its blocks // get requeued: @@ -283,7 +337,7 @@ func (s *channelManager) getReadyChannel(l1Head eth.BlockID) (*channel, error) { } dataPending := firstWithTxData != nil - s.log.Debug("Requested tx data", "l1Head", l1Head, "txdata_pending", dataPending, "blocks_pending", s.blocks.Len()) + s.log.Debug("Requested tx data", "l1Head", l1Head, "txdata_pending", dataPending, "blocks_pending", s.pendingBlocks()) // Short circuit if there is pending tx data or the channel manager is closed if dataPending { @@ -350,7 +404,7 @@ func (s *channelManager) ensureChannelWithSpace(l1Head eth.BlockID) error { "compression_algo", cfg.CompressorConfig.CompressionAlgo, "target_num_frames", cfg.TargetNumFrames, "max_frame_size", cfg.MaxFrameSize, - "use_blobs", cfg.UseBlobs, + "da_type", cfg.DaType, ) s.metr.RecordChannelOpened(pc.ID(), s.pendingBlocks()) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 2685c9359543a..16ebe0197c5fd 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -295,11 +295,12 @@ func newFakeDynamicEthChannelConfig(lgr log.Logger, calldataCfg := ChannelConfig{ MaxFrameSize: 120_000 - 1, TargetNumFrames: 1, + DaType: DaTypeCalldata, } blobCfg := ChannelConfig{ MaxFrameSize: eth.MaxBlobDataSize - 1, TargetNumFrames: 3, // gets closest to amortized fixed tx costs - UseBlobs: true, + DaType: DaTypeBlob, } calldataCfg.InitNoneCompressor() blobCfg.InitNoneCompressor() @@ -353,7 +354,7 @@ func TestChannelManager_TxData(t *testing.T) { cfg.chooseBlobs = tc.chooseBlobsWhenChannelCreated m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - require.Equal(t, tc.chooseBlobsWhenChannelCreated, m.defaultCfg.UseBlobs) + require.Equal(t, tc.chooseBlobsWhenChannelCreated, m.defaultCfg.DaType == DaTypeBlob) // Seed channel manager with a block rng := rand.New(rand.NewSource(99)) @@ -390,8 +391,8 @@ func TestChannelManager_TxData(t *testing.T) { } require.Equal(t, tc.numExpectedAssessments, cfg.assessments) - require.Equal(t, tc.chooseBlobsWhenChannelSubmitted, data.asBlob) - require.Equal(t, tc.chooseBlobsWhenChannelSubmitted, m.defaultCfg.UseBlobs) + require.Equal(t, tc.chooseBlobsWhenChannelSubmitted, data.daType == DaTypeBlob) + require.Equal(t, tc.chooseBlobsWhenChannelSubmitted, m.defaultCfg.DaType == DaTypeBlob) }) } diff --git a/op-batcher/batcher/channel_test.go b/op-batcher/batcher/channel_test.go index 0e1365eceec9c..19759c40ef8de 100644 --- a/op-batcher/batcher/channel_test.go +++ b/op-batcher/batcher/channel_test.go @@ -131,7 +131,7 @@ func TestChannel_NextTxData_singleFrameTx(t *testing.T) { const n = 6 lgr := testlog.Logger(t, log.LevelWarn) ch, err := newChannelWithChannelOut(lgr, metrics.NoopMetrics, ChannelConfig{ - UseBlobs: false, + DaType: DaTypeCalldata, TargetNumFrames: n, CompressorConfig: compressor.Config{ CompressionAlgo: derive.Zlib, @@ -172,7 +172,7 @@ func TestChannel_NextTxData_multiFrameTx(t *testing.T) { const n = 6 lgr := testlog.Logger(t, log.LevelWarn) ch, err := newChannelWithChannelOut(lgr, metrics.NoopMetrics, ChannelConfig{ - UseBlobs: true, + DaType: DaTypeBlob, TargetNumFrames: n, CompressorConfig: compressor.Config{ CompressionAlgo: derive.Zlib, diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index ff62a84ba960b..f04353a676c35 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -45,6 +45,8 @@ type txRef struct { id txID isCancel bool isBlob bool + daType DaType + size int } func (r txRef) String() string { @@ -79,6 +81,10 @@ type RollupClient interface { SyncStatus(ctx context.Context) (*eth.SyncStatus, error) } +type AltDAClient interface { + SetInput(ctx context.Context, data []byte) (altda.CommitmentData, error) +} + // DriverSetup is the collection of input/output interfaces and configuration that the driver operates on. type DriverSetup struct { Log log.Logger @@ -89,7 +95,7 @@ type DriverSetup struct { L1Client L1Client EndpointProvider dial.L2EndpointProvider ChannelConfig ChannelConfigProvider - AltDA *altda.DAClient + AltDA AltDAClient ChannelOutFactory ChannelOutFactory ActiveSeqChanged chan struct{} // optional } @@ -753,6 +759,12 @@ func (l *BatchSubmitter) publishTxToL1(ctx context.Context, queue *txmgr.Queue[t } l.Metr.RecordLatestL1Block(l1tip) + // In AltDA mode, before pulling data out of the state, we make sure + // that the daGroup has not reached the maximum number of goroutines. + // This is to prevent blocking the main event loop when submitting the data to the DA Provider. + if l.Config.UseAltDA && !daGroup.TryGo(func() error { return nil }) { + return io.EOF + } // Collect next transaction data. This pulls data out of the channel, so we need to make sure // to put it back if ever da or txmgr requests fail, by calling l.recordFailedDARequest/recordFailedTx. l.channelMgrMutex.Lock() @@ -812,19 +824,16 @@ func (l *BatchSubmitter) cancelBlockingTx(queue *txmgr.Queue[txRef], receiptsCh l.sendTx(txData{}, true, candidate, queue, receiptsCh) } -// publishToAltDAAndL1 posts the txdata to the DA Provider and then sends the commitment to L1. -func (l *BatchSubmitter) publishToAltDAAndL1(txdata txData, queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group) { - // sanity checks - if nf := len(txdata.frames); nf != 1 { - l.Log.Crit("Unexpected number of frames in calldata tx", "num_frames", nf) - } - if txdata.asBlob { - l.Log.Crit("Unexpected blob txdata with AltDA enabled") +// publishToAltDAAndStoreCommitment posts the txdata to the DA Provider and stores the returned commitment +// in the channelMgr. The commitment will later be sent to the L1 while making sure to follow holocene's strict ordering rules. +func (l *BatchSubmitter) publishToAltDAAndStoreCommitment(txdata txData, daGroup *errgroup.Group) { + if txdata.daType != DaTypeAltDA { + l.Log.Crit("publishToAltDAAndStoreCommitment called with non-AltDA txdata") } // when posting txdata to an external DA Provider, we use a goroutine to avoid blocking the main loop // since it may take a while for the request to return. - goroutineSpawned := daGroup.TryGo(func() error { + daGroup.Go(func() error { // TODO: probably shouldn't be using the global shutdownCtx here, see https://go.dev/blog/context-and-structs // but sendTransaction receives l.killCtx as an argument, which currently is only canceled after waiting for the main loop // to exit, which would wait on this DA call to finish, which would take a long time. @@ -843,17 +852,12 @@ func (l *BatchSubmitter) publishToAltDAAndL1(txdata txData, queue *txmgr.Queue[t } return nil } - l.Log.Info("Set altda input", "commitment", comm, "tx", txdata.ID()) - candidate := l.calldataTxCandidate(comm.TxData()) - l.sendTx(txdata, false, candidate, queue, receiptsCh) + l.Log.Info("Sent txdata to altda layer and received commitment", "commitment", comm, "tx", txdata.ID()) + l.channelMgrMutex.Lock() + l.channelMgr.CacheAltDACommitment(txdata, comm) + l.channelMgrMutex.Unlock() return nil }) - if !goroutineSpawned { - // We couldn't start the goroutine because the errgroup.Group limit - // is already reached. Since we can't send the txdata, we have to - // return it for later processing. We use nil error to skip error logging. - l.recordFailedDARequest(txdata.ID(), nil) - } } // sendTransaction creates & queues for sending a transaction to the batch inbox address with the given `txData`. @@ -861,16 +865,27 @@ func (l *BatchSubmitter) publishToAltDAAndL1(txdata txData, queue *txmgr.Queue[t // The method will block if the queue's MaxPendingTransactions is exceeded. func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group) error { var err error - - // if Alt DA is enabled we post the txdata to the DA Provider and replace it with the commitment. - if l.Config.UseAltDA { - l.publishToAltDAAndL1(txdata, queue, receiptsCh, daGroup) - // we return nil to allow publishStateToL1 to keep processing the next txdata - return nil - } - var candidate *txmgr.TxCandidate - if txdata.asBlob { + switch txdata.daType { + case DaTypeAltDA: + if !l.Config.UseAltDA { + l.Log.Crit("Received AltDA type txdata without AltDA being enabled") + } + if txdata.altDACommitment == nil { + // This means the txdata was not sent to the DA Provider yet. + // This will send the txdata to the DA Provider and store the commitment in the channelMgr. + // Next time this txdata is requested, we will have the commitment and can send it to the L1 (else branch below). + l.publishToAltDAAndStoreCommitment(txdata, daGroup) + // We return here because publishToAltDA is an async operation; the commitment + // is not yet ready to be submitted to the L1. + return nil + } + // This means the txdata was already sent to the DA Provider and we have the commitment + // so we can send the commitment to the L1 + l.Log.Info("Sending altda commitment to L1", "commitment", txdata.altDACommitment, "tx", txdata.ID()) + candidate = l.calldataTxCandidate(txdata.altDACommitment.TxData()) + + case DaTypeBlob: if candidate, err = l.blobTxCandidate(txdata); err != nil { // We could potentially fall through and try a calldata tx instead, but this would // likely result in the chain spending more in gas fees than it is tuned for, so best @@ -878,14 +893,18 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txRef // or configuration issue. return fmt.Errorf("could not create blob tx candidate: %w", err) } - } else { + case DaTypeCalldata: // sanity check if nf := len(txdata.frames); nf != 1 { l.Log.Crit("Unexpected number of frames in calldata tx", "num_frames", nf) } candidate = l.calldataTxCandidate(txdata.CallData()) + default: + l.Log.Crit("Unknown DA type", "da_type", txdata.daType) + } + if candidate == nil { + l.Log.Crit("txcandidate should have been set by one of the three branches above.") } - l.sendTx(txdata, false, candidate, queue, receiptsCh) return nil } @@ -905,7 +924,7 @@ func (l *BatchSubmitter) sendTx(txdata txData, isCancel bool, candidate *txmgr.T candidate.GasLimit = floorDataGas } - queue.Send(txRef{id: txdata.ID(), isCancel: isCancel, isBlob: txdata.asBlob}, *candidate, receiptsCh) + queue.Send(txRef{id: txdata.ID(), isCancel: isCancel, isBlob: txdata.daType == DaTypeBlob, daType: txdata.daType, size: txdata.Len()}, *candidate, receiptsCh) } func (l *BatchSubmitter) blobTxCandidate(data txData) (*txmgr.TxCandidate, error) { @@ -938,6 +957,10 @@ func (l *BatchSubmitter) handleReceipt(r txmgr.TxReceipt[txRef]) { l.recordFailedTx(r.ID.id, r.Err) } else if r.Receipt != nil { l.recordConfirmedTx(r.ID.id, r.Receipt) + if !r.ID.isCancel { + l.Metr.RecordBatchDaType(r.ID.daType.Name()) + l.Metr.RecordBatchDataSizeBytes(r.ID.daType.Name(), r.ID.size) + } } // Both r.Err and r.Receipt can be nil, in which case we do nothing. } @@ -945,10 +968,11 @@ func (l *BatchSubmitter) handleReceipt(r txmgr.TxReceipt[txRef]) { func (l *BatchSubmitter) recordFailedDARequest(id txID, err error) { l.channelMgrMutex.Lock() defer l.channelMgrMutex.Unlock() + failover := errors.Is(err, altda.ErrAltDADown) if err != nil { - l.Log.Warn("DA request failed", logFields(id, err)...) + l.Log.Warn("DA request failed", append([]interface{}{"failoverToEthDA", failover}, logFields(id, err)...)...) } - l.channelMgr.TxFailed(id) + l.channelMgr.AltDASubmissionFailed(id, failover) } func (l *BatchSubmitter) recordFailedTx(id txID, err error) { diff --git a/op-batcher/batcher/driver_test.go b/op-batcher/batcher/driver_test.go index d2e7621858c00..b3b7e42968c5e 100644 --- a/op-batcher/batcher/driver_test.go +++ b/op-batcher/batcher/driver_test.go @@ -3,15 +3,23 @@ package batcher import ( "context" "errors" + "math/big" "sync" "testing" + "time" + altda "github.com/ethereum-optimism/optimism/op-alt-da" + "github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/metrics" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -116,6 +124,8 @@ func TestBatchSubmitter_SafeL1Origin_FailsToResolveRollupClient(t *testing.T) { ep.rollupClientErr = errors.New("failed to resolve rollup client") _, err := bs.safeL1Origin(context.Background()) + log := testlog.Logger(t, log.LevelDebug) + log.Debug("Err", err) require.Error(t, err) } @@ -160,3 +170,219 @@ func TestBatchSubmitter_sendTx_FloorDataGas(t *testing.T) { expectedFloorDataGas := uint64(21_000 + 12*10) require.GreaterOrEqual(t, candidateOut.GasLimit, expectedFloorDataGas) } + +// ======= ALTDA TESTS ======= + +// fakeL1Client is just a dummy struct. All fault injection is done via the fakeTxMgr (which doesn't interact with this fakeL1Client). +type fakeL1Client struct { +} + +func (f *fakeL1Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + if number == nil { + number = big.NewInt(0) + } + return &types.Header{ + Number: number, + ParentHash: common.Hash{}, + Time: 0, + }, nil +} +func (f *fakeL1Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { + return 0, nil +} + +func altDASetup(_ *testing.T, log log.Logger) (*BatchSubmitter, *mockL2EndpointProvider, *altda.MockDAClient, *testutils.FakeTxMgr) { + ep := newEndpointProvider() + + rollupCfg := &rollup.Config{ + Genesis: rollup.Genesis{L2: eth.BlockID{Number: 0}, L1: eth.BlockID{Number: genesisL1Origin}}, + L2ChainID: big.NewInt(1234), + } + batcherCfg := BatcherConfig{ + PollInterval: 10 * time.Millisecond, + UseAltDA: true, + } + + fakeTxMgr := testutils.NewFakeTxMgr(log.With("subsystem", "fake-txmgr"), common.Address{0}) + l1Client := &fakeL1Client{} + + channelCfg := ChannelConfig{ + // SeqWindowSize: 15, + // SubSafetyMargin: 4, + ChannelTimeout: 10, + MaxFrameSize: 150, // so that each channel has exactly 1 frame + TargetNumFrames: 1, + BatchType: derive.SingularBatchType, + CompressorConfig: compressor.Config{ + Kind: compressor.NoneKind, + }, + DaType: DaTypeAltDA, + } + mockAltDAClient := altda.NewCountingGenericCommitmentMockDAClient(log.With("subsystem", "da-client")) + return NewBatchSubmitter(DriverSetup{ + Log: log, + Metr: metrics.NoopMetrics, + RollupConfig: rollupCfg, + ChannelConfig: channelCfg, + Config: batcherCfg, + EndpointProvider: ep, + Txmgr: fakeTxMgr, + L1Client: l1Client, + AltDA: mockAltDAClient, + }), ep, mockAltDAClient, fakeTxMgr +} + +func fakeSyncStatus(unsafeL2BlockNum uint64, L1BlockRef eth.L1BlockRef) *eth.SyncStatus { + return ð.SyncStatus{ + UnsafeL2: eth.L2BlockRef{ + Number: unsafeL2BlockNum, + L1Origin: eth.BlockID{ + Number: 0, + }, + }, + SafeL2: eth.L2BlockRef{ + Number: 0, + L1Origin: eth.BlockID{ + Number: 0, + }, + }, + HeadL1: L1BlockRef, + } +} + +// There are 4 failure cases (unhappy paths) that the op-batcher has to deal with. +// They are outlined in https://github.com/ethereum-optimism/optimism/tree/develop/op-batcher#happy-path +// This test suite covers these 4 cases in the context of AltDA. +func TestBatchSubmitter_AltDA_FailureCase1_L2Reorg(t *testing.T) { + t.Parallel() + log := testlog.Logger(t, log.LevelDebug) + bs, ep, mockAltDAClient, fakeTxMgr := altDASetup(t, log) + + L1Block0 := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, nil, nil, nil, types.DefaultBlockConfig) + L1Block0Ref := eth.L1BlockRef{ + Hash: L1Block0.Hash(), + Number: L1Block0.NumberU64(), + } + // We return incremental syncStatuses to force the op-batcher to entirely process each L2 block one by one. + // To test multi channel behavior, we could return a sync status that is multiple blocks ahead of the current L2 block. + ep.rollupClient.Mock.On("SyncStatus").Times(10).Return(fakeSyncStatus(1, L1Block0Ref), nil) + ep.rollupClient.Mock.On("SyncStatus").Times(10).Return(fakeSyncStatus(2, L1Block0Ref), nil) + ep.rollupClient.Mock.On("SyncStatus").Times(10).Return(fakeSyncStatus(3, L1Block0Ref), nil) + ep.rollupClient.Mock.On("SyncStatus").Times(10).Return(fakeSyncStatus(1, L1Block0Ref), nil) + ep.rollupClient.Mock.On("SyncStatus").Times(10).Return(fakeSyncStatus(2, L1Block0Ref), nil) + ep.rollupClient.Mock.On("SyncStatus").Return(fakeSyncStatus(3, L1Block0Ref), nil) + + L2Block0 := newMiniL2BlockWithNumberParent(1, big.NewInt(0), common.HexToHash("0x0")) + L2Block1 := newMiniL2BlockWithNumberParent(1, big.NewInt(1), L2Block0.Hash()) + L2Block2 := newMiniL2BlockWithNumberParent(1, big.NewInt(2), L2Block1.Hash()) + L2Block2Prime := newMiniL2BlockWithNumberParentAndL1Information(1, big.NewInt(2), L2Block1.Hash(), 101, 0) + L2Block3Prime := newMiniL2BlockWithNumberParent(1, big.NewInt(3), L2Block2Prime.Hash()) + + // L2block0 is the genesis block which is considered safe, so never loaded into the state. + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(1)).Twice().Return(L2Block1, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(2)).Once().Return(L2Block2, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(2)).Once().Return(L2Block2Prime, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(3)).Twice().Return(L2Block3Prime, nil) + + err := bs.StartBatchSubmitting() + require.NoError(t, err) + time.Sleep(1 * time.Second) // 1 second is enough to process all blocks at 10ms poll interval + err = bs.StopBatchSubmitting(context.Background()) + require.NoError(t, err) + + // After the reorg, block 1 needs to be reprocessed, hence why we see 5 store calls: 1, 2, 1, 2', 3' + require.Equal(t, 5, mockAltDAClient.StoreCount) + require.Equal(t, uint64(5), fakeTxMgr.Nonce) + +} + +func TestBatchSubmitter_AltDA_FailureCase2_FailedL1Tx(t *testing.T) { + t.Parallel() + log := testlog.Logger(t, log.LevelDebug) + bs, ep, mockAltDAClient, fakeTxMgr := altDASetup(t, log) + + L1Block0 := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, nil, nil, nil, types.DefaultBlockConfig) + L1Block0Ref := eth.L1BlockRef{ + Hash: L1Block0.Hash(), + Number: L1Block0.NumberU64(), + } + // We return incremental syncStatuses to force the op-batcher to entirely process each L2 block one by one. + // To test multi channel behavior, we could return a sync status that is multiple blocks ahead of the current L2 block. + ep.rollupClient.Mock.On("SyncStatus").Times(10).Return(fakeSyncStatus(1, L1Block0Ref), nil) + ep.rollupClient.Mock.On("SyncStatus").Times(10).Return(fakeSyncStatus(2, L1Block0Ref), nil) + ep.rollupClient.Mock.On("SyncStatus").Times(10).Return(fakeSyncStatus(3, L1Block0Ref), nil) + ep.rollupClient.Mock.On("SyncStatus").Return(fakeSyncStatus(4, L1Block0Ref), nil) + + L2Block0 := newMiniL2BlockWithNumberParent(1, big.NewInt(0), common.HexToHash("0x0")) + L2Block1 := newMiniL2BlockWithNumberParent(1, big.NewInt(1), L2Block0.Hash()) + L2Block2 := newMiniL2BlockWithNumberParent(1, big.NewInt(2), L2Block1.Hash()) + L2Block3 := newMiniL2BlockWithNumberParent(1, big.NewInt(3), L2Block2.Hash()) + L2Block4 := newMiniL2BlockWithNumberParent(1, big.NewInt(4), L2Block3.Hash()) + + // L2block0 is the genesis block which is considered safe, so never loaded into the state. + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(1)).Once().Return(L2Block1, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(2)).Once().Return(L2Block2, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(3)).Once().Return(L2Block3, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(4)).Once().Return(L2Block4, nil) + + fakeTxMgr.ErrorEveryNthSend(2) + err := bs.StartBatchSubmitting() + require.NoError(t, err) + time.Sleep(1 * time.Second) // 1 second is enough to process all blocks at 10ms poll interval + err = bs.StopBatchSubmitting(context.Background()) + require.NoError(t, err) + + require.Equal(t, 4, mockAltDAClient.StoreCount) + // TODO: we should prob also check that the commitments are in order? + require.Equal(t, uint64(4), fakeTxMgr.Nonce) +} + +func TestBatchSubmitter_AltDA_FailureCase3_ChannelTimeout(t *testing.T) { + // This function is not implemented because the batcher channel logic makes it very difficult to inject faults. + // A version of this test was implemented here: https://github.com/Layr-Labs/optimism/blob/4b79c981a13bf096ae2984634d976956fbbfddff/op-batcher/batcher/driver_test.go#L300 + // However we opted to not merge it into the main branch because it has an external dependency on the https://github.com/pingcap/failpoint package, + // and requires a lot of custom test setup and failpoint code injection into the batcher's codebase. + // See https://github.com/ethereum-optimism/optimism/commit/4b79c981a13bf096ae2984634d976956fbbfddff for the full implementation. +} + +func TestBatchSubmitter_AltDA_FailureCase4_FailedBlobSubmission(t *testing.T) { + t.Parallel() + log := testlog.Logger(t, log.LevelDebug) + bs, ep, mockAltDAClient, fakeTxMgr := altDASetup(t, log) + + L1Block0 := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, nil, nil, nil, types.DefaultBlockConfig) + L1Block0Ref := eth.L1BlockRef{ + Hash: L1Block0.Hash(), + Number: L1Block0.NumberU64(), + } + ep.rollupClient.Mock.On("SyncStatus").Return(fakeSyncStatus(4, L1Block0Ref), nil) + + L2Block0 := newMiniL2BlockWithNumberParent(1, big.NewInt(0), common.HexToHash("0x0")) + L2Block1 := newMiniL2BlockWithNumberParent(1, big.NewInt(1), L2Block0.Hash()) + L2Block2 := newMiniL2BlockWithNumberParent(1, big.NewInt(2), L2Block1.Hash()) + L2Block3 := newMiniL2BlockWithNumberParent(1, big.NewInt(3), L2Block2.Hash()) + L2Block4 := newMiniL2BlockWithNumberParent(1, big.NewInt(4), L2Block3.Hash()) + + // L2block0 is the genesis block which is considered safe, so never loaded into the state. + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(1)).Once().Return(L2Block1, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(2)).Once().Return(L2Block2, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(3)).Once().Return(L2Block3, nil) + ep.ethClient.Mock.On("BlockByNumber", big.NewInt(4)).Once().Return(L2Block4, nil) + + mockAltDAClient.DropEveryNthPut(2) + + err := bs.StartBatchSubmitting() + require.NoError(t, err) + time.Sleep(1 * time.Second) // 1 second is enough to process all blocks at 10ms poll interval + err = bs.StopBatchSubmitting(context.Background()) + require.NoError(t, err) + + require.Equal(t, 4, mockAltDAClient.StoreCount) + require.Equal(t, uint64(4), fakeTxMgr.Nonce) +} diff --git a/op-batcher/batcher/service.go b/op-batcher/batcher/service.go index d148b084f310a..35250e102b616 100644 --- a/op-batcher/batcher/service.go +++ b/op-batcher/batcher/service.go @@ -246,34 +246,40 @@ func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error { TargetNumFrames: cfg.TargetNumFrames, SubSafetyMargin: cfg.SubSafetyMargin, BatchType: cfg.BatchType, + // DaType: set below } - switch cfg.DataAvailabilityType { - case flags.BlobsType, flags.AutoType: - if !cfg.TestUseMaxTxSizeForBlobs { - // account for version byte prefix - cc.MaxFrameSize = eth.MaxBlobDataSize - 1 + if bs.UseAltDA { + if cfg.DataAvailabilityType == flags.CalldataType { + cc.DaType = DaTypeAltDA + } else { + return fmt.Errorf("altDA is currently only supported with calldata DA Type") } - cc.UseBlobs = true - case flags.CalldataType: // do nothing - default: - return fmt.Errorf("unknown data availability type: %v", cfg.DataAvailabilityType) - } - if bs.UseAltDA && cc.UseBlobs { - return fmt.Errorf("cannot use data availability type blobs or auto with Alt-DA") - } - - if bs.UseAltDA && cc.MaxFrameSize > altda.MaxInputSize { - return fmt.Errorf("max frame size %d exceeds altDA max input size %d", cc.MaxFrameSize, altda.MaxInputSize) + if cc.MaxFrameSize > altda.MaxInputSize { + return fmt.Errorf("max frame size %d exceeds altDA max input size %d", cc.MaxFrameSize, altda.MaxInputSize) + } + } else { + switch cfg.DataAvailabilityType { + case flags.BlobsType, flags.AutoType: + if !cfg.TestUseMaxTxSizeForBlobs { + // account for version byte prefix + cc.MaxFrameSize = eth.MaxBlobDataSize - 1 + } + cc.DaType = DaTypeBlob + case flags.CalldataType: // do nothing + cc.DaType = DaTypeCalldata + default: + return fmt.Errorf("unknown data availability type: %v", cfg.DataAvailabilityType) + } } cc.InitCompressorConfig(cfg.ApproxComprRatio, cfg.Compressor, cfg.CompressionAlgo) - if cc.UseBlobs && !bs.RollupConfig.IsEcotone(uint64(time.Now().Unix())) { + if cc.UseBlobs() && !bs.RollupConfig.IsEcotone(uint64(time.Now().Unix())) { return errors.New("cannot use Blobs before Ecotone") } - if !cc.UseBlobs && bs.RollupConfig.IsEcotone(uint64(time.Now().Unix())) { + if !cc.UseBlobs() && bs.RollupConfig.IsEcotone(uint64(time.Now().Unix())) { bs.Log.Warn("Ecotone upgrade is active, but batcher is not configured to use Blobs!") } @@ -305,7 +311,7 @@ func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error { calldataCC := cc calldataCC.TargetNumFrames = 1 calldataCC.MaxFrameSize = 120_000 - calldataCC.UseBlobs = false + calldataCC.DaType = DaTypeCalldata calldataCC.ReinitCompressorConfig() bs.ChannelConfig = NewDynamicEthChannelConfig(bs.Log, 10*time.Second, bs.TxManager, cc, calldataCC) @@ -403,10 +409,11 @@ func (bs *BatcherService) initRPCServer(cfg *CLIConfig) error { func (bs *BatcherService) initAltDA(cfg *CLIConfig) error { config := cfg.AltDA - if err := config.Check(); err != nil { + daClient, err := config.NewDAClient() + if err != nil { return err } - bs.AltDA = config.NewDAClient() + bs.AltDA = daClient bs.UseAltDA = config.Enabled return nil } diff --git a/op-batcher/batcher/test_batch_submitter.go b/op-batcher/batcher/test_batch_submitter.go index e3e17f1542eb3..a97b40511f74e 100644 --- a/op-batcher/batcher/test_batch_submitter.go +++ b/op-batcher/batcher/test_batch_submitter.go @@ -28,7 +28,7 @@ func (l *TestBatchSubmitter) JamTxPool(ctx context.Context) error { var candidate *txmgr.TxCandidate var err error cc := l.channelMgr.cfgProvider.ChannelConfig(true) - if cc.UseBlobs { + if cc.UseBlobs() { candidate = l.calldataTxCandidate([]byte{}) } else if candidate, err = l.blobTxCandidate(emptyTxData); err != nil { return err diff --git a/op-batcher/batcher/tx_data.go b/op-batcher/batcher/tx_data.go index 0165f85f079ed..ead74374ea7b1 100644 --- a/op-batcher/batcher/tx_data.go +++ b/op-batcher/batcher/tx_data.go @@ -4,11 +4,37 @@ import ( "fmt" "strings" + altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive/params" "github.com/ethereum-optimism/optimism/op-service/eth" ) +// DaType determines how txData is submitted to L1. +type DaType int + +const ( + // DaTypeCalldata means that the (single) frame in the txData is submitted as calldata. + DaTypeCalldata DaType = iota + // DaTypeBlob means that the frame(s) in the txData are submitted as ethereum 4844 blobs. + DaTypeBlob + // DaTypeAltDA means that the frame(s) in the txData are submitted to an altda da-server. + DaTypeAltDA +) + +func (t DaType) Name() string { + switch t { + case DaTypeCalldata: + return "calldata" + case DaTypeBlob: + return "blob" + case DaTypeAltDA: + return "altda" + default: + return "unknown" + } +} + // txData represents the data for a single transaction. // // Note: The batcher currently sends exactly one frame per transaction. This @@ -16,7 +42,12 @@ import ( // different channels. type txData struct { frames []frameData - asBlob bool // indicates whether this should be sent as blob + // daType represents the DA type which the frames data will be submitted to. + daType DaType + // altDACommitment is non-nil when the frames have been sent to the alt-da server, + // and the received commitment needs to be sent to the L1. + // Should only be present when daType is DaTypeAltDA. + altDACommitment altda.CommitmentData } func singleFrameTxData(frame frameData) txData { diff --git a/op-batcher/flags/flags.go b/op-batcher/flags/flags.go index d62e12ca680a8..072bb58cf91e5 100644 --- a/op-batcher/flags/flags.go +++ b/op-batcher/flags/flags.go @@ -82,8 +82,10 @@ var ( EnvVars: prefixEnvVars("MAX_BLOCKS_PER_SPAN_BATCH"), } TargetNumFramesFlag = &cli.IntFlag{ - Name: "target-num-frames", - Usage: "The target number of frames to create per channel. Controls number of blobs per blob tx, if using Blob DA.", + Name: "target-num-frames", + Usage: "The target number of frames to create per channel. " + + "Controls number of blobs per blob tx, if using Blob DA, " + + "or number of frames per blob, if using altDA.", Value: 1, EnvVars: prefixEnvVars("TARGET_NUM_FRAMES"), } diff --git a/op-batcher/metrics/metrics.go b/op-batcher/metrics/metrics.go index 4eee89e5186f9..e83dd965a8e5d 100644 --- a/op-batcher/metrics/metrics.go +++ b/op-batcher/metrics/metrics.go @@ -54,6 +54,10 @@ type Metricer interface { RecordBlobUsedBytes(num int) + RecordBatchDaType(daType string) + RecordBatchDataSizeBytes(daType string, size int) + RecordFailoverToEthDA() + Document() []opmetrics.DocumentedMetric PendingDABytes() float64 @@ -93,6 +97,10 @@ type Metrics struct { channelOutputBytesTotal prometheus.Counter channelQueueLength prometheus.Gauge + batchSentDATypeTotal prometheus.CounterVec + batchStoredDataSizeBytesTotal prometheus.CounterVec + altDaFailoverTotal prometheus.Counter + batcherTxEvs opmetrics.EventVec blobUsedBytes prometheus.Histogram @@ -202,6 +210,25 @@ func NewMetrics(procName string) *Metrics { Name: "channel_queue_length", Help: "The number of channels currently in memory.", }), + batchSentDATypeTotal: *factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: "batch_sent_da_type_total", + Help: "Total number of batches successfully stored, categorized by DA type.", + }, + []string{"da_type"}, + ), + batchStoredDataSizeBytesTotal: *factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: "batch_stored_data_size_bytes_total", + Help: "Total batch size stored in each DA type (in bytes).", + }, + []string{"da_type"}, + ), + altDaFailoverTotal: factory.NewCounter(prometheus.CounterOpts{ + Namespace: ns, + Name: "alt_da_failover_total", + Help: "Total number of batches that could not be stored in AltDA and were sent to L1 instead", + }), blobUsedBytes: factory.NewHistogram(prometheus.HistogramOpts{ Namespace: ns, Name: "blob_used_bytes", @@ -349,6 +376,18 @@ func (m *Metrics) RecordBlobUsedBytes(num int) { m.blobUsedBytes.Observe(float64(num)) } +func (m *Metrics) RecordBatchDaType(daType string) { + m.batchSentDATypeTotal.With(prometheus.Labels{"da_type": daType}).Inc() +} + +func (m *Metrics) RecordBatchDataSizeBytes(daType string, size int) { + m.batchStoredDataSizeBytesTotal.WithLabelValues(daType).Add(float64(size)) +} + +func (m *Metrics) RecordFailoverToEthDA() { + m.altDaFailoverTotal.Inc() +} + func (m *Metrics) RecordChannelQueueLength(len int) { m.channelQueueLength.Set(float64(len)) } diff --git a/op-batcher/metrics/noop.go b/op-batcher/metrics/noop.go index fc6795058f8a9..e2c3c2f7536d0 100644 --- a/op-batcher/metrics/noop.go +++ b/op-batcher/metrics/noop.go @@ -45,6 +45,11 @@ func (*noopMetrics) RecordBatchTxSubmitted() {} func (*noopMetrics) RecordBatchTxSuccess() {} func (*noopMetrics) RecordBatchTxFailed() {} func (*noopMetrics) RecordBlobUsedBytes(int) {} + +func (*noopMetrics) RecordBatchDaType(string) {} +func (*noopMetrics) RecordBatchDataSizeBytes(string, int) {} +func (*noopMetrics) RecordFailoverToEthDA() {} + func (*noopMetrics) StartBalanceMetrics(log.Logger, *ethclient.Client, common.Address) io.Closer { return nil } diff --git a/op-batcher/readme.md b/op-batcher/readme.md index bce2f84741af5..635396b05116d 100644 --- a/op-batcher/readme.md +++ b/op-batcher/readme.md @@ -49,7 +49,7 @@ The `publishingLoop` which 1. Waits for a signal from the `blockLoadingLoop` 2. Enqueues a new channel, if necessary. 3. Processes some unprocessed blocks into the current channel, triggers the compression of the block data and the creation of frames. -4. Sends frames from the channel queue to the DA layer as (e.g. to Ethereum L1 as calldata or blob transactions). +4. Sends frames from the channel queue to the DA layer (e.g. to Ethereum L1 as calldata or blob transactions). 5. If there is more transaction data to send, go to 2. Else go to 1. The `receiptsLoop` which @@ -97,18 +97,26 @@ architecture-beta The `blockCursor` state variable tracks the next unprocessed block. In each channel, the `frameCursor` tracks the next unsent frame. -### Reorgs +### Failure Cases -When an L2 unsafe reorg is detected, the batch submitter will reset its state, and wait for any in flight transactions to be ingested by the verifier nodes before starting work again. +#### Reorgs -### Tx Failed +When an L2 reorg (safe or unsafe) is detected, the batch submitter will reset its state, and wait for any in flight transactions to be ingested by the verifier nodes before starting work again. + +#### Tx Failed When a Tx fails, an asynchronous receipts handler is triggered. The channel from whence the Tx's frames came has its `frameCursor` rewound, so that all the frames can be resubmitted in order. -### Channel Times Out +> Note: there is an issue with this simple logic. See https://github.com/ethereum-optimism/optimism/issues/13283 + +#### Channel Times Out When a Tx is confirmed, an asynchronous receipts handler is triggered. We only update the batcher's state if the channel timed out on chain. In that case, the `blockCursor` is rewound to the first block added to that channel, and the channel queue is cleared out. This allows the batcher to start fresh building a new channel starting from the same block -- it does not need to refetch blocks from the sequencer. +#### AltDA Submission Fails + +When an AltDA submission fails, the frames get pushed back into their respective channel, and will be retried in the next tick. If the da-server returns a 503 HTTP error, then failover to ethDA-calldata is triggered for that specific channel. Each channel will independently always first try to submit to EigenDA. + ## Design Principles and Optimization Targets At the current time, the batcher should be optimized for correctness, simplicity and robustness. It is considered preferable to prioritize these properties, even at the expense of other potentially desirable properties such as frugality. For example, it is preferable to have the batcher resubmit some data from time to time ("wasting" money on data availability costs) instead of avoiding that by e.g. adding some persistent state to the batcher. diff --git a/op-chain-ops/cmd/op-run-block/main.go b/op-chain-ops/cmd/op-run-block/main.go index 5738fb7ddff23..15d9066f6c983 100644 --- a/op-chain-ops/cmd/op-run-block/main.go +++ b/op-chain-ops/cmd/op-run-block/main.go @@ -321,7 +321,7 @@ func Process(logger log.Logger, config *params.ChainConfig, blockContext vm.BlockContext signer = types.MakeSigner(config, header.Number, header.Time) ) - blockContext = core.NewEVMBlockContext(header, chainCtx, nil, config, statedb) + blockContext = core.NewEVMBlockContext(header, chainCtx, nil, config, statedb, &blockContext.FeeCurrencyContext) vmenv := vm.NewEVM(blockContext, statedb, config, cfg) if beaconRoot := block.ParentBeaconRoot; beaconRoot != nil { core.ProcessBeaconBlockRoot(*beaconRoot, vmenv) @@ -336,7 +336,7 @@ func Process(logger log.Logger, config *params.ChainConfig, for i, tx := range block.Transactions { logger.Info("Processing tx", "i", i, "hash", tx.Hash()) _, _ = fmt.Fprintf(outW, "# Processing tx %d\n", i) - msg, err := core.TransactionToMessage(tx, signer, header.BaseFee) + msg, err := core.TransactionToMessage(tx, signer, header.BaseFee, blockContext.FeeCurrencyContext.ExchangeRates) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } diff --git a/op-chain-ops/cmd/op-simulate/main.go b/op-chain-ops/cmd/op-simulate/main.go index 5c7d95feb2dc3..ea7a0cdbaa9c0 100644 --- a/op-chain-ops/cmd/op-simulate/main.go +++ b/op-chain-ops/cmd/op-simulate/main.go @@ -302,10 +302,11 @@ func simulate(ctx context.Context, logger log.Logger, conf *params.ChainConfig, // run the transaction start := time.Now() + feeCurrencyContext := core.GetFeeCurrencyContext(header, conf, state) // nil block-author, since it defaults to header.coinbase - blockCtx := core.NewEVMBlockContext(header, cCtx, nil, conf, state) + blockCtx := core.NewEVMBlockContext(header, cCtx, nil, conf, state, feeCurrencyContext) evm := vm.NewEVM(blockCtx, state, conf, vmConfig) - receipt, err := core.ApplyTransaction(evm, &gp, state, header, tx, &usedGas) + receipt, err := core.ApplyTransaction(evm, &gp, state, header, tx, &usedGas, feeCurrencyContext) if err != nil { return fmt.Errorf("failed to apply tx: %w", err) } diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 3c906284486bd..0ee18bbf958df 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -282,9 +282,11 @@ var _ ConfigChecker = (*GasTokenDeployConfig)(nil) func (d *GasTokenDeployConfig) Check(log log.Logger) error { if d.UseCustomGasToken { - if d.CustomGasTokenAddress == (common.Address{}) { - return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig) - } + // NOTE: we are using the address(0) as an instruction to deploy the L1 token, + // so this deploy-config validation has to be disabled + // if d.CustomGasTokenAddress == (common.Address{}) { + // return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig) + // } log.Info("Using custom gas token", "address", d.CustomGasTokenAddress) } return nil @@ -319,6 +321,8 @@ type EIP1559DeployConfig struct { EIP1559Denominator uint64 `json:"eip1559Denominator"` // EIP1559DenominatorCanyon is the denominator of EIP1559 base fee market when Canyon is active. EIP1559DenominatorCanyon uint64 `json:"eip1559DenominatorCanyon"` + // EIP1559BaseFeeFloor is the fixed floor for the EIP1559 base fee market. + EIP1559BaseFeeFloor uint64 `json:"eip1559BaseFeeFloor,omitempty"` } var _ ConfigChecker = (*EIP1559DeployConfig)(nil) @@ -946,6 +950,12 @@ type DeployConfig struct { // Legacy, ignored, here for strict-JSON decoding to be accepted. LegacyDeployConfig `evm:"-"` + + // DeployCeloContracts indicates whether to deploy Celo contracts. + DeployCeloContracts bool `json:"deployCeloContracts"` + // Unused, added to make strict config parsing possible + ProxyAdminOwnerIsMultiSig *bool `json:"proxyAdminOwnerIsMultisig,omitempty"` + ExternalSuperchainConfig *common.Address `json:"externalSuperchainConfig,omitempty"` } // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy @@ -1064,6 +1074,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *eth.BlockRef, l2GenesisBlockHa ProtocolVersionsAddress: d.ProtocolVersionsProxy, AltDAConfig: altDA, ChainOpConfig: chainOpConfig, + Cel2Time: d.RegolithTime(l1StartTime), }, nil } @@ -1165,7 +1176,7 @@ func (d *L1Deployments) Check(deployConfig *DeployConfig) error { (name == "OptimismPortal" || name == "L2OutputOracle" || name == "L2OutputOracleProxy") { continue } - if !deployConfig.UseAltDA && + if (!deployConfig.UseAltDA || deployConfig.DACommitmentType == altda.GenericCommitmentString) && (name == "DataAvailabilityChallenge" || name == "DataAvailabilityChallengeProxy") { continue diff --git a/op-chain-ops/genesis/genesis.go b/op-chain-ops/genesis/genesis.go index fa55eda6cf21a..f5c71da612850 100644 --- a/op-chain-ops/genesis/genesis.go +++ b/op-chain-ops/genesis/genesis.go @@ -75,11 +75,16 @@ func NewL2Genesis(config *DeployConfig, l1StartHeader *eth.BlockRef) (*core.Gene JovianTime: config.JovianTime(l1StartTime), PragueTime: config.IsthmusTime(l1StartTime), InteropTime: config.InteropTime(l1StartTime), + Cel2Time: config.RegolithTime(l1StartTime), + GingerbreadBlock: big.NewInt(0), Optimism: ¶ms.OptimismConfig{ EIP1559Denominator: eip1559Denom, EIP1559Elasticity: eip1559Elasticity, EIP1559DenominatorCanyon: &eip1559DenomCanyon, }, + Celo: ¶ms.CeloConfig{ + EIP1559BaseFeeFloor: config.EIP1559BaseFeeFloor, + }, } gasLimit := config.L2GenesisBlockGasLimit diff --git a/op-chain-ops/genesis/testdata/test-deploy-config-full.json b/op-chain-ops/genesis/testdata/test-deploy-config-full.json index 0f332842204f9..cdc5ed06b14d6 100644 --- a/op-chain-ops/genesis/testdata/test-deploy-config-full.json +++ b/op-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -92,5 +92,7 @@ "daChallengeProxy": "0x0000000000000000000000000000000000000000", "daChallengeWindow": 0, "daResolveWindow": 0, - "daResolverRefundPercentage": 0 + "daResolverRefundPercentage": 0, + "deployCeloContracts": false, + "eip1559BaseFeeFloor": 5000000000 } diff --git a/op-chain-ops/script/cheatcodes_external.go b/op-chain-ops/script/cheatcodes_external.go index 5b1b125d4e360..62f0094ea6537 100644 --- a/op-chain-ops/script/cheatcodes_external.go +++ b/op-chain-ops/script/cheatcodes_external.go @@ -516,11 +516,11 @@ func (c *CheatCodesPrecompile) SerializeBytes_9884b232(objectKey string, valueKe // WriteJson implements https://book.getfoundry.sh/cheatcodes/write-json func (c *CheatCodesPrecompile) WriteJson_e23cd19f(data string, path string) error { - return vm.ErrExecutionReverted + return nil } func (c *CheatCodesPrecompile) WriteJson_35d6ad46(data string, path string, valueKey string) error { - return vm.ErrExecutionReverted + return nil } // WriteToml implements https://book.getfoundry.sh/cheatcodes/write-toml diff --git a/op-deployer/pkg/deployer/broadcaster/keyed.go b/op-deployer/pkg/deployer/broadcaster/keyed.go index fe3a4b18d7fb8..422a0c0a12e56 100644 --- a/op-deployer/pkg/deployer/broadcaster/keyed.go +++ b/op-deployer/pkg/deployer/broadcaster/keyed.go @@ -229,7 +229,7 @@ func asTxCandidate(bcast script.Broadcast, blockGasLimit uint64) txmgr.TxCandida // is clamped to the block gas limit since Geth will reject transactions that exceed it before letting them // into the mempool. func padGasLimit(data []byte, gasUsed uint64, creation bool, blockGasLimit uint64) uint64 { - intrinsicGas, err := core.IntrinsicGas(data, nil, nil, creation, true, true, false) + intrinsicGas, err := core.IntrinsicGas(data, nil, nil, creation, true, true, false, nil, nil) // This method never errors - we should look into it if it does. if err != nil { panic(err) diff --git a/op-e2e/actions/altda/altda_test.go b/op-e2e/actions/altda/altda_test.go index e5171d9241f18..ca080e7c2d23b 100644 --- a/op-e2e/actions/altda/altda_test.go +++ b/op-e2e/actions/altda/altda_test.go @@ -1,6 +1,7 @@ package altda import ( + "log/slog" "math/big" "math/rand" "testing" @@ -49,6 +50,12 @@ type L2AltDA struct { type AltDAParam func(p *e2eutils.TestParams) +func WithLogLevel(level slog.Level) AltDAParam { + return func(p *e2eutils.TestParams) { + p.LogLevel = level + } +} + func NewL2AltDA(t helpers.Testing, params ...AltDAParam) *L2AltDA { p := &e2eutils.TestParams{ MaxSequencerDrift: 40, @@ -57,11 +64,12 @@ func NewL2AltDA(t helpers.Testing, params ...AltDAParam) *L2AltDA { L1BlockTime: 12, UseAltDA: true, AllocType: config.AllocTypeAltDA, + LogLevel: log.LevelDebug, } for _, apply := range params { apply(p) } - log := testlog.Logger(t, log.LvlDebug) + log := testlog.Logger(t, p.LogLevel) dp := e2eutils.MakeDeployParams(t, p) sd := e2eutils.Setup(t, dp, helpers.DefaultAlloc) @@ -75,14 +83,13 @@ func NewL2AltDA(t helpers.Testing, params ...AltDAParam) *L2AltDA { engine := helpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath) engCl := engine.EngineClient(t, sd.RollupCfg) - storage := &altda.DAErrFaker{Client: altda.NewMockDAClient(log)} - l1F, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindBasic)) require.NoError(t, err) altDACfg, err := sd.RollupCfg.GetOPAltDAConfig() require.NoError(t, err) + storage := &altda.DAErrFaker{Client: altda.NewMockDAClient(log)} daMgr := altda.NewAltDAWithStorage(log, altDACfg, storage, &altda.NoopMetrics{}) sequencer := helpers.NewL2Sequencer(t, log, l1F, miner.BlobStore(), daMgr, engCl, sd.RollupCfg, 0) @@ -177,6 +184,34 @@ func (a *L2AltDA) ActNewL2Tx(t helpers.Testing) { a.lastCommBn = a.miner.L1Chain().CurrentBlock().Number.Uint64() } +// ActNewL2TxFinalized sends a new L2 transaction, submits a batch containing it to L1 +// and finalizes the L1 and L2 chains (including advancing enough to clear the altda challenge window). +// +// TODO: understand why (notation is l1unsafe/l1safe/l1finalized-l2unsafe/l2safe/l2finalized): +// - the first call advances heads by (0/0/17-71/71/1) +// - second call advances by 0/0/17-204/204/82, +// - but all subsequent calls advance status by exactly 0/0/17-204/204/204. +// +// 17 makes sense because challengeWindow=16 and we create 1 extra block before that, +// and 204 L2blocks = 17 L1blocks * 12 L2blocks/L1block (L1blocktime=12s, L2blocktime=1s) +func (a *L2AltDA) ActNewL2TxFinalized(t helpers.Testing) { + // Include a new l2 batcher transaction, submitting an input commitment to the l1. + a.ActNewL2Tx(t) + // Create ChallengeWindow empty blocks so the above batcher blocks can finalize (can't be challenged anymore) + a.ActL1Blocks(t, a.altDACfg.ChallengeWindow) + // Finalize the L1 chain and the L2 chain (by draining all events and running through derivation pipeline) + // TODO: understand why we need to drain the pipeline before AND after actL1Finalized + a.sequencer.ActL2PipelineFull(t) + a.ActL1Finalized(t) + a.sequencer.ActL2PipelineFull(t) + + // Uncomment the below code to observe the behavior described in the TODO above + // syncStatus := a.sequencer.SyncStatus() + // a.log.Info("Sync status after ActNewL2TxFinalized", + // "unsafeL1", syncStatus.HeadL1.Number, "safeL1", syncStatus.SafeL1.Number, "finalizedL1", syncStatus.FinalizedL1.Number, + // "unsafeL2", syncStatus.UnsafeL2.Number, "safeL2", syncStatus.SafeL2.Number, "finalizedL2", syncStatus.FinalizedL2.Number) +} + func (a *L2AltDA) ActDeleteLastInput(t helpers.Testing) { require.NoError(t, a.storage.Client.DeleteData(a.lastComm)) } @@ -363,7 +398,7 @@ func TestAltDA_ChallengeResolved(gt *testing.T) { } // DA storage service goes offline while sequencer keeps making blocks. When storage comes back online, it should be able to catch up. -func TestAltDA_StorageError(gt *testing.T) { +func TestAltDA_StorageGetError(gt *testing.T) { t := helpers.NewDefaultTesting(gt) harness := NewL2AltDA(t) @@ -528,11 +563,12 @@ func TestAltDA_Finalization(gt *testing.T) { t := helpers.NewDefaultTesting(gt) a := NewL2AltDA(t) - // build L1 block #1 + // Notation everywhere below is l1unsafe/l1safe/l1finalized-l2unsafe/l2safe/l2finalized + // build L1 block #1: 0/0/0-0/0/0 -> 1/1/0-0/0/0 a.ActL1Blocks(t, 1) a.miner.ActL1SafeNext(t) - // Fill with l2 blocks up to the L1 head + // Fill with l2 blocks up to the L1 head: 1/1/0:0/0/0 -> 1/1/0:1/1/0 a.sequencer.ActL1HeadSignal(t) a.sequencer.ActBuildToL1Head(t) @@ -540,7 +576,7 @@ func TestAltDA_Finalization(gt *testing.T) { a.sequencer.ActL1SafeSignal(t) require.Equal(t, uint64(1), a.sequencer.SyncStatus().SafeL1.Number) - // add L1 block #2 + // add L1 block #2: 1/1/0:1/1/0 -> 2/2/1:2/1/0 a.ActL1Blocks(t, 1) a.miner.ActL1SafeNext(t) a.miner.ActL1FinalizeNext(t) @@ -552,7 +588,7 @@ func TestAltDA_Finalization(gt *testing.T) { a.sequencer.ActL1FinalizedSignal(t) a.sequencer.ActL1SafeSignal(t) - // commit all the l2 blocks to L1 + // commit all the l2 blocks to L1: 2/2/1:2/1/0 -> 3/2/1:2/1/0 a.batcher.ActSubmitAll(t) a.miner.ActL1StartBlock(12)(t) a.miner.ActL1IncludeTx(a.dp.Addresses.Batcher)(t) @@ -561,31 +597,31 @@ func TestAltDA_Finalization(gt *testing.T) { // verify a.sequencer.ActL2PipelineFull(t) - // fill with more unsafe L2 blocks + // fill with more unsafe L2 blocks: 3/2/1:2/1/0 -> 3/2/1:3/1/0 a.sequencer.ActL1HeadSignal(t) a.sequencer.ActBuildToL1Head(t) - // submit those blocks too, block #4 + // submit those blocks too, block #4: 3/2/1:3/1/0 -> 4/2/1:3/1/0 a.batcher.ActSubmitAll(t) a.miner.ActL1StartBlock(12)(t) a.miner.ActL1IncludeTx(a.dp.Addresses.Batcher)(t) a.miner.ActL1EndBlock(t) - // add some more L1 blocks #5, #6 + // add some more L1 blocks #5, #6: 4/2/1:3/1/0 -> 6/2/1:3/1/0 a.miner.ActEmptyBlock(t) a.miner.ActEmptyBlock(t) - // and more unsafe L2 blocks + // and more unsafe L2 blocks: 6/2/1:3/1/0 -> 6/2/1:6/1/0 a.sequencer.ActL1HeadSignal(t) a.sequencer.ActBuildToL1Head(t) - // move safe/finalize markers: finalize the L1 chain block with the first batch, but not the second + // move safe/finalize markers: 6/2/1:6/1/0 -> 6/4/3:6/1/0 a.miner.ActL1SafeNext(t) // #2 -> #3 a.miner.ActL1SafeNext(t) // #3 -> #4 a.miner.ActL1FinalizeNext(t) // #1 -> #2 a.miner.ActL1FinalizeNext(t) // #2 -> #3 - // L1 safe and finalized as expected + // L1 safe and finalized as expected: a.sequencer.ActL2PipelineFull(t) a.sequencer.ActL1FinalizedSignal(t) a.sequencer.ActL1SafeSignal(t) @@ -607,3 +643,64 @@ func TestAltDA_Finalization(gt *testing.T) { // given 12s l1 time and 1s l2 time, l2 should be 12 * 3 = 36 blocks finalized require.Equal(t, uint64(36), a.sequencer.SyncStatus().FinalizedL2.Number) } + +// This test tests altDA -> ethDA -> altDA finalization behavior, simulating a temp altDA failure. +func TestAltDA_FinalizationAfterEthDAFailover(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + // we only print critical logs to be able to see the statusLogs + harness := NewL2AltDA(t, WithLogLevel(log.LevelDebug)) + + // We first call this twice because the first 2 times are irregular. + // See ActNewL2TxFinalized's TODO comment. + harness.ActNewL2TxFinalized(t) + harness.ActNewL2TxFinalized(t) + + // ActNewL2TxFinalized advances L1 by (1+ChallengeWindow)L1 blocks, and there are 12 L2 blocks per L1 block. + diffL2Blocks := (1 + harness.altDACfg.ChallengeWindow) * 12 + + for i := 0; i < 5; i++ { + ssBefore := harness.sequencer.SyncStatus() + harness.ActNewL2TxFinalized(t) + ssAfter := harness.sequencer.SyncStatus() + // Finalized head should advance normally in altda mode + require.Equal(t, ssBefore.FinalizedL2.Number+diffL2Blocks, ssAfter.FinalizedL2.Number) + } + + // We swap out altda batcher for ethda batcher + harness.batcher.ActAltDAFailoverToEthDA(t) + + for i := 0; i < 3; i++ { + ssBefore := harness.sequencer.SyncStatus() + harness.ActNewL2TxFinalized(t) + if i == 0 { + // TODO: figure out why we need to act twice for the first time after failover. + // I think it's because the L1 driven finalizedHead is set to L1FinalizedHead-ChallengeWindow (see damgr.go updateFinalizedFromL1), + // so it trails behind by an extra challenge_window when we switch over to ethDA. + harness.ActNewL2TxFinalized(t) + } + ssAfter := harness.sequencer.SyncStatus() + // Even after failover, the finalized head should continue advancing normally + require.Equal(t, ssBefore.FinalizedL2.Number+diffL2Blocks, ssAfter.FinalizedL2.Number) + } + + // Revert back to altda batcher (simulating that altda's temporary outage is resolved) + harness.batcher.ActAltDAFallbackToAltDA(t) + + for i := 0; i < 3; i++ { + ssBefore := harness.sequencer.SyncStatus() + harness.ActNewL2TxFinalized(t) + ssAfter := harness.sequencer.SyncStatus() + + // Even after fallback to altda, the finalized head should continue advancing normally + if i == 0 { + // This is the opposite as the altda->ethda direction. In this case, the first time we fallback to altda, + // the finalized head will advance by 2*diffL2Blocks: in ethda mode when driven by L1 finalization, + // the head is set to L1FinalizedHead-ChallengeWindow. After sending an altda commitment, the finalized head + // is now driven by the finalization of the altda commitment. + require.Equal(t, ssBefore.FinalizedL2.Number+2*diffL2Blocks, ssAfter.FinalizedL2.Number) + } else { + require.Equal(t, ssBefore.FinalizedL2.Number+diffL2Blocks, ssAfter.FinalizedL2.Number) + } + + } +} diff --git a/op-e2e/actions/batcher/l2_batcher_test.go b/op-e2e/actions/batcher/l2_batcher_test.go index 9d080d2bafd67..d7332df2fa211 100644 --- a/op-e2e/actions/batcher/l2_batcher_test.go +++ b/op-e2e/actions/batcher/l2_batcher_test.go @@ -472,7 +472,7 @@ func BigL2Txs(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { data := make([]byte, 120_000) // very large L2 txs, as large as the tx-pool will accept _, err := rng.Read(data[:]) // fill with random bytes, to make compression ineffective require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, nil, false, true, true, false, nil, nil) require.NoError(t, err) if gas > engine.EngineApi.RemainingBlockGas() { break diff --git a/op-e2e/actions/helpers/l1_miner.go b/op-e2e/actions/helpers/l1_miner.go index bf47753f48de5..2506c99143364 100644 --- a/op-e2e/actions/helpers/l1_miner.go +++ b/op-e2e/actions/helpers/l1_miner.go @@ -112,7 +112,8 @@ func (s *L1Miner) ActL1StartBlock(timeDelta uint64) Action { // TODO(client-pod#826) // Unfortunately this is not part of any Geth environment setup, // we just have to apply it, like how the Geth block-builder worker does. - context := core.NewEVMBlockContext(header, s.l1Chain, nil, s.l1Chain.Config(), statedb) + feeCurrencyContext := core.GetFeeCurrencyContext(header, s.l1Chain.Config(), statedb) + context := core.NewEVMBlockContext(header, s.l1Chain, nil, s.l1Chain.Config(), statedb, feeCurrencyContext) // NOTE: Unlikely to be needed for the beacon block root, but we setup any precompile overrides anyways for forwards-compatibility var precompileOverrides vm.PrecompileOverrides if vmConfig := s.l1Chain.GetVMConfig(); vmConfig != nil && vmConfig.PrecompileOverrides != nil { @@ -182,10 +183,11 @@ func (s *L1Miner) IncludeTx(t Testing, tx *types.Transaction) *types.Receipt { return nil } s.l1BuildingState.SetTxContext(tx.Hash(), len(s.L1Transactions)) - blockCtx := core.NewEVMBlockContext(s.l1BuildingHeader, s.l1Chain, nil, s.l1Cfg.Config, s.l1BuildingState) + feeCurrencyContext := core.GetFeeCurrencyContext(s.l1BuildingHeader, s.l1Cfg.Config, s.l1BuildingState) + blockCtx := core.NewEVMBlockContext(s.l1BuildingHeader, s.l1Chain, nil, s.l1Cfg.Config, s.l1BuildingState, feeCurrencyContext) evm := vm.NewEVM(blockCtx, s.l1BuildingState, s.l1Cfg.Config, *s.l1Chain.GetVMConfig()) receipt, err := core.ApplyTransaction( - evm, s.L1GasPool, s.l1BuildingState, s.l1BuildingHeader, tx.WithoutBlobTxSidecar(), &s.l1BuildingHeader.GasUsed) + evm, s.L1GasPool, s.l1BuildingState, s.l1BuildingHeader, tx.WithoutBlobTxSidecar(), &s.l1BuildingHeader.GasUsed, feeCurrencyContext) if err != nil { s.l1TxFailed = append(s.l1TxFailed, tx) t.Fatalf("failed to apply transaction to L1 block (tx %d): %v", len(s.L1Transactions), err) diff --git a/op-e2e/actions/helpers/l2_batcher.go b/op-e2e/actions/helpers/l2_batcher.go index 6ca2924aab4cf..f3799e79b7d8e 100644 --- a/op-e2e/actions/helpers/l2_batcher.go +++ b/op-e2e/actions/helpers/l2_batcher.go @@ -331,6 +331,20 @@ func (s *L2Batcher) ReadNextOutputFrame(t Testing) []byte { return data.Bytes() } +func (s *L2Batcher) ActAltDAFailoverToEthDA(t Testing) { + if !s.l2BatcherCfg.UseAltDA { + t.Fatalf("cannot failover to ethda when already using ethda") + } + s.l2BatcherCfg.UseAltDA = false +} + +func (s *L2Batcher) ActAltDAFallbackToAltDA(t Testing) { + if s.l2BatcherCfg.UseAltDA { + t.Fatalf("cannot fallback to altDA when already using altDA") + } + s.l2BatcherCfg.UseAltDA = true +} + // ActL2BatchSubmit constructs a batch tx from previous buffered L2 blocks, and submits it to L1 func (s *L2Batcher) ActL2BatchSubmit(t Testing, txOpts ...func(tx *types.DynamicFeeTx)) { s.ActL2BatchSubmitRaw(t, s.ReadNextOutputFrame(t), txOpts...) diff --git a/op-e2e/actions/helpers/user_test.go b/op-e2e/actions/helpers/user_test.go index ae3a30feabf6f..4e394c5c8fd14 100644 --- a/op-e2e/actions/helpers/user_test.go +++ b/op-e2e/actions/helpers/user_test.go @@ -100,7 +100,9 @@ func testCrossLayerUser(t *testing.T, allocType config.AllocType) { for _, f := range forks[:i+1] { // activate, all up to and incl this fork, at genesis tc.SetFork(f, 0) } - runCrossLayerUserTest(t, tc) + if fork != "regolith" { // regolith does not need testing for Cel2 + runCrossLayerUserTest(t, tc) + } }) t.Run("after_genesis", func(t *testing.T) { tc := hardforkScheduledTest{ @@ -112,7 +114,9 @@ func testCrossLayerUser(t *testing.T, allocType config.AllocType) { // activate this fork after genesis tc.SetFork(fork, futureTime) tc.runToFork = fork - runCrossLayerUserTest(t, tc) + if fork != "regolith" { // regolith does not need testing for Cel2 + runCrossLayerUserTest(t, tc) + } }) t.Run("not_yet", func(t *testing.T) { tc := hardforkScheduledTest{ @@ -126,7 +130,9 @@ func testCrossLayerUser(t *testing.T, allocType config.AllocType) { if i > 0 { tc.runToFork = forks[i-1] } - runCrossLayerUserTest(t, tc) + if fork != "regolith" { // regolith does not need testing for Cel2 + runCrossLayerUserTest(t, tc) + } }) }) } diff --git a/op-e2e/actions/proofs/celo_operator_fee_test_util.go b/op-e2e/actions/proofs/celo_operator_fee_test_util.go new file mode 100644 index 0000000000000..3cad2dfa74c1b --- /dev/null +++ b/op-e2e/actions/proofs/celo_operator_fee_test_util.go @@ -0,0 +1,18 @@ +package proofs + +import ( + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/addresses" +) + +// In the celo op-geth state transition function we issue the base fee to the fee handler +// if running in a cel2 context, otherwise it is issued to the base fee vault. +// We need to account for this here so that we can correctly account for all funds. +func getBaseFeeRecipientAddress(env *helpers.L2FaultProofEnv) common.Address { + if env.Sd.L2Cfg.Config.IsCel2(env.Sequencer.L2Unsafe().Time) { + return addresses.GetAddressesOrDefault(env.Sd.RollupCfg.L2ChainID, addresses.MainnetAddresses).FeeHandler + } + return predeploys.BaseFeeVaultAddr +} diff --git a/op-e2e/actions/proofs/isthmus_fork_test.go b/op-e2e/actions/proofs/isthmus_fork_test.go index f6e426d7f8b18..7b1d21c6e4f68 100644 --- a/op-e2e/actions/proofs/isthmus_fork_test.go +++ b/op-e2e/actions/proofs/isthmus_fork_test.go @@ -31,7 +31,7 @@ import ( ) var ( - isthmusL1BlockCodeHash = common.HexToHash("0x8e3fe7a416d3e5f3b7be74ddd4e7e58e516fa3f80b67c6d930e3cd7297da4a4b") + isthmusL1BlockCodeHash = common.HexToHash("0x3df0db3bfa482161b4e815804668f8f293288d5afad3bc20fb699383c145ef26") isthmusGasPriceOracleCodeHash = common.HexToHash("0x4d195a9d7caf9fb6d4beaf80de252c626c853afd5868c4f4f8d19c9d301c2679") isthmusOperatorFeeVaultCodeHash = common.HexToHash("0x57dc55c9c09ca456fa728f253fe7b895d3e6aae0706104935fe87c7721001971") ) diff --git a/op-e2e/actions/proofs/operator_fee_test.go b/op-e2e/actions/proofs/operator_fee_test.go index b1cc18ff5e87e..5581509f9c605 100644 --- a/op-e2e/actions/proofs/operator_fee_test.go +++ b/op-e2e/actions/proofs/operator_fee_test.go @@ -84,7 +84,7 @@ func Test_ProgramAction_OperatorFeeConsistency(gt *testing.T) { getCurrentBalances := func() (alice *big.Int, l1FeeVault *big.Int, baseFeeVault *big.Int, sequencerFeeVault *big.Int, operatorFeeVault *big.Int) { alice = balanceAt(env.Alice.Address()) l1FeeVault = balanceAt(predeploys.L1FeeVaultAddr) - baseFeeVault = balanceAt(predeploys.BaseFeeVaultAddr) + baseFeeVault = balanceAt(getBaseFeeRecipientAddress(env)) sequencerFeeVault = balanceAt(predeploys.SequencerFeeVaultAddr) operatorFeeVault = balanceAt(predeploys.OperatorFeeVaultAddr) diff --git a/op-e2e/actions/upgrades/span_batch_test.go b/op-e2e/actions/upgrades/span_batch_test.go index c37f41311de14..a05db864e6697 100644 --- a/op-e2e/actions/upgrades/span_batch_test.go +++ b/op-e2e/actions/upgrades/span_batch_test.go @@ -541,7 +541,7 @@ func TestSpanBatchLowThroughputChain(gt *testing.T) { data := make([]byte, rand.Intn(100)) _, err := crand.Read(data[:]) // fill with random bytes require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, nil, false, true, true, false, nil, nil) require.NoError(t, err) baseFee := seqEngine.L2Chain().CurrentBlock().BaseFee nonce, err := cl.PendingNonceAt(t.Ctx(), addrs[userIdx]) @@ -681,7 +681,7 @@ func TestBatchEquivalence(gt *testing.T) { data := make([]byte, rand.Intn(100)) _, err := crand.Read(data[:]) // fill with random bytes require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, nil, false, true, true, false, nil, nil) require.NoError(t, err) baseFee := seqEngine.L2Chain().CurrentBlock().BaseFee nonce, err := seqEngCl.PendingNonceAt(t.Ctx(), addrs[userIdx]) diff --git a/op-e2e/celo/.prettierrc.toml b/op-e2e/celo/.prettierrc.toml new file mode 100644 index 0000000000000..d5b43d58c0b8c --- /dev/null +++ b/op-e2e/celo/.prettierrc.toml @@ -0,0 +1,4 @@ +trailingComma = "es5" +tabWidth = 2 +semi = false +singleQuote = true diff --git a/op-e2e/celo/babel.config.cjs b/op-e2e/celo/babel.config.cjs new file mode 100644 index 0000000000000..a76dfe63099c9 --- /dev/null +++ b/op-e2e/celo/babel.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }]], +} diff --git a/op-e2e/celo/foundry.toml b/op-e2e/celo/foundry.toml new file mode 100644 index 0000000000000..8df5305625e49 --- /dev/null +++ b/op-e2e/celo/foundry.toml @@ -0,0 +1,19 @@ +[profile.default] + +# Compilation settings +src = '../../packages/contracts-bedrock/src/celo/' +out = 'forge-artifacts' +remappings = [ + '@openzeppelin/contracts-upgradeable/=../../packages/contracts-bedrock/lib/openzeppelin-contracts-upgradeable/contracts', + '@openzeppelin/contracts/=../../packages/contracts-bedrock/lib/openzeppelin-contracts/contracts', + 'forge-std/=../../packages/contracts-bedrock/lib/forge-std/src', +] +allow_paths = ["../../packages/contracts-bedrock/"] +extra_output = ['abi'] +bytecode_hash = 'none' +evm_version = "cancun" +fs_permissions = [ + { access='read', path='../../packages/contracts-bedrock/' }, +] +libs = ["lib"] + diff --git a/op-e2e/celo/jest.config.json b/op-e2e/celo/jest.config.json new file mode 100644 index 0000000000000..aea28c9f68597 --- /dev/null +++ b/op-e2e/celo/jest.config.json @@ -0,0 +1,5 @@ +{ + "transformIgnorePatterns": [ + "node_modules/(?!(string-width|strip-ansi|ansi-regex|test-json-import)/)" + ] +} diff --git a/op-e2e/celo/package-lock.json b/op-e2e/celo/package-lock.json new file mode 100644 index 0000000000000..fc39534dff450 --- /dev/null +++ b/op-e2e/celo/package-lock.json @@ -0,0 +1,6578 @@ +{ + "name": "testsuite", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "testsuite", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "reverse-mirage": "^1.1.0", + "viem": "^2.13.1" + }, + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "babel-jest": "^29.7.0", + "jest": "^29.7.0", + "prettier": "3.3.3" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", + "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==" + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", + "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", + "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.9", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-module-transforms": "^7.24.9", + "@babel/helpers": "^7.24.8", + "@babel/parser": "^7.24.8", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", + "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", + "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz", + "integrity": "sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", + "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", + "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz", + "integrity": "sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.8.tgz", + "integrity": "sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.8", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.8", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", + "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.8", + "@babel/types": "^7.24.8", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", + "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@noble/curves": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "dependencies": { + "@noble/hashes": "1.5.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.5.0.tgz", + "integrity": "sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==", + "dependencies": { + "@noble/curves": "~1.6.0", + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.7" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.4.0.tgz", + "integrity": "sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==", + "dependencies": { + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.8" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "20.14.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", + "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/abitype": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.6.tgz", + "integrity": "sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "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 + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001642", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", + "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/create-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/create-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.829", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz", + "integrity": "sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "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, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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 + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isows": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", + "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reverse-mirage": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reverse-mirage/-/reverse-mirage-1.1.0.tgz", + "integrity": "sha512-cA1O7GR0pn4rMFoaiEG7Skms9GenuW91DtCxeR5hphyNhH90eowV4RmUVlVPVS11CPkezm/iUjnCfmxlHri05w==", + "peerDependencies": { + "typescript": ">=5.0.4", + "viem": ">=2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/viem": { + "version": "2.21.19", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.21.19.tgz", + "integrity": "sha512-FdlkN+UI1IU5sYOmzvygkxsUNjDRD5YHht3gZFu2X9xFv6Z3h9pXq9ycrYQ3F17lNfb41O2Ot4/aqbUkwOv9dA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.11.0", + "@noble/curves": "1.6.0", + "@noble/hashes": "1.5.0", + "@scure/bip32": "1.5.0", + "@scure/bip39": "1.4.0", + "abitype": "1.0.6", + "isows": "1.0.6", + "webauthn-p256": "0.0.10", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webauthn-p256": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.10.tgz", + "integrity": "sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "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 + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/op-e2e/celo/package.json b/op-e2e/celo/package.json new file mode 100644 index 0000000000000..ee0d66c7a7b5c --- /dev/null +++ b/op-e2e/celo/package.json @@ -0,0 +1,24 @@ +{ + "name": "testsuite", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "dist/test.js", + "scripts": { + "test": "jest tests --detectOpenHandles", + "format": "npx prettier . --write" + }, + "author": "Celo Labs Inc.", + "license": "ISC", + "dependencies": { + "reverse-mirage": "^1.1.0", + "viem": "^2.13.1" + }, + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "babel-jest": "^29.7.0", + "jest": "^29.7.0", + "prettier": "3.3.3" + } +} diff --git a/op-e2e/celo/run_all_tests.sh b/op-e2e/celo/run_all_tests.sh new file mode 100755 index 0000000000000..0021e440e032b --- /dev/null +++ b/op-e2e/celo/run_all_tests.sh @@ -0,0 +1,56 @@ +#!/bin/bash +#shellcheck disable=SC1091 +set -eo pipefail + +SCRIPT_DIR=$(readlink -f "$(dirname "$0")") +TEST_GLOB=$1 +spawn_devnet=${SPAWN_DEVNET:-true} + +if [[ $spawn_devnet != false ]]; then + ## Start geth + cd "$SCRIPT_DIR/../.." || exit 1 + trap 'cd "$SCRIPT_DIR/../.." && make devnet-down' EXIT # kill bg job at exit + DEVNET_CELO=true make devnet-up +fi + +cd "$SCRIPT_DIR" || exit 1 +source "$SCRIPT_DIR/shared.sh" + +# Wait for geth to be ready +for _ in {1..10}; do + if cast block &>/dev/null; then + echo geth ready + break + fi + sleep 0.2 +done + +## Run tests +echo Start tests +failures=0 +tests=0 +for f in test_*"$TEST_GLOB"*; do + echo -e "\nRun $f" + if "./$f"; then + tput setaf 2 || true + echo "PASS $f" + else + tput setaf 1 || true + echo "FAIL $f ❌" + ((failures++)) || true + fi + tput sgr0 || true + ((tests++)) || true +done + +## Final summary +echo +if [[ $failures -eq 0 ]]; then + tput setaf 2 || true + echo All tests succeeded! +else + tput setaf 1 || true + echo "$failures/$tests" failed. +fi +tput sgr0 || true +exit "$failures" diff --git a/op-e2e/celo/shared.sh b/op-e2e/celo/shared.sh new file mode 100644 index 0000000000000..7d15e83d45efe --- /dev/null +++ b/op-e2e/celo/shared.sh @@ -0,0 +1,12 @@ +#!/bin/bash +#shellcheck disable=SC2034 # unused vars make sense in a shared file + +export ETH_RPC_URL=http://localhost:9545 +export ETH_RPC_URL_L1=http://localhost:8545 + +export ACC_PRIVKEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +ACC_ADDR=$(cast wallet address $ACC_PRIVKEY) +export ACC_ADDR +export REGISTRY_ADDR=0x000000000000000000000000000000000000ce10 +export TOKEN_ADDR=0x471ece3750da237f93b8e339c536989b8978a438 +export FEE_CURRENCY_DIRECTORY_ADDR=0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF diff --git a/op-e2e/celo/src/OptimismPortal.js b/op-e2e/celo/src/OptimismPortal.js new file mode 100644 index 0000000000000..80b02f3834142 --- /dev/null +++ b/op-e2e/celo/src/OptimismPortal.js @@ -0,0 +1,658 @@ +export const OptimismPortalABI = [ + { + type: 'constructor', + inputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'receive', + stateMutability: 'payable', + }, + { + type: 'function', + name: 'balance', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'depositERC20Transaction', + inputs: [ + { + name: '_to', + type: 'address', + internalType: 'address', + }, + { + name: '_mint', + type: 'uint256', + internalType: 'uint256', + }, + { + name: '_value', + type: 'uint256', + internalType: 'uint256', + }, + { + name: '_gasLimit', + type: 'uint64', + internalType: 'uint64', + }, + { + name: '_isCreation', + type: 'bool', + internalType: 'bool', + }, + { + name: '_data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'depositTransaction', + inputs: [ + { + name: '_to', + type: 'address', + internalType: 'address', + }, + { + name: '_value', + type: 'uint256', + internalType: 'uint256', + }, + { + name: '_gasLimit', + type: 'uint64', + internalType: 'uint64', + }, + { + name: '_isCreation', + type: 'bool', + internalType: 'bool', + }, + { + name: '_data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'donateETH', + inputs: [], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'finalizeWithdrawalTransaction', + inputs: [ + { + name: '_tx', + type: 'tuple', + internalType: 'struct Types.WithdrawalTransaction', + components: [ + { + name: 'nonce', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'sender', + type: 'address', + internalType: 'address', + }, + { + name: 'target', + type: 'address', + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'gasLimit', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'finalizedWithdrawals', + inputs: [ + { + name: '', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'guardian', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'initialize', + inputs: [ + { + name: '_l2Oracle', + type: 'address', + internalType: 'contract L2OutputOracle', + }, + { + name: '_systemConfig', + type: 'address', + internalType: 'contract SystemConfig', + }, + { + name: '_superchainConfig', + type: 'address', + internalType: 'contract SuperchainConfig', + }, + { + name: '_initialBalance', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'isOutputFinalized', + inputs: [ + { + name: '_l2OutputIndex', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'l2Oracle', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract L2OutputOracle', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'l2Sender', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'minimumGasLimit', + inputs: [ + { + name: '_byteCount', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'uint64', + internalType: 'uint64', + }, + ], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'params', + inputs: [], + outputs: [ + { + name: 'prevBaseFee', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'prevBoughtGas', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'prevBlockNum', + type: 'uint64', + internalType: 'uint64', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'paused', + inputs: [], + outputs: [ + { + name: 'paused_', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'proveWithdrawalTransaction', + inputs: [ + { + name: '_tx', + type: 'tuple', + internalType: 'struct Types.WithdrawalTransaction', + components: [ + { + name: 'nonce', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'sender', + type: 'address', + internalType: 'address', + }, + { + name: 'target', + type: 'address', + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'gasLimit', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: '_l2OutputIndex', + type: 'uint256', + internalType: 'uint256', + }, + { + name: '_outputRootProof', + type: 'tuple', + internalType: 'struct Types.OutputRootProof', + components: [ + { + name: 'version', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'stateRoot', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'messagePasserStorageRoot', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'latestBlockhash', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + }, + { + name: '_withdrawalProof', + type: 'bytes[]', + internalType: 'bytes[]', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'provenWithdrawals', + inputs: [ + { + name: '', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [ + { + name: 'outputRoot', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'timestamp', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'l2OutputIndex', + type: 'uint128', + internalType: 'uint128', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'setGasPayingToken', + inputs: [ + { + name: '_token', + type: 'address', + internalType: 'address', + }, + { + name: '_decimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: '_name', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: '_symbol', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'superchainConfig', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract SuperchainConfig', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'systemConfig', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract SystemConfig', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'version', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'view', + }, + { + type: 'event', + name: 'Initialized', + inputs: [ + { + name: 'version', + type: 'uint8', + indexed: false, + internalType: 'uint8', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TransactionDeposited', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'version', + type: 'uint256', + indexed: true, + internalType: 'uint256', + }, + { + name: 'opaqueData', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'WithdrawalFinalized', + inputs: [ + { + name: 'withdrawalHash', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + { + name: 'success', + type: 'bool', + indexed: false, + internalType: 'bool', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'WithdrawalProven', + inputs: [ + { + name: 'withdrawalHash', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'BadTarget', + inputs: [], + }, + { + type: 'error', + name: 'CallPaused', + inputs: [], + }, + { + type: 'error', + name: 'ContentLengthMismatch', + inputs: [], + }, + { + type: 'error', + name: 'EmptyItem', + inputs: [], + }, + { + type: 'error', + name: 'GasEstimation', + inputs: [], + }, + { + type: 'error', + name: 'InvalidDataRemainder', + inputs: [], + }, + { + type: 'error', + name: 'InvalidHeader', + inputs: [], + }, + { + type: 'error', + name: 'LargeCalldata', + inputs: [], + }, + { + type: 'error', + name: 'NoValue', + inputs: [], + }, + { + type: 'error', + name: 'NonReentrant', + inputs: [], + }, + { + type: 'error', + name: 'OnlyCustomGasToken', + inputs: [], + }, + { + type: 'error', + name: 'OutOfGas', + inputs: [], + }, + { + type: 'error', + name: 'SmallGasLimit', + inputs: [], + }, + { + type: 'error', + name: 'TransferFailed', + inputs: [], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [], + }, + { + type: 'error', + name: 'UnexpectedList', + inputs: [], + }, + { + type: 'error', + name: 'UnexpectedString', + inputs: [], + }, +] diff --git a/op-e2e/celo/src/chain.js b/op-e2e/celo/src/chain.js new file mode 100644 index 0000000000000..25dac875054dd --- /dev/null +++ b/op-e2e/celo/src/chain.js @@ -0,0 +1,71 @@ +import { chainConfig } from 'viem/op-stack' +import { defineChain } from 'viem' + +export function makeChainConfigs(l1ChainID, l2ChainID, contractAddresses) { + console.log(process.env) + return { + l2: defineChain({ + formatters: { + ...chainConfig.formatters, + }, + serializers: { + ...chainConfig.serializers, + }, + id: l2ChainID, + name: 'Celo', + nativeCurrency: { + decimals: 18, + name: 'Celo - native currency', + symbol: 'CELO', + }, + rpcUrls: { + default: { + http: [process.env.ETH_RPC_URL], + }, + }, + contracts: { + ...chainConfig.contracts, + l2OutputOracle: { + [l1ChainID]: { + address: contractAddresses.L2OutputOracleProxy, + }, + }, + disputeGameFactory: { + [l1ChainID]: { + address: contractAddresses.DisputeGameFactoryProxy, + }, + }, + portal: { + [l1ChainID]: { + address: contractAddresses.OptimismPortalProxy, + }, + }, + l1StandardBridge: { + [l1ChainID]: { + address: contractAddresses.L1StandardBridgeProxy, + }, + }, + }, + }), + l1: defineChain({ + id: l1ChainID, + testnet: true, + name: 'Ethereum L1', + nativeCurrency: { + decimals: 18, + name: 'Ether', + symbol: 'ETH', + }, + rpcUrls: { + default: { + http: [process.env.ETH_RPC_URL_L1], + }, + }, + contracts: { + multicall3: { + address: contractAddresses.Multicall3, + }, + }, + }), + } +} diff --git a/op-e2e/celo/src/config.js b/op-e2e/celo/src/config.js new file mode 100644 index 0000000000000..7e410dffbdd0d --- /dev/null +++ b/op-e2e/celo/src/config.js @@ -0,0 +1,98 @@ +import { createPublicClient, createWalletClient, http } from 'viem' +import { readContract } from 'viem/actions' +import { constructDepositCustomGas } from './deposit.js' +import { + getERC20, + simulateERC20Transfer, + getERC20BalanceOf, + getERC20Symbol, + getERC20Decimals, + simulateERC20Approve, +} from 'reverse-mirage' +import { + publicActionsL1, + publicActionsL2, + walletActionsL1, + walletActionsL2, +} from 'viem/op-stack' + +export function makeReadContract(contractAddress, contractABI) { + return (client) => { + return { + readContract: (args) => { + const rcArgs = { + address: contractAddress, + abi: contractABI, + functionName: args.functionName, + args: args.args, + } + return readContract(client, rcArgs) + }, + } + } +} + +export function erc20PublicActions(client) { + return { + getERC20: (args) => getERC20(client, args), + getERC20Symbol: (args) => getERC20Symbol(client, args), + getERC20BalanceOf: (args) => getERC20BalanceOf(client, args), + getERC20Decimals: (args) => getERC20Decimals(client, args), + } +} +export function erc20WalletActions(client) { + return { + simulateERC20Transfer: (args) => { + return simulateERC20Transfer(client, { args: args }) + }, + simulateERC20Approve: (args) => { + return simulateERC20Approve(client, { args: args }) + }, + } +} + +export function celoL1PublicActions(client) { + return { + prepareDepositGasPayingTokenERC20: (args) => { + return constructDepositCustomGas(client, args) + }, + } +} + +export function setupClients(l1ChainConfig, l2ChainConfig, account) { + return { + l1: { + public: createPublicClient({ + account, + chain: l1ChainConfig, + transport: http(), + }) + .extend(publicActionsL1()) + .extend(celoL1PublicActions) + .extend(erc20PublicActions), + wallet: createWalletClient({ + account, + chain: l1ChainConfig, + transport: http(), + }) + .extend(erc20WalletActions) + .extend(walletActionsL1()), + }, + l2: { + public: createPublicClient({ + account, + chain: l2ChainConfig, + transport: http(), + }) + .extend(publicActionsL2()) + .extend(erc20PublicActions), + wallet: createWalletClient({ + account, + chain: l2ChainConfig, + transport: http(), + }) + .extend(erc20WalletActions) + .extend(walletActionsL2()), + }, + } +} diff --git a/op-e2e/celo/src/deposit.js b/op-e2e/celo/src/deposit.js new file mode 100644 index 0000000000000..2e1f5ef17dbdf --- /dev/null +++ b/op-e2e/celo/src/deposit.js @@ -0,0 +1,127 @@ +import { getL2TransactionHashes } from 'viem/op-stack' +import { OptimismPortalABI } from './OptimismPortal.js' + +// public client functionality +export async function constructDepositCustomGas(client, parameters) { + const { + account, + chain = client.chain, + gas, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + request: { + data = '0x', + gas: l2Gas, + isCreation = false, + mint, + to = '0x', + value, + }, + targetChain, + } = parameters + + const portalAddress = (() => { + if (parameters.portalAddress) return parameters.portalAddress + if (chain) return targetChain.contracts.portal[chain.id].address + return Object.values(targetChain.contracts.portal)[0].address + })() + const callArgs = { + account: account, + abi: OptimismPortalABI, + address: portalAddress, + chain, + functionName: 'depositERC20Transaction', + /// @notice Entrypoint to depositing an ERC20 token as a custom gas token. + /// This function depends on a well formed ERC20 token. There are only + /// so many checks that can be done on chain for this so it is assumed + /// that chain operators will deploy chains with well formed ERC20 tokens. + /// @param _to Target address on L2. + /// @param _mint Units of ERC20 token to deposit into L2. + /// @param _value Units of ERC20 token to send on L2 to the recipient. + /// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1. + /// @param _isCreation Whether or not the transaction is a contract creation. + /// @param _data Data to trigger the recipient with. + args: [ + isCreation ? zeroAddress : to, + mint ?? value ?? 0n, + value ?? mint ?? 0n, + l2Gas, + isCreation, + data, + ], + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + } + const gas_ = + typeof gas !== 'number' && gas !== null + ? await client.estimateContractGas(callArgs) + : undefined + callArgs.gas = gas_ + const result = client.simulateContract(callArgs) + return { result: result, args: callArgs } +} + +export async function deposit(args, config) { + var spentGas = BigInt(0) + const depositArgs = await config.client.l2.public.buildDepositTransaction({ + mint: args.mint, + to: args.to, + }) + + const celoToken = await config.client.l1.public.getERC20({ + erc20: { + address: config.addresses.CustomGasTokenProxy, + chainID: config.client.l1.public.chain.id, + }, + }) + const portalAddress = + config.client.l2.public.chain.contracts.portal[ + config.client.l1.public.chain.id + ].address + const approve = await config.client.l1.wallet.simulateERC20Approve({ + amount: { amount: args.mint, token: celoToken }, + spender: portalAddress, + }) + if (!approve.result) { + return { + success: false, + l1GasPayment: spentGas, + } + } + + const approveHash = await config.client.l1.wallet.writeContract( + approve.request + ) + // Wait for the L1 transaction to be processed. + const approveReceipt = + await config.client.l1.public.waitForTransactionReceipt({ + hash: approveHash, + }) + + spentGas += approveReceipt.gasUsed * approveReceipt.effectiveGasPrice + const dep = + await config.client.l1.public.prepareDepositGasPayingTokenERC20(depositArgs) + const hash = await config.client.l1.wallet.writeContract(dep.args) + + // Wait for the L1 transaction to be processed. + const receipt = await config.client.l1.public.waitForTransactionReceipt({ + hash: hash, + }) + + spentGas += receipt.gasUsed * receipt.effectiveGasPrice + + // Get the L2 transaction hash from the L1 transaction receipt. + const [l2Hash] = getL2TransactionHashes(receipt) + + // Wait for the L2 transaction to be processed. + const l2Receipt = await config.client.l2.public.waitForTransactionReceipt({ + hash: l2Hash, + }) + + return { + success: l2Receipt.status == 'success', + l1GasPayment: spentGas, + } +} diff --git a/op-e2e/celo/src/withdraw.js b/op-e2e/celo/src/withdraw.js new file mode 100644 index 0000000000000..b52740e757610 --- /dev/null +++ b/op-e2e/celo/src/withdraw.js @@ -0,0 +1,63 @@ +export const withdraw = async function (args, config) { + const initiateHash = await config.client.l2.wallet.initiateWithdrawal({ + request: { + gas: args.gas, + to: args.to, + value: args.amount, + }, + }) + const receipt = await config.client.l2.public.waitForTransactionReceipt({ + hash: initiateHash, + }) + + const l2GasPayment = + receipt.gasUsed * receipt.effectiveGasPrice + receipt.l1Fee + + // FIXME: this blocks longer, the longer the devnet is running, see + // https://github.com/ethereum-optimism/optimism/issues/7668 + // NOTE: this function requires the mulitcall contract to be deployed + // on the L1 chain. + const { output, withdrawal } = await config.client.l1.public.waitToProve({ + receipt, + targetChain: config.client.l2.public.chain, + }) + // + + const proveWithdrawalArgs = + await config.client.l2.public.buildProveWithdrawal({ + output, + withdrawal, + }) + const proveHash = + await config.client.l1.wallet.proveWithdrawal(proveWithdrawalArgs) + + const proveReceipt = await config.client.l1.public.waitForTransactionReceipt({ + hash: proveHash, + }) + if (proveReceipt.status != 'success') { + return { + success: false, + l2GasPayment: l2GasPayment, + } + } + + await config.client.l1.public.waitToFinalize({ + withdrawalHash: withdrawal.withdrawalHash, + targetChain: config.client.l2.public.chain, + }) + + const finalizeHash = await config.client.l1.wallet.finalizeWithdrawal({ + targetChain: config.client.l2.public.chain, + withdrawal, + }) + + const finalizeReceipt = + await config.client.l1.public.waitForTransactionReceipt({ + hash: finalizeHash, + }) + + return { + success: finalizeReceipt.status == 'success', + l2GasPayment: l2GasPayment, + } +} diff --git a/op-e2e/celo/test_npm.sh b/op-e2e/celo/test_npm.sh new file mode 100755 index 0000000000000..89783597300cf --- /dev/null +++ b/op-e2e/celo/test_npm.sh @@ -0,0 +1,6 @@ +#!/bin/bash +#shellcheck disable=SC1091 +set -eo pipefail + +source shared.sh +npm test diff --git a/op-e2e/celo/test_weth_bridge.sh b/op-e2e/celo/test_weth_bridge.sh new file mode 100755 index 0000000000000..19ff0ddb2cbb2 --- /dev/null +++ b/op-e2e/celo/test_weth_bridge.sh @@ -0,0 +1,42 @@ +#!/bin/bash +#shellcheck disable=SC2086,SC1091 +set -eo pipefail +set -x + +source shared.sh +SCRIPT_DIR=$(readlink -f "$(dirname "$0")") +CONTRACTS_DIR=$SCRIPT_DIR/../../packages/contracts-bedrock + +# Deploy WETH +L1_WETH=$( + ETH_RPC_URL=$ETH_RPC_URL_L1 forge create --broadcast --private-key=$ACC_PRIVKEY --root $CONTRACTS_DIR $CONTRACTS_DIR/src/universal/WETH98.sol:WETH98 --json | jq .deployedTo -r +) + +# create ERC20 token on L2 +L2_TOKEN=$( + cast send --private-key $ACC_PRIVKEY 0x4200000000000000000000000000000000000012 "createOptimismMintableERC20(address,string,string)" $L1_WETH "Wrapped Ether" "WETH" --json \ + | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address +) + +# Wrap some ETH +ETH_RPC_URL=$ETH_RPC_URL_L1 cast send --private-key $ACC_PRIVKEY $L1_WETH --value 1ether +# Approve transfer to bridge +L1_BRIDGE_ADDR=$(cast call 0x4200000000000000000000000000000000000010 'otherBridge() returns (address)') +ETH_RPC_URL=$ETH_RPC_URL_L1 cast send --private-key $ACC_PRIVKEY $L1_WETH 'approve(address, uint256) returns (bool)' $L1_BRIDGE_ADDR 1ether +# Bridge to L2 +ETH_RPC_URL=$ETH_RPC_URL_L1 cast send --private-key $ACC_PRIVKEY $L1_BRIDGE_ADDR 'bridgeERC20(address _localToken, address _remoteToken, uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData)' $L1_WETH $L2_TOKEN 0.3ether 50000 0x --gas-limit 6000000 + +# Setup up oracle and FeeCurrencyDirectory +ORACLE=$(forge create --broadcast --private-key=$ACC_PRIVKEY --root $CONTRACTS_DIR $CONTRACTS_DIR/src/celo/testing/MockSortedOracles.sol:MockSortedOracles --json | jq .deployedTo -r) +cast send --private-key $ACC_PRIVKEY $ORACLE 'setMedianRate(address, uint256)' $L2_TOKEN 100000000000000000 +cast send --private-key $ACC_PRIVKEY $FEE_CURRENCY_DIRECTORY_ADDR 'setCurrencyConfig(address, address, uint256)' $L2_TOKEN $ORACLE 60000 + +# Check balance from bridging (we intentionally don't do this right after bridging, since it takes a bit) +L2_BALANCE=$(cast call $L2_TOKEN 'balanceOf(address) returns (uint256)' $ACC_ADDR) +echo L2 balance: $L2_BALANCE +[[ $(echo $L2_BALANCE | awk '{print $1}') -gt 0 ]] || (echo "Bridging to L2 failed!"; exit 1) + +# Send fee currency tx! +#TXHASH=$(~/op-geth/e2e_test/js-tests/send_tx.mjs 901 $ACC_PRIVKEY $L2_TOKEN) +#cast receipt $TXHASH +echo You can use privkey $ACC_PRIVKEY to pay for txs with $L2_TOKEN, now. diff --git a/op-e2e/celo/tests/setup.js b/op-e2e/celo/tests/setup.js new file mode 100644 index 0000000000000..d6f96224650a7 --- /dev/null +++ b/op-e2e/celo/tests/setup.js @@ -0,0 +1,64 @@ +import { setupClients } from '../src/config.js' +import { makeChainConfigs } from '../src/chain.js' +import { privateKeyToAccount } from 'viem/accounts' +import { readFileSync } from 'fs' + +// Default Anvil dev account that has a pre-allocation on the op-devnet: +// "test test test test test test test test test test test junk" mnemonic account, +// on path "m/44'/60'/0'/0/6". +// Address: 0x976EA74026E726554dB657fA54763abd0C3a0aa9. +const privKey = + '0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e' + +async function waitForNoError(func, timeout) { + const start = Date.now() + while (Date.now() - start < timeout) { + try { + await func() + return true + } catch (error) {} + await new Promise((r) => setTimeout(r, 1000)) + } + return false +} + +async function waitReachable(client, timeout) { + const f = async () => client.getChainId() + return waitForNoError(f, timeout) +} + +async function waitForNextL2Output(client, l2ChainConfig, timeout) { + const f = async () => + client.waitForNextL2Output({ + pollingInterval: 500, + l2BlockNumber: 0, + targetChain: l2ChainConfig, + }) + return waitForNoError(f, timeout) +} + +export async function setup() { + const contractAddrs = JSON.parse( + readFileSync('../../.devnet/addresses.json', 'utf8') + ) + const config = { account: privateKeyToAccount(privKey) } + const chainConfig = makeChainConfigs(900, 901, contractAddrs) + + config.client = setupClients( + chainConfig.l1, + chainConfig.l2, + config.account, + contractAddrs + ) + config.addresses = contractAddrs + + const success = await Promise.all([ + waitReachable(config.client.l1.public, 10_000), + waitReachable(config.client.l2.public, 10_000), + waitForNextL2Output(config.client.l1.public, chainConfig.l2, 60_000), + ]) + if (success.every((v) => v == true)) { + return config + } + throw new Error('l1 and l2 clients not reachable within the deadline') +} diff --git a/op-e2e/celo/tests/tokenduality.test.js b/op-e2e/celo/tests/tokenduality.test.js new file mode 100644 index 0000000000000..9980c81fece42 --- /dev/null +++ b/op-e2e/celo/tests/tokenduality.test.js @@ -0,0 +1,42 @@ +import { createAmountFromString } from 'reverse-mirage' +import { setup } from './setup.js' + +const minute = 60 * 1000 +let config = {} + +beforeAll(async () => { + config = await setup() +}, 30_000) + +test( + 'test token duality', + async () => { + const receiverAddr = '0x000000000000000000000000000000000000dEaD' + const dualityToken = await config.client.l2.public.getERC20({ + erc20: { + address: '0x471ece3750da237f93b8e339c536989b8978a438', + chainID: config.client.l2.public.chain.id, + }, + }) + const balanceBefore = await config.client.l2.public.getBalance({ + address: receiverAddr, + }) + + const sendAmount = createAmountFromString(dualityToken, '100') + const { request } = await config.client.l2.wallet.simulateERC20Transfer({ + to: receiverAddr, + amount: sendAmount, + }) + const transferHash = await config.client.l2.wallet.writeContract(request) + const receipt = await config.client.l2.public.waitForTransactionReceipt({ + hash: transferHash, + }) + expect(receipt.status).toBe('success') + const balanceAfter = await config.client.l2.public.getBalance({ + address: receiverAddr, + }) + + expect(balanceAfter).toBe(balanceBefore + sendAmount.amount) + }, + 1 * minute +) diff --git a/op-e2e/celo/tests/withdraw_deposit.test.js b/op-e2e/celo/tests/withdraw_deposit.test.js new file mode 100644 index 0000000000000..b7235239f4d1f --- /dev/null +++ b/op-e2e/celo/tests/withdraw_deposit.test.js @@ -0,0 +1,77 @@ +import { withdraw } from '../src/withdraw.js' +import { deposit } from '../src/deposit.js' +import { parseEther } from 'viem' +import { setup } from './setup.js' + +const minute = 60 * 1000 +var config = {} + +beforeAll(async () => { + config = await setup() +}, minute) + +test( + 'execute a withdraw and a deposit in succession', + async () => { + const celoToken = await config.client.l1.public.getERC20({ + erc20: { + address: config.addresses.CustomGasTokenProxy, + chainID: config.client.l1.public.chain.id, + }, + }) + const balanceL1Before = await config.client.l1.public.getERC20BalanceOf({ + erc20: celoToken, + address: config.account.address, + }) + const balanceL2Before = await config.client.l2.public.getBalance({ + address: config.account.address, + }) + const withdrawAmount = parseEther('1') + const withdrawResult = await withdraw( + { + amount: withdrawAmount, + to: config.account.address, + gas: 21_000n, + }, + config + ) + expect(withdrawResult.success).toBe(true) + const balanceL1AfterWithdraw = + await config.client.l1.public.getERC20BalanceOf({ + erc20: celoToken, + address: config.account.address, + }) + const balanceL2AfterWithdraw = await config.client.l2.public.getBalance({ + address: config.account.address, + }) + expect(balanceL1AfterWithdraw.amount).toBe( + balanceL1Before.amount + BigInt(withdrawAmount) + ) + expect(balanceL2AfterWithdraw).toBe( + balanceL2Before - BigInt(withdrawAmount) - withdrawResult.l2GasPayment + ) + const depositResult = await deposit( + { + mint: withdrawAmount, + to: config.account.address, + }, + config + ) + expect(depositResult.success).toBe(true) + + const balanceL1AfterDeposit = + await config.client.l1.public.getERC20BalanceOf({ + erc20: celoToken, + address: config.account.address, + }) + const balanceL2AfterDeposit = await config.client.l2.public.getBalance({ + address: config.account.address, + }) + + expect(balanceL1AfterDeposit.amount).toBe(balanceL1Before.amount) + expect(balanceL2AfterDeposit).toBe( + balanceL2Before - withdrawResult.l2GasPayment + ) + }, + 15 * minute +) diff --git a/op-e2e/config/init.go b/op-e2e/config/init.go index 148e0625195fb..561dfbdcb4ac7 100644 --- a/op-e2e/config/init.go +++ b/op-e2e/config/init.go @@ -49,10 +49,11 @@ const ( type AllocType string const ( - AllocTypeStandard AllocType = "standard" - AllocTypeAltDA AllocType = "alt-da" - AllocTypeL2OO AllocType = "l2oo" - AllocTypeMTCannon AllocType = "mt-cannon" + AllocTypeStandard AllocType = "standard" + AllocTypeAltDA AllocType = "alt-da" + AllocTypeAltDAGeneric AllocType = "alt-da-generic" + AllocTypeL2OO AllocType = "l2oo" + AllocTypeMTCannon AllocType = "mt-cannon" DefaultAllocType = AllocTypeStandard ) @@ -66,14 +67,14 @@ func (a AllocType) Check() error { func (a AllocType) UsesProofs() bool { switch a { - case AllocTypeStandard, AllocTypeMTCannon, AllocTypeAltDA: + case AllocTypeStandard, AllocTypeMTCannon, AllocTypeAltDA, AllocTypeAltDAGeneric: return true default: return false } } -var allocTypes = []AllocType{AllocTypeStandard, AllocTypeAltDA, AllocTypeL2OO, AllocTypeMTCannon} +var allocTypes = []AllocType{AllocTypeStandard, AllocTypeAltDA, AllocTypeAltDAGeneric, AllocTypeL2OO, AllocTypeMTCannon} var ( // All of the following variables are set in the init function diff --git a/op-e2e/e2eutils/geth/wait.go b/op-e2e/e2eutils/geth/wait.go index 8356058afda75..17c6f16226ca7 100644 --- a/op-e2e/e2eutils/geth/wait.go +++ b/op-e2e/e2eutils/geth/wait.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum/go-ethereum" @@ -86,6 +87,31 @@ func WaitForTransaction(hash common.Hash, client *ethclient.Client, timeout time } } +// WaitForBlockWithTxFromSender waits for a block with a transaction from a specific sender address. +// It starts from the current block and checks the next nBlocks blocks. +func WaitForBlockWithTxFromSender(sender common.Address, client *ethclient.Client, nBlocks uint64) (*types.Block, error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + blockNum, err := client.BlockNumber(ctx) + if err != nil { + return nil, err + } + for blockNum := blockNum; blockNum < blockNum+nBlocks; blockNum++ { + blockL1, err := WaitForBlock(big.NewInt(0).SetUint64(blockNum), client) + if err != nil { + return nil, err + } + batcherTxCount, err := transactions.TransactionsBySenderCount(blockL1, sender) + if err != nil { + return nil, err + } + if batcherTxCount > 0 { + return blockL1, nil + } + } + return nil, fmt.Errorf("no block with tx from sender %s found in the last %d blocks", sender.Hex(), nBlocks) +} + type waitForBlockOptions struct { noChangeTimeout time.Duration absoluteTimeout time.Duration diff --git a/op-e2e/e2eutils/setup.go b/op-e2e/e2eutils/setup.go index b999fb3ea4d0c..495d8e4c75ae7 100644 --- a/op-e2e/e2eutils/setup.go +++ b/op-e2e/e2eutils/setup.go @@ -1,6 +1,7 @@ package e2eutils import ( + "log/slog" "math/big" "os" "path" @@ -51,6 +52,7 @@ type TestParams struct { L1BlockTime uint64 UseAltDA bool AllocType config.AllocType + LogLevel slog.Level } func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams { @@ -66,7 +68,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams { deployConfig.UseAltDA = tp.UseAltDA ApplyDeployConfigForks(deployConfig) - logger := log.NewLogger(log.DiscardHandler()) + logger := log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stdout, tp.LogLevel, true)) require.NoError(t, deployConfig.Check(logger)) require.Equal(t, addresses.Batcher, deployConfig.BatchSenderAddress) require.Equal(t, addresses.Proposer, deployConfig.L2OutputOracleProposer) diff --git a/op-e2e/e2eutils/transactions/count.go b/op-e2e/e2eutils/transactions/count.go index 0f4d41fe04786..7f9f05c2857f5 100644 --- a/op-e2e/e2eutils/transactions/count.go +++ b/op-e2e/e2eutils/transactions/count.go @@ -5,7 +5,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -func TransactionsBySender(block *types.Block, sender common.Address) (int64, error) { +// TransactionsBySenderCount returns the number of transactions in the block that were sent by the given sender. +func TransactionsBySenderCount(block *types.Block, sender common.Address) (int64, error) { txCount := int64(0) for _, tx := range block.Transactions() { signer := types.NewCancunSigner(tx.ChainId()) @@ -19,3 +20,18 @@ func TransactionsBySender(block *types.Block, sender common.Address) (int64, err } return txCount, nil } + +func TransactionsBySender(block *types.Block, sender common.Address) ([]*types.Transaction, error) { + txs := make([]*types.Transaction, 0) + for _, tx := range block.Transactions() { + signer := types.NewCancunSigner(tx.ChainId()) + txSender, err := types.Sender(signer, tx) + if err != nil { + return nil, err + } + if txSender == sender { + txs = append(txs, tx) + } + } + return txs, nil +} diff --git a/op-e2e/opgeth/op_geth_test.go b/op-e2e/opgeth/op_geth_test.go index 36ea6f457faa7..159552520e0ab 100644 --- a/op-e2e/opgeth/op_geth_test.go +++ b/op-e2e/opgeth/op_geth_test.go @@ -236,6 +236,7 @@ func TestGethOnlyPendingBlockIsLatest(t *testing.T) { } func TestPreregolith(t *testing.T) { + t.Skip("Not applicable to Celo chains") futureTimestamp := hexutil.Uint64(4) tests := []struct { name string @@ -416,6 +417,7 @@ func TestPreregolith(t *testing.T) { } func TestRegolith(t *testing.T) { + t.Skip("Not applicable to Celo chains") tests := []struct { name string regolithTime hexutil.Uint64 diff --git a/op-e2e/system/altda/concurrent_test.go b/op-e2e/system/altda/concurrent_test.go index ef11a879dc70d..45918db78fd33 100644 --- a/op-e2e/system/altda/concurrent_test.go +++ b/op-e2e/system/altda/concurrent_test.go @@ -7,20 +7,22 @@ import ( "time" op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum-optimism/optimism/op-batcher/flags" + "github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/stretchr/testify/require" ) +// TestBatcherConcurrentAltDARequests tests that the batcher can submit parallel requests +// to the alt-da server. It does not check that the requests are correctly ordered and interpreted +// by op nodes. func TestBatcherConcurrentAltDARequests(t *testing.T) { op_e2e.InitParallel(t) - numL1TxsExpected := int64(10) - cfg := e2esys.DefaultSystemConfig(t) // Manually configure these since the alt-DA values aren't // set at all in the standard config unless UseAltDA is set. @@ -32,11 +34,9 @@ func TestBatcherConcurrentAltDARequests(t *testing.T) { cfg.DeployConfig.DABondSize = 1000000 cfg.DeployConfig.DAResolverRefundPercentage = 0 cfg.BatcherMaxPendingTransactions = 0 // no limit on parallel txs - // ensures that batcher txs are as small as possible - cfg.BatcherMaxL1TxSizeBytes = derive.FrameV0OverHeadSize + 1 /*version bytes*/ + 1 cfg.BatcherBatchType = 0 cfg.DataAvailabilityType = flags.CalldataType - cfg.BatcherMaxConcurrentDARequest = uint64(numL1TxsExpected) + cfg.BatcherMaxConcurrentDARequest = 2 // disable batcher because we start it manually below cfg.DisableBatcher = true @@ -46,14 +46,15 @@ func TestBatcherConcurrentAltDARequests(t *testing.T) { sys.Close() }) - // make every request take 5 seconds, such that only concurrent requests will be able to make progress fast enough + // make every request take 5 seconds, such that only if 2 altda requests are made + // concurrently will 2 batcher txs be able to land in a single L1 block sys.FakeAltDAServer.SetPutRequestLatency(5 * time.Second) l1Client := sys.NodeClient("l1") l2Seq := sys.NodeClient("sequencer") - // we wait for numL1TxsExpected L2 blocks to have been produced, just to make sure the sequencer is working properly - _, err = geth.WaitForBlock(big.NewInt(numL1TxsExpected), l2Seq) + // we wait for 10 L2 blocks to have been produced, just to make sure the sequencer is working properly + _, err = geth.WaitForBlock(big.NewInt(10), l2Seq) require.NoError(t, err, "Waiting for L2 blocks") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -65,15 +66,14 @@ func TestBatcherConcurrentAltDARequests(t *testing.T) { err = driver.StartBatchSubmitting() require.NoError(t, err) - // Iterate over up to 10 blocks. The number of transactions sent by the batcher should - // exceed the number of blocks. + // We make sure that some block has more than 1 batcher tx checkBlocks := 10 for i := 0; i < checkBlocks; i++ { block, err := geth.WaitForBlock(big.NewInt(int64(startingL1BlockNum)+int64(i)), l1Client) require.NoError(t, err, "Waiting for l1 blocks") // there are possibly other services (proposer/challenger) in the background sending txs // so we only count the batcher txs - batcherTxCount, err := transactions.TransactionsBySender(block, cfg.DeployConfig.BatchSenderAddress) + batcherTxCount, err := transactions.TransactionsBySenderCount(block, cfg.DeployConfig.BatchSenderAddress) require.NoError(t, err) if batcherTxCount > 1 { return @@ -82,3 +82,58 @@ func TestBatcherConcurrentAltDARequests(t *testing.T) { t.Fatalf("did not find more than 1 batcher tx per block in %d blocks", checkBlocks) } + +// The Holocene fork enforced a new strict batch ordering rule, see https://specs.optimism.io/protocol/holocene/derivation.html +// This test makes sure that concurrent requests to the alt-da server that are responded out of order +// are submitted to the L1 chain in the correct order by the batcher. +func TestBatcherCanHandleOutOfOrderDAServerResponses(t *testing.T) { + op_e2e.InitParallel(t) + // Not sure whether WithAllocType is needed here, as the tests pass even without them + // (see mslipper's comments for the TestBatcherConcurrentAltDARequests test above)) + // TODO: understand how the DeployConfigs are related to the AllocTypes + // I asked here https://discord.com/channels/1244729134312198194/1332175015180767265/1332456541067935834 but have yet to get an answer. + cfg := e2esys.HoloceneSystemConfig(t, new(hexutil.Uint64), e2esys.WithAllocType(config.AllocTypeAltDAGeneric)) + cfg.DeployConfig.UseAltDA = true + cfg.DeployConfig.DACommitmentType = "GenericCommitment" + // TODO: figure out why the below are needed even in GenericCommitment mode which doesn't use the DAChallenge Contract + cfg.DeployConfig.DAChallengeWindow = 16 + cfg.DeployConfig.DAResolveWindow = 16 + cfg.DeployConfig.DABondSize = 1000000 + cfg.DeployConfig.DAResolverRefundPercentage = 0 + cfg.BatcherMaxPendingTransactions = 0 // no limit on parallel txs + cfg.BatcherBatchType = 0 + cfg.DataAvailabilityType = flags.CalldataType + cfg.BatcherMaxConcurrentDARequest = 2 + cfg.BatcherMaxL1TxSizeBytes = 150 // enough to fit a single compressed empty L1 block, but not 2 + cfg.Nodes["sequencer"].SafeDBPath = t.TempDir() // needed for SafeHeadAtL1Block() below + + sys, err := cfg.Start(t) + require.NoError(t, err, "Error starting up system") + t.Cleanup(func() { + sys.Close() + }) + sys.FakeAltDAServer.SetOutOfOrderResponses(true) + + l1Client := sys.NodeClient("l1") + l2SeqCL := sys.RollupClient("sequencer") + + checkBlocksL1 := int64(15) + l2SafeHeadMovedCount := 0 + l2SafeHeadMovedCountExpected := 3 + l2SafeHeadCur := uint64(0) + for i := int64(0); i < checkBlocksL1; i++ { + _, err := geth.WaitForBlock(big.NewInt(i), l1Client, geth.WithNoChangeTimeout(5*time.Minute)) + require.NoError(t, err, "Waiting for l1 blocks") + newL2SafeHead, err := l2SeqCL.SafeHeadAtL1Block(context.Background(), uint64(i)) + require.NoError(t, err) + if newL2SafeHead.SafeHead.Number > l2SafeHeadCur { + l2SafeHeadMovedCount++ + l2SafeHeadCur = newL2SafeHead.SafeHead.Number + } + if l2SafeHeadMovedCount == l2SafeHeadMovedCountExpected { + return + } + } + t.Fatalf("L2SafeHead only advanced %d times (expected >= %d) in %d L1 blocks", l2SafeHeadMovedCount, l2SafeHeadMovedCountExpected, checkBlocksL1) + +} diff --git a/op-e2e/system/altda/failover_test.go b/op-e2e/system/altda/failover_test.go new file mode 100644 index 0000000000000..b1d55598bfaaf --- /dev/null +++ b/op-e2e/system/altda/failover_test.go @@ -0,0 +1,84 @@ +package altda + +import ( + "math/big" + "testing" + + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive/params" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-batcher/flags" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" + "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" + "github.com/stretchr/testify/require" +) + +// TestBatcher_FailoverToEthDA_FallbackToAltDA tests that the batcher will failover to ethDA +// if the da-server returns 503. It also tests that the batcher successfully returns to normal +// behavior of posting batches to altda once it becomes available again +// (i.e. the da-server doesn't return 503 anymore). +func TestBatcher_FailoverToEthDA_FallbackToAltDA(t *testing.T) { + op_e2e.InitParallel(t) + + nChannelsFailover := uint64(2) + + cfg := e2esys.DefaultSystemConfig(t, e2esys.WithLogLevel(log.LevelCrit)) + cfg.DeployConfig.UseAltDA = true + cfg.DeployConfig.DACommitmentType = "GenericCommitment" + cfg.DeployConfig.DAChallengeWindow = 16 + cfg.DeployConfig.DAResolveWindow = 16 + cfg.DeployConfig.DABondSize = 1000000 + cfg.DeployConfig.DAResolverRefundPercentage = 0 + // With these settings, the batcher will post a single commitment per L1 block, + // so it's easy to trigger failover and observe the commitment changing on the next L1 block. + cfg.BatcherMaxPendingTransactions = 1 // no limit on parallel txs + cfg.BatcherMaxConcurrentDARequest = 1 + cfg.BatcherBatchType = 0 + // We make channels as small as possible, such that they contain a single commitment. + // This is because failover to ethDA happens on a per-channel basis (each new channel is sent to altDA first). + // Hence, we can quickly observe the failover (to ethda) and fallback (to altda) behavior. + // cfg.BatcherMaxL1TxSizeBytes = 1200 + // currently altda commitments can only be sent as calldata + cfg.DataAvailabilityType = flags.CalldataType + + sys, err := cfg.Start(t) + require.NoError(t, err, "Error starting up system") + defer sys.Close() + l1Client := sys.NodeClient("l1") + + startBlockL1, err := geth.WaitForBlockWithTxFromSender(cfg.DeployConfig.BatchSenderAddress, l1Client, 10) + require.NoError(t, err) + + // Simulate altda server returning 503 + sys.FakeAltDAServer.SetPutFailoverForNRequests(nChannelsFailover) + + countEthDACommitment := uint64(0) + + // There is some nondeterministic timing behavior that affects whether the batcher has already + // posted batches before seeing the above SetPutFailoverForNRequests behavior change. + // Most likely, sequence of blocks will be: altDA, ethDA, ethDA, altDA, altDA, altDA. + // 2 ethDA are expected (and checked for) because nChannelsFailover=2, so da-server will return 503 for 2 requests only, + // and the batcher always tries altda first for a new channel, and failsover to ethDA only if altda returns 503. + for blockNumL1 := startBlockL1.NumberU64(); blockNumL1 < startBlockL1.NumberU64()+6; blockNumL1++ { + blockL1, err := geth.WaitForBlock(big.NewInt(0).SetUint64(blockNumL1), l1Client) + require.NoError(t, err) + batcherTxs, err := transactions.TransactionsBySender(blockL1, cfg.DeployConfig.BatchSenderAddress) + require.NoError(t, err) + require.Equal(t, 1, len(batcherTxs)) // sanity check: ensure BatcherMaxPendingTransactions=1 is working + batcherTx := batcherTxs[0] + if batcherTx.Data()[0] == 1 { + t.Log("blockL1", blockNumL1, "batcherTxType", "altda") + } else if batcherTx.Data()[0] == 0 { + t.Log("blockL1", blockNumL1, "batcherTxType", "ethda") + } else { + t.Fatalf("unexpected batcherTxType: %v", batcherTx.Data()[0]) + } + if batcherTx.Data()[0] == byte(params.DerivationVersion0) { + countEthDACommitment++ + } + } + require.Equal(t, nChannelsFailover, countEthDACommitment, "Expected %v ethDA commitments, got %v", nChannelsFailover, countEthDACommitment) + +} diff --git a/op-e2e/system/da/brotli_batcher_test.go b/op-e2e/system/da/brotli_batcher_test.go index 3bc9b4b5dd9d5..aa62afe0dc023 100644 --- a/op-e2e/system/da/brotli_batcher_test.go +++ b/op-e2e/system/da/brotli_batcher_test.go @@ -86,7 +86,7 @@ func TestBrotliBatcherFjord(t *testing.T) { opts.Value = big.NewInt(1_000_000_000) opts.Nonce = 1 // Already have deposit opts.ToAddr = &common.Address{0xff, 0xff} - opts.Gas, err = core.IntrinsicGas(opts.Data, nil, nil, false, true, true, false) + opts.Gas, err = core.IntrinsicGas(opts.Data, nil, nil, false, true, true, false, nil, nil) require.NoError(t, err) opts.VerifyOnClients(l2Verif) }) diff --git a/op-e2e/system/da/eip4844_test.go b/op-e2e/system/da/eip4844_test.go index 2f7c2ffe3e02b..9a2742d7ef432 100644 --- a/op-e2e/system/da/eip4844_test.go +++ b/op-e2e/system/da/eip4844_test.go @@ -142,7 +142,7 @@ func testSystem4844E2E(t *testing.T, multiBlob bool, daType batcherFlags.DataAva opts.ToAddr = &common.Address{0xff, 0xff} // put some random data in the tx to make it fill up maxBlobsPerBlock blobs (multi-blob case) opts.Data = testutils.RandomData(rand.New(rand.NewSource(420)), 400) - opts.Gas, err = core.IntrinsicGas(opts.Data, nil, nil, false, true, true, false) + opts.Gas, err = core.IntrinsicGas(opts.Data, nil, nil, false, true, true, false, nil, nil) require.NoError(t, err) opts.VerifyOnClients(l2Verif) }) diff --git a/op-e2e/system/da/multi_test.go b/op-e2e/system/da/multi_test.go index 461270282008b..e8b7ea6ff2664 100644 --- a/op-e2e/system/da/multi_test.go +++ b/op-e2e/system/da/multi_test.go @@ -52,7 +52,7 @@ func TestBatcherMultiTx(t *testing.T) { block, err := l1Client.BlockByNumber(ctx, big.NewInt(int64(i))) require.NoError(t, err) - batcherTxCount, err := transactions.TransactionsBySender(block, cfg.DeployConfig.BatchSenderAddress) + batcherTxCount, err := transactions.TransactionsBySenderCount(block, cfg.DeployConfig.BatchSenderAddress) require.NoError(t, err) totalBatcherTxsCount += batcherTxCount diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index dcc4429b032cc..6cc28c6e9b640 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "errors" "fmt" + "log/slog" "math/big" "net" "os" @@ -91,6 +92,7 @@ var ( type SystemConfigOpts struct { AllocType config.AllocType + LogLevel slog.Level } type SystemConfigOpt func(s *SystemConfigOpts) @@ -101,9 +103,16 @@ func WithAllocType(allocType config.AllocType) SystemConfigOpt { } } +func WithLogLevel(level slog.Level) SystemConfigOpt { + return func(s *SystemConfigOpts) { + s.LogLevel = level + } +} + func DefaultSystemConfig(t testing.TB, opts ...SystemConfigOpt) SystemConfig { sco := &SystemConfigOpts{ AllocType: config.DefaultAllocType, + LogLevel: slog.LevelInfo, } for _, opt := range opts { opt(sco) @@ -114,7 +123,7 @@ func DefaultSystemConfig(t testing.TB, opts ...SystemConfigOpt) SystemConfig { require.Nil(t, deployConfig.L2GenesisJovianTimeOffset, "jovian not supported yet") deployConfig.L1GenesisBlockTimestamp = hexutil.Uint64(time.Now().Unix()) e2eutils.ApplyDeployConfigForks(deployConfig) - require.NoError(t, deployConfig.Check(testlog.Logger(t, log.LevelInfo)), + require.NoError(t, deployConfig.Check(testlog.Logger(t, sco.LogLevel).New("role", "config-check")), "Deploy config is invalid, do you need to run make devnet-allocs?") l1Deployments := config.L1Deployments(sco.AllocType) require.NoError(t, l1Deployments.Check(deployConfig)) @@ -176,11 +185,12 @@ func DefaultSystemConfig(t testing.TB, opts ...SystemConfigOpt) SystemConfig { }, }, Loggers: map[string]log.Logger{ - RoleVerif: testlog.Logger(t, log.LevelInfo).New("role", RoleVerif), - RoleSeq: testlog.Logger(t, log.LevelInfo).New("role", RoleSeq), - "batcher": testlog.Logger(t, log.LevelInfo).New("role", "batcher"), - "proposer": testlog.Logger(t, log.LevelInfo).New("role", "proposer"), - "da-server": testlog.Logger(t, log.LevelInfo).New("role", "da-server"), + RoleVerif: testlog.Logger(t, sco.LogLevel).New("role", RoleVerif), + RoleSeq: testlog.Logger(t, sco.LogLevel).New("role", RoleSeq), + "batcher": testlog.Logger(t, sco.LogLevel).New("role", "batcher"), + "proposer": testlog.Logger(t, sco.LogLevel).New("role", "proposer"), + "da-server": testlog.Logger(t, sco.LogLevel).New("role", "da-server"), + "config-check": testlog.Logger(t, sco.LogLevel).New("role", "config-check"), }, GethOptions: map[string][]geth.GethOption{}, P2PTopology: nil, // no P2P connectivity by default @@ -286,12 +296,10 @@ type SystemConfig struct { // L1FinalizedDistance is the distance from the L1 head that L1 blocks will be artificially finalized on. L1FinalizedDistance uint64 - Premine map[common.Address]*big.Int - Nodes map[string]*rollupNode.Config // Per node config. Don't use populate rollup.Config - Loggers map[string]log.Logger - GethOptions map[string][]geth.GethOption - ProposerLogger log.Logger - BatcherLogger log.Logger + Premine map[common.Address]*big.Int + Nodes map[string]*rollupNode.Config // Per node config. Don't use populate rollup.Config + Loggers map[string]log.Logger + GethOptions map[string][]geth.GethOption ExternalL2Shim string @@ -608,7 +616,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, // sanity-check the deploy config require.Nil(t, cfg.DeployConfig.L2GenesisJovianTimeOffset, "Jovian is not supported in op-e2e tests yet") - if err := cfg.DeployConfig.Check(testlog.Logger(t, log.LevelInfo)); err != nil { + if err := cfg.DeployConfig.Check(cfg.Loggers["config-check"]); err != nil { return nil, err } @@ -707,6 +715,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, IsthmusTime: cfg.DeployConfig.IsthmusTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), JovianTime: cfg.DeployConfig.JovianTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + Cel2Time: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, AltDAConfig: rollupAltDAConfig, ChainOpConfig: ¶ms.OptimismConfig{ @@ -848,6 +857,24 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, } } + // The altDACLIConfig is shared by the batcher and rollup nodes. + var altDACLIConfig altda.CLIConfig + if cfg.DeployConfig.UseAltDA { + fakeAltDAServer := altda.NewFakeDAServer("127.0.0.1", 0, sys.Cfg.Loggers["da-server"]) + if err := fakeAltDAServer.Start(); err != nil { + return nil, fmt.Errorf("failed to start fake altDA server: %w", err) + } + sys.FakeAltDAServer = fakeAltDAServer + + altDACLIConfig = altda.CLIConfig{ + Enabled: cfg.DeployConfig.UseAltDA, + DAServerURL: fakeAltDAServer.HttpEndpoint(), + VerifyOnRead: true, + GenericDA: true, + MaxConcurrentRequests: cfg.BatcherMaxConcurrentDARequest, + } + } + // Rollup nodes // Ensure we are looping through the nodes in alphabetical order @@ -862,7 +889,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, if err := c.LoadPersisted(cfg.Loggers[name]); err != nil { return nil, err } - + c.AltDA = altDACLIConfig if p, ok := p2pNodes[name]; ok { c.P2P = p @@ -964,22 +991,6 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, batcherTargetNumFrames = 1 } - var batcherAltDACLIConfig altda.CLIConfig - if cfg.DeployConfig.UseAltDA { - fakeAltDAServer := altda.NewFakeDAServer("127.0.0.1", 0, sys.Cfg.Loggers["da-server"]) - if err := fakeAltDAServer.Start(); err != nil { - return nil, fmt.Errorf("failed to start fake altDA server: %w", err) - } - sys.FakeAltDAServer = fakeAltDAServer - - batcherAltDACLIConfig = altda.CLIConfig{ - Enabled: cfg.DeployConfig.UseAltDA, - DAServerURL: fakeAltDAServer.HttpEndpoint(), - VerifyOnRead: true, - GenericDA: true, - MaxConcurrentRequests: cfg.BatcherMaxConcurrentDARequest, - } - } batcherCLIConfig := &bss.CLIConfig{ L1EthRpc: sys.EthInstances[RoleL1].UserRPC().RPC(), L2EthRpc: sys.EthInstances[RoleSeq].UserRPC().RPC(), @@ -1002,7 +1013,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, MaxBlocksPerSpanBatch: cfg.BatcherMaxBlocksPerSpanBatch, DataAvailabilityType: sys.Cfg.DataAvailabilityType, CompressionAlgo: derive.Zlib, - AltDA: batcherAltDACLIConfig, + AltDA: altDACLIConfig, } // Apply batcher cli modifications diff --git a/op-e2e/system/fees/fees_test.go b/op-e2e/system/fees/fees_test.go index 80f5febc19ebe..e804af2223a69 100644 --- a/op-e2e/system/fees/fees_test.go +++ b/op-e2e/system/fees/fees_test.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/contracts/addresses" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" @@ -155,7 +156,11 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { require.Equal(t, decimals.Uint64(), uint64(6), "wrong gpo decimals") - baseFeeRecipientStartBalance := balanceAt(predeploys.BaseFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) + baseFeeRecipient := predeploys.BaseFeeVaultAddr + if sys.RollupConfig.IsCel2(sys.L2GenesisCfg.Timestamp) { + baseFeeRecipient = addresses.MainnetAddresses.FeeHandler + } + baseFeeRecipientStartBalance := balanceAt(baseFeeRecipient, big.NewInt(rpc.EarliestBlockNumber.Int64())) l1FeeRecipientStartBalance := balanceAt(predeploys.L1FeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) sequencerFeeVaultStartBalance := balanceAt(predeploys.SequencerFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) operatorFeeVaultStartBalance := balanceAt(predeploys.OperatorFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) @@ -193,7 +198,7 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { coinbaseEndBalance := balanceAt(header.Coinbase, header.Number) endBalance := balanceAt(fromAddr, header.Number) - baseFeeRecipientEndBalance := balanceAt(predeploys.BaseFeeVaultAddr, header.Number) + baseFeeRecipientEndBalance := balanceAt(baseFeeRecipient, header.Number) l1Header, err := l1.HeaderByNumber(context.Background(), nil) require.Nil(t, err) diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 3a7235fd8bfb0..e31a964749d1a 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -36,6 +36,17 @@ func init() { cli.VersionFlag.(*cli.BoolFlag).Category = MiscCategory } +func init() { + DeprecatedFlags = append(DeprecatedFlags, deprecatedP2PFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, P2PFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, oplog.CLIFlagsWithCategory(EnvVarPrefix, OperationsCategory)...) + optionalFlags = append(optionalFlags, oppprof.CLIFlagsWithCategory(EnvVarPrefix, OperationsCategory)...) + optionalFlags = append(optionalFlags, DeprecatedFlags...) + optionalFlags = append(optionalFlags, opflags.CLIFlags(EnvVarPrefix, RollupCategory)...) + optionalFlags = append(optionalFlags, altda.CLIFlags(EnvVarPrefix, AltDACategory)...) + Flags = append(requiredFlags, optionalFlags...) +} + func prefixEnvVars(names ...string) []string { envs := make([]string, 0, len(names)) for _, name := range names { @@ -263,6 +274,13 @@ var ( Value: false, Category: SequencerCategory, } + SequencerUseFinalizedL1Flag = &cli.BoolFlag{ + Name: "sequencer.use-finalized", + Usage: "Enable use of only finalized L1 blocks as L1 origin. Overwrites the value of 'sequencer.l1-confs'.", + EnvVars: prefixEnvVars("SEQUENCER_USE_FINALIZED"), + Value: false, + Category: SequencerCategory, + } L1EpochPollIntervalFlag = &cli.DurationFlag{ Name: "l1.epoch-poll-interval", Usage: "Poll interval for retrieving new L1 epoch updates such as safe and finalized block changes. Disabled if 0 or negative.", @@ -468,6 +486,7 @@ var optionalFlags = []cli.Flag{ L1RPCMaxConcurrency, L1HTTPPollInterval, L1CacheSize, + SequencerUseFinalizedL1Flag, VerifierL1Confs, SequencerEnabledFlag, SequencerStoppedFlag, @@ -512,17 +531,6 @@ var DeprecatedFlags = []cli.Flag{ // Flags contains the list of configuration options available to the binary. var Flags []cli.Flag -func init() { - DeprecatedFlags = append(DeprecatedFlags, deprecatedP2PFlags(EnvVarPrefix)...) - optionalFlags = append(optionalFlags, P2PFlags(EnvVarPrefix)...) - optionalFlags = append(optionalFlags, oplog.CLIFlagsWithCategory(EnvVarPrefix, OperationsCategory)...) - optionalFlags = append(optionalFlags, oppprof.CLIFlagsWithCategory(EnvVarPrefix, OperationsCategory)...) - optionalFlags = append(optionalFlags, DeprecatedFlags...) - optionalFlags = append(optionalFlags, opflags.CLIFlags(EnvVarPrefix, RollupCategory)...) - optionalFlags = append(optionalFlags, altda.CLIFlags(EnvVarPrefix, AltDACategory)...) - Flags = append(requiredFlags, optionalFlags...) -} - func CheckRequired(ctx *cli.Context) error { for _, f := range requiredFlags { if !ctx.IsSet(f.Names()[0]) { diff --git a/op-node/node/node.go b/op-node/node/node.go index e4df4b729e7ae..6b539e9b7e0be 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -423,7 +423,10 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config) error { if cfg.AltDA.Enabled && err != nil { return fmt.Errorf("failed to get altDA config: %w", err) } - altDA := altda.NewAltDA(n.log, cfg.AltDA, rpCfg, n.metrics.AltDAMetrics) + altDA, err := altda.NewAltDA(n.log, cfg.AltDA, rpCfg, n.metrics.AltDAMetrics) + if err != nil { + return fmt.Errorf("failed to create altDA: %w", err) + } if cfg.SafeDBPath != "" { n.log.Info("Safe head database enabled", "path", cfg.SafeDBPath) safeDB, err := safedb.NewSafeDB(n.log, cfg.SafeDBPath) @@ -537,6 +540,30 @@ func (n *OpNode) initP2PSigner(ctx context.Context, cfg *Config) (err error) { } func (n *OpNode) Start(ctx context.Context) error { + // If n.cfg.Driver.SequencerUseFinalized is true, sequencer does not use non-finalized L1 blocks as L1 origin + // The OpNode periodically fetches the latest safe and finalized L1 block heights (1 epoch ≒ 6.4 minutes by default), + // but these values are not available immediately after startup until the first polling occurs. + // In some cases, this can cause the sequencer to get stuck because it fails to retrieve the next L1 block. + // To prevent this, fetch and initialize the latest safe and finalized L1 block references at startup. + if n.cfg.Driver.SequencerUseFinalized { + reqCtx, reqCancel := context.WithTimeout(ctx, time.Second*20) + defer reqCancel() + + finalizedRef, err := n.l1Source.L1BlockRefByLabel(reqCtx, eth.Finalized) + if err != nil { + log.Warn("failed to fetch L1 block", "label", eth.Finalized, "err", err) + } else if finalizedRef != (eth.L1BlockRef{}) { + n.OnNewL1Finalized(reqCtx, finalizedRef) + } + + safeRef, err := n.l1Source.L1BlockRefByLabel(reqCtx, eth.Safe) + if err != nil { + log.Warn("failed to fetch L1 block", "label", eth.Safe, "err", err) + } else if safeRef != (eth.L1BlockRef{}) { + n.OnNewL1Safe(reqCtx, safeRef) + } + } + if n.interopSys != nil { if err := n.interopSys.Start(ctx); err != nil { n.log.Error("Could not start interop sub system", "err", err) diff --git a/op-node/rollup/chain_spec.go b/op-node/rollup/chain_spec.go index 80dcf14127aa7..7fcfaa7bed3d5 100644 --- a/op-node/rollup/chain_spec.go +++ b/op-node/rollup/chain_spec.go @@ -30,6 +30,12 @@ const ( // ChainSpec instead of reading the rollup configuration field directly. const maxSequencerDriftFjord = 1800 +// Normal OP chains wait for five confirmations while Celo waits for finalization, which can take +// up to 3 * 32 blocks. So we should allow for more drift to compensate. +// 3 * 32 - 5 = 91 blocks +// 91 * 12s block time = 1092 +const maxSequencerDriftCelo = maxSequencerDriftFjord + 1092 + type ForkName string const ( @@ -160,7 +166,11 @@ func (s *ChainSpec) IsFeatMaxSequencerDriftConstant(t uint64) bool { // should always be queried via the ChainSpec. func (s *ChainSpec) MaxSequencerDrift(t uint64) uint64 { if s.IsFeatMaxSequencerDriftConstant(t) { - return maxSequencerDriftFjord + if s.config.IsCel2(t) { + return maxSequencerDriftCelo + } else { + return maxSequencerDriftFjord + } } return s.config.MaxSequencerDrift } diff --git a/op-node/rollup/derive/altda_data_source.go b/op-node/rollup/derive/altda_data_source.go index 2945a2a9e57b2..315b40be6e851 100644 --- a/op-node/rollup/derive/altda_data_source.go +++ b/op-node/rollup/derive/altda_data_source.go @@ -40,8 +40,10 @@ func (s *AltDADataSource) Next(ctx context.Context) (eth.Data, error) { // there is not commitment in the current origin. if err := s.fetcher.AdvanceL1Origin(ctx, s.l1, s.id.ID()); err != nil { if errors.Is(err, altda.ErrReorgRequired) { + s.log.Warn("reorg required, resetting altDA L1 origin", "origin", s.id) return nil, NewResetError(errors.New("new expired challenge")) } + s.log.Warn("failed to advance altDA L1 origin", "err", err) return nil, NewTemporaryError(fmt.Errorf("failed to advance altDA L1 origin: %w", err)) } @@ -58,6 +60,7 @@ func (s *AltDADataSource) Next(ctx context.Context) (eth.Data, error) { // If the tx data type is not altDA, we forward it downstream to let the next // steps validate and potentially parse it as L1 DA inputs. if data[0] != params.DerivationVersion1 { + s.log.Info("forwarding downstream non altDA data", "version_byte", data[0]) return data, nil } @@ -79,7 +82,7 @@ func (s *AltDADataSource) Next(ctx context.Context) (eth.Data, error) { return nil, NewResetError(err) } else if errors.Is(err, altda.ErrExpiredChallenge) { // this commitment was challenged and the challenge expired. - s.log.Warn("challenge expired, skipping batch", "comm", s.comm) + s.log.Warn("challenge expired, skipping batch", "comm", s.comm, "err", err) s.comm = nil // skip the input return s.Next(ctx) diff --git a/op-node/rollup/derive/frame.go b/op-node/rollup/derive/frame.go index e18562560e796..5e697375df27a 100644 --- a/op-node/rollup/derive/frame.go +++ b/op-node/rollup/derive/frame.go @@ -10,10 +10,10 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive/params" ) -// Frames cannot be larger than 1 MB. // Data transactions that carry frames are generally not larger than 128 KB due to L1 network conditions, // but we leave space to grow larger anyway (gas limit allows for more data). -const MaxFrameLen = 1_000_000 +// For AltDA, frames size can be larger. Setting to 16 MB as current blob limit for EigenDA. +const MaxFrameLen = 16_000_000 // Data Format // @@ -87,7 +87,7 @@ func (f *Frame) UnmarshalBinary(r ByteReader) error { return fmt.Errorf("reading frame_data_length: %w", eofAsUnexpectedMissing(err)) } - // Cap frame length to MaxFrameLen (currently 1MB) + // Cap frame length to MaxFrameLen if frameLength > MaxFrameLen { return fmt.Errorf("frame_data_length is too large: %d", frameLength) } diff --git a/op-node/rollup/derive/isthmus_upgrade_transactions.go b/op-node/rollup/derive/isthmus_upgrade_transactions.go index 25bee50b06191..fc5dc03cb2303 100644 --- a/op-node/rollup/derive/isthmus_upgrade_transactions.go +++ b/op-node/rollup/derive/isthmus_upgrade_transactions.go @@ -30,7 +30,8 @@ var ( OperatorFeeVaultAddress = crypto.CreateAddress(OperatorFeeVaultDeployerAddress, 0) // Bytecodes - l1BlockIsthmusDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b506106ae806100206000396000f3fe608060405234801561001057600080fd5b50600436106101825760003560e01c806364ca23ef116100d8578063b80777ea1161008c578063e591b28211610066578063e591b282146103b0578063e81b2c6d146103d2578063f8206140146103db57600080fd5b8063b80777ea14610337578063c598591814610357578063d84447151461037757600080fd5b80638381f58a116100bd5780638381f58a146103115780638b239f73146103255780639e8c49661461032e57600080fd5b806364ca23ef146102e157806368d5dca6146102f557600080fd5b80634397dfef1161013a57806354fd4d501161011457806354fd4d501461025d578063550fcdc91461029f5780635cf24969146102d857600080fd5b80634397dfef146101fc578063440a5e20146102245780634d5d9a2a1461022c57600080fd5b806309bd5a601161016b57806309bd5a60146101a457806316d3bc7f146101c057806321326849146101ed57600080fd5b8063015d8eb914610187578063098999be1461019c575b600080fd5b61019a6101953660046105bc565b6103e4565b005b61019a610523565b6101ad60025481565b6040519081526020015b60405180910390f35b6008546101d49067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101b7565b604051600081526020016101b7565b6040805173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815260126020820152016101b7565b61019a61052d565b6008546102489068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101b7565b60408051808201909152600581527f312e362e3000000000000000000000000000000000000000000000000000000060208201525b6040516101b7919061062e565b60408051808201909152600381527f45544800000000000000000000000000000000000000000000000000000000006020820152610292565b6101ad60015481565b6003546101d49067ffffffffffffffff1681565b6003546102489068010000000000000000900463ffffffff1681565b6000546101d49067ffffffffffffffff1681565b6101ad60055481565b6101ad60065481565b6000546101d49068010000000000000000900467ffffffffffffffff1681565b600354610248906c01000000000000000000000000900463ffffffff1681565b60408051808201909152600581527f45746865720000000000000000000000000000000000000000000000000000006020820152610292565b60405173deaddeaddeaddeaddeaddeaddeaddeaddead000181526020016101b7565b6101ad60045481565b6101ad60075481565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461048b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61052b610535565b565b61052b610548565b61053d610548565b60a43560a01c600855565b73deaddeaddeaddeaddeaddeaddeaddeaddead000133811461057257633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b803567ffffffffffffffff811681146105b757600080fd5b919050565b600080600080600080600080610100898b0312156105d957600080fd5b6105e28961059f565b97506105f060208a0161059f565b9650604089013595506060890135945061060c60808a0161059f565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b8181101561065b5785810183015185820160400152820161063f565b8181111561066d576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a") + // The L1Block contract code had to be altered to support the CustomGasToken feature. + l1BlockIsthmusDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b50610b43806100206000396000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806364ca23ef116100e3578063b80777ea1161008c578063e591b28211610066578063e591b28214610383578063e81b2c6d146103a5578063f8206140146103ae57600080fd5b8063b80777ea1461033b578063c59859181461035b578063d84447151461037b57600080fd5b80638381f58a116100bd5780638381f58a146103155780638b239f73146103295780639e8c49661461033257600080fd5b806364ca23ef146102d257806368d5dca6146102e657806371cfaa3f1461030257600080fd5b80634397dfef1161014557806354fd4d501161011f57806354fd4d501461027f578063550fcdc9146102c15780635cf24969146102c957600080fd5b80634397dfef14610210578063440a5e20146102465780634d5d9a2a1461024e57600080fd5b806309bd5a601161017657806309bd5a60146101af57806316d3bc7f146101cb57806321326849146101f857600080fd5b8063015d8eb914610192578063098999be146101a7575b600080fd5b6101a56101a03660046109ae565b6103b7565b005b6101a56104f6565b6101b860025481565b6040519081526020015b60405180910390f35b6008546101df9067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101c2565b610200610500565b60405190151581526020016101c2565b61021861053f565b6040805173ffffffffffffffffffffffffffffffffffffffff909316835260ff9091166020830152016101c2565b6101a5610553565b60085461026a9068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101c2565b60408051808201909152600581527f312e362e3000000000000000000000000000000000000000000000000000000060208201525b6040516101c29190610a20565b6102b461055b565b6101b860015481565b6003546101df9067ffffffffffffffff1681565b60035461026a9068010000000000000000900463ffffffff1681565b6101a5610310366004610a93565b61056a565b6000546101df9067ffffffffffffffff1681565b6101b860055481565b6101b860065481565b6000546101df9068010000000000000000900467ffffffffffffffff1681565b60035461026a906c01000000000000000000000000900463ffffffff1681565b6102b461061f565b60405173deaddeaddeaddeaddeaddeaddeaddeaddead000181526020016101c2565b6101b860045481565b6101b860075481565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461045e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b6104fe610629565b565b60008061050b61053f565b5073ffffffffffffffffffffffffffffffffffffffff1673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee141592915050565b60008061054a61063c565b90939092509050565b6104fe6106bd565b6060610565610714565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146105b7576040517f3cc50b4500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6105c3848484846107d5565b604080518381526020810183905260ff85169173ffffffffffffffffffffffffffffffffffffffff8716917f10e43c4d58f3ef4edae7c1ca2e7f02d46b2cadbcc046737038527ed8486ffeb0910160405180910390a350505050565b60606105656108a7565b6106316106bd565b60a43560a01c600855565b6000808061067261066e60017f04adb1412b2ddc16fcc0d4538d5c8f07cf9c83abecc6b41f6f69037b708fbcec610af8565b5490565b73ffffffffffffffffffffffffffffffffffffffff811693509050826106b1575073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee92601292509050565b60a081901c9150509091565b73deaddeaddeaddeaddeaddeaddeaddeaddead00013381146106e757633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b6060600061072061063c565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff82160161079957505060408051808201909152600381527f4554480000000000000000000000000000000000000000000000000000000000602082015290565b6107cf6107ca61066e60017fa48b38a4b44951360fbdcbfaaeae5ed6ae92585412e9841b70ec72ed8cd05764610af8565b61095d565b91505090565b61083b61080360017f04adb1412b2ddc16fcc0d4538d5c8f07cf9c83abecc6b41f6f69037b708fbcec610af8565b74ff000000000000000000000000000000000000000060a086901b1673ffffffffffffffffffffffffffffffffffffffff8716179055565b61086e61086960017f657c3582c29b3176614e3a33ddd1ec48352696a04e92b3c0566d72010fa8863d610af8565b839055565b6108a161089c60017fa48b38a4b44951360fbdcbfaaeae5ed6ae92585412e9841b70ec72ed8cd05764610af8565b829055565b50505050565b606060006108b361063c565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff82160161092c57505060408051808201909152600581527f4574686572000000000000000000000000000000000000000000000000000000602082015290565b6107cf6107ca61066e60017f657c3582c29b3176614e3a33ddd1ec48352696a04e92b3c0566d72010fa8863d610af8565b60405160005b82811a1561097357600101610963565b80825260208201838152600082820152505060408101604052919050565b803567ffffffffffffffff811681146109a957600080fd5b919050565b600080600080600080600080610100898b0312156109cb57600080fd5b6109d489610991565b97506109e260208a01610991565b965060408901359550606089013594506109fe60808a01610991565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b81811015610a4d57858101830151858201604001528201610a31565b81811115610a5f576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60008060008060808587031215610aa957600080fd5b843573ffffffffffffffffffffffffffffffffffffffff81168114610acd57600080fd5b9350602085013560ff81168114610ae357600080fd5b93969395505050506040820135916060013590565b600082821015610b31577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50039056fea164736f6c634300080f000a") gasPriceOracleIsthmusDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b50611c3c806100206000396000f3fe608060405234801561001057600080fd5b50600436106101775760003560e01c806368d5dca6116100d8578063c59859181161008c578063f45e65d811610066578063f45e65d8146102ca578063f8206140146102d2578063fe173b971461026957600080fd5b8063c59859181461029c578063de26c4a1146102a4578063f1c7a58b146102b757600080fd5b80638e98b106116100bd5780638e98b1061461026f578063960e3a2314610277578063b54501bc1461028957600080fd5b806368d5dca61461024c5780636ef25c3a1461026957600080fd5b8063313ce5671161012f5780634ef6e224116101145780634ef6e224146101de578063519b4bd3146101fb57806354fd4d501461020357600080fd5b8063313ce567146101c457806349948e0e146101cb57600080fd5b8063275aedd211610160578063275aedd2146101a1578063291b0383146101b45780632e0f2625146101bc57600080fd5b80630c18c1621461017c57806322b90ab314610197575b600080fd5b6101846102da565b6040519081526020015b60405180910390f35b61019f6103fb565b005b6101846101af36600461168e565b610584565b61019f61070f565b610184600681565b6006610184565b6101846101d93660046116d6565b610937565b6000546101eb9060ff1681565b604051901515815260200161018e565b61018461096e565b61023f6040518060400160405280600581526020017f312e342e3000000000000000000000000000000000000000000000000000000081525081565b60405161018e91906117a5565b6102546109cf565b60405163ffffffff909116815260200161018e565b48610184565b61019f610a54565b6000546101eb90610100900460ff1681565b6000546101eb9062010000900460ff1681565b610254610c4e565b6101846102b23660046116d6565b610caf565b6101846102c536600461168e565b610da9565b610184610e85565b610184610f78565b6000805460ff1615610373576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f47617350726963654f7261636c653a206f76657268656164282920697320646560448201527f707265636174656400000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190611818565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104c4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e2073657420697345636f746f6e6520666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161036a565b60005460ff1615610557576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a2045636f746f6e6520616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000805462010000900460ff1661059d57506000919050565b610709620f42406106668473420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16634d5d9a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610607573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062b9190611831565b63ffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821583830293840490921491909117011790565b6106709190611886565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166316d3bc7f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106f391906118c1565b67ffffffffffffffff1681019081106000031790565b92915050565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146107d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973497374686d757320666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161036a565b600054610100900460ff1661086f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20497374686d75732063616e206f6e6c7960448201527f2062652061637469766174656420616674657220466a6f726400000000000000606482015260840161036a565b60005462010000900460ff1615610908576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a20497374686d757320616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff1662010000179055565b60008054610100900460ff16156109515761070982610fd9565b60005460ff16156109655761070982610ff8565b6107098261109c565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16635cf249696040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166368d5dca66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a30573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190611831565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610af7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973466a6f726420666c616700606482015260840161036a565b60005460ff16610b89576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20466a6f72642063616e206f6e6c79206260448201527f65206163746976617465642061667465722045636f746f6e6500000000000000606482015260840161036a565b600054610100900460ff1615610c20576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f47617350726963654f7261636c653a20466a6f726420616c726561647920616360448201527f7469766500000000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100179055565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663c59859186040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a30573d6000803e3d6000fd5b60008054610100900460ff1615610cf657620f4240610ce1610cd0846111f0565b51610cdc9060446118eb565b61150d565b610cec906010611903565b6107099190611886565b6000610d018361156c565b60005490915060ff1615610d155792915050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d74573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d989190611818565b610da290826118eb565b9392505050565b60008054610100900460ff16610e41576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f47617350726963654f7261636c653a206765744c314665655570706572426f7560448201527f6e64206f6e6c7920737570706f72747320466a6f726400000000000000000000606482015260840161036a565b6000610e4e8360446118eb565b90506000610e5d60ff83611886565b610e6790836118eb565b610e729060106118eb565b9050610e7d816115fc565b949350505050565b6000805460ff1615610f19576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a207363616c61722829206973206465707260448201527f6563617465640000000000000000000000000000000000000000000000000000606482015260840161036a565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663f82061406040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b6000610709610fe7836111f0565b51610ff39060446118eb565b6115fc565b6000806110048361156c565b9050600061101061096e565b611018610c4e565b611023906010611940565b63ffffffff166110339190611903565b9050600061103f610f78565b6110476109cf565b63ffffffff166110579190611903565b9050600061106582846118eb565b61106f9085611903565b905061107d6006600a611a8c565b611088906010611903565b6110929082611886565b9695505050505050565b6000806110a88361156c565b9050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa15801561110b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061112f9190611818565b61113761096e565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015611196573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ba9190611818565b6111c490856118eb565b6111ce9190611903565b6111d89190611903565b90506111e66006600a611a8c565b610e7d9082611886565b606061137f565b818153600101919050565b600082840393505b83811015610da25782810151828201511860001a159093029260010161120a565b825b60208210611277578251611242601f836111f7565b52602092909201917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09091019060210161122d565b8115610da257825161128c60018403836111f7565b520160010192915050565b60006001830392505b61010782106112d8576112ca8360ff166112c560fd6112c58760081c60e001896111f7565b6111f7565b9350610106820391506112a0565b60078210611305576112fe8360ff166112c5600785036112c58760081c60e001896111f7565b9050610da2565b610e7d8360ff166112c58560081c8560051b01876111f7565b61137782820361135b61134b84600081518060001a8160011a60081b178160021a60101b17915050919050565b639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b6180003860405139618000604051016020830180600d8551820103826002015b818110156114b2576000805b50508051604051600082901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b909118909152840190818303908484106114075750611442565b600184019350611fff821161143c578251600081901a600182901a60081b1760029190911a60101b17810361143c5750611442565b506113ab565b8383106114505750506114b2565b6001830392508583111561146e5761146b878788860361122b565b96505b611482600985016003850160038501611202565b915061148f878284611297565b9650506114a7846114a28684860161131e565b61131e565b91505080935061139f565b50506114c4838384885185010361122b565b925050506040519150618000820180820391508183526020830160005b838110156114f95782810151828201526020016114e1565b506000920191825250602001604052919050565b60008061151d83620cc394611903565b611547907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd763200611a98565b90506115576064620f4240611b0c565b81121561070957610da26064620f4240611b0c565b80516000908190815b818110156115ef5784818151811061158f5761158f611bc8565b01602001517fff00000000000000000000000000000000000000000000000000000000000000166000036115cf576115c86004846118eb565b92506115dd565b6115da6010846118eb565b92505b806115e781611bf7565b915050611575565b50610e7d826104406118eb565b6000806116088361150d565b90506000611614610f78565b61161c6109cf565b63ffffffff1661162c9190611903565b61163461096e565b61163c610c4e565b611647906010611940565b63ffffffff166116579190611903565b61166191906118eb565b905061166f60066002611903565b61167a90600a611a8c565b6116848284611903565b610e7d9190611886565b6000602082840312156116a057600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156116e857600080fd5b813567ffffffffffffffff8082111561170057600080fd5b818401915084601f83011261171457600080fd5b813581811115611726576117266116a7565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561176c5761176c6116a7565b8160405282815287602084870101111561178557600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b818110156117d2578581018301518582016040015282016117b6565b818111156117e4576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561182a57600080fd5b5051919050565b60006020828403121561184357600080fd5b815163ffffffff81168114610da257600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000826118bc577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b6000602082840312156118d357600080fd5b815167ffffffffffffffff81168114610da257600080fd5b600082198211156118fe576118fe611857565b500190565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561193b5761193b611857565b500290565b600063ffffffff8083168185168183048111821515161561196357611963611857565b02949350505050565b600181815b808511156119c557817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156119ab576119ab611857565b808516156119b857918102915b93841c9390800290611971565b509250929050565b6000826119dc57506001610709565b816119e957506000610709565b81600181146119ff5760028114611a0957611a25565b6001915050610709565b60ff841115611a1a57611a1a611857565b50506001821b610709565b5060208310610133831016604e8410600b8410161715611a48575081810a610709565b611a52838361196c565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115611a8457611a84611857565b029392505050565b6000610da283836119cd565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03841381151615611ad257611ad2611857565b827f8000000000000000000000000000000000000000000000000000000000000000038412811615611b0657611b06611857565b50500190565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615611b4d57611b4d611857565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615611b8857611b88611857565b60008712925087820587128484161615611ba457611ba4611857565b87850587128184161615611bba57611bba611857565b505050929093029392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611c2857611c28611857565b506001019056fea164736f6c634300080f000a") operatorFeeVaultDeploymentBytecode = common.FromHex("0x60e060405234801561001057600080fd5b5073420000000000000000000000000000000000001960a0526000608052600160c05260805160a05160c0516107ef6100a7600039600081816101b3015281816102450152818161044b015261048601526000818160b8015281816101800152818161039a01528181610429015281816104c201526105b70152600081816101ef01528181610279015261029d01526107ef6000f3fe60806040526004361061009a5760003560e01c806382356d8a1161006957806384411d651161004e57806384411d651461021d578063d0e12f9014610233578063d3e5792b1461026757600080fd5b806382356d8a146101a45780638312f149146101e057600080fd5b80630d9019e1146100a65780633ccfd60b1461010457806354fd4d501461011b57806366d003ac1461017157600080fd5b366100a157005b600080fd5b3480156100b257600080fd5b506100da7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561011057600080fd5b5061011961029b565b005b34801561012757600080fd5b506101646040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100fb9190610671565b34801561017d57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006100da565b3480156101b057600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516100fb919061074e565b3480156101ec57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040519081526020016100fb565b34801561022957600080fd5b5061020f60005481565b34801561023f57600080fd5b506101d37f000000000000000000000000000000000000000000000000000000000000000081565b34801561027357600080fd5b5061020f7f000000000000000000000000000000000000000000000000000000000000000081565b7f0000000000000000000000000000000000000000000000000000000000000000471015610376576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b60004790508060008082825461038c9190610762565b9091555050604080518281527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166020820152338183015290517fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba9181900360600190a17f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee817f0000000000000000000000000000000000000000000000000000000000000000337f000000000000000000000000000000000000000000000000000000000000000060405161047a94939291906107a1565b60405180910390a160017f000000000000000000000000000000000000000000000000000000000000000060018111156104b6576104b66106e4565b0361057a5760006104e77f000000000000000000000000000000000000000000000000000000000000000083610649565b905080610576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e7400000000000000000000000000000000606482015260840161036d565b5050565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015262061a80602482015260606044820152600060648201527342000000000000000000000000000000000000169063c2b3e5ac9083906084016000604051808303818588803b15801561062d57600080fd5b505af1158015610641573d6000803e3d6000fd5b505050505050565b6000610656835a8461065d565b9392505050565b6000806000806000858888f1949350505050565b600060208083528351808285015260005b8181101561069e57858101830151858201604001528201610682565b818111156106b0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6002811061074a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b6020810161075c8284610713565b92915050565b6000821982111561079c577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500190565b84815273ffffffffffffffffffffffffffffffffffffffff848116602083015283166040820152608081016107d96060830184610713565b9594505050505056fea164736f6c634300080f000a") @@ -52,7 +53,7 @@ func IsthmusNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { To: nil, Mint: big.NewInt(0), Value: big.NewInt(0), - Gas: 425_000, + Gas: 675_000, IsSystemTransaction: false, Data: l1BlockIsthmusDeploymentBytecode, }).MarshalBinary() diff --git a/op-node/rollup/derive/isthmus_upgrade_transactions_test.go b/op-node/rollup/derive/isthmus_upgrade_transactions_test.go index c005d57b16130..8b222a443cd4f 100644 --- a/op-node/rollup/derive/isthmus_upgrade_transactions_test.go +++ b/op-node/rollup/derive/isthmus_upgrade_transactions_test.go @@ -59,7 +59,7 @@ func TestIsthmusNetworkTransactions(t *testing.T) { require.Equal(t, deployL1BlockSender, common.HexToAddress("0x4210000000000000000000000000000000000003")) require.Equal(t, deployIsthmusL1BlockSource.SourceHash(), deployL1Block.SourceHash()) require.Nil(t, deployL1Block.To()) - require.Equal(t, uint64(425_000), deployL1Block.Gas()) // TODO + require.Equal(t, uint64(675_000), deployL1Block.Gas()) // TODO require.Equal(t, l1BlockIsthmusDeploymentBytecode, deployL1Block.Data()) deployGasPriceOracleSender, deployGasPriceOracle := toDepositTxn(t, upgradeTxns[1]) diff --git a/op-node/rollup/driver/config.go b/op-node/rollup/driver/config.go index 5446e4da16a41..421eabf7f4388 100644 --- a/op-node/rollup/driver/config.go +++ b/op-node/rollup/driver/config.go @@ -24,4 +24,8 @@ type Config struct { // RecoverMode forces the sequencer to select the next L1 Origin exactly, and create an empty block, // to be compatible with verifiers forcefully generating the same block while catching up the sequencing window timeout. RecoverMode bool `json:"recover_mode"` + + // SequencerUseFinalized is true when sequencer should use only finalized L1 blocks as origin. + // If this is set to true, the value of `SequencerConfDepth` is ignored. + SequencerUseFinalized bool `json:"sequencer_use_finalized"` } diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index b726392507c28..4b9583dcadcf5 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-node/rollup/event" "github.com/ethereum-optimism/optimism/op-node/rollup/finality" + "github.com/ethereum-optimism/optimism/op-node/rollup/finalized" "github.com/ethereum-optimism/optimism/op-node/rollup/sequencing" "github.com/ethereum-optimism/optimism/op-node/rollup/status" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" @@ -237,8 +238,13 @@ func NewDriver( if driverCfg.SequencerEnabled { asyncGossiper := async.NewAsyncGossiper(driverCtx, network, log, metrics) attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, l2) - sequencerConfDepth := confdepth.NewConfDepth(driverCfg.SequencerConfDepth, statusTracker.L1Head, l1) - findL1Origin := sequencing.NewL1OriginSelector(driverCtx, log, cfg, sequencerConfDepth) + var seqL1Blocks sequencing.L1Blocks + if driverCfg.SequencerUseFinalized { + seqL1Blocks = finalized.NewFinalized(statusTracker.L1Finalized, l1, log) + } else { + seqL1Blocks = confdepth.NewConfDepth(driverCfg.SequencerConfDepth, statusTracker.L1Head, l1) + } + findL1Origin := sequencing.NewL1OriginSelector(driverCtx, log, cfg, seqL1Blocks) sys.Register("origin-selector", findL1Origin, opts) sequencer = sequencing.NewSequencer(driverCtx, log, cfg, attrBuilder, findL1Origin, sequencerStateListener, sequencerConductor, asyncGossiper, metrics) diff --git a/op-node/rollup/finalized/finalized.go b/op-node/rollup/finalized/finalized.go new file mode 100644 index 0000000000000..fd10253efd174 --- /dev/null +++ b/op-node/rollup/finalized/finalized.go @@ -0,0 +1,32 @@ +package finalized + +import ( + "context" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type finalized struct { + derive.L1Fetcher + l1Finalized func() eth.L1BlockRef + log log.Logger +} + +func NewFinalized(l1Finalized func() eth.L1BlockRef, fetcher derive.L1Fetcher, log log.Logger) *finalized { + return &finalized{L1Fetcher: fetcher, l1Finalized: l1Finalized, log: log} +} + +func (f *finalized) L1BlockRefByNumber(ctx context.Context, num uint64) (eth.L1BlockRef, error) { + l1Finalized := f.l1Finalized() + if num == 0 || num <= l1Finalized.Number { + return f.L1Fetcher.L1BlockRefByNumber(ctx, num) + } + f.log.Warn("requested L1 block is beyond local finalized height", "requested_block", num, "finalized_block", l1Finalized.Number) + return eth.L1BlockRef{}, ethereum.NotFound +} + +var _ derive.L1Fetcher = (*finalized)(nil) diff --git a/op-node/rollup/finalized/finalized_test.go b/op-node/rollup/finalized/finalized_test.go new file mode 100644 index 0000000000000..1f7df16731c04 --- /dev/null +++ b/op-node/rollup/finalized/finalized_test.go @@ -0,0 +1,59 @@ +package finalized + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testutils" +) + +var testFinalHash = common.Hash{0x01} + +type finalizedTest struct { + name string + final uint64 + hash common.Hash // hash of finalized block + req uint64 + pass bool +} + +func (ft *finalizedTest) Run(t *testing.T) { + l1Fetcher := &testutils.MockL1Source{} + l1Finalized := eth.L1BlockRef{Number: ft.final, Hash: ft.hash} + l1FinalizedGetter := func() eth.L1BlockRef { return l1Finalized } + + f := NewFinalized(l1FinalizedGetter, l1Fetcher, log.New()) + + if ft.pass { + // no calls to the l1Fetcher are made if the block number is not finalized yet + l1Fetcher.ExpectL1BlockRefByNumber(ft.req, eth.L1BlockRef{Number: ft.req}, nil) + } + + out, err := f.L1BlockRefByNumber(context.Background(), ft.req) + l1Fetcher.AssertExpectations(t) + + if ft.pass { + require.NoError(t, err) + require.Equal(t, out, eth.L1BlockRef{Number: ft.req}) + } else { + require.Equal(t, ethereum.NotFound, err) + } +} + +func TestFinalized(t *testing.T) { + testCases := []finalizedTest{ + {name: "finalized", final: 10, hash: testFinalHash, req: 10, pass: true}, + {name: "finalized past", final: 10, hash: testFinalHash, req: 8, pass: true}, + {name: "not finalized", final: 10, hash: testFinalHash, req: 11, pass: false}, + {name: "no L1 state", req: 10, pass: false}, + } + for _, tc := range testCases { + t.Run(tc.name, tc.Run) + } +} diff --git a/op-node/rollup/status/status.go b/op-node/rollup/status/status.go index 9937a54731143..2b113cd5f20f5 100644 --- a/op-node/rollup/status/status.go +++ b/op-node/rollup/status/status.go @@ -147,3 +147,8 @@ func (st *StatusTracker) SyncStatus() *eth.SyncStatus { func (st *StatusTracker) L1Head() eth.L1BlockRef { return st.SyncStatus().HeadL1 } + +// L1Finalized is a helper function to get the latest known finalized L1 block. +func (st *StatusTracker) L1Finalized() eth.L1BlockRef { + return st.SyncStatus().FinalizedL1 +} diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index b38c80dd1a7b9..96ba6d3da46ae 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -94,6 +94,7 @@ type Config struct { // "Regolith" is the loose deposited rock that sits on top of Bedrock. // Active if RegolithTime != nil && L2 block timestamp >= *RegolithTime, inactive otherwise. RegolithTime *uint64 `json:"regolith_time,omitempty"` + Cel2Time *uint64 `json:"cel2_time,omitempty"` // CanyonTime sets the activation time of the Canyon network upgrade. // Active if CanyonTime != nil && L2 block timestamp >= *CanyonTime, inactive otherwise. @@ -475,6 +476,10 @@ func (c *Config) IsInterop(timestamp uint64) bool { return c.InteropTime != nil && timestamp >= *c.InteropTime } +func (c *Config) IsCel2(timestamp uint64) bool { + return c.Cel2Time != nil && timestamp >= *c.Cel2Time +} + func (c *Config) IsRegolithActivationBlock(l2BlockTime uint64) bool { return c.IsRegolith(l2BlockTime) && l2BlockTime >= c.BlockTime && @@ -755,6 +760,7 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) { // only print in config if set at all ctx = append(ctx, "pectra_blob_schedule_time", fmtForkTimeOrUnset(c.PectraBlobScheduleTime)) } + ctx = append(ctx, "cel2_time", fmtForkTimeOrUnset(c.Cel2Time)) log.Info("Rollup Config", ctx...) } diff --git a/op-node/service.go b/op-node/service.go index 965be47a40d88..99803b4525efa 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/urfave/cli/v2" altda "github.com/ethereum-optimism/optimism/op-alt-da" @@ -192,12 +193,13 @@ func NewConfigPersistence(ctx *cli.Context) node.ConfigPersistence { func NewDriverConfig(ctx *cli.Context) *driver.Config { return &driver.Config{ - VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name), - SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name), - SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name), - SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name), - SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name), - RecoverMode: ctx.Bool(flags.SequencerRecoverMode.Name), + VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name), + SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name), + SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name), + SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name), + SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name), + RecoverMode: ctx.Bool(flags.SequencerRecoverMode.Name), + SequencerUseFinalized: ctx.Bool(flags.SequencerUseFinalizedL1Flag.Name), } } @@ -211,6 +213,7 @@ func NewRollupConfigFromCLI(log log.Logger, ctx *cli.Context) (*rollup.Config, e if err != nil { return nil, err } + applyCeloHardforks(rollupConfig) applyOverrides(ctx, rollupConfig) return rollupConfig, nil } @@ -284,6 +287,31 @@ func applyOverrides(ctx *cli.Context, rollupConfig *rollup.Config) { } } +// applyCeloHardforks modifies the rollupConfig to apply Celo-specific hardforks. +// This code is a shortcut and the proper config should be added to the superchain registry. +// See https://github.com/celo-org/op-geth/issues/389 +func applyCeloHardforks(rollupConfig *rollup.Config) { + switch rollupConfig.L2ChainID.Uint64() { + case params.CeloMainnetChainID: + activationTime := params.CeloMainnetIsthmusTimestamp + rollupConfig.HoloceneTime = &activationTime + rollupConfig.IsthmusTime = &activationTime + rollupConfig.PectraBlobScheduleTime = &activationTime + case params.CeloAlfajoresChainID: + activationTime := params.AlfajoresIsthmusTimestamp + rollupConfig.HoloceneTime = &activationTime + rollupConfig.IsthmusTime = &activationTime + rollupConfig.PectraBlobScheduleTime = &activationTime + case params.CeloBaklavaChainID: + activationTime := params.BaklavaIsthmusTimestamp + rollupConfig.HoloceneTime = &activationTime + rollupConfig.IsthmusTime = &activationTime + rollupConfig.PectraBlobScheduleTime = &activationTime + default: + // No Celo hardforks for other chains, do nothing. + } +} + func NewSyncConfig(ctx *cli.Context, log log.Logger) (*sync.Config, error) { if ctx.IsSet(flags.L2EngineSyncEnabled.Name) && ctx.IsSet(flags.SyncModeFlag.Name) { return nil, errors.New("cannot set both --l2.engine-sync and --syncmode at the same time") diff --git a/op-program/client/l2/engineapi/block_processor.go b/op-program/client/l2/engineapi/block_processor.go index 03fe63475ea8b..5fe72c3639cfa 100644 --- a/op-program/client/l2/engineapi/block_processor.go +++ b/op-program/client/l2/engineapi/block_processor.go @@ -88,7 +88,8 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (* mkEVM := func() *vm.EVM { // Unfortunately this is not part of any Geth environment setup, // we just have to apply it, like how the Geth block-builder worker does. - context := core.NewEVMBlockContext(header, provider, nil, provider.Config(), statedb) + feeCurrencyContext := core.GetFeeCurrencyContext(header, provider.Config(), statedb) + context := core.NewEVMBlockContext(header, provider, nil, provider.Config(), statedb, feeCurrencyContext) // NOTE: Unlikely to be needed for the beacon block root, but we setup any precompile overrides anyways for forwards-compatibility var precompileOverrides vm.PrecompileOverrides if vmConfig := provider.GetVMConfig(); vmConfig != nil && vmConfig.PrecompileOverrides != nil { @@ -145,7 +146,8 @@ func (b *BlockProcessor) CheckTxWithinGasLimit(tx *types.Transaction) error { func (b *BlockProcessor) AddTx(tx *types.Transaction) (*types.Receipt, error) { txIndex := len(b.transactions) b.state.SetTxContext(tx.Hash(), txIndex) - receipt, err := core.ApplyTransaction(b.evm, b.gasPool, b.state, b.header, tx, &b.header.GasUsed) + feeCurrencyContext := core.GetFeeCurrencyContext(b.header, b.evm.ChainConfig(), b.state) + receipt, err := core.ApplyTransaction(b.evm, b.gasPool, b.state, b.header, tx, &b.header.GasUsed, feeCurrencyContext) if err != nil { return nil, fmt.Errorf("failed to apply transaction to L2 block (tx %d): %w", txIndex, err) } diff --git a/op-program/client/l2/fast_canon.go b/op-program/client/l2/fast_canon.go index 053c7c1a88de6..d2477bd8d3a39 100644 --- a/op-program/client/l2/fast_canon.go +++ b/op-program/client/l2/fast_canon.go @@ -116,7 +116,7 @@ func (o *FastCanonicalBlockHeaderOracle) getHistoricalBlockHash(head *types.Head // for safety. But it shouldn't be required since we only read from state statedb.MakeSinglethreaded() - context := core.NewEVMBlockContext(head, o.ctx, nil, o.config, statedb) + context := core.NewEVMBlockContext(head, o.ctx, nil, o.config, statedb, &common.FeeCurrencyContext{}) vmenv := vm.NewEVM(context, statedb, o.config, vm.Config{}) var caller common.Address // can be anything as long as it's not the system contract gas := uint64(1000000) diff --git a/op-service/predeploys/addresses.go b/op-service/predeploys/addresses.go index e461f0c86faaf..80915da0b3215 100644 --- a/op-service/predeploys/addresses.go +++ b/op-service/predeploys/addresses.go @@ -44,6 +44,16 @@ const ( EntryPoint_v060 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" SenderCreator_v070 = "0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C" EntryPoint_v070 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + + // Celo + CeloRegistry = "0x000000000000000000000000000000000000ce10" + GoldToken = "0x471ece3750da237f93b8e339c536989b8978a438" + FeeHandler = "0xcd437749e43a154c07f3553504c68fbfd56b8778" + MentoFeeHandlerSeller = "0x4efa274b7e33476c961065000d58ee09f7921a74" + UniswapFeeHandlerSeller = "0xd3aee28548dbb65df03981f0dc0713bfcbd10a97" + SortedOracles = "0xefb84935239dacdecf7c5ba76d8de40b077b7b33" + AddressSortedLinkedListWithMedian = "0xED477A99035d0c1e11369F1D7A4e587893cc002B" + FeeCurrency = "0x4200000000000000000000000000000000001022" ) var ( @@ -88,6 +98,18 @@ var ( Predeploys = make(map[string]*Predeploy) PredeploysByAddress = make(map[common.Address]*Predeploy) + + // Celo + CeloRegistryAddr = common.HexToAddress(CeloRegistry) + GoldTokenAddr = common.HexToAddress(GoldToken) + FeeHandlerAddr = common.HexToAddress(FeeHandler) + MentoFeeHandlerSellerAddr = common.HexToAddress(MentoFeeHandlerSeller) + UniswapFeeHandlerSellerAddr = common.HexToAddress(UniswapFeeHandlerSeller) + SortedOraclesAddr = common.HexToAddress(SortedOracles) + AddressSortedLinkedListWithMedianAddr = common.HexToAddress(AddressSortedLinkedListWithMedian) + FeeCurrencyAddr = common.HexToAddress(FeeCurrency) + + CeloPredeploys = make(map[string]*Predeploy) ) func init() { @@ -175,6 +197,19 @@ func init() { ProxyDisabled: true, } + // Celo + CeloPredeploys["CeloRegistry"] = &Predeploy{Address: CeloRegistryAddr} + CeloPredeploys["GoldToken"] = &Predeploy{Address: GoldTokenAddr} + CeloPredeploys["FeeHandler"] = &Predeploy{Address: FeeHandlerAddr} + CeloPredeploys["MentoFeeHandlerSeller"] = &Predeploy{Address: MentoFeeHandlerSellerAddr} + CeloPredeploys["UniswapFeeHandlerSeller"] = &Predeploy{Address: UniswapFeeHandlerSellerAddr} + CeloPredeploys["SortedOracles"] = &Predeploy{Address: SortedOraclesAddr} + CeloPredeploys["AddressSortedLinkedListWithMedian"] = &Predeploy{Address: AddressSortedLinkedListWithMedianAddr} + CeloPredeploys["FeeCurrency"] = &Predeploy{Address: FeeCurrencyAddr} + for key, predeploy := range CeloPredeploys { + Predeploys[key] = predeploy + } + for _, predeploy := range Predeploys { PredeploysByAddress[predeploy.Address] = predeploy } diff --git a/op-service/testutils/fake_txmgr.go b/op-service/testutils/fake_txmgr.go new file mode 100644 index 0000000000000..828ba18426887 --- /dev/null +++ b/op-service/testutils/fake_txmgr.go @@ -0,0 +1,77 @@ +package testutils + +import ( + "context" + "errors" + "math/big" + + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +// FakeTxMgr is a fake txmgr.TxManager for testing the op-batcher. +type FakeTxMgr struct { + log log.Logger + FromAddr common.Address + Closed bool + Nonce uint64 + errorEveryNthSend uint // 0 means never error, 1 means every send errors, etc. + sendCount uint +} + +var _ txmgr.TxManager = (*FakeTxMgr)(nil) + +func NewFakeTxMgr(log log.Logger, from common.Address) *FakeTxMgr { + return &FakeTxMgr{ + log: log, + FromAddr: from, + } +} + +func (f *FakeTxMgr) ErrorEveryNthSend(n uint) { + f.errorEveryNthSend = n +} + +func (f *FakeTxMgr) Send(ctx context.Context, candidate txmgr.TxCandidate) (*types.Receipt, error) { + // We currently only use the FakeTxMgr to test the op-batcher, which only uses SendAsync. + // Send makes it harder to track failures and nonce management (prob need to add mutex, etc). + // We can implement this if/when its needed. + panic("FakeTxMgr does not implement Send") +} +func (f *FakeTxMgr) SendAsync(ctx context.Context, candidate txmgr.TxCandidate, ch chan txmgr.SendResponse) { + f.log.Debug("SendingAsync tx", "nonce", f.Nonce) + f.sendCount++ + var sendResponse txmgr.SendResponse + if f.errorEveryNthSend != 0 && f.sendCount%f.errorEveryNthSend == 0 { + sendResponse.Err = errors.New("errorEveryNthSend") + } else { + sendResponse.Receipt = &types.Receipt{ + BlockHash: common.Hash{}, + BlockNumber: big.NewInt(0), + } + sendResponse.Nonce = f.Nonce + f.Nonce++ + } + ch <- sendResponse +} +func (f *FakeTxMgr) From() common.Address { + return f.FromAddr +} +func (f *FakeTxMgr) BlockNumber(ctx context.Context) (uint64, error) { + return 0, nil +} +func (f *FakeTxMgr) API() rpc.API { + return rpc.API{} +} +func (f *FakeTxMgr) Close() { + f.Closed = true +} +func (f *FakeTxMgr) IsClosed() bool { + return f.Closed +} +func (f *FakeTxMgr) SuggestGasPriceCaps(ctx context.Context) (tipCap *big.Int, baseFee *big.Int, blobBaseFee *big.Int, err error) { + return nil, nil, nil, nil +} diff --git a/ops/scripts/celo-update-op-geth.py b/ops/scripts/celo-update-op-geth.py new file mode 100755 index 0000000000000..ae7b20f6ff985 --- /dev/null +++ b/ops/scripts/celo-update-op-geth.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +import subprocess +import os +import sys + +def main(): + if len(sys.argv) < 2: + print('Please provide an op-geth commit or branch name') + sys.exit(1) + + version = sys.argv[1] + for project in ('.',): + print(f'Updating {project}...') + update_mod(project, version) + + +def update_mod(project, version): + print('Replacing...') + subprocess.run([ + 'go', + 'mod', + 'edit', + '-replace', + f'github.com/ethereum/go-ethereum=github.com/celo-org/op-geth@{version}' + ], cwd=os.path.join(project), check=True) + print('Tidying...') + subprocess.run([ + 'go', + 'mod', + 'tidy' + ], cwd=os.path.join(project), check=True) + + +if __name__ == '__main__': + main() diff --git a/packages/contracts-bedrock/scripts/celo/gen_l2_token_cmds.sh b/packages/contracts-bedrock/scripts/celo/gen_l2_token_cmds.sh new file mode 100755 index 0000000000000..4daad0394f3c7 --- /dev/null +++ b/packages/contracts-bedrock/scripts/celo/gen_l2_token_cmds.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +if [ -z "$1" ]; then + echo "Create commands to deploy L2 tokens for bridging from Ethereum" + echo + echo "Usage: $(basename "$0") [ ...]" + exit 1 +fi + +echo +echo "Commands to deploy L2 tokens for bridging from Ethereum:" +echo + +ETH_RPC_URL=https://ethereum-rpc.publicnode.com +export ETH_RPC_URL + +for address in "$@"; do + symbol=$(cast call "$address" "symbol() returns (string)" --json | jq -r '.[0]') + name=$(cast call "$address" "name() returns (string)" --json | jq -r '.[0]') + decimals=$(cast call "$address" "decimals() returns (uint256)" --json | jq -r '.[0]') + echo "cast send 0x4200000000000000000000000000000000000012 \"createOptimismMintableERC20WithDecimals(address,string,string,uint8)\" $address \"$name (Celo native bridge)\" \"$symbol\" $decimals --private-key \$PRIVKEY" +done diff --git a/packages/contracts-bedrock/scripts/celo/verify_token_blockscout.sh b/packages/contracts-bedrock/scripts/celo/verify_token_blockscout.sh new file mode 100755 index 0000000000000..e43cc053a6d0d --- /dev/null +++ b/packages/contracts-bedrock/scripts/celo/verify_token_blockscout.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +if [ -z "$*" ]; then + echo "Verify L2 bridged tokens on Blockscout" + echo + echo "Usage: $0 [ ...]" + exit 1 +fi + +for BRIDGED_TOKEN in "$@"; do + forge verify-contract \ + --verifier=blockscout \ + --verifier-url=https://celo.blockscout.com/api/ \ + "$BRIDGED_TOKEN" \ + src/universal/OptimismMintableERC20.sol:OptimismMintableERC20 +done diff --git a/packages/contracts-bedrock/scripts/celo/verify_token_celoscan.sh b/packages/contracts-bedrock/scripts/celo/verify_token_celoscan.sh new file mode 100755 index 0000000000000..9e1e23a078b4b --- /dev/null +++ b/packages/contracts-bedrock/scripts/celo/verify_token_celoscan.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +if [ -z "$*" ]; then + echo "Verify L2 bridged tokens on Celoscan" + echo + echo "Usage: $0 [ ...]" + exit 1 +fi + +for BRIDGED_TOKEN in "$@"; do + # cast_call
+ function cast_call() { + cast call --json --rpc-url https://forno.celo.org "$1" "$2" | jq -r ".[0]" + } + + REMOTE_TOKEN=$(cast_call "$BRIDGED_TOKEN" "REMOTE_TOKEN()(address)") + NAME=$(cast_call "$BRIDGED_TOKEN" "name()(string)") + SYMBOL=$(cast_call "$BRIDGED_TOKEN" "symbol()(string)") + DECIMALS=$(cast_call "$BRIDGED_TOKEN" "decimals()(uint8)") + + CONSTRUCTOR_ARGS=$(cast abi-encode "constructor(address,address,string,string,uint8)" 0x4200000000000000000000000000000000000010 "$REMOTE_TOKEN" "$NAME" "$SYMBOL" "$DECIMALS") + CONSTRUCTOR_ARGS=${CONSTRUCTOR_ARGS#0x} + + forge verify-contract \ + --verifier=etherscan \ + --verifier-url=https://api.celoscan.io/api/ \ + --constructor-args="$CONSTRUCTOR_ARGS" \ + --skip-is-verified-check \ + "$BRIDGED_TOKEN" \ + src/universal/OptimismMintableERC20.sol:OptimismMintableERC20 +done diff --git a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh index 18f698f911178..b0c1c37c1a6a7 100755 --- a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh +++ b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -euo pipefail +# Celo: contract changes are handled differently, skip semver check for now. +exit 0 + # Grab the directory of the contracts-bedrock package. SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index 4dbbf9bd31d56..a61180ea1a240 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -32,6 +32,41 @@ var excludeContracts = []string{ // TODO: Interfaces that need to be fixed "IInitializable", "IOptimismMintableERC20", "ILegacyMintableERC20", "KontrolCheatsBase", "IResolvedDelegateProxy", + + // Temporarily excluded, differences seem harmless + "IERC20Upgradeable", + + // Celo + "IExchange", + "IEscrow", + "IAccounts", + "IOracle", + "ICeloToken", + "ICeloRegistry", + "IStableTokenMento", + "ILockedGold", + "IBreakerBox", + "ISortedOracles", + "IElection", + "IStableTokenV2", + "IStableToken", + "IMetaTransactionWallet", + "IAttestations", + "IOdisPayments", + "IUniswapV2RouterMin", + "IFeeHandlerSeller", + "IRandom", + "IFreezer", + "IValidators", + "IReserve", + "IFeeCurrencyDirectory", + "IFeeHandler", + "ICeloVersionedContract", + "IMetaTransactionWalletDeployer", + "IGovernance", + "IReleaseGold", + "IUniswapV2FactoryMin", + "IFederatedAttestations", } type ContractDefinition struct { diff --git a/packages/contracts-bedrock/scripts/contract_map.sh b/packages/contracts-bedrock/scripts/contract_map.sh new file mode 100755 index 0000000000000..caa8a716a13ba --- /dev/null +++ b/packages/contracts-bedrock/scripts/contract_map.sh @@ -0,0 +1,140 @@ +#!/bin/bash +set -o pipefail + +L1_URL="${1:?Must specify L1 RPC URL}" +L1_ADDRESSES="${2:?Must specify L1 addresses json}" +OUTPUT="${3:-relations}" + +addresses=$(jq -r '.[]' "$L1_ADDRESSES") + +contract_addresses=() +processed_addresses=() +dots=() + +while IFS= read -r address; do + contract_addresses+=("$address") +done <<< "$addresses" + +address_exists() { + local addr="$1" + for processed_addr in "${processed_addresses[@]}"; do + if [[ "$processed_addr" == "$addr" ]]; then + return 0 + fi + done + return 1 +} + +check_admin() { + local addr="$1" + admin=$(cast adm "$addr" --rpc-url "$L1_URL") + + if [[ $? == 0 && "$admin" != "0x0000000000000000000000000000000000000000" ]]; then + contract_addresses+=( "$admin" ) + echo " -> Admin: $admin" + add_relation "$admin" "$addr" "admin" + fi + + return 0 +} + +check_owners() { + local addr="$1" + + # suppressing stderr (and unset -e) as failure is expected when this abi does not exist + # getOwners defined in OwnerManager on GnosisSafe contract + if owners=$(cast call "$addr" --rpc-url "$L1_URL" 'getOwners()(address[])' 2>/dev/null) ; then + # trim pseudo json output + tr=$(echo "$owners" | tr -d '[],') + owners_arr=( "$tr" ) + + # Iterate over the values + for owner in "${owners_arr[@]}"; do + echo " -> Multisig Owner: $owner" + add_relation "$owner" "$addr" "multisig_owner" + contract_addresses+=( "$owner" ) + done + fi + + # owner defined in Ownable on OpenZeppelin abstract contract + if owner=$(cast call "$addr" --rpc-url "$L1_URL" 'owner()(address)' 2>/dev/null) ; then + echo " -> Owner: $owner" + add_relation "$owner" "$addr" "owner" + contract_addresses+=( "$owner" ) + fi + + return 0 +} + +check_implementation() { + local addr="$1" + + impl=$(cast implementation "$addr" --rpc-url "$L1_URL") + + if [[ $? == 0 && "$impl" != "0x0000000000000000000000000000000000000000" ]]; then + contract_addresses+=( "$impl" ) + echo " -> Impl: $impl" + add_relation "$addr" "$impl" "proxies" + fi + + return 0 +} + +get_name() { + local addr + local result + + addr=$(cast to-check-sum-address "$1") + result=$(jq -r "to_entries | map(select(.value == \"$addr\")) | .[0].key" "$L1_ADDRESSES") + + if [[ ${#result} -gt 4 ]]; then + printf "%s\n(%s)" "$addr" "$result" + else + echo "$addr" + fi +} + +add_relation() { + local source="$1" + local destination="$2" + local label="$3" + + local source_name + local destination_name + + source_name=$(get_name "$source") + destination_name=$(get_name "$destination") + + dots+=("\"$source_name\" -> \"$destination_name\"[label = \"$label\"];") +} + +# while loop to allow for modification of the array during iteration +i=0 +while [ $i -lt ${#contract_addresses[@]} ]; do + address="$(cast to-check-sum-address "${contract_addresses[$i]}")" + if address_exists "$address"; then + # already processed this address, skip iteration + i=$((i + 1)) + continue + fi + + echo "Checking $address" + + check_admin "$address" + check_owners "$address" + check_implementation "$address" + + processed_addresses+=("$address") + i=$((i + 1)) +done + +# write out chart +echo "digraph {" > "$OUTPUT".dot +echo "rankdir=\"LR\";" >> "$OUTPUT".dot +for dot in "${dots[@]}"; do + echo "$dot" >> "$OUTPUT".dot +done +echo "}" >> "$OUTPUT".dot + +dot "$OUTPUT".dot -Tpng -o "$OUTPUT".png +open "$OUTPUT".png diff --git a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol index 5cd42db66f8b6..d291185d0dc46 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol @@ -92,6 +92,8 @@ contract DeployConfig is Script { bool public useInterop; bool public useUpgradedFork; + bool public deployCeloContracts; + function read(string memory _path) public { console.log("DeployConfig: reading file %s", _path); try vm.readFile(_path) returns (string memory data_) { @@ -178,6 +180,9 @@ contract DeployConfig is Script { useInterop = _readOr(_json, "$.useInterop", false); useUpgradedFork; + + // Celo specific config + deployCeloContracts = _readOr(_json, "$.deployCeloContracts", false); } function fork() public view returns (Fork fork_) { @@ -234,6 +239,11 @@ contract DeployConfig is Script { fundDevAccounts = _fundDevAccounts; } + /// @notice Allow the `deployCeloContracts` config to be overridden. + function setDeployCeloContracts(bool _deployCeloContracts) public { + deployCeloContracts = _deployCeloContracts; + } + /// @notice Allow the `useUpgradedFork` config to be overridden in testing environments /// @dev When true, the forked system WILL be upgraded in setUp(). /// When false, the forked system WILL NOT be upgraded in setUp(). diff --git a/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh b/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh new file mode 100755 index 0000000000000..ad59b3c638022 --- /dev/null +++ b/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash + +# This script is used to generate the getting-started.json configuration file +# used in the Getting Started quickstart guide on the docs site. Avoids the +# need to have the getting-started.json committed to the repo since it's an +# invalid JSON file when not filled in, which is annoying. + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +CONTRACTS_BASE=$(dirname "$(dirname "$SCRIPT_DIR")") +reqenv() { + if [ -z "${!1}" ]; then + echo "Error: environment variable '$1' is undefined" + exit 1 + fi +} + +append_with_default() { + json_key="$1" + env_var_name="$2" + default_value="$3" + var_value="${!env_var_name}" + + if [ -z "$var_value" ] || [ "$var_value" == "None" ]; then + var_value="$default_value" + fi + + echo " \"$json_key\": \"$var_value\"," >> tmp_config.json +} + +# Check required environment variables +reqenv "DEPLOYMENT_CONTEXT" +reqenv "GS_ADMIN_ADDRESS" +reqenv "GS_BATCHER_ADDRESS" +reqenv "GS_PROPOSER_ADDRESS" +reqenv "GS_SEQUENCER_ADDRESS" +reqenv "L1_RPC_URL" +reqenv "L1_CHAIN_ID" +reqenv "L2_CHAIN_ID" +reqenv "L1_BLOCK_TIME" +reqenv "L2_BLOCK_TIME" +reqenv "FUNDS_DEV_ACCOUNTS" +reqenv "USE_ALTDA" +reqenv "DEPLOY_CELO_CONTRACTS" +reqenv "USE_CUSTOM_GAS_TOKEN" +reqenv "CUSTOM_GAS_TOKEN_ADDRESS" + +# Get the finalized block timestamp and hash +block=$(cast block finalized --rpc-url "$L1_RPC_URL") +timestamp=$(echo "$block" | awk '/timestamp/ { print $2 }') +blockhash=$(echo "$block" | awk '/hash/ { print $2 }') +batchInboxAddressSuffix=$(printf "%0$((37 - ${#L2_CHAIN_ID}))d" 0)$L2_CHAIN_ID +batchInboxAddress=0xfff$batchInboxAddressSuffix + +# Start generating the config file in a temporary file + +cat << EOL > tmp_config.json + { + "l1StartingBlockTag": "$blockhash", + + "l1ChainID": $L1_CHAIN_ID, + "l2ChainID": $L2_CHAIN_ID, + "l2BlockTime": $L2_BLOCK_TIME, + "l1BlockTime": $L1_BLOCK_TIME, + + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + + "p2pSequencerAddress": "$GS_SEQUENCER_ADDRESS", + "batchInboxAddress": "$batchInboxAddress", + "batchSenderAddress": "$GS_BATCHER_ADDRESS", + + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": $timestamp, + + "l2OutputOracleProposer": "$GS_PROPOSER_ADDRESS", + "l2OutputOracleChallenger": "$GS_ADMIN_ADDRESS", + + "finalizationPeriodSeconds": 12, + + "proxyAdminOwner": "$GS_ADMIN_ADDRESS", + "baseFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "l1FeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "sequencerFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "finalSystemOwner": "$GS_ADMIN_ADDRESS", + "superchainConfigGuardian": "$GS_ADMIN_ADDRESS", + + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + + "gasPriceOracleOverhead": 0, + "gasPriceOracleScalar": 1000000, + + "deployCeloContracts": $DEPLOY_CELO_CONTRACTS, + + "enableGovernance": $ENABLE_GOVERNANCE, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "$GS_ADMIN_ADDRESS", + + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + + "eip1559Denominator": 400, + "eip1559DenominatorCanyon": 400, + "eip1559Elasticity": 5, + "eip1559BaseFeeFloor": 5000000000, +EOL + +# Append conditional environment variables with their corresponding default values +# Activate granite fork +if [ -n "${GRANITE_TIME_OFFSET}" ]; then + append_with_default "l2GenesisGraniteTimeOffset" "GRANITE_TIME_OFFSET" "0x0" +fi +# Activate holocene fork +if [ -n "${HOLOCENE_TIME_OFFSET}" ]; then + append_with_default "l2GenesisHoloceneTimeOffset" "HOLOCENE_TIME_OFFSET" "0x0" +fi + +# Activate the interop fork +if [ -n "${INTEROP_TIME_OFFSET}" ]; then + append_with_default "l2GenesisInteropTimeOffset" "INTEROP_TIME_OFFSET" "0x0" +fi + +# Already forked updates +append_with_default "l2GenesisFjordTimeOffset" "FJORD_TIME_OFFSET" "0x0" +append_with_default "l2GenesisRegolithTimeOffset" "REGOLITH_TIME_OFFSET" "0x0" +append_with_default "l2GenesisEcotoneTimeOffset" "ECOTONE_TIME_OFFSET" "0x0" +append_with_default "l2GenesisDeltaTimeOffset" "DELTA_TIME_OFFSET" "0x0" +append_with_default "l2GenesisCanyonTimeOffset" "CANYON_TIME_OFFSET" "0x0" +append_with_default "l2GenesisGraniteTimeOffset" "GRANITE_TIME_OFFSET" "0x0" + +# Continue generating the config file +cat << EOL >> tmp_config.json + "systemConfigStartBlock": 0, + + "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + + "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", + "faultGameMaxDepth": 44, + "faultGameClockExtension": 0, + "faultGameMaxClockDuration": 1200, + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "faultGameSplitDepth": 14, + "faultGameWithdrawalDelay": 600, + + "preimageOracleMinProposalSize": 1800000, + "preimageOracleChallengePeriod": 300, + + "fundDevAccounts": $FUNDS_DEV_ACCOUNTS, + "useFaultProofs": false, + "proofMaturityDelaySeconds": 604800, + "disputeGameFinalityDelaySeconds": 302400, + "respectedGameType": 0, + + "useAltDA": $USE_ALTDA, + "daCommitmentType": "GenericCommitment", + "daChallengeWindow": 1, + "daResolveWindow": 1, + + "useCustomGasToken": $USE_CUSTOM_GAS_TOKEN, + "customGasTokenAddress": "$CUSTOM_GAS_TOKEN_ADDRESS" +} +EOL + +# Write the final config file +mv tmp_config.json "$CONTRACTS_BASE/deploy-config/$DEPLOYMENT_CONTEXT.json" + +echo "Wrote config file to $CONTRACTS_BASE/deploy-config/$DEPLOYMENT_CONTEXT.json" diff --git a/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh b/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh new file mode 100755 index 0000000000000..2da9ac5039342 --- /dev/null +++ b/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# This script is used to generate the getting-started.json configuration file +# used in the Getting Started quickstart guide on the docs site. Avoids the +# need to have the getting-started.json committed to the repo since it's an +# invalid JSON file when not filled in, which is annoying. + +reqenv() { + if [ -z "${!1}" ]; then + echo "Error: environment variable '$1' is undefined" + exit 1 + fi +} + +# Check required environment variables +reqenv "GS_ADMIN_ADDRESS" +reqenv "GS_BATCHER_ADDRESS" +reqenv "GS_PROPOSER_ADDRESS" +reqenv "GS_SEQUENCER_ADDRESS" +reqenv "L1_RPC_URL" + +# Get the finalized block timestamp and hash +block=$(cast block finalized --rpc-url "$L1_RPC_URL") +timestamp=$(echo "$block" | awk '/timestamp/ { print $2 }') +blockhash=$(echo "$block" | awk '/hash/ { print $2 }') + +# Generate the config file +config=$(cat << EOL +{ + "l1StartingBlockTag": "$blockhash", + + "l1ChainID": $L1_CHAIN_ID, + "l2ChainID": $L2_CHAIN_ID, + "l2BlockTime": 2, + "l1BlockTime": 12, + + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + + "p2pSequencerAddress": "$GS_SEQUENCER_ADDRESS", + "batchInboxAddress": "0xff00000000000000000000000000000000042069", + "batchSenderAddress": "$GS_BATCHER_ADDRESS", + + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": $timestamp, + + "l2OutputOracleProposer": "$GS_PROPOSER_ADDRESS", + "l2OutputOracleChallenger": "$GS_ADMIN_ADDRESS", + + "finalizationPeriodSeconds": 12, + + "proxyAdminOwner": "$GS_ADMIN_ADDRESS", + "baseFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "l1FeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "sequencerFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "finalSystemOwner": "$GS_ADMIN_ADDRESS", + "superchainConfigGuardian": "$GS_ADMIN_ADDRESS", + + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + + "gasPriceOracleOverhead": 2100, + "gasPriceOracleScalar": 1000000, + + "enableGovernance": true, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "$GS_ADMIN_ADDRESS", + + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "l2GenesisRegolithTimeOffset": "0x0", + + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250, + "eip1559Elasticity": 6, + + "l2GenesisDeltaTimeOffset": null, + "l2GenesisCanyonTimeOffset": "0x0", + + "systemConfigStartBlock": 0, + + "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + + "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", + "faultGameMaxDepth": 44, + "faultGameMaxDuration": 1200, + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "faultGameSplitDepth": 14, + + "preimageOracleMinProposalSize": 1800000, + "preimageOracleChallengePeriod": 86400 +} +EOL +) + +# Write the config file +echo "$config" > deploy-config/"$DEPLOYMENT_CONTEXT".json diff --git a/packages/contracts-bedrock/snapshots/abi/CalledByVm.json b/packages/contracts-bedrock/snapshots/abi/CalledByVm.json new file mode 100644 index 0000000000000..0637a088a01e8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/CalledByVm.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json b/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json new file mode 100644 index 0000000000000..1f095b33d3bb0 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json @@ -0,0 +1,247 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + } + ], + "name": "getAddressFor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + } + ], + "name": "getAddressForOrDie", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + } + ], + "name": "getAddressForString", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + } + ], + "name": "getAddressForStringOrDie", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "identifierHashes", + "type": "bytes32[]" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "isOneOf", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "registry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setAddressFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "identifier", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "RegistryUpdated", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json b/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json new file mode 100644 index 0000000000000..4bdf6bbac31f3 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json @@ -0,0 +1,354 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json b/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json new file mode 100644 index 0000000000000..4c4ccb64968e8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json @@ -0,0 +1,246 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "currencies", + "outputs": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrencies", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getCurrencyConfig", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "internalType": "struct IFeeCurrencyDirectory.CurrencyConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "removeCurrencies", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "name": "setCurrencyConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/FeeHandler.json b/packages/contracts-bedrock/snapshots/abi/FeeHandler.json new file mode 100644 index 0000000000000..a584a53f686d0 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/FeeHandler.json @@ -0,0 +1,813 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [], + "name": "FIXED1_UINT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_BURN", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "activateToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "addToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "burnCelo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "burnFraction", + "outputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "celoToBeBurned", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountToBurn", + "type": "uint256" + } + ], + "name": "dailySellLimitHit", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "deactivateToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "distribute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "distributeAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeBeneficiary", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getPastBurnForToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenCurrentDaySellLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenDailySellLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenHandler", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenMaxSlippage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenToDistribute", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "handle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "handleAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "newFeeBeneficiary", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newBurnFraction", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "handlers", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newLimits", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "newMaxSlippages", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastLimitDay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "removeToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "sell", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fraction", + "type": "uint256" + } + ], + "name": "setBurnFraction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newLimit", + "type": "uint256" + } + ], + "name": "setDailySellLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "setFeeBeneficiary", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "setHandler", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMax", + "type": "uint256" + } + ], + "name": "setMaxSplippage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "fraction", + "type": "uint256" + } + ], + "name": "BurnFractionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "burning", + "type": "uint256" + } + ], + "name": "DailyLimitHit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLimit", + "type": "uint256" + } + ], + "name": "DailyLimitSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DailySellLimitUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newBeneficiary", + "type": "address" + } + ], + "name": "FeeBeneficiarySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "MaxSlippageSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SoldAndBurnedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "TokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "TokenRemoved", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/Freezable.json b/packages/contracts-bedrock/snapshots/abi/Freezable.json new file mode 100644 index 0000000000000..dc8fa7e0f21ca --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/Freezable.json @@ -0,0 +1,93 @@ +[ + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/GoldToken.json b/packages/contracts-bedrock/snapshots/abi/GoldToken.json new file mode 100644 index 0000000000000..a52ef10b6a528 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/GoldToken.json @@ -0,0 +1,552 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "circulatingSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getBurnedAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "increaseSupply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "transferWithComment", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "TransferComment", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/Initializable.json b/packages/contracts-bedrock/snapshots/abi/Initializable.json new file mode 100644 index 0000000000000..aeef476ab67fd --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/Initializable.json @@ -0,0 +1,26 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "testingDeployment", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json b/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json new file mode 100644 index 0000000000000..7190d528858e5 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json @@ -0,0 +1,350 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "midPriceNumerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "midPriceDenominator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "calculateMinAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "tokenAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newMininumReports", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "minimumReports", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sellTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "buyTokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "sell", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMininumReports", + "type": "uint256" + } + ], + "name": "setMinimumReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "minimumReports", + "type": "uint256" + } + ], + "name": "MinimumReportsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "soldTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "boughtTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokenSold", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json b/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json new file mode 100644 index 0000000000000..f56f9b579aa57 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json @@ -0,0 +1,249 @@ +[ + { + "inputs": [], + "name": "DENOMINATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "expired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isOldestReportExpired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "medianTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "numRates", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "numerators", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + } + ], + "name": "setMedianRate", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "setMedianTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "setMedianTimestampToNow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "rate", + "type": "uint256" + } + ], + "name": "setNumRates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "setOldestReportExpired", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json index 5a5763c73962b..57523467d292e 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json @@ -180,6 +180,90 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "recipients", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "decimals", diff --git a/packages/contracts-bedrock/snapshots/abi/SortedOracles.json b/packages/contracts-bedrock/snapshots/abi/SortedOracles.json new file mode 100644 index 0000000000000..12a253c5c08be --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/SortedOracles.json @@ -0,0 +1,832 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "addOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "breakerBox", + "outputs": [ + { + "internalType": "contract IBreakerBox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "deleteEquivalentToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "equivalentTokens", + "outputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getEquivalentToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getOracles", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getRates", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "enum SortedLinkedListWithMedian.MedianRelation[]", + "name": "", + "type": "uint8[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTimestamps", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "enum SortedLinkedListWithMedian.MedianRelation[]", + "name": "", + "type": "uint8[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTokenReportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isOldestReportExpired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOracle", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRateWithoutEquivalentMapping", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "numRates", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "numTimestamps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "oracles", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "n", + "type": "uint256" + } + ], + "name": "removeExpiredReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "removeOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "lesserKey", + "type": "address" + }, + { + "internalType": "address", + "name": "greaterKey", + "type": "address" + } + ], + "name": "report", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IBreakerBox", + "name": "newBreakerBox", + "type": "address" + } + ], + "name": "setBreakerBox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "equivalentToken", + "type": "address" + } + ], + "name": "setEquivalentToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "setReportExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "setTokenReportExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenReportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newBreakerBox", + "type": "address" + } + ], + "name": "BreakerBoxUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "equivalentToken", + "type": "address" + } + ], + "name": "EquivalentTokenSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "MedianUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "OracleAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "OracleRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "OracleReportRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "OracleReported", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "reportExpiry", + "type": "uint256" + } + ], + "name": "ReportExpirySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportExpiry", + "type": "uint256" + } + ], + "name": "TokenReportExpirySet", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json b/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json new file mode 100644 index 0000000000000..693b960cea99c --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json @@ -0,0 +1,742 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "disable", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "broker", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "gatewayFeeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gatewayFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "exchange", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "address[]", + "name": "initialBalanceAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "initialBalanceValues", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_broker", + "type": "address" + }, + { + "internalType": "address", + "name": "_validators", + "type": "address" + }, + { + "internalType": "address", + "name": "_exchange", + "type": "address" + } + ], + "name": "initializeV2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_broker", + "type": "address" + } + ], + "name": "setBroker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_exchange", + "type": "address" + } + ], + "name": "setExchange", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_validators", + "type": "address" + } + ], + "name": "setValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "transferWithComment", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "validators", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "broker", + "type": "address" + } + ], + "name": "BrokerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "name": "ExchangeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "TransferComment", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "validators", + "type": "address" + } + ], + "name": "ValidatorsUpdated", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json b/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json new file mode 100644 index 0000000000000..19c31c979af28 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json @@ -0,0 +1,481 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "midPriceNumerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "midPriceDenominator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "calculateMinAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getRoutersForToken", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "tokenAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newMininumReports", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "minimumReports", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "removeRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sellTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "buyTokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "sell", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMininumReports", + "type": "uint256" + } + ], + "name": "setMinimumReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "minimumReports", + "type": "uint256" + } + ], + "name": "MinimumReportsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "tokneAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "quote", + "type": "uint256" + } + ], + "name": "ReceivedQuote", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterAddressRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterAddressSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterUsed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "soldTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "boughtTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokenSold", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json b/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json new file mode 100644 index 0000000000000..dc8fa7e0f21ca --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json @@ -0,0 +1,93 @@ +[ + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 3801ba4bfacf7..d4e496e8acccb 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -204,12 +204,12 @@ "sourceCodeHash": "0x918965e52bbd358ac827ebe35998f5d8fa5ca77d8eb9ab8986b44181b9aaa48a" }, "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20": { - "initCodeHash": "0xc3289416829b252c830ad7d389a430986a7404df4fe0be37cb19e1c40907f047", - "sourceCodeHash": "0xf5e29dd5c750ea935c7281ec916ba5277f5610a0a9e984e53ae5d5245b3cf2f4" + "initCodeHash": "0x9de2243fb496091b0e434ccff90eb0f0fc97fd1be72a3896c96dec1c622fb26f", + "sourceCodeHash": "0xc0ac18b0b8b43bb1b6addaadb258ede2c6cd268515d57638c3d3deccc263ca8f" }, "src/universal/OptimismMintableERC20Factory.sol:OptimismMintableERC20Factory": { - "initCodeHash": "0xdb4d93a65cf9d3e3af77d3d62249f06580e80a0431542350f953f0a4041566b4", - "sourceCodeHash": "0xd1bad4408c26eb9c7b0ddcb088f0d4e3be73a43d899263ec8610f4d41a178ec7" + "initCodeHash": "0x01e6bde145bf9ecd19b2668c13f67682ab7de0a9df43c7d2f9009ac732755d51", + "sourceCodeHash": "0xc041f6b559c7fc9b08fb41afbd187401d2bcb7e164f9fe7bc7969e008cc76da8" }, "src/universal/StorageSetter.sol:StorageSetter": { "initCodeHash": "0x8831c079f7b7a52679e8a15e0ea14e30ea7bb4f93feed0fcd369942fe8c1f1ec", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json b/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json new file mode 100644 index 0000000000000..0637a088a01e8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json b/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json new file mode 100644 index 0000000000000..17b0df2bd7f9e --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json @@ -0,0 +1,23 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "32", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "mapping(bytes32 => address)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json new file mode 100644 index 0000000000000..418a98546cf77 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "0", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "1", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "3", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "4", + "type": "string" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json new file mode 100644 index 0000000000000..61ccdc5fb1511 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 1, + "slot": "0", + "type": "address" + }, + { + "bytes": "32", + "label": "currencies", + "offset": 0, + "slot": "1", + "type": "mapping(address => struct IFeeCurrencyDirectory.CurrencyConfig)" + }, + { + "bytes": "32", + "label": "currencyList", + "offset": 0, + "slot": "2", + "type": "address[]" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json b/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json new file mode 100644 index 0000000000000..468bb7dc38921 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json @@ -0,0 +1,72 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "_status", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "lastLimitDay", + "offset": 0, + "slot": "3", + "type": "uint256" + }, + { + "bytes": "32", + "label": "burnFraction", + "offset": 0, + "slot": "4", + "type": "struct FixidityLib.Fraction" + }, + { + "bytes": "20", + "label": "feeBeneficiary", + "offset": 0, + "slot": "5", + "type": "address" + }, + { + "bytes": "32", + "label": "celoToBeBurned", + "offset": 0, + "slot": "6", + "type": "uint256" + }, + { + "bytes": "32", + "label": "tokenStates", + "offset": 0, + "slot": "7", + "type": "mapping(address => struct FeeHandler.TokenState)" + }, + { + "bytes": "64", + "label": "activeTokens", + "offset": 0, + "slot": "8", + "type": "struct EnumerableSet.AddressSet" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json b/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json new file mode 100644 index 0000000000000..fb89bbc7e1ab3 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json b/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json new file mode 100644 index 0000000000000..67b349856d86c --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 1, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "totalSupply_", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "allowed", + "offset": 0, + "slot": "3", + "type": "mapping(address => mapping(address => uint256))" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json b/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json new file mode 100644 index 0000000000000..b29972a4de8eb --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json b/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json new file mode 100644 index 0000000000000..a66c44056e6d0 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "minimumReports", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json b/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json new file mode 100644 index 0000000000000..c44ef116af950 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "32", + "label": "numerators", + "offset": 0, + "slot": "0", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "medianTimestamp", + "offset": 0, + "slot": "1", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "numRates", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "expired", + "offset": 0, + "slot": "3", + "type": "mapping(address => bool)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json b/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json new file mode 100644 index 0000000000000..e1e5e1736aff6 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json @@ -0,0 +1,72 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "32", + "label": "rates", + "offset": 0, + "slot": "1", + "type": "mapping(address => struct SortedLinkedListWithMedian.List)" + }, + { + "bytes": "32", + "label": "timestamps", + "offset": 0, + "slot": "2", + "type": "mapping(address => struct SortedLinkedListWithMedian.List)" + }, + { + "bytes": "32", + "label": "isOracle", + "offset": 0, + "slot": "3", + "type": "mapping(address => mapping(address => bool))" + }, + { + "bytes": "32", + "label": "oracles", + "offset": 0, + "slot": "4", + "type": "mapping(address => address[])" + }, + { + "bytes": "32", + "label": "reportExpirySeconds", + "offset": 0, + "slot": "5", + "type": "uint256" + }, + { + "bytes": "32", + "label": "tokenReportExpirySeconds", + "offset": 0, + "slot": "6", + "type": "mapping(address => uint256)" + }, + { + "bytes": "20", + "label": "breakerBox", + "offset": 0, + "slot": "7", + "type": "contract IBreakerBox" + }, + { + "bytes": "32", + "label": "equivalentTokens", + "offset": 0, + "slot": "8", + "type": "mapping(address => struct SortedOracles.EquivalentToken)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json b/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json new file mode 100644 index 0000000000000..eea3cafe6e902 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json @@ -0,0 +1,142 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "54", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "55", + "type": "string" + }, + { + "bytes": "1440", + "label": "__gap", + "offset": 0, + "slot": "56", + "type": "uint256[45]" + }, + { + "bytes": "32", + "label": "_HASHED_NAME", + "offset": 0, + "slot": "101", + "type": "bytes32" + }, + { + "bytes": "32", + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "102", + "type": "bytes32" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "103", + "type": "uint256[50]" + }, + { + "bytes": "32", + "label": "_nonces", + "offset": 0, + "slot": "153", + "type": "mapping(address => struct CountersUpgradeable.Counter)" + }, + { + "bytes": "32", + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "154", + "type": "bytes32" + }, + { + "bytes": "1568", + "label": "__gap", + "offset": 0, + "slot": "155", + "type": "uint256[49]" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "204", + "type": "address" + }, + { + "bytes": "1568", + "label": "__gap", + "offset": 0, + "slot": "205", + "type": "uint256[49]" + }, + { + "bytes": "20", + "label": "validators", + "offset": 0, + "slot": "254", + "type": "address" + }, + { + "bytes": "20", + "label": "broker", + "offset": 0, + "slot": "255", + "type": "address" + }, + { + "bytes": "20", + "label": "exchange", + "offset": 0, + "slot": "256", + "type": "address" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json b/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json new file mode 100644 index 0000000000000..3688a3204dec1 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "minimumReports", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "routerAddresses", + "offset": 0, + "slot": "3", + "type": "mapping(address => struct EnumerableSet.AddressSet)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json b/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json new file mode 100644 index 0000000000000..fb89bbc7e1ab3 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol b/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol new file mode 100644 index 0000000000000..f67beaaf59a55 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +abstract contract AbstractFeeCurrency is ERC20 { + modifier onlyVm() { + require(msg.sender == address(0), "Only VM can call"); + _; + } + + function debitGasFees(address from, uint256 value) external onlyVm { + _burn(from, value); + } + + // New function signature, will be used when all fee currencies have migrated + function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) public onlyVm { + require(recipients.length == amounts.length, "Recipients and amounts must be the same length."); + + for (uint256 i = 0; i < recipients.length; i++) { + _mint(recipients[i], amounts[i]); + } + } + + // Old function signature for backwards compatibility + function creditGasFees( + address from, + address feeRecipient, + address, // gatewayFeeRecipient, unused + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256, // gatewayFee, unused + uint256 baseTxFee + ) + public + onlyVm + { + // Calling the new creditGasFees would make sense here, but that is not + // possible due to its calldata arguments. + _mint(from, refund); + _mint(feeRecipient, tipTxFee); + _mint(communityFund, baseTxFee); + } +} diff --git a/packages/contracts-bedrock/src/celo/CalledByVm.sol b/packages/contracts-bedrock/src/celo/CalledByVm.sol new file mode 100644 index 0000000000000..c3f6efe12072e --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CalledByVm.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +contract CalledByVm { + modifier onlyVm() { + require(msg.sender == address(0), "Only VM can call"); + _; + } +} diff --git a/packages/contracts-bedrock/src/celo/CeloPredeploys.sol b/packages/contracts-bedrock/src/celo/CeloPredeploys.sol new file mode 100644 index 0000000000000..2ca38a8457606 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloPredeploys.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title CeloPredeploys +/// @notice Contains constant addresses for protocol contracts that are pre-deployed to the L2 system. +library CeloPredeploys { + address internal constant CELO_REGISTRY = 0x000000000000000000000000000000000000ce10; + address internal constant GOLD_TOKEN = 0x471EcE3750Da237f93B8E339c536989b8978a438; + address internal constant FEE_HANDLER = 0xcD437749E43A154C07F3553504c68fBfD56B8778; + address internal constant MENTO_FEE_HANDLER_SELLER = 0x4eFa274B7e33476C961065000D58ee09F7921A74; + address internal constant UNISWAP_FEE_HANDLER_SELLER = 0xD3aeE28548Dbb65DF03981f0dC0713BfCBd10a97; + address internal constant SORTED_ORACLES = 0xefB84935239dAcdecF7c5bA76d8dE40b077B7b33; + address internal constant ADDRESS_SORTED_LINKED_LIST_WITH_MEDIAN = 0xED477A99035d0c1e11369F1D7A4e587893cc002B; + address internal constant FEE_CURRENCY = 0x4200000000000000000000000000000000001022; + address internal constant FEE_CURRENCY_DIRECTORY = 0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF; + address internal constant cUSD = 0x765DE816845861e75A25fCA122bb6898B8B1282a; + + /// @notice Returns the name of the predeploy at the given address. + function getName(address _addr) internal pure returns (string memory out_) { + // require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); + + if (_addr == CELO_REGISTRY) return "CeloRegistry"; + if (_addr == GOLD_TOKEN) return "GoldToken"; + if (_addr == FEE_HANDLER) return "FeeHandler"; + if (_addr == MENTO_FEE_HANDLER_SELLER) return "MentoFeeHandlerSeller"; + if (_addr == UNISWAP_FEE_HANDLER_SELLER) return "UniswapFeeHandlerSeller"; + if (_addr == SORTED_ORACLES) return "SortedOracles"; + if (_addr == ADDRESS_SORTED_LINKED_LIST_WITH_MEDIAN) return "AddressSortedLinkedListWithMedian"; + if (_addr == FEE_CURRENCY) return "FeeCurrency"; + if (_addr == FEE_CURRENCY_DIRECTORY) return "FeeCurrencyDirectory"; + if (_addr == cUSD) return "cUSD"; + + revert("Predeploys: unnamed predeploy"); + } +} diff --git a/packages/contracts-bedrock/src/celo/CeloRegistry.sol b/packages/contracts-bedrock/src/celo/CeloRegistry.sol new file mode 100644 index 0000000000000..7da4cfb35ddfe --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloRegistry.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; + +import "./interfaces/ICeloRegistry.sol"; +import "./Initializable.sol"; + +/** + * @title Routes identifiers to addresses. + */ +contract CeloRegistry is ICeloRegistry, Ownable, Initializable { + mapping(bytes32 => address) public registry; + + event RegistryUpdated(string identifier, bytes32 indexed identifierHash, address indexed addr); + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + */ + function initialize() external initializer { + _transferOwnership(msg.sender); + } + + /** + * @notice Associates the given address with the given identifier. + * @param identifier Identifier of contract whose address we want to set. + * @param addr Address of contract. + */ + function setAddressFor(string calldata identifier, address addr) external onlyOwner { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + registry[identifierHash] = addr; + emit RegistryUpdated(identifier, identifierHash, addr); + } + + /** + * @notice Gets address associated with the given identifierHash. + * @param identifierHash Identifier hash of contract whose address we want to look up. + * @dev Throws if address not set. + */ + function getAddressForOrDie(bytes32 identifierHash) external view returns (address) { + require(registry[identifierHash] != address(0), "identifier has no registry entry"); + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifierHash. + * @param identifierHash Identifier hash of contract whose address we want to look up. + */ + function getAddressFor(bytes32 identifierHash) external view returns (address) { + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifier. + * @param identifier Identifier of contract whose address we want to look up. + * @dev Throws if address not set. + */ + function getAddressForStringOrDie(string calldata identifier) external view returns (address) { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + require(registry[identifierHash] != address(0), "identifier has no registry entry"); + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifier. + * @param identifier Identifier of contract whose address we want to look up. + */ + function getAddressForString(string calldata identifier) external view returns (address) { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + return registry[identifierHash]; + } + + /** + * @notice Iterates over provided array of identifiers, getting the address for each. + * Returns true if `sender` matches the address of one of the provided identifiers. + * @param identifierHashes Array of hashes of approved identifiers. + * @param sender Address in question to verify membership. + * @return True if `sender` corresponds to the address of any of `identifiers` + * registry entries. + */ + function isOneOf(bytes32[] calldata identifierHashes, address sender) external view returns (bool) { + for (uint256 i = 0; i < identifierHashes.length; i++) { + if (registry[identifierHashes[i]] == sender) { + return true; + } + } + return false; + } +} diff --git a/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol b/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol new file mode 100644 index 0000000000000..21fc7ff3181a1 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Initializable.sol"; +import "./interfaces/IOracle.sol"; +import "./interfaces/IFeeCurrencyDirectory.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +contract FeeCurrencyDirectory is IFeeCurrencyDirectory, Initializable, Ownable { + mapping(address => CurrencyConfig) public currencies; + address[] private currencyList; + + constructor(bool test) Initializable(test) { } + + /** + * @notice Initializes the contract with the owner set. + */ + function initialize() public initializer { + _transferOwnership(msg.sender); + } + + /** + * @notice Sets the currency configuration for a token. + * @dev This action can only be performed by the contract owner. + * @param token The token address. + * @param oracle The oracle address for price fetching. + * @param intrinsicGas The intrinsic gas value for transactions. + */ + function setCurrencyConfig(address token, address oracle, uint256 intrinsicGas) external onlyOwner { + require(oracle != address(0), "Oracle address cannot be zero"); + require(intrinsicGas > 0, "Intrinsic gas cannot be zero"); + require(currencies[token].oracle == address(0), "Currency already in the directory"); + + currencies[token] = CurrencyConfig({ oracle: oracle, intrinsicGas: intrinsicGas }); + currencyList.push(token); + } + + /** + * @notice Removes a token from the directory. + * @dev This action can only be performed by the contract owner. + * @param token The token address to remove. + * @param index The index in the list of directory currencies. + */ + function removeCurrencies(address token, uint256 index) external onlyOwner { + require(index < currencyList.length, "Index out of bounds"); + require(currencyList[index] == token, "Index does not match token"); + + delete currencies[token]; + currencyList[index] = currencyList[currencyList.length - 1]; + currencyList.pop(); + } + + /** + * @notice Returns the list of all currency addresses. + * @return An array of addresses. + */ + function getCurrencies() public view returns (address[] memory) { + return currencyList; + } + + /** + * @notice Returns the configuration for a currency. + * @param token The address of the token. + * @return Currency configuration of the token. + */ + function getCurrencyConfig(address token) public view returns (CurrencyConfig memory) { + return currencies[token]; + } + + /** + * @notice Retrieves exchange rate between token and CELO. + * @param token The token address whose price is to be fetched. + * @return numerator The exchange rate numerator. + * @return denominator The exchange rate denominator. + */ + function getExchangeRate(address token) public view returns (uint256 numerator, uint256 denominator) { + require(currencies[token].oracle != address(0), "Currency not in the directory"); + (numerator, denominator) = IOracle(currencies[token].oracle).getExchangeRate(token); + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } +} diff --git a/packages/contracts-bedrock/src/celo/FeeHandler.sol b/packages/contracts-bedrock/src/celo/FeeHandler.sol new file mode 100644 index 0000000000000..00a1b0bde4fcb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeHandler.sol @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; +import "./common/Freezable.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; + +import "./common/interfaces/IFeeHandler.sol"; +import "./common/interfaces/IFeeHandlerSeller.sol"; + +// TODO move to IStableToken when it adds method getExchangeRegistryId +import "./interfaces/IStableTokenMento.sol"; +import "./common/interfaces/ICeloVersionedContract.sol"; +import "./common/interfaces/ICeloToken.sol"; +import "./stability/interfaces/ISortedOracles.sol"; + +// Using the minimal required signatures in the interfaces so more contracts could be compatible +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +// An implementation of FeeHandler as described in CIP-52 +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract FeeHandler is + Ownable, + Initializable, + UsingRegistry, + ICeloVersionedContract, + Freezable, + IFeeHandler, + ReentrancyGuard +{ + using FixidityLib for FixidityLib.Fraction; + using EnumerableSet for EnumerableSet.AddressSet; + + uint256 public constant FIXED1_UINT = 1000000000000000000000000; // TODO move to FIX and add check + + // Min units that can be burned + uint256 public constant MIN_BURN = 200; + + // last day the daily limits were updated + uint256 public lastLimitDay; + + FixidityLib.Fraction public burnFraction; // 80% + + address public feeBeneficiary; + + uint256 public celoToBeBurned; + + // This mapping can not be public because it contains a FixidityLib.Fraction + // and that'd be only supported with experimental features in this + // compiler version + mapping(address => TokenState) private tokenStates; + + struct TokenState { + address handler; + FixidityLib.Fraction maxSlippage; + // Max amounts that can be burned in a day for a token + uint256 dailySellLimit; + // Max amounts that can be burned today for a token + uint256 currentDaySellLimit; + uint256 toDistribute; + // Historical amounts burned by this contract + uint256 pastBurn; + } + + EnumerableSet.AddressSet private activeTokens; + + event SoldAndBurnedToken(address token, uint256 value); + event DailyLimitSet(address tokenAddress, uint256 newLimit); + event DailyLimitHit(address token, uint256 burning); + event MaxSlippageSet(address token, uint256 maxSlippage); + event DailySellLimitUpdated(uint256 amount); + event FeeBeneficiarySet(address newBeneficiary); + event BurnFractionSet(uint256 fraction); + event TokenAdded(address tokenAddress, address handlerAddress); + event TokenRemoved(address tokenAddress); + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + */ + function initialize( + address _registryAddress, + address newFeeBeneficiary, + uint256 newBurnFraction, + address[] calldata tokens, + address[] calldata handlers, + uint256[] calldata newLimits, + uint256[] calldata newMaxSlippages + ) + external + initializer + { + require(tokens.length == handlers.length, "handlers length should match tokens length"); + require(tokens.length == newLimits.length, "limits length should match tokens length"); + require(tokens.length == newMaxSlippages.length, "maxSlippage length should match tokens length"); + + _transferOwnership(msg.sender); + setRegistry(_registryAddress); + _setFeeBeneficiary(newFeeBeneficiary); + _setBurnFraction(newBurnFraction); + + for (uint256 i = 0; i < tokens.length; i++) { + _addToken(tokens[i], handlers[i]); + _setDailySellLimit(tokens[i], newLimits[i]); + _setMaxSplippage(tokens[i], newMaxSlippages[i]); + } + } + + // Without this the contract cant receive Celo as native transfer + receive() external payable { } + + /** + * @dev Returns the handler address for the specified token. + * @param tokenAddress The address of the token for which to return the handler. + * @return The address of the handler contract for the specified token. + */ + function getTokenHandler(address tokenAddress) external view returns (address) { + return tokenStates[tokenAddress].handler; + } + + /** + * @dev Returns a boolean indicating whether the specified token is active or not. + * @param tokenAddress The address of the token for which to retrieve the active status. + * @return A boolean representing the active status of the specified token. + */ + function getTokenActive(address tokenAddress) external view returns (bool) { + return activeTokens.contains(tokenAddress); + } + + /** + * @dev Returns the maximum slippage percentage for the specified token. + * @param tokenAddress The address of the token for which to retrieve the maximum + * slippage percentage. + * @return The maximum slippage percentage as a uint256 value. + */ + function getTokenMaxSlippage(address tokenAddress) external view returns (uint256) { + return FixidityLib.unwrap(tokenStates[tokenAddress].maxSlippage); + } + + /** + * @dev Returns the daily burn limit for the specified token. + * @param tokenAddress The address of the token for which to retrieve the daily burn limit. + * @return The daily burn limit as a uint256 value. + */ + function getTokenDailySellLimit(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].dailySellLimit; + } + + /** + * @dev Returns the current daily sell limit for the specified token. + * @param tokenAddress The address of the token for which to retrieve the current daily limit. + * @return The current daily limit as a uint256 value. + */ + function getTokenCurrentDaySellLimit(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].currentDaySellLimit; + } + + /** + * @dev Returns the amount of tokens available to distribute for the specified token. + * @param tokenAddress The address of the token for which to retrieve the amount of + * tokens available to distribute. + * @return The amount of tokens available to distribute as a uint256 value. + */ + function getTokenToDistribute(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].toDistribute; + } + + function getActiveTokens() public view returns (address[] memory) { + return activeTokens.values(); + } + + /** + * @dev Sets the fee beneficiary address to the specified address. + * @param beneficiary The address to set as the fee beneficiary. + */ + function setFeeBeneficiary(address beneficiary) external onlyOwner { + return _setFeeBeneficiary(beneficiary); + } + + function _setFeeBeneficiary(address beneficiary) private { + feeBeneficiary = beneficiary; + emit FeeBeneficiarySet(beneficiary); + } + + /** + * @dev Sets the burn fraction to the specified value. + * @param fraction The value to set as the burn fraction. + */ + function setBurnFraction(uint256 fraction) external onlyOwner { + return _setBurnFraction(fraction); + } + + function _setBurnFraction(uint256 newFraction) private { + FixidityLib.Fraction memory fraction = FixidityLib.wrap(newFraction); + require(FixidityLib.lte(fraction, FixidityLib.fixed1()), "Burn fraction must be less than or equal to 1"); + burnFraction = fraction; + emit BurnFractionSet(newFraction); + } + + /** + * @dev Sets the burn fraction to the specified value. Token has to have a handler set. + * @param tokenAddress The address of the token to sell + */ + function sell(address tokenAddress) external { + return _sell(tokenAddress); + } + + /** + * @dev Adds a new token to the contract with the specified token and handler addresses. + * @param tokenAddress The address of the token to add. + * @param handlerAddress The address of the handler contract for the specified token. + */ + function addToken(address tokenAddress, address handlerAddress) external onlyOwner { + _addToken(tokenAddress, handlerAddress); + } + + function _addToken(address tokenAddress, address handlerAddress) private { + require(handlerAddress != address(0), "Can't set handler to zero"); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = handlerAddress; + + activeTokens.add(tokenAddress); + emit TokenAdded(tokenAddress, handlerAddress); + } + + /** + * @notice Allows the owner to activate a specified token. + * @param tokenAddress The address of the token to be activated. + */ + function activateToken(address tokenAddress) external onlyOwner { + _activateToken(tokenAddress); + } + + function _activateToken(address tokenAddress) private { + TokenState storage tokenState = tokenStates[tokenAddress]; + require( + tokenState.handler != address(0) || tokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + "Handler has to be set to activate token" + ); + activeTokens.add(tokenAddress); + } + + /** + * @dev Deactivates the specified token by marking it as inactive. + * @param tokenAddress The address of the token to deactivate. + */ + function deactivateToken(address tokenAddress) external onlyOwner { + _deactivateToken(tokenAddress); + } + + function _deactivateToken(address tokenAddress) private { + activeTokens.remove(tokenAddress); + } + + /** + * @notice Allows the owner to set a handler contract for a specified token. + * @param tokenAddress The address of the token to set the handler for. + * @param handlerAddress The address of the handler contract to be set. + */ + function setHandler(address tokenAddress, address handlerAddress) external onlyOwner { + _setHandler(tokenAddress, handlerAddress); + } + + function _setHandler(address tokenAddress, address handlerAddress) private { + require(handlerAddress != address(0), "Can't set handler to zero, use deactivateToken"); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = handlerAddress; + } + + function removeToken(address tokenAddress) external onlyOwner { + _removeToken(tokenAddress); + } + + function _removeToken(address tokenAddress) private { + _deactivateToken(tokenAddress); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = address(0); + emit TokenRemoved(tokenAddress); + } + + function _sell(address tokenAddress) private onlyWhenNotFrozen nonReentrant { + IERC20 token = IERC20(tokenAddress); + + TokenState storage tokenState = tokenStates[tokenAddress]; + require(tokenState.handler != address(0), "Handler has to be set to sell token"); + require(FixidityLib.unwrap(tokenState.maxSlippage) != 0, "Max slippage has to be set to sell token"); + FixidityLib.Fraction memory balanceToProcess = + FixidityLib.newFixed(token.balanceOf(address(this)) - tokenState.toDistribute); + + uint256 balanceToBurn = (burnFraction.multiply(balanceToProcess).fromFixed()); + + tokenState.toDistribute = tokenState.toDistribute + balanceToProcess.fromFixed() - balanceToBurn; + + // small numbers cause rounding errors and zero case should be skipped + if (balanceToBurn < MIN_BURN) { + return; + } + + if (dailySellLimitHit(tokenAddress, balanceToBurn)) { + // in case the limit is hit, burn the max possible + balanceToBurn = tokenState.currentDaySellLimit; + emit DailyLimitHit(tokenAddress, balanceToBurn); + } + + token.transfer(tokenState.handler, balanceToBurn); + IFeeHandlerSeller handler = IFeeHandlerSeller(tokenState.handler); + + uint256 celoReceived = handler.sell( + tokenAddress, + registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + balanceToBurn, + FixidityLib.unwrap(tokenState.maxSlippage) + ); + + celoToBeBurned = celoToBeBurned + celoReceived; + tokenState.pastBurn = tokenState.pastBurn + balanceToBurn; + updateLimits(tokenAddress, balanceToBurn); + + emit SoldAndBurnedToken(tokenAddress, balanceToBurn); + } + + /** + * @dev Distributes the available tokens for the specified token address to the fee beneficiary. + * @param tokenAddress The address of the token for which to distribute the available tokens. + */ + function distribute(address tokenAddress) external { + return _distribute(tokenAddress); + } + + function _distribute(address tokenAddress) private onlyWhenNotFrozen nonReentrant { + require(feeBeneficiary != address(0), "Can't distribute to the zero address"); + IERC20 token = IERC20(tokenAddress); + uint256 tokenBalance = token.balanceOf(address(this)); + + TokenState storage tokenState = tokenStates[tokenAddress]; + require( + tokenState.handler != address(0) || tokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + "Handler has to be set to sell token" + ); + + // safty check to avoid a revert due balance + uint256 balanceToDistribute = Math.min(tokenBalance, tokenState.toDistribute); + + if (balanceToDistribute == 0) { + // don't distribute with zero balance + return; + } + + token.transfer(feeBeneficiary, balanceToDistribute); + tokenState.toDistribute = tokenState.toDistribute - balanceToDistribute; + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + /** + * @notice Allows owner to set max slippage for a token. + * @param token Address of the token to set. + * @param newMax New sllipage to set, as Fixidity fraction. + */ + function setMaxSplippage(address token, uint256 newMax) external onlyOwner { + _setMaxSplippage(token, newMax); + } + + function _setMaxSplippage(address token, uint256 newMax) private { + TokenState storage tokenState = tokenStates[token]; + require(newMax != 0, "Cannot set max slippage to zero"); + tokenState.maxSlippage = FixidityLib.wrap(newMax); + require( + FixidityLib.lte(tokenState.maxSlippage, FixidityLib.fixed1()), "Splippage must be less than or equal to 1" + ); + emit MaxSlippageSet(token, newMax); + } + + /** + * @notice Allows owner to set the daily burn limit for a token. + * @param token Address of the token to set. + * @param newLimit The new limit to set, in the token units. + */ + function setDailySellLimit(address token, uint256 newLimit) external onlyOwner { + _setDailySellLimit(token, newLimit); + } + + function _setDailySellLimit(address token, uint256 newLimit) private { + TokenState storage tokenState = tokenStates[token]; + tokenState.dailySellLimit = newLimit; + emit DailyLimitSet(token, newLimit); + } + + /** + * @dev Burns CELO tokens according to burnFraction. + */ + function burnCelo() external { + return _burnCelo(); + } + + /** + * @dev Distributes the available tokens for all registered tokens to the feeBeneficiary. + */ + function distributeAll() external { + return _distributeAll(); + } + + function _distributeAll() private { + for (uint256 i = 0; i < EnumerableSet.length(activeTokens); i++) { + address token = activeTokens.at(i); + _distribute(token); + } + // distribute Celo + _distribute(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + /** + * @dev Distributes the available tokens for all registered tokens to the feeBeneficiary. + */ + function handleAll() external { + return _handleAll(); + } + + function _handleAll() private { + for (uint256 i = 0; i < EnumerableSet.length(activeTokens); i++) { + // calling _handle would trigger may burn Celo and distributions + // that can be just batched at the end + address token = activeTokens.at(i); + _sell(token); + } + _distributeAll(); // distributes Celo as well + _burnCelo(); + } + + /** + * @dev Distributes the the token for to the feeBeneficiary. + */ + function handle(address tokenAddress) external { + return _handle(tokenAddress); + } + + function _handle(address tokenAddress) private { + // Celo doesn't have to be exchanged for anything + if (tokenAddress != registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)) { + _sell(tokenAddress); + } + _burnCelo(); + _distribute(tokenAddress); + _distribute(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + /** + * @notice Burns all the Celo balance of this contract. + */ + function _burnCelo() private { + TokenState storage tokenState = tokenStates[registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)]; + ICeloToken celo = ICeloToken(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + + uint256 balanceOfCelo = address(this).balance; + + uint256 balanceToProcess = balanceOfCelo - tokenState.toDistribute - celoToBeBurned; + uint256 currentBalanceToBurn = FixidityLib.newFixed(balanceToProcess).multiply(burnFraction).fromFixed(); + uint256 totalBalanceToBurn = currentBalanceToBurn + celoToBeBurned; + celo.burn(totalBalanceToBurn); + + celoToBeBurned = 0; + tokenState.toDistribute = tokenState.toDistribute + balanceToProcess - currentBalanceToBurn; + } + + /** + * @param token The address of the token to query. + * @return The amount burned for a token. + */ + function getPastBurnForToken(address token) external view returns (uint256) { + return tokenStates[token].pastBurn; + } + + /** + * @param token The address of the token to query. + * @param amountToBurn The amount of the token to burn. + * @return Returns true if burning amountToBurn would exceed the daily limit. + */ + function dailySellLimitHit(address token, uint256 amountToBurn) public returns (bool) { + TokenState storage tokenState = tokenStates[token]; + + if (tokenState.dailySellLimit == 0) { + // if no limit set, assume uncapped + return false; + } + + uint256 currentDay = block.timestamp / 1 days; + // Pattern borrowed from Reserve.sol + if (currentDay > lastLimitDay) { + lastLimitDay = currentDay; + tokenState.currentDaySellLimit = tokenState.dailySellLimit; + } + + return amountToBurn >= tokenState.currentDaySellLimit; + } + + /** + * @notice Updates the current day limit for a token. + * @param token The address of the token to query. + * @param amountBurned the amount of the token that was burned. + */ + function updateLimits(address token, uint256 amountBurned) private { + TokenState storage tokenState = tokenStates[token]; + + if (tokenState.dailySellLimit == 0) { + // if no limit set, assume uncapped + return; + } + tokenState.currentDaySellLimit = tokenState.currentDaySellLimit - amountBurned; + emit DailySellLimitUpdated(amountBurned); + } + + /** + * @notice Allows owner to transfer tokens of this contract. It's meant for governance to + * trigger use cases not contemplated in this contract. + * @param token The address of the token to transfer. + * @param recipient The address of the recipient to transfer the tokens to. + * @param value The amount of tokens to transfer. + * @return A boolean indicating whether the transfer was successful or not. + */ + function transfer(address token, address recipient, uint256 value) external onlyOwner returns (bool) { + return IERC20(token).transfer(recipient, value); + } +} diff --git a/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol b/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol new file mode 100644 index 0000000000000..4d22125af4d64 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +import "./common/FixidityLib.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "./UsingRegistry.sol"; +import "./common/Initializable.sol"; + +// Abstract class for a FeeHandlerSeller, as defined in CIP-52 +// https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +abstract contract FeeHandlerSeller is Ownable, Initializable, UsingRegistry { + using FixidityLib for FixidityLib.Fraction; + + // Address of the token + // Minimal number of reports in SortedOracles contract + mapping(address => uint256) public minimumReports; + + event MinimumReportsSet(address tokenAddress, uint256 minimumReports); + event TokenSold(address soldTokenAddress, address boughtTokenAddress, uint256 amount); + + constructor(bool testingDeployment) Initializable(testingDeployment) { } + + function initialize( + address _registryAddress, + address[] calldata tokenAddresses, + uint256[] calldata newMininumReports + ) + external + initializer + { + _transferOwnership(msg.sender); + setRegistry(_registryAddress); + + for (uint256 i = 0; i < tokenAddresses.length; i++) { + _setMinimumReports(tokenAddresses[i], newMininumReports[i]); + } + } + + /** + * @notice Allows owner to set the minimum number of reports required. + * @param newMininumReports The new update minimum number of reports required. + */ + function setMinimumReports(address tokenAddress, uint256 newMininumReports) public onlyOwner { + _setMinimumReports(tokenAddress, newMininumReports); + } + + function _setMinimumReports(address tokenAddress, uint256 newMininumReports) internal { + minimumReports[tokenAddress] = newMininumReports; + emit MinimumReportsSet(tokenAddress, newMininumReports); + } + + /** + * @dev Calculates the minimum amount of tokens that should be received for the specified + * amount with the given mid-price and maximum slippage. + * @param midPriceNumerator The numerator of the mid-price for the token pair. + * @param midPriceDenominator The denominator of the mid-price for the token pair. + * @param amount The amount of tokens to be exchanged. + * @param maxSlippage The maximum slippage percentage as a fraction of the mid-price. + * @return The minimum amount of tokens that should be received as a uint256 value. + */ + function calculateMinAmount( + uint256 midPriceNumerator, + uint256 midPriceDenominator, + uint256 amount, + uint256 maxSlippage // as fraction + ) + public + pure + returns (uint256) + { + FixidityLib.Fraction memory maxSlippageFraction = FixidityLib.wrap(maxSlippage); + + FixidityLib.Fraction memory price = FixidityLib.newFixedFraction(midPriceNumerator, midPriceDenominator); + FixidityLib.Fraction memory amountFraction = FixidityLib.newFixed(amount); + FixidityLib.Fraction memory totalAmount = price.multiply(amountFraction); + + return totalAmount.subtract(price.multiply(maxSlippageFraction).multiply(amountFraction)).fromFixed(); + } + + /** + * @notice Allows owner to transfer tokens of this contract. It's meant for governance to + * trigger use cases not contemplated in this contract. + * @param token The address of the token to transfer. + * @param amount The amount of tokens to transfer. + * @param to The address of the recipient to transfer the tokens to. + * @return A boolean indicating whether the transfer was successful or not. + */ + function transfer(address token, uint256 amount, address to) external onlyOwner returns (bool) { + return IERC20(token).transfer(to, amount); + } +} diff --git a/packages/contracts-bedrock/src/celo/GoldToken.sol b/packages/contracts-bedrock/src/celo/GoldToken.sol new file mode 100644 index 0000000000000..e7236678670a7 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/GoldToken.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; +import "./CalledByVm.sol"; +import "./Initializable.sol"; +import "./interfaces/ICeloToken.sol"; +import "./common/interfaces/ICeloVersionedContract.sol"; + +contract GoldToken is Initializable, CalledByVm, UsingRegistry, IERC20, ICeloToken, ICeloVersionedContract { + // Address of the TRANSFER precompiled contract. + // solhint-disable state-visibility + address constant TRANSFER = address(0xff - 2); + string constant NAME = "Celo native asset"; + string constant SYMBOL = "CELO"; + uint8 constant DECIMALS = 18; + uint256 internal totalSupply_; + // solhint-enable state-visibility + + mapping(address => mapping(address => uint256)) internal allowed; + + // Burn address is 0xdEaD because truffle is having buggy behaviour with the zero address + address constant BURN_ADDRESS = address(0x000000000000000000000000000000000000dEaD); + + event TransferComment(string comment); + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 2, 0); + } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + * @param registryAddress Address of the Registry contract. + */ + function initialize(address registryAddress) external initializer { + totalSupply_ = 0; + _transferOwnership(msg.sender); + setRegistry(registryAddress); + } + + /** + * @notice Transfers CELO from one address to another. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + // solhint-disable-next-line no-simple-event-func-name + function transfer(address to, uint256 value) external returns (bool) { + return _transferWithCheck(to, value); + } + + /** + * @notice Transfers CELO from one address to another with a comment. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @param comment The transfer comment + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool) { + bool succeeded = _transferWithCheck(to, value); + emit TransferComment(comment); + return succeeded; + } + + /** + * @notice This function allows a user to burn a specific amount of tokens. + * Burning is implemented by sending tokens to the burn address. + * @param value: The amount of CELO to burn. + * @return True if burn was successful. + */ + function burn(uint256 value) external returns (bool) { + // not using transferWithCheck as the burn address can potentially be the zero address + return _transfer(BURN_ADDRESS, value); + } + + /** + * @notice Approve a user to transfer CELO on behalf of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function approve(address spender, uint256 value) external returns (bool) { + require(spender != address(0), "cannot set allowance for 0"); + allowed[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + /** + * @notice Increases the allowance of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The increment of the amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function increaseAllowance(address spender, uint256 value) external returns (bool) { + require(spender != address(0), "cannot set allowance for 0"); + uint256 oldValue = allowed[msg.sender][spender]; + uint256 newValue = oldValue + value; + allowed[msg.sender][spender] = newValue; + emit Approval(msg.sender, spender, newValue); + return true; + } + + /** + * @notice Decreases the allowance of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The decrement of the amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function decreaseAllowance(address spender, uint256 value) external returns (bool) { + uint256 oldValue = allowed[msg.sender][spender]; + uint256 newValue = oldValue - value; + allowed[msg.sender][spender] = newValue; + emit Approval(msg.sender, spender, newValue); + return true; + } + + /** + * @notice Transfers CELO from one address to another on behalf of a user. + * @param from The address to transfer CELO from. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool) { + require(to != address(0), "transfer attempted to reserved address 0x0"); + require(value <= balanceOf(from), "transfer value exceeded balance of sender"); + require(value <= allowed[from][msg.sender], "transfer value exceeded sender's allowance for spender"); + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(from, to, value)); + require(success, "CELO transfer failed"); + + allowed[from][msg.sender] = allowed[from][msg.sender] - value; + emit Transfer(from, to, value); + return true; + } + + /** + * @notice Mints new CELO and gives it to 'to'. + * @param to The account for which to mint tokens. + * @param value The amount of CELO to mint. + */ + function mint(address to, uint256 value) external onlyVm returns (bool) { + if (value == 0) { + return true; + } + + require(to != address(0), "mint attempted to reserved address 0x0"); + totalSupply_ = totalSupply_ + value; + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(address(0), to, value)); + require(success, "CELO transfer failed"); + + emit Transfer(address(0), to, value); + return true; + } + + /** + * @return The name of the CELO token. + */ + function name() external pure returns (string memory) { + return NAME; + } + + /** + * @return The symbol of the CELO token. + */ + function symbol() external pure returns (string memory) { + return SYMBOL; + } + + /** + * @return The number of decimal places to which CELO is divisible. + */ + function decimals() external pure returns (uint8) { + return DECIMALS; + } + + /** + * @return The total amount of CELO in existence, including what the burn address holds. + */ + function totalSupply() external view returns (uint256) { + return totalSupply_; + } + + /** + * @return The total amount of CELO in existence, not including what the burn address holds. + */ + function circulatingSupply() external view returns (uint256) { + return totalSupply_ - getBurnedAmount() - balanceOf(address(0)); + } + + /** + * @notice Gets the amount of owner's CELO allowed to be spent by spender. + * @param owner The owner of the CELO. + * @param spender The spender of the CELO. + * @return The amount of CELO owner is allowing spender to spend. + */ + function allowance(address owner, address spender) external view returns (uint256) { + return allowed[owner][spender]; + } + + /** + * @notice Increases the variable for total amount of CELO in existence. + * @param amount The amount to increase counter by + */ + function increaseSupply(uint256 amount) external onlyVm { + totalSupply_ = totalSupply_ + amount; + } + + /** + * @notice Gets the amount of CELO that has been burned. + * @return The total amount of Celo that has been sent to the burn address. + */ + function getBurnedAmount() public view returns (uint256) { + return balanceOf(BURN_ADDRESS); + } + + /** + * @notice Gets the balance of the specified address. + * @param owner The address to query the balance of. + * @return The balance of the specified address. + */ + function balanceOf(address owner) public view returns (uint256) { + return owner.balance; + } + + /** + * @notice internal CELO transfer from one address to another. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function _transfer(address to, uint256 value) internal returns (bool) { + require(value <= balanceOf(msg.sender), "transfer value exceeded balance of sender"); + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(msg.sender, to, value)); + require(success, "CELO transfer failed"); + emit Transfer(msg.sender, to, value); + return true; + } + + /** + * @notice Internal CELO transfer from one address to another. + * @param to The address to transfer CELO to. Zero address will revert. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function _transferWithCheck(address to, uint256 value) internal returns (bool) { + require(to != address(0), "transfer attempted to reserved address 0x0"); + return _transfer(to, value); + } +} diff --git a/packages/contracts-bedrock/src/celo/Initializable.sol b/packages/contracts-bedrock/src/celo/Initializable.sol new file mode 100644 index 0000000000000..7929728eef4ed --- /dev/null +++ b/packages/contracts-bedrock/src/celo/Initializable.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +contract Initializable { + bool public initialized; + + modifier initializer() { + require(!initialized, "contract already initialized"); + initialized = true; + _; + } + + constructor(bool testingDeployment) { + if (!testingDeployment) { + initialized = true; + } + } +} diff --git a/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol b/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol new file mode 100644 index 0000000000000..e5a9ff455f391 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./interfaces/IStableTokenMento.sol"; + +import "./common/interfaces/IFeeHandlerSeller.sol"; +import "./stability/interfaces/ISortedOracles.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; + +import "./FeeHandlerSeller.sol"; + +// An implementation of FeeHandlerSeller supporting interfaces compatible with +// Mento +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract MentoFeeHandlerSeller is FeeHandlerSeller { + using FixidityLib for FixidityLib.Fraction; + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) FeeHandlerSeller(test) { } + + // without this line the contract can't receive native Celo transfers + receive() external payable { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 maxSlippage // as fraction, + ) + external + returns (uint256) + { + require( + buyTokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), "Buy token can only be gold token" + ); + + IStableTokenMento stableToken = IStableTokenMento(sellTokenAddress); + require(amount <= stableToken.balanceOf(address(this)), "Balance of token to burn not enough"); + + address exchangeAddress = registry.getAddressForOrDie(stableToken.getExchangeRegistryId()); + + IExchange exchange = IExchange(exchangeAddress); + + uint256 minAmount = 0; + + ISortedOracles sortedOracles = getSortedOracles(); + + require( + sortedOracles.numRates(sellTokenAddress) >= minimumReports[sellTokenAddress], + "Number of reports for token not enough" + ); + + (uint256 rateNumerator, uint256 rateDenominator) = sortedOracles.medianRate(sellTokenAddress); + minAmount = calculateMinAmount(rateNumerator, rateDenominator, amount, maxSlippage); + + // TODO an upgrade would be to compare using routers as well + stableToken.approve(exchangeAddress, amount); + exchange.sell(amount, minAmount, false); + + IERC20 goldToken = getGoldToken(); + uint256 celoAmount = goldToken.balanceOf(address(this)); + goldToken.transfer(msg.sender, celoAmount); + + emit TokenSold(sellTokenAddress, buyTokenAddress, amount); + return celoAmount; + } +} diff --git a/packages/contracts-bedrock/src/celo/StableTokenV2.sol b/packages/contracts-bedrock/src/celo/StableTokenV2.sol new file mode 100644 index 0000000000000..68632df65abc9 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/StableTokenV2.sol @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { ERC20PermitUpgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { IStableTokenV2 } from "./interfaces/IStableToken.sol"; +import { CalledByVm } from "./CalledByVm.sol"; + +/** + * @title ERC20 token with minting and burning permissioned to a broker and validators. + */ +contract StableTokenV2 is IStableTokenV2, ERC20PermitUpgradeable, CalledByVm, OwnableUpgradeable { + address public validators; + address public broker; + address public exchange; + + event TransferComment(string comment); + event BrokerUpdated(address broker); + event ValidatorsUpdated(address validators); + event ExchangeUpdated(address exchange); + + /** + * @dev Restricts a function so it can only be executed by an address that's allowed to mint. + * Currently that's the broker, validators, or exchange. + */ + modifier onlyMinter() { + address sender = _msgSender(); + require(sender == broker || sender == validators || sender == exchange, "StableTokenV2: not allowed to mint"); + _; + } + + /** + * @dev Restricts a function so it can only be executed by an address that's allowed to burn. + * Currently that's the broker or exchange. + */ + modifier onlyBurner() { + address sender = _msgSender(); + require(sender == broker || sender == exchange, "StableTokenV2: not allowed to burn"); + _; + } + + /** + * @notice The constructor for the StableTokenV2 contract. + * @dev Should be called with disable=true in deployments when + * it's accessed through a Proxy. + * Call this with disable=false during testing, when used + * without a proxy. + * @param disable Set to true to run `_disableInitializers()` inherited from + * openzeppelin-contracts-upgradeable/Initializable.sol + */ + constructor(bool disable) { + if (disable) { + _disableInitializers(); + } + } + + /** + * @notice Initializes a StableTokenV2. + * It keeps the same signature as the original initialize() function + * in legacy/StableToken.sol + * @param _name The name of the stable token (English) + * @param _symbol A short symbol identifying the token (e.g. "cUSD") + * @param initialBalanceAddresses Array of addresses with an initial balance. + * @param initialBalanceValues Array of balance values corresponding to initialBalanceAddresses. + * deprecated-param exchangeIdentifier String identifier of exchange in registry (for specific fiat pairs) + */ + function initialize( + // slither-disable-start shadowing-local + string calldata _name, + string calldata _symbol, + // slither-disable-end shadowing-local + address[] calldata initialBalanceAddresses, + uint256[] calldata initialBalanceValues + ) + external + initializer + { + __ERC20_init_unchained(_name, _symbol); + __ERC20Permit_init(_symbol); + _transferOwnership(_msgSender()); + + require(initialBalanceAddresses.length == initialBalanceValues.length, "Array length mismatch"); + for (uint256 i = 0; i < initialBalanceAddresses.length; i += 1) { + _mint(initialBalanceAddresses[i], initialBalanceValues[i]); + } + } + + /** + * @notice Initializes a StableTokenV2 contract + * when upgrading from legacy/StableToken.sol. + * It sets the addresses that were previously read from the Registry. + * It runs the ERC20PermitUpgradeable initializer. + * @dev This function is only callable once. + * @param _broker The address of the Broker contract. + * @param _validators The address of the Validators contract. + * @param _exchange The address of the Exchange contract. + */ + function initializeV2( + address _broker, + address _validators, + address _exchange + ) + external + reinitializer(2) + onlyOwner + { + _setBroker(_broker); + _setValidators(_validators); + _setExchange(_exchange); + __ERC20Permit_init(symbol()); + } + + /** + * @notice Sets the address of the Broker contract. + * @dev This function is only callable by the owner. + * @param _broker The address of the Broker contract. + */ + function setBroker(address _broker) external onlyOwner { + _setBroker(_broker); + } + + /** + * @notice Sets the address of the Validators contract. + * @dev This function is only callable by the owner. + * @param _validators The address of the Validators contract. + */ + function setValidators(address _validators) external onlyOwner { + _setValidators(_validators); + } + + /** + * @notice Sets the address of the Exchange contract. + * @dev This function is only callable by the owner. + * @param _exchange The address of the Exchange contract. + */ + function setExchange(address _exchange) external onlyOwner { + _setExchange(_exchange); + } + + /** + * @notice Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + * @param comment The transfer comment. + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool) { + emit TransferComment(comment); + return transfer(to, value); + } + + /** + * @notice Mints new StableToken and gives it to 'to'. + * @param to The account for which to mint tokens. + * @param value The amount of StableToken to mint. + */ + function mint(address to, uint256 value) external onlyMinter returns (bool) { + _mint(to, value); + return true; + } + + /** + * @notice Burns StableToken from the balance of msg.sender. + * @param value The amount of StableToken to burn. + */ + function burn(uint256 value) external onlyBurner returns (bool) { + _burn(msg.sender, value); + return true; + } + + /** + * @notice Set the address of the Broker contract and emit an event + * @param _broker The address of the Broker contract. + */ + function _setBroker(address _broker) internal { + broker = _broker; + emit BrokerUpdated(_broker); + } + + /** + * @notice Set the address of the Validators contract and emit an event + * @param _validators The address of the Validators contract. + */ + function _setValidators(address _validators) internal { + validators = _validators; + emit ValidatorsUpdated(_validators); + } + + /** + * @notice Set the address of the Exchange contract and emit an event + * @param _exchange The address of the Exchange contract. + */ + function _setExchange(address _exchange) internal { + exchange = _exchange; + emit ExchangeUpdated(_exchange); + } + + /// @inheritdoc ERC20Upgradeable + function transferFrom( + address from, + address to, + uint256 amount + ) + public + override(ERC20Upgradeable, IStableTokenV2) + returns (bool) + { + return ERC20Upgradeable.transferFrom(from, to, amount); + } + + /// @inheritdoc ERC20Upgradeable + function transfer(address to, uint256 amount) public override(ERC20Upgradeable, IStableTokenV2) returns (bool) { + return ERC20Upgradeable.transfer(to, amount); + } + + /// @inheritdoc ERC20Upgradeable + function balanceOf(address account) public view override(ERC20Upgradeable, IStableTokenV2) returns (uint256) { + return ERC20Upgradeable.balanceOf(account); + } + + /// @inheritdoc ERC20Upgradeable + function approve( + address spender, + uint256 amount + ) + public + override(ERC20Upgradeable, IStableTokenV2) + returns (bool) + { + return ERC20Upgradeable.approve(spender, amount); + } + + /// @inheritdoc ERC20Upgradeable + function allowance( + address owner, + address spender + ) + public + view + override(ERC20Upgradeable, IStableTokenV2) + returns (uint256) + { + return ERC20Upgradeable.allowance(owner, spender); + } + + /// @inheritdoc ERC20Upgradeable + function totalSupply() public view override(ERC20Upgradeable, IStableTokenV2) returns (uint256) { + return ERC20Upgradeable.totalSupply(); + } + + /// @inheritdoc ERC20PermitUpgradeable + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) + public + override(ERC20PermitUpgradeable, IStableTokenV2) + { + ERC20PermitUpgradeable.permit(owner, spender, value, deadline, v, r, s); + } + + /** + * @notice Reserve balance for making payments for gas in this StableToken currency. + * @param from The account to reserve balance from + * @param value The amount of balance to reserve + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. After the tx is executed, gas is refunded to the sender and credited to the + * various tx fee recipients via a call to `creditGasFees`. + */ + function debitGasFees(address from, uint256 value) external onlyVm { + _burn(from, value); + } + + /** + * @notice Alternative function to credit balance after making payments + * for gas in this StableToken currency. + * @param from The account to debit balance from + * @param feeRecipient Coinbase address + * @param gatewayFeeRecipient Gateway address + * @param communityFund Community fund address + * @param refund amount to be refunded by the VM + * @param tipTxFee Coinbase fee + * @param baseTxFee Community fund fee + * @param gatewayFee Gateway fee + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. Before the tx is executed, gas is debited from the sender via a call to + * `debitGasFees`. + */ + function creditGasFees( + address from, + address feeRecipient, + address gatewayFeeRecipient, + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256 gatewayFee, + uint256 baseTxFee + ) + external + onlyVm + { + // slither-disable-next-line uninitialized-local + uint256 amountToBurn; + _mint(from, refund + tipTxFee + gatewayFee + baseTxFee); + + if (feeRecipient != address(0)) { + _transfer(from, feeRecipient, tipTxFee); + } else if (tipTxFee > 0) { + amountToBurn += tipTxFee; + } + + if (gatewayFeeRecipient != address(0)) { + _transfer(from, gatewayFeeRecipient, gatewayFee); + } else if (gatewayFee > 0) { + amountToBurn += gatewayFee; + } + + if (communityFund != address(0)) { + _transfer(from, communityFund, baseTxFee); + } else if (baseTxFee > 0) { + amountToBurn += baseTxFee; + } + + if (amountToBurn > 0) { + _burn(from, amountToBurn); + } + } +} diff --git a/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol b/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol new file mode 100644 index 0000000000000..54ce14eaf37cf --- /dev/null +++ b/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; + +import "./common/interfaces/IFeeHandlerSeller.sol"; +import "./stability/interfaces/ISortedOracles.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; +import "./FeeHandlerSeller.sol"; + +import "./uniswap/interfaces/IUniswapV2RouterMin.sol"; +import "./uniswap/interfaces/IUniswapV2FactoryMin.sol"; + +// An implementation of FeeHandlerSeller supporting interfaces compatible with +// Uniswap V2 API +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract UniswapFeeHandlerSeller is FeeHandlerSeller { + using FixidityLib for FixidityLib.Fraction; + using EnumerableSet for EnumerableSet.AddressSet; + + uint256 constant MAX_TIMESTAMP_BLOCK_EXCHANGE = 20; + uint256 constant MAX_NUMBER_ROUTERS_PER_TOKEN = 3; + mapping(address => EnumerableSet.AddressSet) private routerAddresses; + + event ReceivedQuote(address indexed tokneAddress, address indexed router, uint256 quote); + event RouterUsed(address router); + event RouterAddressSet(address token, address router); + event RouterAddressRemoved(address token, address router); + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) FeeHandlerSeller(test) { } + + // without this line the contract can't receive native Celo transfers + receive() external payable { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + /** + * @notice Allows owner to set the router for a token. + * @param token Address of the token to set. + * @param router The new router. + */ + function setRouter(address token, address router) external onlyOwner { + _setRouter(token, router); + } + + function _setRouter(address token, address router) private { + require(router != address(0), "Router can't be address zero"); + routerAddresses[token].add(router); + require(routerAddresses[token].values().length <= MAX_NUMBER_ROUTERS_PER_TOKEN, "Max number of routers reached"); + emit RouterAddressSet(token, router); + } + + /** + * @notice Allows owner to remove a router for a token. + * @param token Address of the token. + * @param router Address of the router to remove. + */ + function removeRouter(address token, address router) external onlyOwner { + routerAddresses[token].remove(router); + emit RouterAddressRemoved(token, router); + } + + /** + * @notice Get the list of routers for a token. + * @param token The address of the token to query. + * @return An array of all the allowed router. + */ + function getRoutersForToken(address token) external view returns (address[] memory) { + return routerAddresses[token].values(); + } + + /** + * @dev Calculates the minimum amount of tokens that can be received for a given amount of sell tokens, + * taking into account the slippage and the rates of the sell token and CELO token on the Uniswap V2 pair. + * @param sellTokenAddress The address of the sell token. + * @param maxSlippage The maximum slippage allowed. + * @param amount The amount of sell tokens to be traded. + * @param bestRouter The Uniswap V2 router with the best price. + * @return The minimum amount of tokens that can be received. + */ + function calculateAllMinAmount( + address sellTokenAddress, + uint256 maxSlippage, + uint256 amount, + IUniswapV2RouterMin bestRouter + ) + private + view + returns (uint256) + { + ISortedOracles sortedOracles = getSortedOracles(); + uint256 minReports = minimumReports[sellTokenAddress]; + + require(sortedOracles.numRates(sellTokenAddress) >= minReports, "Number of reports for token not enough"); + + uint256 minimalSortedOracles = 0; + // if minimumReports for this token is zero, assume the check is not needed + if (minReports > 0) { + (uint256 rateNumerator, uint256 rateDenominator) = sortedOracles.medianRate(sellTokenAddress); + + minimalSortedOracles = calculateMinAmount(rateNumerator, rateDenominator, amount, maxSlippage); + } + + IERC20 celoToken = getGoldToken(); + address pair = IUniswapV2FactoryMin(bestRouter.factory()).getPair(sellTokenAddress, address(celoToken)); + uint256 minAmountPair = + calculateMinAmount(IERC20(sellTokenAddress).balanceOf(pair), celoToken.balanceOf(pair), amount, maxSlippage); + + return Math.max(minAmountPair, minimalSortedOracles); + } + + // This function explicitly defines few variables because it was getting error "stack too deep" + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 maxSlippage // as fraction, + ) + external + returns (uint256) + { + require( + buyTokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), "Buy token can only be gold token" + ); + + require(routerAddresses[sellTokenAddress].values().length > 0, "routerAddresses should be non empty"); + + // An improvement to this function would be to allow the user to pass a path as argument + // and if it generates a better outcome that the ones enabled that gets used + // and the user gets a reward + + IERC20 celoToken = getGoldToken(); + + IUniswapV2RouterMin bestRouter; + uint256 bestRouterQuote = 0; + + address[] memory path = new address[](2); + + path[0] = sellTokenAddress; + path[1] = address(celoToken); + + for (uint256 i = 0; i < routerAddresses[sellTokenAddress].values().length; i++) { + address poolAddress = routerAddresses[sellTokenAddress].at(i); + IUniswapV2RouterMin router = IUniswapV2RouterMin(poolAddress); + + // Using the second return value becuase it's the last argument, + // the previous values show how many tokens are exchanged in each path + // so the first value would be equivalent to balanceToBurn + uint256 wouldGet = router.getAmountsOut(amount, path)[1]; + + emit ReceivedQuote(sellTokenAddress, poolAddress, wouldGet); + if (wouldGet > bestRouterQuote) { + bestRouterQuote = wouldGet; + bestRouter = router; + } + } + + require(bestRouterQuote != 0, "Can't exchange with zero quote"); + + uint256 minAmount = 0; + minAmount = calculateAllMinAmount(sellTokenAddress, maxSlippage, amount, bestRouter); + + IERC20(sellTokenAddress).approve(address(bestRouter), amount); + bestRouter.swapExactTokensForTokens( + amount, minAmount, path, address(this), block.timestamp + MAX_TIMESTAMP_BLOCK_EXCHANGE + ); + + uint256 celoAmount = celoToken.balanceOf(address(this)); + celoToken.transfer(msg.sender, celoAmount); + emit RouterUsed(address(bestRouter)); + emit TokenSold(sellTokenAddress, buyTokenAddress, amount); + return celoAmount; + } +} diff --git a/packages/contracts-bedrock/src/celo/UsingRegistry.sol b/packages/contracts-bedrock/src/celo/UsingRegistry.sol new file mode 100644 index 0000000000000..b5bf928d11f22 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/UsingRegistry.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./interfaces/IAccounts.sol"; +import "./interfaces/IFreezer.sol"; +import "./interfaces/ICeloRegistry.sol"; + +import "./governance/interfaces/IElection.sol"; +import "./governance/interfaces/IGovernance.sol"; +import "./governance/interfaces/ILockedGold.sol"; +import "./governance/interfaces/IValidators.sol"; + +import "./identity/interfaces/IRandom.sol"; +import "./identity/interfaces/IAttestations.sol"; + +import "./stability/interfaces/ISortedOracles.sol"; + +import "./mento/interfaces/IExchange.sol"; +import "./mento/interfaces/IReserve.sol"; +import "./mento/interfaces/IStableToken.sol"; + +contract UsingRegistry is Ownable { + event RegistrySet(address indexed registryAddress); + + // solhint-disable state-visibility + bytes32 constant ACCOUNTS_REGISTRY_ID = keccak256(abi.encodePacked("Accounts")); + bytes32 constant ATTESTATIONS_REGISTRY_ID = keccak256(abi.encodePacked("Attestations")); + bytes32 constant DOWNTIME_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("DowntimeSlasher")); + bytes32 constant DOUBLE_SIGNING_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("DoubleSigningSlasher")); + bytes32 constant ELECTION_REGISTRY_ID = keccak256(abi.encodePacked("Election")); + bytes32 constant EXCHANGE_REGISTRY_ID = keccak256(abi.encodePacked("Exchange")); + bytes32 constant FREEZER_REGISTRY_ID = keccak256(abi.encodePacked("Freezer")); + bytes32 constant GOLD_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("GoldToken")); + bytes32 constant GOVERNANCE_REGISTRY_ID = keccak256(abi.encodePacked("Governance")); + bytes32 constant GOVERNANCE_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("GovernanceSlasher")); + bytes32 constant LOCKED_GOLD_REGISTRY_ID = keccak256(abi.encodePacked("LockedGold")); + bytes32 constant RESERVE_REGISTRY_ID = keccak256(abi.encodePacked("Reserve")); + bytes32 constant RANDOM_REGISTRY_ID = keccak256(abi.encodePacked("Random")); + bytes32 constant SORTED_ORACLES_REGISTRY_ID = keccak256(abi.encodePacked("SortedOracles")); + bytes32 constant STABLE_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("StableToken")); + bytes32 constant VALIDATORS_REGISTRY_ID = keccak256(abi.encodePacked("Validators")); + // solhint-enable state-visibility + + ICeloRegistry public registry; + + modifier onlyRegisteredContract(bytes32 identifierHash) { + require(registry.getAddressForOrDie(identifierHash) == msg.sender, "only registered contract"); + _; + } + + modifier onlyRegisteredContracts(bytes32[] memory identifierHashes) { + require(registry.isOneOf(identifierHashes, msg.sender), "only registered contracts"); + _; + } + + /** + * @notice Updates the address pointing to a Registry contract. + * @param registryAddress The address of a registry contract for routing to other contracts. + */ + function setRegistry(address registryAddress) public onlyOwner { + require(registryAddress != address(0), "Cannot register the null address"); + registry = ICeloRegistry(registryAddress); + emit RegistrySet(registryAddress); + } + + function getAccounts() internal view returns (IAccounts) { + return IAccounts(registry.getAddressForOrDie(ACCOUNTS_REGISTRY_ID)); + } + + function getAttestations() internal view returns (IAttestations) { + return IAttestations(registry.getAddressForOrDie(ATTESTATIONS_REGISTRY_ID)); + } + + function getElection() internal view returns (IElection) { + return IElection(registry.getAddressForOrDie(ELECTION_REGISTRY_ID)); + } + + function getExchange() internal view returns (IExchange) { + return IExchange(registry.getAddressForOrDie(EXCHANGE_REGISTRY_ID)); + } + + function getFreezer() internal view returns (IFreezer) { + return IFreezer(registry.getAddressForOrDie(FREEZER_REGISTRY_ID)); + } + + function getGoldToken() internal view returns (IERC20) { + return IERC20(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + function getGovernance() internal view returns (IGovernance) { + return IGovernance(registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID)); + } + + function getLockedGold() internal view returns (ILockedGold) { + return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID)); + } + + function getRandom() internal view returns (IRandom) { + return IRandom(registry.getAddressForOrDie(RANDOM_REGISTRY_ID)); + } + + function getReserve() internal view returns (IReserve) { + return IReserve(registry.getAddressForOrDie(RESERVE_REGISTRY_ID)); + } + + function getSortedOracles() internal view returns (ISortedOracles) { + return ISortedOracles(registry.getAddressForOrDie(SORTED_ORACLES_REGISTRY_ID)); + } + + function getStableToken() internal view returns (IStableToken) { + return IStableToken(registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID)); + } + + function getValidators() internal view returns (IValidators) { + return IValidators(registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID)); + } +} diff --git a/packages/contracts-bedrock/src/celo/common/FixidityLib.sol b/packages/contracts-bedrock/src/celo/common/FixidityLib.sol new file mode 100644 index 0000000000000..613da18562198 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/FixidityLib.sol @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title FixidityLib + * @author Gadi Guy, Alberto Cuesta Canada + * @notice This library provides fixed point arithmetic with protection against + * overflow. + * All operations are done with uint256 and the operands must have been created + * with any of the newFrom* functions, which shift the comma digits() to the + * right and check for limits, or with wrap() which expects a number already + * in the internal representation of a fraction. + * When using this library be sure to use maxNewFixed() as the upper limit for + * creation of fixed point numbers. + * @dev All contained functions are pure and thus marked internal to be inlined + * on consuming contracts at compile time for gas efficiency. + */ +library FixidityLib { + struct Fraction { + uint256 value; + } + + /** + * @notice Number of positions that the comma is shifted to the right. + */ + function digits() internal pure returns (uint8) { + return 24; + } + + uint256 private constant FIXED1_UINT = 1000000000000000000000000; + + /** + * @notice This is 1 in the fixed point units used in this library. + * @dev Test fixed1() equals 10^digits() + * Hardcoded to 24 digits. + */ + function fixed1() internal pure returns (Fraction memory) { + return Fraction(FIXED1_UINT); + } + + /** + * @notice Wrap a uint256 that represents a 24-decimal fraction in a Fraction + * struct. + * @param x Number that already represents a 24-decimal fraction. + * @return A Fraction struct with contents x. + */ + function wrap(uint256 x) internal pure returns (Fraction memory) { + return Fraction(x); + } + + /** + * @notice Unwraps the uint256 inside of a Fraction struct. + */ + function unwrap(Fraction memory x) internal pure returns (uint256) { + return x.value; + } + + /** + * @notice The amount of decimals lost on each multiplication operand. + * @dev Test mulPrecision() equals sqrt(fixed1) + */ + function mulPrecision() internal pure returns (uint256) { + return 1000000000000; + } + + /** + * @notice Maximum value that can be converted to fixed point. Optimize for deployment. + * @dev + * Test maxNewFixed() equals maxUint256() / fixed1() + */ + function maxNewFixed() internal pure returns (uint256) { + return 115792089237316195423570985008687907853269984665640564; + } + + /** + * @notice Converts a uint256 to fixed point Fraction + * @dev Test newFixed(0) returns 0 + * Test newFixed(1) returns fixed1() + * Test newFixed(maxNewFixed()) returns maxNewFixed() * fixed1() + * Test newFixed(maxNewFixed()+1) fails + */ + function newFixed(uint256 x) internal pure returns (Fraction memory) { + require(x <= maxNewFixed(), "can't create fixidity number larger than maxNewFixed()"); + return Fraction(x * FIXED1_UINT); + } + + /** + * @notice Converts a uint256 in the fixed point representation of this + * library to a non decimal. All decimal digits will be truncated. + */ + function fromFixed(Fraction memory x) internal pure returns (uint256) { + return x.value / FIXED1_UINT; + } + + /** + * @notice Converts two uint256 representing a fraction to fixed point units, + * equivalent to multiplying dividend and divisor by 10^digits(). + * @param numerator numerator must be <= maxNewFixed() + * @param denominator denominator must be <= maxNewFixed() and denominator can't be 0 + * @dev + * Test newFixedFraction(1,0) fails + * Test newFixedFraction(0,1) returns 0 + * Test newFixedFraction(1,1) returns fixed1() + * Test newFixedFraction(1,fixed1()) returns 1 + */ + function newFixedFraction(uint256 numerator, uint256 denominator) internal pure returns (Fraction memory) { + Fraction memory convertedNumerator = newFixed(numerator); + Fraction memory convertedDenominator = newFixed(denominator); + return divide(convertedNumerator, convertedDenominator); + } + + /** + * @notice Returns the integer part of a fixed point number. + * @dev + * Test integer(0) returns 0 + * Test integer(fixed1()) returns fixed1() + * Test integer(newFixed(maxNewFixed())) returns maxNewFixed()*fixed1() + */ + function integer(Fraction memory x) internal pure returns (Fraction memory) { + return Fraction((x.value / FIXED1_UINT) * FIXED1_UINT); // Can't overflow + } + + /** + * @notice Returns the fractional part of a fixed point number. + * In the case of a negative number the fractional is also negative. + * @dev + * Test fractional(0) returns 0 + * Test fractional(fixed1()) returns 0 + * Test fractional(fixed1()-1) returns 10^24-1 + */ + function fractional(Fraction memory x) internal pure returns (Fraction memory) { + return Fraction(x.value - (x.value / FIXED1_UINT) * FIXED1_UINT); // Can't overflow + } + + /** + * @notice x+y. + * @dev The maximum value that can be safely used as an addition operator is defined as + * maxFixedAdd = maxUint256()-1 / 2, or + * 57896044618658097711785492504343953926634992332820282019728792003956564819967. + * Test add(maxFixedAdd,maxFixedAdd) equals maxFixedAdd + maxFixedAdd + * Test add(maxFixedAdd+1,maxFixedAdd+1) throws + */ + function add(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + uint256 z = x.value + y.value; + require(z >= x.value, "add overflow detected"); + return Fraction(z); + } + + /** + * @notice x-y. + * @dev + * Test subtract(6, 10) fails + */ + function subtract(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + require(x.value >= y.value, "substraction underflow detected"); + return Fraction(x.value - y.value); + } + + /** + * @notice x*y. If any of the operators is higher than the max multiplier value it + * might overflow. + * @dev The maximum value that can be safely used as a multiplication operator + * (maxFixedMul) is calculated as sqrt(maxUint256()*fixed1()), + * or 340282366920938463463374607431768211455999999999999 + * Test multiply(0,0) returns 0 + * Test multiply(maxFixedMul,0) returns 0 + * Test multiply(0,maxFixedMul) returns 0 + * Test multiply(fixed1()/mulPrecision(),fixed1()*mulPrecision()) returns fixed1() + * Test multiply(maxFixedMul,maxFixedMul) is around maxUint256() + * Test multiply(maxFixedMul+1,maxFixedMul+1) fails + */ + function multiply(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + if (x.value == 0 || y.value == 0) return Fraction(0); + if (y.value == FIXED1_UINT) return x; + if (x.value == FIXED1_UINT) return y; + + // Separate into integer and fractional parts + // x = x1 + x2, y = y1 + y2 + uint256 x1 = integer(x).value / FIXED1_UINT; + uint256 x2 = fractional(x).value; + uint256 y1 = integer(y).value / FIXED1_UINT; + uint256 y2 = fractional(y).value; + + // (x1 + x2) * (y1 + y2) = (x1 * y1) + (x1 * y2) + (x2 * y1) + (x2 * y2) + uint256 x1y1 = x1 * y1; + if (x1 != 0) require(x1y1 / x1 == y1, "overflow x1y1 detected"); + + // x1y1 needs to be multiplied back by fixed1 + // solium-disable-next-line mixedcase + uint256 fixed_x1y1 = x1y1 * FIXED1_UINT; + if (x1y1 != 0) require(fixed_x1y1 / x1y1 == FIXED1_UINT, "overflow x1y1 * fixed1 detected"); + x1y1 = fixed_x1y1; + + uint256 x2y1 = x2 * y1; + if (x2 != 0) require(x2y1 / x2 == y1, "overflow x2y1 detected"); + + uint256 x1y2 = x1 * y2; + if (x1 != 0) require(x1y2 / x1 == y2, "overflow x1y2 detected"); + + x2 = x2 / mulPrecision(); + y2 = y2 / mulPrecision(); + uint256 x2y2 = x2 * y2; + if (x2 != 0) require(x2y2 / x2 == y2, "overflow x2y2 detected"); + + // result = fixed1() * x1 * y1 + x1 * y2 + x2 * y1 + x2 * y2 / fixed1(); + Fraction memory result = Fraction(x1y1); + result = add(result, Fraction(x2y1)); // Add checks for overflow + result = add(result, Fraction(x1y2)); // Add checks for overflow + result = add(result, Fraction(x2y2)); // Add checks for overflow + return result; + } + + /** + * @notice 1/x + * @dev + * Test reciprocal(0) fails + * Test reciprocal(fixed1()) returns fixed1() + * Test reciprocal(fixed1()*fixed1()) returns 1 // Testing how the fractional is truncated + * Test reciprocal(1+fixed1()*fixed1()) returns 0 // Testing how the fractional is truncated + * Test reciprocal(newFixedFraction(1, 1e24)) returns newFixed(1e24) + */ + function reciprocal(Fraction memory x) internal pure returns (Fraction memory) { + require(x.value != 0, "can't call reciprocal(0)"); + return Fraction((FIXED1_UINT * FIXED1_UINT) / x.value); // Can't overflow + } + + /** + * @notice x/y. If the dividend is higher than the max dividend value, it + * might overflow. You can use multiply(x,reciprocal(y)) instead. + * @dev The maximum value that can be safely used as a dividend (maxNewFixed) is defined as + * divide(maxNewFixed,newFixedFraction(1,fixed1())) is around maxUint256(). + * This yields the value 115792089237316195423570985008687907853269984665640564. + * Test maxNewFixed equals maxUint256()/fixed1() + * Test divide(maxNewFixed,1) equals maxNewFixed*(fixed1) + * Test divide(maxNewFixed+1,multiply(mulPrecision(),mulPrecision())) throws + * Test divide(fixed1(),0) fails + * Test divide(maxNewFixed,1) = maxNewFixed*(10^digits()) + * Test divide(maxNewFixed+1,1) throws + */ + function divide(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + require(y.value != 0, "can't divide by 0"); + uint256 X = x.value * FIXED1_UINT; + require(X / FIXED1_UINT == x.value, "overflow at divide"); + return Fraction(X / y.value); + } + + /** + * @notice x > y + */ + function gt(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value > y.value; + } + + /** + * @notice x >= y + */ + function gte(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value >= y.value; + } + + /** + * @notice x < y + */ + function lt(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value < y.value; + } + + /** + * @notice x <= y + */ + function lte(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value <= y.value; + } + + /** + * @notice x == y + */ + function equals(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value == y.value; + } + + /** + * @notice x <= 1 + */ + function isProperFraction(Fraction memory x) internal pure returns (bool) { + return lte(x, fixed1()); + } +} diff --git a/packages/contracts-bedrock/src/celo/common/Freezable.sol b/packages/contracts-bedrock/src/celo/common/Freezable.sol new file mode 100644 index 0000000000000..7541ea6fa5717 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/Freezable.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../UsingRegistry.sol"; + +contract Freezable is UsingRegistry { + // onlyWhenNotFrozen functions can only be called when `frozen` is false, otherwise they will + // revert. + modifier onlyWhenNotFrozen() { + require(!getFreezer().isFrozen(address(this)), "can't call when contract is frozen"); + _; + } +} diff --git a/packages/contracts-bedrock/src/celo/common/Initializable.sol b/packages/contracts-bedrock/src/celo/common/Initializable.sol new file mode 100644 index 0000000000000..92baac5494d3b --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/Initializable.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +contract Initializable { + bool public initialized; + + constructor(bool testingDeployment) { + if (!testingDeployment) { + initialized = true; + } + } + + modifier initializer() { + require(!initialized, "contract already initialized"); + initialized = true; + _; + } +} diff --git a/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol b/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol new file mode 100644 index 0000000000000..5bf2033f31726 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the non- ERC20 shared interface for all Celo Tokens, and + * in the absence of interface inheritance is intended as a companion to IERC20.sol. + */ +interface ICeloToken { + function transferWithComment(address, uint256, string calldata) external returns (bool); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function burn(uint256 value) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol b/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol new file mode 100644 index 0000000000000..37b1538c2a121 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface ICeloVersionedContract { + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256); +} diff --git a/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol b/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol new file mode 100644 index 0000000000000..b707a446a685a --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +import "../FixidityLib.sol"; + +interface IFeeHandler { + // sets the portion of the fee that should be burned. + function setBurnFraction(uint256 fraction) external; + + function addToken(address tokenAddress, address handlerAddress) external; + function removeToken(address tokenAddress) external; + + function setHandler(address tokenAddress, address handlerAddress) external; + + // marks token to be handled in "handleAll()) + function activateToken(address tokenAddress) external; + function deactivateToken(address tokenAddress) external; + + function sell(address tokenAddress) external; + + // calls exchange(tokenAddress), and distribute(tokenAddress) + function handle(address tokenAddress) external; + + // main entrypoint for a burn, iterates over token and calles handle + function handleAll() external; + + // Sends the balance of token at tokenAddress to feesBeneficiary, + // according to the entry tokensToDistribute[tokenAddress] + function distribute(address tokenAddress) external; + + // burns the balance of Celo in the contract minus the entry of tokensToDistribute[CeloAddress] + function burnCelo() external; + + // calls distribute for all the nonCeloTokens + function distributeAll() external; + + // in case some funds need to be returned or moved to another contract + function transfer(address token, address recipient, uint256 value) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol b/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol new file mode 100644 index 0000000000000..c3a9df0ee324a --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +import "../FixidityLib.sol"; + +interface IFeeHandlerSeller { + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 minAmount + ) + external + returns (uint256); + // in case some funds need to be returned or moved to another contract + function transfer(address token, uint256 amount, address to) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol new file mode 100644 index 0000000000000..38ae7359e0e06 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "@openzeppelin/contracts/utils/math/Math.sol"; + +import "./SortedLinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by address. + */ +library AddressSortedLinkedList { + using SortedLinkedList for SortedLinkedList.List; + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert( + SortedLinkedList.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.insert(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(SortedLinkedList.List storage list, address key) public { + list.remove(toBytes(key)); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update( + SortedLinkedList.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.update(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(SortedLinkedList.List storage list, address key) public view returns (bool) { + return list.contains(toBytes(key)); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(SortedLinkedList.List storage list, address key) public view returns (uint256) { + return list.getValue(toBytes(key)); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + */ + function getElements(SortedLinkedList.List storage list) public view returns (address[] memory, uint256[] memory) { + bytes32[] memory byteKeys = list.getKeys(); + address[] memory keys = new address[](byteKeys.length); + uint256[] memory values = new uint256[](byteKeys.length); + for (uint256 i = 0; i < byteKeys.length; i = i + 1) { + keys[i] = toAddress(byteKeys[i]); + values[i] = list.values[byteKeys[i]]; + } + return (keys, values); + } + + /** + * @notice Returns the minimum of `max` and the number of elements in the list > threshold. + * @param list A storage pointer to the underlying list. + * @param threshold The number that the element must exceed to be included. + * @param max The maximum number returned by this function. + * @return The minimum of `max` and the number of elements in the list > threshold. + */ + function numElementsGreaterThan( + SortedLinkedList.List storage list, + uint256 threshold, + uint256 max + ) + public + view + returns (uint256) + { + uint256 revisedMax = Math.min(max, list.list.numElements); + bytes32 key = list.list.head; + for (uint256 i = 0; i < revisedMax; i = i + 1) { + if (list.getValue(key) < threshold) { + return i; + } + key = list.list.elements[key].previousKey; + } + return revisedMax; + } + + /** + * @notice Returns the N greatest elements of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the greatest elements. + */ + function headN(SortedLinkedList.List storage list, uint256 n) public view returns (address[] memory) { + bytes32[] memory byteKeys = list.headN(n); + address[] memory keys = new address[](n); + for (uint256 i = 0; i < n; i = i + 1) { + keys[i] = toAddress(byteKeys[i]); + } + return keys; + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(SortedLinkedList.List storage list) public view returns (address[] memory) { + return headN(list, list.list.numElements); + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(SortedLinkedList.List storage list) public view returns (uint256) { + return list.list.numElements; + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(SortedLinkedList.List storage list) public view returns (address) { + return toAddress(list.list.head); + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(SortedLinkedList.List storage list) public view returns (address) { + return toAddress(list.list.tail); + } + + /** + * @notice Gets lesser and greater for address that has increased it's value. + * @param list A storage pointer to the underlying list. + * @param group The original address. + * @param newValue New value that has to be bigger or equal than the previous one. + * @param loopLimit The max limit of loops that will be executed. + */ + function getLesserAndGreaterOfAddressThatIncreasedValue( + SortedLinkedList.List storage list, + address group, + uint256 newValue, + uint256 loopLimit + ) + public + view + returns (address previous, address next) + { + (, previous, next) = get(list, group); + + while (next != address(0) && loopLimit != 0 && newValue > getValue(list, next)) { + previous = next; + (,, next) = get(list, previous); + loopLimit--; + } + + if (loopLimit == 0) { + return (address(0), address(0)); + } + } + + /** + * @notice Gets lesser and greater for address that has decreased it's value. + * @param list A storage pointer to the underlying list. + * @param group The original address. + * @param newValue New value that has to be smaller or equal than the previous one. + * @param loopLimit The max limit of loops that will be executed. + */ + function getLesserAndGreaterOfAddressThatDecreasedValue( + SortedLinkedList.List storage list, + address group, + uint256 newValue, + uint256 loopLimit + ) + public + view + returns (address previous, address next) + { + (, previous, next) = get(list, group); + while (previous != address(0) && loopLimit != 0 && newValue < getValue(list, previous)) { + next = previous; + (, previous,) = get(list, next); + loopLimit--; + } + if (loopLimit == 0) { + return (address(0), address(0)); + } + } + + function toBytes(address a) public pure returns (bytes32) { + return bytes32(uint256(uint160(a)) << 96); + } + + function toAddress(bytes32 b) public pure returns (address) { + return address(uint160(uint256(b) >> 96)); + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return exists Whether or not the key exists. + * @return previousKey Previous key. + * @return nextKey Next key. + */ + function get( + SortedLinkedList.List storage list, + address key + ) + internal + view + returns (bool exists, address previousKey, address nextKey) + { + LinkedList.Element memory element = list.get(toBytes(key)); + exists = element.exists; + if (element.exists) { + previousKey = toAddress(element.previousKey); + nextKey = toAddress(element.nextKey); + } + } +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol new file mode 100644 index 0000000000000..2ddf56612244e --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "./SortedLinkedListWithMedian.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by address. + */ +library AddressSortedLinkedListWithMedian { + using SortedLinkedListWithMedian for SortedLinkedListWithMedian.List; + + function toBytes(address a) public pure returns (bytes32) { + return bytes32(uint256(uint160(a)) << 96); + } + + function toAddress(bytes32 b) public pure returns (address) { + return address(uint160(uint256(b) >> 96)); + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert( + SortedLinkedListWithMedian.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.insert(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(SortedLinkedListWithMedian.List storage list, address key) public { + list.remove(toBytes(key)); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update( + SortedLinkedListWithMedian.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.update(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(SortedLinkedListWithMedian.List storage list, address key) public view returns (bool) { + return list.contains(toBytes(key)); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(SortedLinkedListWithMedian.List storage list, address key) public view returns (uint256) { + return list.getValue(toBytes(key)); + } + + /** + * @notice Returns the median value of the sorted list. + * @param list A storage pointer to the underlying list. + * @return The median value. + */ + function getMedianValue(SortedLinkedListWithMedian.List storage list) public view returns (uint256) { + return list.getValue(list.median); + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getHead()); + } + + /** + * @notice Returns the key of the median element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the median element in the list. + */ + function getMedian(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getMedian()); + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getTail()); + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(SortedLinkedListWithMedian.List storage list) external view returns (uint256) { + return list.getNumElements(); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + * @return Array of relations to median of corresponding list elements. + */ + function getElements(SortedLinkedListWithMedian.List storage list) + public + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + bytes32[] memory byteKeys = list.getKeys(); + address[] memory keys = new address[](byteKeys.length); + uint256[] memory values = new uint256[](byteKeys.length); + // prettier-ignore + SortedLinkedListWithMedian.MedianRelation[] memory relations = + new SortedLinkedListWithMedian.MedianRelation[](keys.length); + for (uint256 i = 0; i < byteKeys.length; i++) { + keys[i] = toAddress(byteKeys[i]); + values[i] = list.getValue(byteKeys[i]); + relations[i] = list.relation[byteKeys[i]]; + } + return (keys, values, relations); + } +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol new file mode 100644 index 0000000000000..d04e8b7e027cb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +/** + * @title Maintains a doubly linked list keyed by bytes32. + * @dev Following the `next` pointers will lead you to the head, rather than the tail. + */ +library LinkedList { + struct Element { + bytes32 previousKey; + bytes32 nextKey; + bool exists; + } + + struct List { + bytes32 head; + bytes32 tail; + uint256 numElements; + mapping(bytes32 => Element) elements; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param previousKey The key of the element that comes before the element to insert. + * @param nextKey The key of the element that comes after the element to insert. + */ + function insert(List storage list, bytes32 key, bytes32 previousKey, bytes32 nextKey) internal { + require(key != bytes32(0), "Key must be defined"); + require(!contains(list, key), "Can't insert an existing element"); + require(previousKey != key && nextKey != key, "Key cannot be the same as previousKey or nextKey"); + + Element storage element = list.elements[key]; + element.exists = true; + + if (list.numElements == 0) { + list.tail = key; + list.head = key; + } else { + require(previousKey != bytes32(0) || nextKey != bytes32(0), "Either previousKey or nextKey must be defined"); + + element.previousKey = previousKey; + element.nextKey = nextKey; + + if (previousKey != bytes32(0)) { + require(contains(list, previousKey), "If previousKey is defined, it must exist in the list"); + Element storage previousElement = list.elements[previousKey]; + require(previousElement.nextKey == nextKey, "previousKey must be adjacent to nextKey"); + previousElement.nextKey = key; + } else { + list.tail = key; + } + + if (nextKey != bytes32(0)) { + require(contains(list, nextKey), "If nextKey is defined, it must exist in the list"); + Element storage nextElement = list.elements[nextKey]; + require(nextElement.previousKey == previousKey, "previousKey must be adjacent to nextKey"); + nextElement.previousKey = key; + } else { + list.head = key; + } + } + + list.numElements = list.numElements + 1; + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, bytes32(0), list.tail); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + Element storage element = list.elements[key]; + require(key != bytes32(0) && contains(list, key), "key not in list"); + if (element.previousKey != bytes32(0)) { + Element storage previousElement = list.elements[element.previousKey]; + previousElement.nextKey = element.nextKey; + } else { + list.tail = element.nextKey; + } + + if (element.nextKey != bytes32(0)) { + Element storage nextElement = list.elements[element.nextKey]; + nextElement.previousKey = element.previousKey; + } else { + list.head = element.previousKey; + } + + delete list.elements[key]; + list.numElements = list.numElements - 1; + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param previousKey The key of the element that comes before the updated element. + * @param nextKey The key of the element that comes after the updated element. + */ + function update(List storage list, bytes32 key, bytes32 previousKey, bytes32 nextKey) internal { + require(key != bytes32(0) && key != previousKey && key != nextKey && contains(list, key), "key on in list"); + remove(list, key); + insert(list, key, previousKey, nextKey); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.elements[key].exists; + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function get(List storage list, bytes32 key) internal view returns (Element memory) { + return list.elements[key]; + } + + /** + * @notice Returns the keys of the N elements at the head of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the N elements at the head of the list. + * @dev Reverts if n is greater than the number of elements in the list. + */ + function headN(List storage list, uint256 n) internal view returns (bytes32[] memory) { + require(n <= list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + bytes32 key = list.head; + for (uint256 i = 0; i < n; i = i + 1) { + keys[i] = key; + key = list.elements[key].previousKey; + } + return keys; + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return headN(list, list.numElements); + } +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol new file mode 100644 index 0000000000000..9703cf565523d --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "./LinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by bytes32. + */ +library SortedLinkedList { + using LinkedList for LinkedList.List; + + struct List { + LinkedList.List list; + mapping(bytes32 => uint256) values; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + require(key != bytes32(0) && key != lesserKey && key != greaterKey && !contains(list, key), "invalid key"); + require( + (lesserKey != bytes32(0) || greaterKey != bytes32(0)) || list.list.numElements == 0, + "greater and lesser key zero" + ); + require(contains(list, lesserKey) || lesserKey == bytes32(0), "invalid lesser key"); + require(contains(list, greaterKey) || greaterKey == bytes32(0), "invalid greater key"); + (lesserKey, greaterKey) = getLesserAndGreater(list, value, lesserKey, greaterKey); + list.list.insert(key, lesserKey, greaterKey); + list.values[key] = value; + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + list.list.remove(key); + list.values[key] = 0; + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + remove(list, key); + insert(list, key, value, lesserKey, greaterKey); + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, 0, bytes32(0), list.list.tail); + } + + /** + * @notice Removes N elements from the head of the list and returns their keys. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to pop. + * @return The keys of the popped elements. + */ + function popN(List storage list, uint256 n) internal returns (bytes32[] memory) { + require(n <= list.list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + for (uint256 i = 0; i < n; i = i + 1) { + bytes32 key = list.list.head; + keys[i] = key; + remove(list, key); + } + return keys; + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.list.contains(key); + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function get(List storage list, bytes32 key) internal view returns (LinkedList.Element memory) { + return list.list.get(key); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(List storage list, bytes32 key) internal view returns (uint256) { + return list.values[key]; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + */ + function getElements(List storage list) internal view returns (bytes32[] memory, uint256[] memory) { + bytes32[] memory keys = getKeys(list); + uint256[] memory values = new uint256[](keys.length); + for (uint256 i = 0; i < keys.length; i = i + 1) { + values[i] = list.values[keys[i]]; + } + return (keys, values); + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return list.list.getKeys(); + } + + /** + * @notice Returns first N greatest elements of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the first n elements. + * @dev Reverts if n is greater than the number of elements in the list. + */ + function headN(List storage list, uint256 n) internal view returns (bytes32[] memory) { + return list.list.headN(n); + } + + /** + * @notice Returns the keys of the elements greaterKey than and less than the provided value. + * @param list A storage pointer to the underlying list. + * @param value The element value. + * @param lesserKey The key of the element which could be just left of the new value. + * @param greaterKey The key of the element which could be just right of the new value. + * @return The correct lesserKey keys. + * @return The correct greaterKey keys. + */ + function getLesserAndGreater( + List storage list, + uint256 value, + bytes32 lesserKey, + bytes32 greaterKey + ) + private + view + returns (bytes32, bytes32) + { + // Check for one of the following conditions and fail if none are met: + // 1. The value is less than the current lowest value + // 2. The value is greater than the current greatest value + // 3. The value is just greater than the value for `lesserKey` + // 4. The value is just less than the value for `greaterKey` + if (lesserKey == bytes32(0) && isValueBetween(list, value, lesserKey, list.list.tail)) { + return (lesserKey, list.list.tail); + } else if (greaterKey == bytes32(0) && isValueBetween(list, value, list.list.head, greaterKey)) { + return (list.list.head, greaterKey); + } else if ( + lesserKey != bytes32(0) && isValueBetween(list, value, lesserKey, list.list.elements[lesserKey].nextKey) + ) { + return (lesserKey, list.list.elements[lesserKey].nextKey); + } else if ( + greaterKey != bytes32(0) + && isValueBetween(list, value, list.list.elements[greaterKey].previousKey, greaterKey) + ) { + return (list.list.elements[greaterKey].previousKey, greaterKey); + } + + require(false, "get lesser and greater failure"); + return (0, 0); + } + + /** + * @notice Returns whether or not a given element is between two other elements. + * @param list A storage pointer to the underlying list. + * @param value The element value. + * @param lesserKey The key of the element whose value should be lesserKey. + * @param greaterKey The key of the element whose value should be greaterKey. + * @return True if the given element is between the two other elements. + */ + function isValueBetween( + List storage list, + uint256 value, + bytes32 lesserKey, + bytes32 greaterKey + ) + private + view + returns (bool) + { + bool isLesser = lesserKey == bytes32(0) || list.values[lesserKey] <= value; + bool isGreater = greaterKey == bytes32(0) || list.values[greaterKey] >= value; + return isLesser && isGreater; + } +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol new file mode 100644 index 0000000000000..458ef55422077 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "./LinkedList.sol"; +import "./SortedLinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by bytes32. + */ +library SortedLinkedListWithMedian { + using SortedLinkedList for SortedLinkedList.List; + + enum MedianAction { + None, + Lesser, + Greater + } + + enum MedianRelation { + Undefined, + Lesser, + Greater, + Equal + } + + struct List { + SortedLinkedList.List list; + bytes32 median; + mapping(bytes32 => MedianRelation) relation; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + list.list.insert(key, value, lesserKey, greaterKey); + LinkedList.Element storage element = list.list.list.elements[key]; + + MedianAction action = MedianAction.None; + if (list.list.list.numElements == 1) { + list.median = key; + list.relation[key] = MedianRelation.Equal; + } else if (list.list.list.numElements % 2 == 1) { + // When we have an odd number of elements, and the element that we inserted is less than + // the previous median, we need to slide the median down one element, since we had previously + // selected the greater of the two middle elements. + if (element.previousKey == bytes32(0) || list.relation[element.previousKey] == MedianRelation.Lesser) { + action = MedianAction.Lesser; + list.relation[key] = MedianRelation.Lesser; + } else { + list.relation[key] = MedianRelation.Greater; + } + } else { + // When we have an even number of elements, and the element that we inserted is greater than + // the previous median, we need to slide the median up one element, since we always select + // the greater of the two middle elements. + if (element.nextKey == bytes32(0) || list.relation[element.nextKey] == MedianRelation.Greater) { + action = MedianAction.Greater; + list.relation[key] = MedianRelation.Greater; + } else { + list.relation[key] = MedianRelation.Lesser; + } + } + updateMedian(list, action); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + MedianAction action = MedianAction.None; + if (list.list.list.numElements == 0) { + list.median = bytes32(0); + } else if (list.list.list.numElements % 2 == 0) { + // When we have an even number of elements, we always choose the higher of the two medians. + // Thus, if the element we're removing is greaterKey than or equal to the median we need to + // slide the median left by one. + if (list.relation[key] == MedianRelation.Greater || list.relation[key] == MedianRelation.Equal) { + action = MedianAction.Lesser; + } + } else { + // When we don't have an even number of elements, we just choose the median value. + // Thus, if the element we're removing is less than or equal to the median, we need to slide + // median right by one. + if (list.relation[key] == MedianRelation.Lesser || list.relation[key] == MedianRelation.Equal) { + action = MedianAction.Greater; + } + } + updateMedian(list, action); + + list.list.remove(key); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + remove(list, key); + insert(list, key, value, lesserKey, greaterKey); + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, 0, bytes32(0), list.list.list.tail); + } + + /** + * @notice Removes N elements from the head of the list and returns their keys. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to pop. + * @return The keys of the popped elements. + */ + function popN(List storage list, uint256 n) internal returns (bytes32[] memory) { + require(n <= list.list.list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + for (uint256 i = 0; i < n; i++) { + bytes32 key = list.list.list.head; + keys[i] = key; + remove(list, key); + } + return keys; + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.list.contains(key); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(List storage list, bytes32 key) internal view returns (uint256) { + return list.list.values[key]; + } + + /** + * @notice Returns the median value of the sorted list. + * @param list A storage pointer to the underlying list. + * @return The median value. + */ + function getMedianValue(List storage list) internal view returns (uint256) { + return getValue(list, list.median); + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(List storage list) internal view returns (bytes32) { + return list.list.list.head; + } + + /** + * @notice Returns the key of the median element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the median element in the list. + */ + function getMedian(List storage list) internal view returns (bytes32) { + return list.median; + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(List storage list) internal view returns (bytes32) { + return list.list.list.tail; + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(List storage list) internal view returns (uint256) { + return list.list.list.numElements; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + * @return Array of relations to median of corresponding list elements. + */ + function getElements(List storage list) + internal + view + returns (bytes32[] memory, uint256[] memory, MedianRelation[] memory) + { + bytes32[] memory keys = getKeys(list); + uint256[] memory values = new uint256[](keys.length); + MedianRelation[] memory relations = new MedianRelation[](keys.length); + for (uint256 i = 0; i < keys.length; i++) { + values[i] = list.list.values[keys[i]]; + relations[i] = list.relation[keys[i]]; + } + return (keys, values, relations); + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return list.list.getKeys(); + } + + /** + * @notice Moves the median pointer right or left of its current value. + * @param list A storage pointer to the underlying list. + * @param action Which direction to move the median pointer. + */ + function updateMedian(List storage list, MedianAction action) private { + LinkedList.Element storage previousMedian = list.list.list.elements[list.median]; + if (action == MedianAction.Lesser) { + list.relation[list.median] = MedianRelation.Greater; + list.median = previousMedian.previousKey; + } else if (action == MedianAction.Greater) { + list.relation[list.median] = MedianRelation.Lesser; + list.median = previousMedian.nextKey; + } + list.relation[list.median] = MedianRelation.Equal; + } +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol new file mode 100644 index 0000000000000..f099ce364a270 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IElection { + function electValidatorSigners() external view returns (address[] memory); + function electNValidatorSigners(uint256, uint256) external view returns (address[] memory); + function vote(address, uint256, address, address) external returns (bool); + function activate(address) external returns (bool); + function revokeActive(address, uint256, address, address, uint256) external returns (bool); + function revokeAllActive(address, address, address, uint256) external returns (bool); + function revokePending(address, uint256, address, address, uint256) external returns (bool); + function markGroupIneligible(address) external; + function markGroupEligible(address, address, address) external; + function allowedToVoteOverMaxNumberOfGroups(address) external returns (bool); + function forceDecrementVotes( + address, + uint256, + address[] calldata, + address[] calldata, + uint256[] calldata + ) + external + returns (uint256); + function setAllowedToVoteOverMaxNumberOfGroups(bool flag) external; + + // view functions + function getElectableValidators() external view returns (uint256, uint256); + function getElectabilityThreshold() external view returns (uint256); + function getNumVotesReceivable(address) external view returns (uint256); + function getTotalVotes() external view returns (uint256); + function getActiveVotes() external view returns (uint256); + function getTotalVotesByAccount(address) external view returns (uint256); + function getPendingVotesForGroupByAccount(address, address) external view returns (uint256); + function getActiveVotesForGroupByAccount(address, address) external view returns (uint256); + function getTotalVotesForGroupByAccount(address, address) external view returns (uint256); + function getActiveVoteUnitsForGroupByAccount(address, address) external view returns (uint256); + function getTotalVotesForGroup(address) external view returns (uint256); + function getActiveVotesForGroup(address) external view returns (uint256); + function getPendingVotesForGroup(address) external view returns (uint256); + function getGroupEligibility(address) external view returns (bool); + function getGroupEpochRewards(address, uint256, uint256[] calldata) external view returns (uint256); + function getGroupsVotedForByAccount(address) external view returns (address[] memory); + function getEligibleValidatorGroups() external view returns (address[] memory); + function getTotalVotesForEligibleValidatorGroups() external view returns (address[] memory, uint256[] memory); + function getCurrentValidatorSigners() external view returns (address[] memory); + function canReceiveVotes(address, uint256) external view returns (bool); + function hasActivatablePendingVotes(address, address) external view returns (bool); + function validatorSignerAddressFromCurrentSet(uint256 index) external view returns (address); + function numberValidatorsInCurrentSet() external view returns (uint256); + + // only owner + function setElectableValidators(uint256, uint256) external returns (bool); + function setMaxNumGroupsVotedFor(uint256) external returns (bool); + function setElectabilityThreshold(uint256) external returns (bool); + + // only VM + function distributeEpochRewards(address, uint256, address, address) external; +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol new file mode 100644 index 0000000000000..883844ea8f219 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IGovernance { + function votePartially( + uint256 proposalId, + uint256 index, + uint256 yesVotes, + uint256 noVotes, + uint256 abstainVotes + ) + external + returns (bool); + + function isVoting(address) external view returns (bool); + function getAmountOfGoldUsedForVoting(address account) external view returns (uint256); + + function getProposal(uint256 proposalId) + external + view + returns (address, uint256, uint256, uint256, string memory, uint256, bool); + + function getReferendumStageDuration() external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol new file mode 100644 index 0000000000000..38002d58914c7 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface ILockedGold { + function lock() external payable; + function incrementNonvotingAccountBalance(address, uint256) external; + function decrementNonvotingAccountBalance(address, uint256) external; + function getAccountTotalLockedGold(address) external view returns (uint256); + function getTotalLockedGold() external view returns (uint256); + function getPendingWithdrawals(address) external view returns (uint256[] memory, uint256[] memory); + function getPendingWithdrawal(address account, uint256 index) external view returns (uint256, uint256); + function getTotalPendingWithdrawals(address) external view returns (uint256); + function unlock(uint256) external; + function relock(uint256, uint256) external; + function withdraw(uint256) external; + function slash( + address account, + uint256 penalty, + address reporter, + uint256 reward, + address[] calldata lessers, + address[] calldata greaters, + uint256[] calldata indices + ) + external; + function isSlasher(address) external view returns (bool); + function unlockingPeriod() external view returns (uint256); + function getAccountNonvotingLockedGold(address account) external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol new file mode 100644 index 0000000000000..e211ce7399e37 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IReleaseGold { + function transfer(address, uint256) external; + function unlockGold(uint256) external; + function withdrawLockedGold(uint256) external; + function authorizeVoteSigner(address payable, uint8, bytes32, bytes32) external; + function authorizeValidatorSigner(address payable, uint8, bytes32, bytes32) external; + function authorizeValidatorSignerWithPublicKey(address payable, uint8, bytes32, bytes32, bytes calldata) external; + function authorizeValidatorSignerWithKeys( + address payable, + uint8, + bytes32, + bytes32, + bytes calldata, + bytes calldata, + bytes calldata + ) + external; + function authorizeAttestationSigner(address payable, uint8, bytes32, bytes32) external; + function revokeActive(address, uint256, address, address, uint256) external; + function revokePending(address, uint256, address, address, uint256) external; + + // view functions + function getTotalBalance() external view returns (uint256); + function getRemainingTotalBalance() external view returns (uint256); + function getRemainingUnlockedBalance() external view returns (uint256); + function getRemainingLockedBalance() external view returns (uint256); + function getCurrentReleasedTotalAmount() external view returns (uint256); + function isRevoked() external view returns (bool); + + // only beneficiary + function setCanExpire(bool) external; + function withdraw(uint256) external; + function lockGold(uint256) external; + function relockGold(uint256, uint256) external; + function setAccount(string calldata, bytes calldata, address, uint8, bytes32, bytes32) external; + function createAccount() external; + function setAccountName(string calldata) external; + function setAccountWalletAddress(address, uint8, bytes32, bytes32) external; + function setAccountDataEncryptionKey(bytes calldata) external; + function setAccountMetadataURL(string calldata) external; + + // only owner + function setBeneficiary(address payable) external; + + // only release owner + function setLiquidityProvision() external; + function setMaxDistribution(uint256) external; + function refundAndFinalize() external; + function revoke() external; + function expire() external; +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol new file mode 100644 index 0000000000000..8a10e91fc8129 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IValidators { + function registerValidator(bytes calldata, bytes calldata, bytes calldata) external returns (bool); + function deregisterValidator(uint256) external returns (bool); + function affiliate(address) external returns (bool); + function deaffiliate() external returns (bool); + function updateBlsPublicKey(bytes calldata, bytes calldata) external returns (bool); + function registerValidatorGroup(uint256) external returns (bool); + function deregisterValidatorGroup(uint256) external returns (bool); + function addMember(address) external returns (bool); + function addFirstMember(address, address, address) external returns (bool); + function removeMember(address) external returns (bool); + function reorderMember(address, address, address) external returns (bool); + function updateCommission() external; + function setNextCommissionUpdate(uint256) external; + function resetSlashingMultiplier() external; + + // only owner + function setCommissionUpdateDelay(uint256) external; + function setMaxGroupSize(uint256) external returns (bool); + function setMembershipHistoryLength(uint256) external returns (bool); + function setValidatorScoreParameters(uint256, uint256) external returns (bool); + function setGroupLockedGoldRequirements(uint256, uint256) external returns (bool); + function setValidatorLockedGoldRequirements(uint256, uint256) external returns (bool); + function setSlashingMultiplierResetPeriod(uint256) external; + + // view functions + function getMaxGroupSize() external view returns (uint256); + function getCommissionUpdateDelay() external view returns (uint256); + function getValidatorScoreParameters() external view returns (uint256, uint256); + function getMembershipHistory(address) + external + view + returns (uint256[] memory, address[] memory, uint256, uint256); + function calculateEpochScore(uint256) external view returns (uint256); + function calculateGroupEpochScore(uint256[] calldata) external view returns (uint256); + function getAccountLockedGoldRequirement(address) external view returns (uint256); + function meetsAccountLockedGoldRequirements(address) external view returns (bool); + function getValidatorBlsPublicKeyFromSigner(address) external view returns (bytes memory); + function getValidator(address account) + external + view + returns (bytes memory, bytes memory, address, uint256, address); + function getValidatorGroup(address) + external + view + returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256); + function getGroupNumMembers(address) external view returns (uint256); + function getTopGroupValidators(address, uint256) external view returns (address[] memory); + function getGroupsNumMembers(address[] calldata accounts) external view returns (uint256[] memory); + function getNumRegisteredValidators() external view returns (uint256); + function groupMembershipInEpoch(address, uint256, uint256) external view returns (address); + + // only registered contract + function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool); + function updatePublicKeys( + address, + address, + bytes calldata, + bytes calldata, + bytes calldata + ) + external + returns (bool); + function getValidatorLockedGoldRequirements() external view returns (uint256, uint256); + function getGroupLockedGoldRequirements() external view returns (uint256, uint256); + function getRegisteredValidators() external view returns (address[] memory); + function getRegisteredValidatorSigners() external view returns (address[] memory); + function getRegisteredValidatorGroups() external view returns (address[] memory); + function isValidatorGroup(address) external view returns (bool); + function isValidator(address) external view returns (bool); + function getValidatorGroupSlashingMultiplier(address) external view returns (uint256); + function getMembershipInLastEpoch(address) external view returns (address); + function getMembershipInLastEpochFromSigner(address) external view returns (address); + + // only VM + function updateValidatorScoreFromSigner(address, uint256) external; + function distributeEpochPaymentsFromSigner(address, uint256) external returns (uint256); + + // only slasher + function forceDeaffiliateIfValidator(address) external; + function halveSlashingMultiplier(address) external; +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol new file mode 100644 index 0000000000000..5c1a1d7a8f484 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IAttestations { + function revoke(bytes32, uint256) external; + function withdraw(address) external; + + // view functions + function getUnselectedRequest(bytes32, address) external view returns (uint32, uint32, address); + function getAttestationIssuers(bytes32, address) external view returns (address[] memory); + function getAttestationStats(bytes32, address) external view returns (uint32, uint32); + function batchGetAttestationStats(bytes32[] calldata) + external + view + returns (uint256[] memory, address[] memory, uint64[] memory, uint64[] memory); + function getAttestationState(bytes32, address, address) external view returns (uint8, uint32, address); + function getCompletableAttestations( + bytes32, + address + ) + external + view + returns (uint32[] memory, address[] memory, uint256[] memory, bytes memory); + function getAttestationRequestFee(address) external view returns (uint256); + function getMaxAttestations() external view returns (uint256); + function validateAttestationCode(bytes32, address, uint8, bytes32, bytes32) external view returns (address); + function lookupAccountsForIdentifier(bytes32) external view returns (address[] memory); + function requireNAttestationsRequested(bytes32, address, uint32) external view; + + // only owner + function setAttestationRequestFee(address, uint256) external; + function setAttestationExpiryBlocks(uint256) external; + function setSelectIssuersWaitBlocks(uint256) external; + function setMaxAttestations(uint256) external; +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol new file mode 100644 index 0000000000000..87c145a4a1bb9 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface IEscrow { + function transfer( + bytes32 identifier, + address token, + uint256 value, + uint256 expirySeconds, + address paymentId, + uint256 minAttestations + ) + external + returns (bool); + function transferWithTrustedIssuers( + bytes32 identifier, + address token, + uint256 value, + uint256 expirySeconds, + address paymentId, + uint256 minAttestations, + address[] calldata trustedIssuers + ) + external + returns (bool); + function withdraw(address paymentID, uint8 v, bytes32 r, bytes32 s) external returns (bool); + function revoke(address paymentID) external returns (bool); + + // view functions + function getReceivedPaymentIds(bytes32 identifier) external view returns (address[] memory); + function getSentPaymentIds(address sender) external view returns (address[] memory); + function getTrustedIssuersPerPayment(address paymentId) external view returns (address[] memory); + function getDefaultTrustedIssuers() external view returns (address[] memory); + function MAX_TRUSTED_ISSUERS_PER_PAYMENT() external view returns (uint256); + + // onlyOwner functions + function addDefaultTrustedIssuer(address trustedIssuer) external; + function removeDefaultTrustedIssuer(address trustedIssuer, uint256 index) external; +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol new file mode 100644 index 0000000000000..c0586eb9e44dc --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface IFederatedAttestations { + function registerAttestationAsIssuer(bytes32 identifier, address account, uint64 issuedOn) external; + function registerAttestation( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn, + uint8 v, + bytes32 r, + bytes32 s + ) + external; + function revokeAttestation(bytes32 identifier, address issuer, address account) external; + function batchRevokeAttestations( + address issuer, + bytes32[] calldata identifiers, + address[] calldata accounts + ) + external; + + // view functions + function lookupAttestations( + bytes32 identifier, + address[] calldata trustedIssuers + ) + external + view + returns (uint256[] memory, address[] memory, address[] memory, uint64[] memory, uint64[] memory); + function lookupIdentifiers( + address account, + address[] calldata trustedIssuers + ) + external + view + returns (uint256[] memory, bytes32[] memory); + function validateAttestationSig( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn, + uint8 v, + bytes32 r, + bytes32 s + ) + external + view; + function getUniqueAttestationHash( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn + ) + external + pure + returns (bytes32); +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol new file mode 100644 index 0000000000000..ca188432c0dda --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface IOdisPayments { + function payInCUSD(address account, uint256 value) external; + function totalPaidCUSD(address) external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol new file mode 100644 index 0000000000000..65cf3082d685c --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IRandom { + function revealAndCommit(bytes32, bytes32, address) external; + function randomnessBlockRetentionWindow() external view returns (uint256); + function random() external view returns (bytes32); + function getBlockRandomness(uint256) external view returns (bytes32); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol b/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol new file mode 100644 index 0000000000000..734dcddeb941d --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IAccounts { + function isAccount(address) external view returns (bool); + function voteSignerToAccount(address) external view returns (address); + function validatorSignerToAccount(address) external view returns (address); + function attestationSignerToAccount(address) external view returns (address); + function signerToAccount(address) external view returns (address); + function getAttestationSigner(address) external view returns (address); + function getValidatorSigner(address) external view returns (address); + function getVoteSigner(address) external view returns (address); + function hasAuthorizedVoteSigner(address) external view returns (bool); + function hasAuthorizedValidatorSigner(address) external view returns (bool); + function hasAuthorizedAttestationSigner(address) external view returns (bool); + + function setAccountDataEncryptionKey(bytes calldata) external; + function setMetadataURL(string calldata) external; + function setName(string calldata) external; + function setWalletAddress(address, uint8, bytes32, bytes32) external; + function setAccount(string calldata, bytes calldata, address, uint8, bytes32, bytes32) external; + + function getDataEncryptionKey(address) external view returns (bytes memory); + function getWalletAddress(address) external view returns (address); + function getMetadataURL(address) external view returns (string memory); + function batchGetMetadataURL(address[] calldata) external view returns (uint256[] memory, bytes memory); + function getName(address) external view returns (string memory); + + function authorizeVoteSigner(address, uint8, bytes32, bytes32) external; + function authorizeValidatorSigner(address, uint8, bytes32, bytes32) external; + function authorizeValidatorSignerWithPublicKey(address, uint8, bytes32, bytes32, bytes calldata) external; + function authorizeValidatorSignerWithKeys( + address, + uint8, + bytes32, + bytes32, + bytes calldata, + bytes calldata, + bytes calldata + ) + external; + function authorizeAttestationSigner(address, uint8, bytes32, bytes32) external; + function createAccount() external returns (bool); + + function setPaymentDelegation(address, uint256) external; + function getPaymentDelegation(address) external view returns (address, uint256); + function isSigner(address, address, bytes32) external view returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol b/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol new file mode 100644 index 0000000000000..95e586da3954f --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface ICeloRegistry { + function setAddressFor(string calldata, address) external; + function getAddressForOrDie(bytes32) external view returns (address); + function getAddressFor(bytes32) external view returns (address); + function getAddressForStringOrDie(string calldata identifier) external view returns (address); + function getAddressForString(string calldata identifier) external view returns (address); + function isOneOf(bytes32[] calldata, address) external view returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol b/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol new file mode 100644 index 0000000000000..5bf2033f31726 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the non- ERC20 shared interface for all Celo Tokens, and + * in the absence of interface inheritance is intended as a companion to IERC20.sol. + */ +interface ICeloToken { + function transferWithComment(address, uint256, string calldata) external returns (bool); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function burn(uint256 value) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol b/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol new file mode 100644 index 0000000000000..37b1538c2a121 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface ICeloVersionedContract { + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol b/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol new file mode 100644 index 0000000000000..5c6ab9051ccf2 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IFeeCurrencyDirectory { + struct CurrencyConfig { + address oracle; + uint256 intrinsicGas; + } + + /** + * @notice Returns the list of all currency addresses. + * @return An array of addresses. + */ + function getCurrencies() external view returns (address[] memory); + /** + * @notice Returns the configuration for a currency. + * @param token The address of the token. + * @return Currency configuration of the token. + */ + function getCurrencyConfig(address token) external view returns (CurrencyConfig memory); + + /** + * @notice Retrieves exchange rate between token and CELO. + * @param token The token address whose price is to be fetched. + * @return numerator The exchange rate numerator. + * @return denominator The exchange rate denominator. + */ + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol b/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol new file mode 100644 index 0000000000000..a629b3325a5ba --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IFreezer { + function isFrozen(address) external view returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol b/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol new file mode 100644 index 0000000000000..5c7f392814b61 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IMetaTransactionWallet { + function setEip712DomainSeparator() external; + function executeMetaTransaction( + address, + uint256, + bytes calldata, + uint8, + bytes32, + bytes32 + ) + external + returns (bytes memory); + function executeTransaction(address, uint256, bytes calldata) external returns (bytes memory); + function executeTransactions( + address[] calldata, + uint256[] calldata, + bytes calldata, + uint256[] calldata + ) + external + returns (bytes memory, uint256[] memory); + + // view functions + function getMetaTransactionDigest(address, uint256, bytes calldata, uint256) external view returns (bytes32); + function getMetaTransactionSigner( + address, + uint256, + bytes calldata, + uint256, + uint8, + bytes32, + bytes32 + ) + external + view + returns (address); + + //only owner + function setSigner(address) external; +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol b/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol new file mode 100644 index 0000000000000..5828bee3c7467 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IMetaTransactionWalletDeployer { + function deploy(address, address, bytes calldata) external; +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol b/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol new file mode 100644 index 0000000000000..b3ae66a92756c --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.0; + +/// Possibly not final version +interface IOracle { + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol b/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol new file mode 100644 index 0000000000000..b13febff81fc8 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.5.17 <9.0.0; + +interface IStableTokenV2 { + function totalSupply() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function transfer(address recipient, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) + external; + + /** + * @notice Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + * @param comment The transfer comment. + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool); + + /** + * @notice Initializes a StableTokenV2. + * It keeps the same signature as the original initialize() function + * in legacy/StableToken.sol + * @param _name The name of the stable token (English) + * @param _symbol A short symbol identifying the token (e.g. "cUSD") + * @param initialBalanceAddresses Array of addresses with an initial balance. + * @param initialBalanceValues Array of balance values corresponding to initialBalanceAddresses. + * deprecated-param exchangeIdentifier String identifier of exchange in registry (for specific fiat pairs) + */ + function initialize( + string calldata _name, + string calldata _symbol, + address[] calldata initialBalanceAddresses, + uint256[] calldata initialBalanceValues + ) + external; + + /** + * @notice Initializes a StableTokenV2 contract + * when upgrading from legacy/StableToken.sol. + * It sets the addresses that were previously read from the Registry. + * It runs the ERC20PermitUpgradeable initializer. + * @dev This function is only callable once. + * @param _broker The address of the Broker contract. + * @param _validators The address of the Validators contract. + * @param _exchange The address of the Exchange contract. + */ + function initializeV2(address _broker, address _validators, address _exchange) external; + + /** + * @notice Gets the address of the Broker contract. + */ + function broker() external returns (address); + + /** + * @notice Gets the address of the Validators contract. + */ + function validators() external returns (address); + + /** + * @notice Gets the address of the Exchange contract. + */ + function exchange() external returns (address); + + function debitGasFees(address from, uint256 value) external; + + function creditGasFees( + address from, + address feeRecipient, + address gatewayFeeRecipient, + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256 gatewayFee, + uint256 baseTxFee + ) + external; +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol b/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol new file mode 100644 index 0000000000000..b309071d9f0ad --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the functions specific to Celo Stable Tokens, and in the + * absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol. + */ +interface IStableTokenMento { + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function setInflationParameters(uint256, uint256) external; + + function valueToUnits(uint256) external view returns (uint256); + + function unitsToValue(uint256) external view returns (uint256); + + function getInflationParameters() external view returns (uint256, uint256, uint256, uint256); + + // NOTE: duplicated with IERC20.sol, remove once interface inheritance is supported. + function balanceOf(address) external view returns (uint256); + + function getExchangeRegistryId() external view returns (bytes32); + + function approve(address spender, uint256 value) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol b/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol new file mode 100644 index 0000000000000..4e15e8a8750d4 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IExchange { + function buy(uint256, uint256, bool) external returns (uint256); + + function sell(uint256, uint256, bool) external returns (uint256); + + function exchange(uint256, uint256, bool) external returns (uint256); + + function setUpdateFrequency(uint256) external; + + function getBuyTokenAmount(uint256, bool) external view returns (uint256); + + function getSellTokenAmount(uint256, bool) external view returns (uint256); + + function getBuyAndSellBuckets(bool) external view returns (uint256, uint256); +} diff --git a/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol b/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol new file mode 100644 index 0000000000000..14f77c10549a1 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IReserve { + function setTobinTaxStalenessThreshold(uint256) external; + + function addToken(address) external returns (bool); + + function removeToken(address, uint256) external returns (bool); + + function transferGold(address payable, uint256) external returns (bool); + + function transferExchangeGold(address payable, uint256) external returns (bool); + + function getReserveGoldBalance() external view returns (uint256); + + function getUnfrozenReserveGoldBalance() external view returns (uint256); + + function getOrComputeTobinTax() external returns (uint256, uint256); + + function getTokens() external view returns (address[] memory); + + function getReserveRatio() external view returns (uint256); + + function addExchangeSpender(address) external; + + function removeExchangeSpender(address, uint256) external; + + function addSpender(address) external; + + function removeSpender(address) external; +} diff --git a/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol b/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol new file mode 100644 index 0000000000000..c0b681dfb8aee --- /dev/null +++ b/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the functions specific to Celo Stable Tokens, and in the + * absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol. + */ +interface IStableToken { + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function setInflationParameters(uint256, uint256) external; + + function valueToUnits(uint256) external view returns (uint256); + + function unitsToValue(uint256) external view returns (uint256); + + function getInflationParameters() external view returns (uint256, uint256, uint256, uint256); + + // NOTE: duplicated with IERC20.sol, remove once interface inheritance is supported. + function balanceOf(address) external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol b/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol new file mode 100644 index 0000000000000..d2209dac5d2c8 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.15; + +import "../../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../../lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; + +import "./interfaces/ISortedOracles.sol"; +import "../common/interfaces/ICeloVersionedContract.sol"; +import "./interfaces/IBreakerBox.sol"; + +import "../common/FixidityLib.sol"; +import "../common/Initializable.sol"; +import "../common/linkedlists/AddressSortedLinkedListWithMedian.sol"; +import "../common/linkedlists/SortedLinkedListWithMedian.sol"; +import "./interfaces/IOracle.sol"; + +/** + * @title SortedOracles + * + * @notice This contract stores a collection of exchange rates with CELO + * expressed in units of other assets. The most recent exchange rates + * are gathered off-chain by oracles, who then use the `report` function to + * submit the rates to this contract. Before submitting a rate report, an + * oracle's address must be added to the `isOracle` mapping for a specific + * rateFeedId, with the flag set to true. While submitting a report requires + * an address to be added to the mapping, no additional permissions are needed + * to read the reports, the calculated median rate, or the list of oracles. + * + * @dev A unique rateFeedId identifies each exchange rate. In the initial implementation + * of this contract, the rateFeedId was set as the address of the stable + * asset contract that used the rate. However, this implementation has since + * been updated, and the rateFeedId block.timestamp also refers to an address derived from the + * concatenation other asset symbols. This change enables the contract to store multiple exchange rates for a + * single token. As a result of this change, there may be instances + * where the term "token" is used in the contract code. These useages of the term + * "token" are actually referring to the rateFeedId. + * + */ +contract SortedOracles is ISortedOracles, IOracle, ICeloVersionedContract, Ownable, Initializable { + using SafeMath for uint256; + using AddressSortedLinkedListWithMedian for SortedLinkedListWithMedian.List; + using FixidityLib for FixidityLib.Fraction; + + struct EquivalentToken { + address token; + } + + uint256 private constant FIXED1_UINT = 1e24; + + // Maps a rateFeedID to a sorted list of report values. + mapping(address => SortedLinkedListWithMedian.List) private rates; + // Maps a rateFeedID to a sorted list of report timestamps. + mapping(address => SortedLinkedListWithMedian.List) private timestamps; + mapping(address => mapping(address => bool)) public isOracle; + mapping(address => address[]) public oracles; + + // `reportExpirySeconds` is the fallback value used to determine reporting + // frequency. Initially it was the _only_ value but we later introduced + // the per token mapping in `tokenReportExpirySeconds`. If a token + // doesn't have a value in the mapping (i.e. it's 0), the fallback is used. + // See: #getTokenReportExpirySeconds + uint256 public reportExpirySeconds; + // Maps a rateFeedId to its report expiry time in seconds. + mapping(address => uint256) public tokenReportExpirySeconds; + + IBreakerBox public breakerBox; + // Maps a token address to its equivalent token address. + // Original token will return the median value same as the value of equivalent token. + mapping(address => EquivalentToken) public equivalentTokens; + + event OracleAdded(address indexed token, address indexed oracleAddress); + event OracleRemoved(address indexed token, address indexed oracleAddress); + event OracleReported(address indexed token, address indexed oracle, uint256 timestamp, uint256 value); + event OracleReportRemoved(address indexed token, address indexed oracle); + event MedianUpdated(address indexed token, uint256 value); + event ReportExpirySet(uint256 reportExpiry); + event TokenReportExpirySet(address token, uint256 reportExpiry); + event BreakerBoxUpdated(address indexed newBreakerBox); + event EquivalentTokenSet(address indexed token, address indexed equivalentToken); + + modifier onlyOracle(address token) { + require(isOracle[token][msg.sender], "sender was not an oracle for token addr"); + _; + } + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function initialize(uint256 _reportExpirySeconds) external initializer { + _transferOwnership(msg.sender); + setReportExpiry(_reportExpirySeconds); + } + + /** + * @notice Sets the report expiry parameter for a rateFeedId. + * @param _token The token for which the report expiry is being set. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function setTokenReportExpiry(address _token, uint256 _reportExpirySeconds) external onlyOwner { + require(_reportExpirySeconds > 0, "report expiry seconds must be > 0"); + require(_reportExpirySeconds != tokenReportExpirySeconds[_token], "token reportExpirySeconds hasn't changed"); + tokenReportExpirySeconds[_token] = _reportExpirySeconds; + emit TokenReportExpirySet(_token, _reportExpirySeconds); + } + + /** + * @notice Adds a new Oracle for a specified rate feed. + * @param token The token for which the specified oracle is to be added. + * @param oracleAddress The address of the oracle. + */ + function addOracle(address token, address oracleAddress) external onlyOwner { + // solhint-disable-next-line reason-string + require( + token != address(0) && oracleAddress != address(0) && !isOracle[token][oracleAddress], + "token addr was null or oracle addr was null or oracle addr is already an oracle for token addr" + ); + isOracle[token][oracleAddress] = true; + oracles[token].push(oracleAddress); + emit OracleAdded(token, oracleAddress); + } + + /** + * @notice Removes an Oracle from a specified rate feed. + * @param token The token from which the specified oracle is to be removed. + * @param oracleAddress The address of the oracle. + * @param index The index of `oracleAddress` in the list of oracles. + */ + function removeOracle(address token, address oracleAddress, uint256 index) external onlyOwner { + // solhint-disable-next-line reason-string + require( + token != address(0) && oracleAddress != address(0) && oracles[token].length > index + && oracles[token][index] == oracleAddress, + "token addr null or oracle addr null or index of token oracle not mapped to oracle addr" + ); + isOracle[token][oracleAddress] = false; + oracles[token][index] = oracles[token][oracles[token].length.sub(1)]; + oracles[token].pop(); + if (reportExists(token, oracleAddress)) { + removeReport(token, oracleAddress); + } + emit OracleRemoved(token, oracleAddress); + } + + /** + * @notice Removes a report that is expired. + * @param token The token for which the expired report is to be removed. + * @param n The number of expired reports to remove, at most (deterministic upper gas bound). + */ + function removeExpiredReports(address token, uint256 n) external { + require( + token != address(0) && n < timestamps[token].getNumElements(), + "token addr null or trying to remove too many reports" + ); + for (uint256 i = 0; i < n; i = i.add(1)) { + (bool isExpired, address oldestAddress) = isOldestReportExpired(token); + if (isExpired) { + removeReport(token, oldestAddress); + } else { + break; + } + } + } + + /** + * @notice Sets the equivalent token for a token. + * @param token The address of the token. + * @param equivalentToken The address of the equivalent token. + */ + function setEquivalentToken(address token, address equivalentToken) external onlyOwner { + require(token != address(0), "token address cannot be 0"); + require(equivalentToken != address(0), "equivalentToken address cannot be 0"); + equivalentTokens[token] = EquivalentToken(equivalentToken); + emit EquivalentTokenSet(token, equivalentToken); + } + + /** + * @notice Sets the equivalent token for a token. + * @param token The address of the token. + */ + function deleteEquivalentToken(address token) external onlyOwner { + require(token != address(0), "token address cannot be 0"); + delete equivalentTokens[token]; + emit EquivalentTokenSet(token, address(0)); + } + + /** + * @notice Updates an oracle value and the median. + * @param token The token for which the rate is being reported. + * @param value The number of stable asset that equate to one unit of collateral asset, for the + * specified rateFeedId, expressed as a fixidity value. + * @param lesserKey The element which should be just left of the new oracle value. + * @param greaterKey The element which should be just right of the new oracle value. + * @dev Note that only one of `lesserKey` or `greaterKey` needs to be correct to reduce friction. + */ + function report(address token, uint256 value, address lesserKey, address greaterKey) external onlyOracle(token) { + uint256 originalMedian = rates[token].getMedianValue(); + if (rates[token].contains(msg.sender)) { + rates[token].update(msg.sender, value, lesserKey, greaterKey); + + // Rather than update the timestamp, we remove it and re-add it at the + // head of the list later. The reason for this is that we need to handle + // a few different cases: + // 1. This oracle is the only one to report so far. lesserKey = address(0) + // 2. Other oracles have reported since this one's last report. lesserKey = getHead() + // 3. Other oracles have reported, but the most recent is this one. + // lesserKey = key immediately after getHead() + // + // However, if we just remove this timestamp, timestamps[token].getHead() + // does the right thing in all cases. + timestamps[token].remove(msg.sender); + } else { + rates[token].insert(msg.sender, value, lesserKey, greaterKey); + } + timestamps[token].insert( + msg.sender, + // solhint-disable-next-line not-rely-on-time + block.timestamp, + timestamps[token].getHead(), + address(0) + ); + emit OracleReported(token, msg.sender, block.timestamp, value); + uint256 newMedian = rates[token].getMedianValue(); + if (newMedian != originalMedian) { + emit MedianUpdated(token, newMedian); + } + + if (address(breakerBox) != address(0)) { + breakerBox.checkAndSetBreakers(token); + } + } + + /** + * @notice Gets the equivalent token for a token. + * @param token The address of the token. + * @return The address of the equivalent token. + */ + function getEquivalentToken(address token) external view returns (address) { + return (equivalentTokens[token].token); + } + + /** + * @notice Returns the median timestamp. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the median timestamp is being retrieved. + * @return uint256 The median report timestamp for the specified rateFeedId. + */ + function medianTimestamp(address token) external view returns (uint256) { + return timestamps[token].getMedianValue(); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the timestamps are being retrieved. + * @return keys Keys of nn unpacked list of elements from largest to smallest. + * @return values Values of an unpacked list of elements from largest to smallest. + * @return relations Relations of an unpacked list of elements from largest to smallest. + */ + function getTimestamps(address token) + external + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + return timestamps[token].getElements(); + } + + /** + * @notice Returns the list of oracles for a speficied rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the oracles are being retrieved. + * @return address[] A list of oracles for the given rateFeedId. + */ + function getOracles(address token) external view returns (address[] memory) { + return oracles[token]; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the rates are being retrieved. + * @return keys Keys of an unpacked list of elements from largest to smallest. + * @return values Values of an unpacked list of elements from largest to smallest. + * @return relations Relations of an unpacked list of elements from largest to smallest. + */ + function getRates(address token) + external + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + return rates[token].getElements(); + } + + /** + * @notice Returns the exchange rate for a specified token. + * @param token The token for which the exchange rate is being retrieved. + * @return numerator uint256 The exchange rate for the specified token. + * @return denominator uint256 The denominator for the exchange rate. + */ + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator) { + (numerator, denominator) = medianRate(token); + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 4, 0); + } + + /** + * @notice Sets the report expiry parameter. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function setReportExpiry(uint256 _reportExpirySeconds) public onlyOwner { + require(_reportExpirySeconds > 0, "report expiry seconds must be > 0"); + require(_reportExpirySeconds != reportExpirySeconds, "reportExpirySeconds hasn't changed"); + reportExpirySeconds = _reportExpirySeconds; + emit ReportExpirySet(_reportExpirySeconds); + } + + /** + * @notice Sets the address of the BreakerBox. + * @param newBreakerBox The new BreakerBox address. + */ + function setBreakerBox(IBreakerBox newBreakerBox) public onlyOwner { + require(address(newBreakerBox) != address(0), "BreakerBox address must be set"); + breakerBox = newBreakerBox; + emit BreakerBoxUpdated(address(newBreakerBox)); + } + + /** + * @notice Returns the median of the currently stored rates for a specified rateFeedId. + * @dev Please note that this function respects the equivalentToken mapping, and so may + * return the median identified as an equivalent to the supplied rateFeedId. + * @param token The token for which the median value is being retrieved. + * @return uint256 The median exchange rate for rateFeedId (fixidity). + * @return uint256 denominator + */ + function medianRate(address token) public view returns (uint256, uint256) { + EquivalentToken storage equivalentToken = equivalentTokens[token]; + if (equivalentToken.token != address(0)) { + (uint256 equivalentMedianRate, uint256 denominator) = + medianRateWithoutEquivalentMapping(equivalentToken.token); + return (equivalentMedianRate, denominator); + } + + return medianRateWithoutEquivalentMapping(token); + } + + /** + * @notice Returns the number of rates that are currently stored for a specifed rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the number of rates is being retrieved. + * @return uint256 The number of reported oracle rates stored for the given rateFeedId. + */ + function numRates(address token) public view returns (uint256) { + return rates[token].getNumElements(); + } + + /** + * @notice Check if last report is expired. + * @param token The token for which the expired report is to be checked. + * @return bool A bool indicating if the last report is expired. + * @return address Oracle address of the last report. + */ + function isOldestReportExpired(address token) public view returns (bool, address) { + // solhint-disable-next-line reason-string + require(token != address(0)); + address oldest = timestamps[token].getTail(); + uint256 timestamp = timestamps[token].getValue(oldest); + // solhint-disable-next-line not-rely-on-time + if (block.timestamp.sub(timestamp) >= getTokenReportExpirySeconds(token)) { + return (true, oldest); + } + return (false, oldest); + } + + /** + * @notice Returns the median of the currently stored rates for a specified rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * @param token The token for which the median value is being retrieved. + * @return uint256 The median exchange rate for rateFeedId (fixidity). + * @return uint256 denominator + */ + function medianRateWithoutEquivalentMapping(address token) public view returns (uint256, uint256) { + return (rates[token].getMedianValue(), numRates(token) == 0 ? 0 : FIXED1_UINT); + } + + /** + * @notice Returns the number of timestamps. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the number of timestamps is being retrieved. + * @return uint256 The number of oracle report timestamps for the specified rateFeedId. + */ + function numTimestamps(address token) public view returns (uint256) { + return timestamps[token].getNumElements(); + } + + /** + * @notice Returns the expiry for specified rateFeedId if it exists, if not the default is returned. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the report expiry is being retrieved. + * @return The report expiry in seconds. + */ + function getTokenReportExpirySeconds(address token) public view returns (uint256) { + if (tokenReportExpirySeconds[token] == 0) { + return reportExpirySeconds; + } + + return tokenReportExpirySeconds[token]; + } + + /** + * @notice Checks if a report exists for a specified rateFeedId from a given oracle. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the report should be checked. + * @param oracle The oracle whose report should be checked. + * @return bool True if a report exists, false otherwise. + */ + function reportExists(address token, address oracle) internal view returns (bool) { + return rates[token].contains(oracle) && timestamps[token].contains(oracle); + } + + /** + * @notice Removes an oracle value and updates the median. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the oracle report should be removed. + * @param oracle The oracle whose value should be removed. + * @dev This can be used to delete elements for oracles that have been removed. + * However, a > 1 elements reports list should always be maintained + */ + function removeReport(address token, address oracle) private { + if (numTimestamps(token) == 1 && reportExists(token, oracle)) return; + uint256 originalMedian = rates[token].getMedianValue(); + rates[token].remove(oracle); + timestamps[token].remove(oracle); + emit OracleReportRemoved(token, oracle); + uint256 newMedian = rates[token].getMedianValue(); + if (newMedian != originalMedian) { + emit MedianUpdated(token, newMedian); + if (address(breakerBox) != address(0)) { + breakerBox.checkAndSetBreakers(token); + } + } + } +} diff --git a/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol b/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol new file mode 100644 index 0000000000000..26430da7a3bea --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.0; + +/** + * @title Breaker Box Interface + * @notice Defines the basic interface for the Breaker Box + */ +interface IBreakerBox { + /** + * @dev Used to keep track of the status of a breaker for a specific rate feed. + * + * - TradingMode: Represents the trading mode the breaker is in for a rate feed. + * This uses a bitmask approach, meaning each bit represents a + * different trading mode. The final trading mode of the rate feed + * is obtained by applying a logical OR operation to the TradingMode + * of all breakers associated with that rate feed. This allows multiple + * breakers to contribute to the final trading mode simultaneously. + * Possible values: + * 0: bidirectional trading. + * 1: inflow only. + * 2: outflow only. + * 3: trading halted. + * + * - LastUpdatedTime: Records the last time the breaker status was updated. This is + * used to manage cooldown periods before the breaker can be reset. + * + * - Enabled: Indicates whether the breaker is enabled for the associated rate feed. + */ + struct BreakerStatus { + uint8 tradingMode; + uint64 lastUpdatedTime; + bool enabled; + } + + /** + * @notice Emitted when a new breaker is added to the breaker box. + * @param breaker The address of the breaker. + */ + event BreakerAdded(address indexed breaker); + + /** + * @notice Emitted when a breaker is removed from the breaker box. + * @param breaker The address of the breaker. + */ + event BreakerRemoved(address indexed breaker); + + /** + * @notice Emitted when a breaker is tripped by a rate feed. + * @param breaker The address of the breaker. + * @param rateFeedID The address of the rate feed. + */ + event BreakerTripped(address indexed breaker, address indexed rateFeedID); + + /** + * @notice Emitted when a new rate feed is added to the breaker box. + * @param rateFeedID The address of the rate feed. + */ + event RateFeedAdded(address indexed rateFeedID); + + /** + * @notice Emitted when dependencies for a rate feed are set. + * @param rateFeedID The address of the rate feed. + * @param dependencies The addresses of the dependendent rate feeds. + */ + event RateFeedDependenciesSet(address indexed rateFeedID, address[] indexed dependencies); + + /** + * @notice Emitted when a rate feed is removed from the breaker box. + * @param rateFeedID The address of the rate feed. + */ + event RateFeedRemoved(address indexed rateFeedID); + + /** + * @notice Emitted when the trading mode for a rate feed is updated + * @param rateFeedID The address of the rate feed. + * @param tradingMode The new trading mode. + */ + event TradingModeUpdated(address indexed rateFeedID, uint256 tradingMode); + + /** + * @notice Emitted after a reset attempt is successful. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetSuccessful(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted after a reset attempt fails when the + * rate feed fails the breakers reset criteria. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetAttemptCriteriaFail(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted after a reset attempt fails when cooldown time has not elapsed. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetAttemptNotCool(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted when the sortedOracles address is updated. + * @param newSortedOracles The address of the new sortedOracles. + */ + event SortedOraclesUpdated(address indexed newSortedOracles); + + /** + * @notice Emitted when the breaker is enabled or disabled for a rate feed. + * @param breaker The address of the breaker. + * @param rateFeedID The address of the rate feed. + * @param status Indicating the status. + */ + event BreakerStatusUpdated(address breaker, address rateFeedID, bool status); + + /** + * @notice Checks breakers for the rateFeedID and sets correct trading mode + * if any breakers are tripped or need to be reset. + * @param rateFeedID The address of the rate feed to run checks for. + */ + function checkAndSetBreakers(address rateFeedID) external; + + /** + * @notice Retrives an array of all breaker addresses. + */ + function getBreakers() external view returns (address[] memory); + + /** + * @notice Checks if a breaker with the specified address has been added to the breaker box. + * @param breaker The address of the breaker to check; + * @return A bool indicating whether or not the breaker has been added. + */ + function isBreaker(address breaker) external view returns (bool); + + /** + * @notice Gets the trading mode for the specified rateFeedID. + * @param rateFeedID The address of the rate feed to retrieve the trading mode for. + */ + function getRateFeedTradingMode(address rateFeedID) external view returns (uint8 tradingMode); +} diff --git a/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol b/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol new file mode 100644 index 0000000000000..b3ae66a92756c --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.0; + +/// Possibly not final version +interface IOracle { + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +} diff --git a/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol b/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol new file mode 100644 index 0000000000000..ecea4210cd40e --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface ISortedOracles { + function addOracle(address, address) external; + function removeOracle(address, address, uint256) external; + function report(address, uint256, address, address) external; + function removeExpiredReports(address, uint256) external; + function isOldestReportExpired(address token) external view returns (bool, address); + function numRates(address) external view returns (uint256); + function medianRate(address) external view returns (uint256, uint256); + function numTimestamps(address) external view returns (uint256); + function medianTimestamp(address) external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol b/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol new file mode 100644 index 0000000000000..fd00f42c01bbb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: MIT +// Modified from OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +import "../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "../../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "../../../lib/openzeppelin-contracts/contracts/utils/Context.sol"; + +import "../CalledByVm.sol"; + +/** + * @dev Implementation of the {IERC20} interface + Celo debit/creditGasFees. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract FeeCurrency is Context, IERC20, IERC20Metadata, CalledByVm { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer(address from, address to, uint256 amount) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** + * @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + /** + * @notice Reserve balance for making payments for gas in this StableToken currency. + * @param from The account to reserve balance from + * @param value The amount of balance to reserve + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. After the tx is executed, gas is refunded to the sender and credited to the + * various tx fee recipients via a call to `creditGasFees`. Note too that the events emitted + * by `creditGasFees` reflect the *net* gas fee payments for the transaction. + */ + function debitGasFees(address from, uint256 value) external onlyVm { + _balances[from] -= value; + _totalSupply -= value; + } + + /** + * @notice Alternative function to credit balance after making payments + * for gas in this StableToken currency. + * @param from The account to debit balance from + * @param feeRecipient Coinbase address + * legacy param gatewayFeeRecipient Gateway address (UNUSED!) + * @param communityFund Community fund address + * @param tipTxFee Coinbase fee + * @param baseTxFee Community fund fee + * legacy param gatewayFee Gateway fee (UNUSED!) + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. Before the tx is executed, gas is debited from the sender via a call to + * `debitGasFees`. Note too that the events emitted by `creditGasFees` reflect the *net* gas fee + * payments for the transaction. + */ + function creditGasFees( + address from, + address feeRecipient, + address, // gatewayFeeRecipient + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256, // gatewayFee + uint256 baseTxFee + ) + external + onlyVm + { + _balances[from] += refund; + + refund += _creditGas(from, communityFund, baseTxFee); + refund += _creditGas(from, feeRecipient, tipTxFee); + _totalSupply += refund; + } + + function _creditGas(address from, address to, uint256 value) internal returns (uint256) { + if (to == address(0)) { + return 0; + } + _balances[to] += value; + emit Transfer(from, to, value); + return value; + } +} diff --git a/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol b/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol new file mode 100644 index 0000000000000..d51fa2a7c56c4 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { IOracle } from "../interfaces/IOracle.sol"; + +/** + * @title A mock SortedOracles for testing. + */ +contract MockSortedOracles is IOracle { + uint256 public constant DENOMINATOR = 1000000000000000000000000; + mapping(address => uint256) public numerators; + mapping(address => uint256) public medianTimestamp; + mapping(address => uint256) public numRates; + mapping(address => bool) public expired; + + function setMedianRate(address token, uint256 numerator) external returns (bool) { + numerators[token] = numerator; + return true; + } + + function setMedianTimestamp(address token, uint256 timestamp) external { + medianTimestamp[token] = timestamp; + } + + function setMedianTimestampToNow(address token) external { + // solhint-disable-next-line not-rely-on-time + medianTimestamp[token] = uint128(block.timestamp); + } + + function setNumRates(address token, uint256 rate) external { + numRates[token] = rate; // This change may break something, TODO + } + + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator) { + return medianRate(token); + } + + function medianRate(address token) public view returns (uint256, uint256) { + if (numerators[token] > 0) { + return (numerators[token], DENOMINATOR); + } + return (0, 0); + } + + function isOldestReportExpired(address token) public view returns (bool, address) { + return (expired[token], token); + } + + function setOldestReportExpired(address token) public { + expired[token] = true; + } +} diff --git a/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol b/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol new file mode 100644 index 0000000000000..14c6495920a1f --- /dev/null +++ b/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IUniswapV2FactoryMin { + function getPair(address tokenA, address tokenB) external view returns (address pair); +} diff --git a/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol b/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol new file mode 100644 index 0000000000000..f1755edb137d0 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IUniswapV2RouterMin { + function factory() external pure returns (address); + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) + external + returns (uint256[] memory amounts); + function getAmountsOut( + uint256 amountIn, + address[] calldata path + ) + external + view + returns (uint256[] memory amounts); +} diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol index 93ee9b9269ab2..a2453914c13d2 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol @@ -7,6 +7,7 @@ import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draf // Libraries import { Preinstalls } from "src/libraries/Preinstalls.sol"; +import { AbstractFeeCurrency } from "src/celo/AbstractFeeCurrency.sol"; // Interfaces import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; @@ -20,7 +21,7 @@ import { ILegacyMintableERC20 } from "interfaces/legacy/ILegacyMintableERC20.sol /// use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa. /// Designed to be backwards compatible with the older StandardL2ERC20 token which was only /// meant for use on L2. -contract OptimismMintableERC20 is ERC20Permit, ISemver { +contract OptimismMintableERC20 is ERC20Permit, ISemver, AbstractFeeCurrency { /// @notice Address of the corresponding version of this token on the remote chain. address public immutable REMOTE_TOKEN; @@ -48,7 +49,7 @@ contract OptimismMintableERC20 is ERC20Permit, ISemver { /// @notice Semantic version. /// @custom:semver 1.4.0-beta.5 - string public constant version = "1.4.0-beta.5"; + string public constant version = "1.4.0-beta.5-celo"; /// @notice Getter function for the permit2 address. It deterministically deployed /// so it will always be at the same address. It is also included as a preinstall, diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol index 37af174cce5bc..4a0793d60ffba 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol @@ -52,7 +52,7 @@ contract OptimismMintableERC20Factory is ISemver, Initializable, IOptimismERC20F /// is responsible for deploying OptimismMintableERC20 contracts. /// @notice Semantic version. /// @custom:semver 1.10.1 - string public constant version = "1.10.1"; + string public constant version = "1.10.1-celo"; /// @notice Constructs the OptimismMintableERC20Factory contract. constructor() { diff --git a/packages/contracts-bedrock/test/L1/StandardValidator.t.sol b/packages/contracts-bedrock/test/L1/StandardValidator.t.sol index 8c05bd5e867be..4a26ac8614fd1 100644 --- a/packages/contracts-bedrock/test/L1/StandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/StandardValidator.t.sol @@ -994,6 +994,11 @@ contract StandardValidatorV180_Test is StandardValidatorTest { } function test_validate_opMainnet_succeeds() public { + // Celo: Skip failing test + // This has been removed upstream in https://github.com/ethereum-optimism/optimism/pull/15397 and will be + // fixed by the next rebase. + vm.skip(true); + string memory rpcUrl = vm.envOr(string("MAINNET_RPC_URL"), string("")); if (bytes(rpcUrl).length == 0) { return; diff --git a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol index f105a86632ab1..5af13b119b4fb 100644 --- a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol @@ -170,6 +170,7 @@ contract L2GenesisTest is Test { /// @notice Tests the number of accounts in the genesis setup function _test_allocs_size(string memory _path) internal { genesis.cfg().setFundDevAccounts(false); + genesis.cfg().setDeployCeloContracts(true); genesis.runWithLatestLocal(_dummyL1Deps()); genesis.writeGenesisAllocs(_path); diff --git a/packages/contracts-bedrock/test/libraries/SafeCall.t.sol b/packages/contracts-bedrock/test/libraries/SafeCall.t.sol index 60aba27f83ebb..e7af93f5879d1 100644 --- a/packages/contracts-bedrock/test/libraries/SafeCall.t.sol +++ b/packages/contracts-bedrock/test/libraries/SafeCall.t.sol @@ -5,6 +5,8 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; import { VmSafe } from "forge-std/Vm.sol"; import { StdCheatsSafe } from "forge-std/StdCheats.sol"; +import { AddressSortedLinkedList } from "src/celo/common/linkedlists/AddressSortedLinkedList.sol"; +import { AddressSortedLinkedListWithMedian } from "src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol"; // Libraries import { LibString } from "@solady/utils/LibString.sol"; @@ -16,6 +18,10 @@ contract SafeCall_Test is Test { vm.deal(_addr, 0); vm.assume(_addr != address(this)); assumeAddressIsNot(_addr, StdCheatsSafe.AddressType.ForgeAddress, StdCheatsSafe.AddressType.Precompile); + + // ignore address of library contract whose functions have 'public' or 'external' visibilities + vm.assume(_addr != address(AddressSortedLinkedList)); + vm.assume(_addr != address(AddressSortedLinkedListWithMedian)); } /// @notice Internal helper function for `send` tests diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index 22d919c3c2c59..214f01e4b9736 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -356,7 +356,7 @@ contract Initializer_Test is CommonTest { function test_cannotReinitialize_succeeds() public { // Collect exclusions. uint256 j; - string[] memory excludes = new string[](8); + string[] memory excludes = new string[](10); // Contract is currently not being deployed as part of the standard deployment script. excludes[j++] = "src/L2/OptimismSuperchainERC20.sol"; // Periphery contracts don't get deployed as part of the standard deployment script. @@ -374,6 +374,7 @@ contract Initializer_Test is CommonTest { excludes[j++] = "src/L1/OPContractsManager.sol"; // L2 contract initialization is tested in Predeploys.t.sol excludes[j++] = "src/L2/*"; + excludes[9] = "src/celo/*"; // Get all contract names in the src directory, minus the excluded contracts. string[] memory contractNames = ForgeArtifacts.getContractNames("src/*", excludes);